0

I have a web API controller, when I call the default Get action this works, when I call another specific action (GetReservationsForCustomer) this also works, but one action gives an error (GetReservationsByDate), it seems to route to the default Get action. Here is the code:

    // GET: api/Reservations
    public IQueryable<Reservation> GetReservations()
    {
        return db.Reservations;
    }

    [ResponseType(typeof(ReservationDTO))]
    public IHttpActionResult GetReservationsForCustomer(int CustomerId)
    {
        IEnumerable<Reservation> reservations = db.Reservations.Where(r => r.CustomerId == CustomerId).ToList();
        List<ReservationDTO> reservationList = new List<ReservationDTO>();

        foreach(Reservation reservation in reservations)
        {
            reservationList.Add(new ReservationDTO
            {
                id = reservation.id,
                ReservationStart = reservation.ReservationStart,
                Covers = reservation.Covers
            });
        }

        return Ok(reservationList);
    }

    [ResponseType(typeof(ListReservationDTO))]
    public IHttpActionResult GetReservationsByDate(DateTime StartDate, DateTime EndDate)
    {
        IEnumerable<Reservation> reservations = new List<Reservation>();

        if (EndDate != null)
        {
            reservations = db.Reservations.Where(r => r.ReservationStart.Date >= StartDate.Date && r.ReservationStart.Date >= EndDate.Date).ToList();
        }
        else
        {
            reservations = db.Reservations.Where(r => r.ReservationStart.Date == StartDate.Date).ToList();
        }

        List<ReservationDTO> reservationList = new List<ReservationDTO>();
        foreach (Reservation res in reservations)
        {
            reservationList.Add(new ReservationDTO
            {
                id = res.id,
                ReservationStart = res.ReservationStart,
                Covers = res.Covers,
                CustomerEmail = res.Customer.EmailAddress,
                CustomerName = res.Customer.Name,
                CustomerPhone = res.Customer.PhoneNumber
            });
        }

        return Ok(reservationList);
    }

Here is my API call:

http://localhost:55601/api/Reservations/GetReservationsByDate/?StartDate=2018-03-04:T12:30:00

And here is the response:

{
    "Message": "The request is invalid.",
    "MessageDetail": "The parameters dictionary contains a null entry for parameter 'id' of non-nullable type 'System.Int32' for method 'System.Web.Http.IHttpActionResult GetReservation(Int32)' in 'GreenLionBookings.API.ReservationsController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter."
}

Please note the specifics of the action are not relevant at this stage, I've butchered it a fair bit trying to get this to work! I've tried specifying a start date and end date and neither seems to work. It always seems to get routed to the default Get action.

Here is my RouteConfig:

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}

And here is my WebApiConfig:

    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

        // Web API routes
        config.MapHttpAttributeRoutes();

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }

So both basically just default.

Why is this not routing to the correct action, just for this one and not the others? I have another controller (Customers) which seems to work correctly for all actions too. I've read this and this, and also this and this which I actually thought was pretty relevant and quite helpful, but didn't solve my problem.

What am I doing wrong here?

Matt G
  • 444
  • 5
  • 23
  • `endDate` is a required parameter with no default. Therefore, that action is not a candidate for matching the route and the default route is chosen. That subsequently fails because there is no value for `id`. – Aluan Haddad Apr 08 '18 at 06:24
  • @AluanHaddad I've tried it with enddate too and it makes no difference. I've also tried it removing enddate completely from the action. – Matt G Apr 08 '18 at 06:26
  • 1
    You should leave out the action name of the url and let the framework match the request against the parameters name, try it like this http://localhost:55601/api/Reservations?StartDate=2018-03-04:T12:30:00 – Marcus Höglund Apr 08 '18 at 06:37
  • @MarcusHöglund that seems to have done the trick. I'm getting a different error now but at least its routing to the correct controller: { "Message": "The request is invalid.", "MessageDetail": "The parameters dictionary contains a null entry for parameter 'StartDate' of non-nullable type 'System.DateTime' for method 'System.Web.Http.IHttpActionResult GetReservationsByDate(System.DateTime, System.DateTime)' in 'GreenLionBookings.API.ReservationsController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter." } – Matt G Apr 08 '18 at 06:52
  • But not sure why it says the parameter is null? – Matt G Apr 08 '18 at 06:53
  • 1
    looks like you have a typeo in the date, 2018-03-04:T12:30:00 should be 2018-03-04T12:30:00 – Marcus Höglund Apr 08 '18 at 07:07
  • Also, as Aluan pointed out, you need to set the EndDate to nullable DateTime, use DateTime?, its a value type and not a reference type – Marcus Höglund Apr 08 '18 at 07:09
  • @MarcusHöglund thanks for the typo catch. Also changing DateTime to nullable requires changing the code to use [...].Value.[...]. And also .Date can't be used in LINQ queries. It's working now, thanks. Although there's a logic error somewhere, it just returns all records at the moment. – Matt G Apr 08 '18 at 21:15
  • 1
    @MattG Np, should I post this as an answer then? – Marcus Höglund Apr 09 '18 at 04:57
  • 1
    @MarcusHöglund yes please and I will mark as accepted. Cheers – Matt G Apr 11 '18 at 05:56

1 Answers1

1

First of all, you have a typeo in the date.

This 2018-03-04:T12:30:00 should be 2018-03-04T12:30:00.

Then, to solve the routing problem, you could leave out the action name of the url and let the framework match the request against the parameters name.

Try it like this

api/Reservations?StartDate=2018-03-04T12:30:00&EndDate=2018-03-05T12:30:00 

Then, if you want to be able to send nullable values to EndDate which is a value type of DateTime; make the DateTime nullable

[ResponseType(typeof(ListReservationDTO))]
public IHttpActionResult GetReservationsByDate(DateTime StartDate, DateTime? EndDate)

Notice the DateTime? which is a shorthand for Nullable<DateTime>

Marcus Höglund
  • 16,172
  • 11
  • 47
  • 69