I’ve been writing an alternative to the MVC routing mechanism for ASP.NET Web API based on prior approaches that I have worked with. I’ll blog more about that in the future.
Part of my motivation for writing this new router was because I didn’t know how to do what I wanted to do with MVC Routing and I knew how to do it “my way”. Classic NIH! However, I wanted my routing to plug in nicely to Web API and be as consistent as possible with the way MVC routing works. Which has lead me to a much deeper understanding of how MVC routing works and how to get stuff done.
In the process I discovered a number of subtleties that I would say are not immediately apparent. I figured it might be valuable to share them.
Assuming a controller that looks like this,
public class CustomerController : ApiController
{
public HttpResponseMessage Get(int? id)
{
return new HttpResponseMessage()
{
Content = new StringContent("Here is a customer with id: " + id)
};
}
[ActionName("MailingAddress")]
public HttpResponseMessage GetMailingAddress(int? id)
{
return new HttpResponseMessage()
{
Content = new StringContent("Mailing address for customer id: " + id)
};
}
}
a route like this:
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "test",
routeTemplate: "api/{controller}/{id}/{action}",
defaults: new { id = RouteParameter.Optional,
action = RouteParameter.Optional });
gives the following responses
/api/customer => OK
/api/customer/23 => OK
/api/customer/23/Mailingaddress => OK
/api/customer/Mailingaddress => OK // Equivalent to /api/customer with null id
/api/customer/blah => OK // This is not great as it means it will
// match to anything. Need an Id constraint
/api/customer/blah/yuck => 404
and if we add a route constraint for the id parameter,
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "test",
routeTemplate: "api/{controller}/{id}/{action}",
constraints: new { id = @"d+"},
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional });
it gives
/api/customer => 404 // The Id constraint is failing even though the
// Id is marked as optional
/api/customer/23 => OK
/api/customer/23/Mailingaddress => OK
/api/customer/Mailingaddress => 404
/api/customer/blah => 404
/api/customer/blah/yuck => 404
so if we adjust the Id constraint slightly,
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "test",
routeTemplate: "api/{controller}/{id}/{action}",
constraints: new { id = @"d*"},
defaults: new { id = RouteParameter.Optional, action = RouteParameter.Optional });
we get this slightly different result
/api/customer => OK // The Id constraint passes
/api/customer/23 => OK
/api/customer/23/Mailingaddress => OK
/api/customer/Mailingaddress => 404 // Even though the Id constraint passed
// and the id is optional, we are still not
// allowed to drop the segment.
/api/customer/blah => 404
/api/customer/blah/yuck => 404