MVC Routing dot net

Subtleties of MVC Routing

MVC Routing dot net

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

Related Blog