I’m running into problems with my Web API routing setup when dealing with nested routes. I have a route configuration that looks like this:
routes.MapHttpRoute(
name: "CustomerOrders",
routeTemplate: "api/customers/{customerID}/orders/{orderID}",
defaults: new { controller = "CustomerOrders", orderID = UrlParameter.Optional }
);
My controller has two methods:
public Order Get(int customerID, int orderID)
{
// Return specific order for customer
}
public IEnumerable<Order> Get(int customerID)
{
// Return all orders for customer
}
When I call /api/customers/1234/orders, I get an error saying that the parameters dictionary has a null entry for parameter orderID of non-nullable type System.Int32. The error mentions that optional parameters must be nullable types or declared as optional parameters.
I expected the routing to pick the method with just the customerID parameter, but it seems like it’s trying to use the method with both parameters instead. What am I missing in my route setup?
Web API’s method selection algorithm prioritizes exact parameter matches over optional ones. When you hit /api/customers/1234/orders, the framework sees both methods as candidates but picks the one with more parameters since your route template includes {orderID} even though it’s optional. I ran into the same thing on a project with overlapping route patterns. What worked best was splitting them into separate routes with specific templates:
routes.MapHttpRoute(
name: "CustomerOrderById",
routeTemplate: "api/customers/{customerID}/orders/{orderID}",
defaults: new { controller = "CustomerOrders" },
constraints: new { orderID = @"\d+" }
);
routes.MapHttpRoute(
name: "CustomerOrders",
routeTemplate: "api/customers/{customerID}/orders",
defaults: new { controller = "CustomerOrders" }
);
This way each route maps to its intended method without ambiguity. The constraint on orderID ensures proper matching when the parameter’s present.
You can also use action-based routing with the Route attribute directly on your methods. Tried this yesterday and it worked great:
[Route("api/customers/{customerID}/orders/{orderID:int}")]
public Order Get(int customerID, int orderID) { ... }
[Route("api/customers/{customerID}/orders")]
public IEnumerable<Order> Get(int customerID) { ... }
Way cleaner than messing with global route config imo.
This happens because Web API can’t figure out which method to call when you have an optional route parameter but declare it as a non-nullable int in your controller. I ran into this exact problem last year. The easiest fix is making the parameter nullable in your controller method:
public object Get(int customerID, int? orderID = null) {
if (orderID.HasValue) {
// Return specific order
return GetSpecificOrder(customerID, orderID.Value);
} else {
// Return all orders for customer
return GetAllOrdersForCustomer(customerID);
}
}
This kills the overloading confusion completely. Web API will always hit this one method, and you just handle the branching logic inside based on whether the optional parameter has a value.