Constraints in ASP.NET Routing

ASP.NET routing is a new feature incorporated in the upcoming .NET 3.5 Service Pack 1 release.  While closely associated with the implementation of ASP.NET MVC (Model-View-Controller), it's really a separate piece of functionality consolidated in two assemblies

  • System.Web.Routing, and 
  • System.Web.Abstractions

I've been looking at routing recently as part of ASP.NET Dynamic Data, and soon found myself intrigued by the various permutations you can cook up.  While there are quite a few blog posts and  tutorials on the routing feature, I haven't found many (yet) that talk much about the new constraint capabilities introduced in ASP.NET MVC Preview 3.

In a nutshell, ASP.NET routing allows you to set up a RouteTable consisting of URL 'templates' against which the System.Web.UrlRoutingModule (referenced in web.config) compares each requested URL to see if there is a match.  If there is, the HTTP request is handed off to a specific implementation of an IHttpHandler for servicing.  ASP.NET MVC comes with its own implementation of an IHttpHandler (System.Web.Mvc.MvcRouteHandler), but where's the fun in that... so that's when I decided to try my own hand.

As I mentioned, each route is really a template. For instance, a URL of

dayinhistory/{year}/{month}/{day}

would match things like

https://mysite.com/dayinhistory/2000/1/1

and assign values to "year", "month", and "day" in a Values collection that can be accessed within the route handler.  Unfortunately, it would also match URLs like

https://mysite.com/dayinhistory/red/sox/rock

https://mysite.com/dayinhistory/2000/56/40

neither of which represents a valid date.

While each route definition can be initialized with a collection of Constraints, prior to ASP.NET MVC Preview 3, the constraint itself could only be a string representing a regular expression.  That works for some scenarios, but it doesn't really help when you have more complex validation, such as date checking.

With Preview 3, a new interface, IRouteConstraint, was added to System.Web.Routing and vastly expands your constraint-checking capabilities.  Now you can specify a constraint not only as string (to be interpreted as a regular expression, as in the past) but also as a custom class implementing this new interface.  IRouteConstraint mandates implementation of a Match method, within which you can do pretty much anything and then simply return true if the validation succeeds or false if it fails. 

As you can see from the Match signature below, you have access to the selected route, the substituted URL parameters, and the HTTP context object to perform the validation.  The routeDirection parameter is an enumeration that tells you whether the method is executing on an 'incoming' basis, that is the UrlRoutingModule is trying to find a match for a requested URL, or on an 'outgoing' basis, in which you're requesting a URL to be created to match a route - generally in response to invoking GetVirtualPath to build URLs for links within your own pages.

 public bool Match(HttpContextBase httpContext, Route route, 
     string parameterName, RouteValueDictionary values, RouteDirection routeDirection)

 

As an example, here's a simple class I created to validate the date URL pattern mentioned above.  It simply tries to create a date from the parameter values extracted from the URL, and if that fails (i.e., an exception is thrown), the validation fails.

 public class DateConstraintValidator : IRouteConstraint
{
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, 
                      RouteValueDictionary values, RouteDirection routeDirection)
    {
        try
        {
            DateTime potentialDate = new DateTime(
                Int32.Parse(values["year"].ToString()), 
                Int32.Parse(values["month"].ToString()), 
                Int32.Parse(values["day"].ToString()));
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }
}

 

Now, my route definition in global.asax looks like this (DateUrlRouter is my custom IHttpHandler that instantiates a single ASPX page to display the date in a Calendar control):

 Route r = new Route("dayinhistory/{year}/{month}/{day}", 
    null,                
    new RouteValueDictionary { {"year", new DateConstraintValidator()} }, 
    new DateUrlRouter());
routes.Add(r);

 

While I only have one constraint on "year", that's really enough, because the Match method has access to all of the substituted values as one of its parameters.  (Actually, it doesn't really matter what value is supplied for the string "year" here, since the constraint reference will come along for the ride anyway!)

If you're interested in seeing this all in action, check out my sample on Skydrive: