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