MVC Route Constraints

I started to dig into MVC3 routing a bit more over the weekend.  I came across some routing constraints and realized that there is a clear progression.  I created an out of the box MVC3 web application (I am using Razor) and then added a Product Controller with the default methods and 1 View for the Details method.  I made 1 change to the out of the box convention – I changed the name of the int parameter to the Details method to productId.

 

image

The Details controller method pushes the parameter back out to the View:

public ActionResult Details(int productId) { return View(productId); }

and the view parrots the productId back to the user:

@{ ViewBag.Title = "Details"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Details</h2> <br /> You Entered = @Model

 

I then dropped into Global.asax to work with the constraints.  The first thing I did was to add a route to the routing table to account for the productid parameter:

routes.MapRoute( "Product", "Product/{productId}", new { controller = "Product", action = "Details" } );

This worked fine to a point.  Product/1 resolved:

image

 

but Product/foo was allowed by the routing engine and the method threw an error:

 

image

 

To handle this malformed parameter, I added a regular expression to the constraint definition (using Stephen Walter’s blog post as an example:

 

routes.MapRoute( "ProductWithConstraint", "Product/{productId}", new { controller = "Product", action = "Details" }, new { productId = @"\d+" } );

Now, when Product/foo comes in, I got a 404.

image

The next scenario I wanted to handle was that only some integers are allowed as a parameter.  For example, perhaps only products that are in stock are allowed – which means that you need to do a database call before defining the route constraint definition.  To that end, I created a new class that inherited from IRouteConstraint (based on Yuri Nayyeri blog post):

public class ProductRouteConstraint: IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if ((routeDirection == RouteDirection.IncomingRequest) && (parameterName.ToLower(CultureInfo.InvariantCulture) == "productid")) { int productId = 0; try { productId = Convert.ToInt32(values["productId"]); if (productId > 0 && productId < 10) { return true; } } catch (FormatException formatException) { return false; } } return false; } }

I then added this constraint to the routing table:

 

routes.MapRoute( "ProductWithConstraintClass", "Product/{productId}", new { controller = "Product", action = "Details" }, new { productId = new ProductRouteConstraint() } );

And I got the desired result – products less than 0 or greater than 10 returned a 404.

So the progression in my mind is:

  1. No constraints
  2. Reg Ex constraints in the Global.asax
  3. Create a class that implements IRouteConstraint

I my mind, I would rather skip #2 and go to #3 altogether.  Having all of the logic encapsulated in 1 class seems cleaner and not having the Global.asax mucked up with custom defs makes for a more maintainable solution.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: