Creating custom route constraints in ASP.NET MVC

Routing

In ASP.NET MVC the Routing Module is the URL routing engine that directs the incoming requests to controller actions. There are cases where we need to impose constraints over the incoming requests that are mapped routes. These constraints are used to avoid from directing invalid requests to the controller actions.

In this article we are going to see how to create a simple custom route constraint and supply it to a mapped route.

Constraints

All the mapped routes are stored as RouteCollection in the Routes property of the RouteTable class. The RouteCollection has bunch of extension methods to map or ignore routes. One of the overloaded methods of MapRoute takes the constraints parameter through which we can supply our constraint to the route.


public static Route MapRoute(this RouteCollection routes, string name, 
	string url, object defaults, object constraints);

We can pass constraints in two ways. We can either pass as a regular expression string or we can pass an instance that implements IRouteConstraint. In simple cases we can use regular expressions and in the advanced cases we can go for the IRouteConstraint implementations.

DateConstraint

Let say we have a BlogController with an action Archive that takes a single parameter datetime as a string

public ActionResult Archive(string datetime)
{
	//
}

The input datetime should be in the format yyyyMMdd ex. 20120404. Since the datetime is passed as a string the routing engine directs requests that contain invalid dates also to the action. All the below requests will be received by the action even though the first two requests have invalid dates.

Ex.
http://mysite.com/blog/archive/1 (invalid)
http://mysite.com/blog/archive/posts (invalid)
http://mysite.com/blog/archive/20120404 (valid)

Lets create a date constraint that checks the passed string is a valid datetime and if then direct the request to the action else reject it. For creating that constraint we have to implement the IRouteConstraint interface. The IRouteConstraint is a simple that contains a single method.

public interface IRouteConstraint
{        
	bool Match(HttpContextBase httpContext, Route route, string parameterName, 
		RouteValueDictionary values, RouteDirection routeDirection);
}

If the request is valid then we should return true from the Match method else false.

Here is our implementation which is simple and straight.

public class DateConstraint : IRouteConstraint
{
	public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
	{
		DateTime dt;

		return DateTime.TryParseExact(values[parameterName].ToString(), 
		"yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out dt);
	}
}

Let see how we can pass the constraint on registering the route.

routes.MapRoute(
	name: "archive",
	url: "blog/archive/{datetime}",
	defaults: new { controller = "Blog", action = "Archive" },
	constraints: new { datetime = new DateConstraint() }
);

Issue with the default route

Whenever we create an MVC application a default a route is registered in Global.asax.cs.

routes.MapRoute(
   name: "Default",
   url: "{controller}/{action}/{id}",
   defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

If we have this default route then again the invalid requests are directed to the action, this is because the routing engine works in that way. Whenever the application receives a request the framework matches it with the first registered route in the routing table and if it is ok then directs the request to the corresponding controller and action else tries for the next route and so on. If no route matches the incoming request then the server issues a 404 to the client.

In our example the invalid requests rejected by the constrained route is accepted by the following default route. To avoid that we have to specify another constraint to the default route to avoid any requests that will be directed to the Archive action of the BlogController.

public class EliminateControllerAction : IRouteConstraint
{
	private readonly string _controller;
	private readonly string _action;

	public EliminateControllerAction(string controller, string action)
	{
		_controller = controller;
		_action = action;
	}

	public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
	{
		return String.Compare(values["controller"].ToString(), _controller, true) != 0 
			|| String.Compare(values["action"].ToString(), _action, true) != 0;
	}
}

In the Match method we are returning false if the controller is Blog and action is Archive. So all the requests that will be directed to that action will not be matched by this route.

That’s it. Now if anyone tries sending an invalid date in the request they will get a 404 resource not found.

Download Sample

blog comments powered by Disqus