by Keyvan Nayyeri via Keyvan Nayyeri on 4/15/2009 3:54:11 PM
In a recent blog post Simone did a great job by listing all the 13 major extensibility points available in ASP.NET MVC with a short description and references for further reading.
It’s been a while that I wanted to write more about ASP.NET MVC but like all other topics, it has been a decision only! However, I thought that it’s a good start point to go over these extensibility points with some introductory posts as starter guides. So here is the first part covering custom route constraints in ASP.NET MVC.
As you may know, ASP.NET MVC (and hopefully the next versions of ASP.NET WebForms) has a powerful routing mechanism to route requests to their corresponding controller and action methods with appropriate parameters with some basic constraint declaration mechanisms.
While there is a simple way to add routes to the routing engine, it’s also possible to validate requests based on your business scenarios and more complex constraints. This is an extensibility point in ASP.NET MVC routing mechanism that is called route constraint in which you define custom constraints that check the validity of requests, and notify the routing engine if it should route this request with this route object and pattern or not.
Let me clarify this with a very common example that you’ve seen in many sites. Many of the sites and blogs have an archive calendar where you can find posts for a specific year, month or day by navigating to a unique URL that includes the year, month, and day numbers. Of course, these numbers are integer but their range should be limited somehow. For instance, you don’t expect a year number to be smaller than 1900 or larger than 2100, or a month number to be smaller than 1 or larger than 12. In a more complex case, the range of day numbers can vary by month and there is a dependency between the month and day numbers. You would agree that it should be very good to check these ranges and validate incoming requests to make sure they contain valid values.
You can add a route constraint by implementing the IRouteConstraint interface in ASP.NET MVC, and adding it to your routes through a few steps that I’ll write in a moment. IRouteConstraint has a single Match function that returns a Boolean value which determines whether this request should be processed by the route object or not. It also has some parameters:
Here I write a simple ASP.NET MVC application that simulates such an archive system but only displays the year, month, and day numbers in the view. I write some constraints to check the abovementioned ranges and exclude invalid requests from routing engine.
First, I create an ArchiveController with an Index action method to display the values of year, month, and day parameters.
using System.Web.Mvc;
namespace RouteConstraintSample.Controllers
{
public class ArchiveController : Controller
public ActionResult Index(int year, int month, int day)
ViewData["Year"] = year;
ViewData["Month"] = month;
ViewData["Day"] = day;
return View();
}
I also create a view to display these values.
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage" %>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
Archive
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>
Archive</h2>
<p>
Here are the information extracted from URL:
</p>
<br />
Year:
<%= ViewData["Year"] %>
Month:
<%= ViewData["Month"]%>
Day:
<%= ViewData["Day"]%>
In the main step I need to create three separate constraints for year, month, and day validation that later I will apply in my routing definitions.
I start with a simple constraint for year values called YearRouteConstraint.
using System;
using System.Globalization;
using System.Web;
using System.Web.Routing;
namespace RouteConstraintSample.Routes
public class YearRouteConstraint : IRouteConstraint
#region IRouteConstraint Members
public bool Match(HttpContextBase httpContext, Route route,
string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
if ((routeDirection == RouteDirection.IncomingRequest) &&
(parameterName.ToLower(CultureInfo.InvariantCulture) == "year"))
try
int year = Convert.ToInt32(values["year"]);
if ((year >= 1900) && (year <= 2100))
return true;
catch
return false;
#endregion
The logic behind this implementation is simple. I check to make sure that this request is coming from a client, and also check that the parameter name is equal to my expected value. Of course, this may not be a big deal because you can map the correct parameter to this constraint, but it’s always better to close doors to avoid unexpected issues in the lower levels of code. In the main body of the Match function I extract the value of the year parameter and check its range. If it falls in the range then I return the true value to process the request by the route otherwise ignore it.
The MonthRouteConstraint has a very similar logic that checks the value of the month to be between 1 and 12.
public class MonthRouteConstraint : IRouteConstraint
(parameterName.ToLower(CultureInfo.InvariantCulture) == "month"))
int month = Convert.ToInt32(values["month"]);
if ((month >= 1) && (month <= 12))
The last constraint is a good example of constraints that validate the value of a parameter that depends on other parameter.
public class DayRouteConstraint : IRouteConstraint
(parameterName.ToLower(CultureInfo.InvariantCulture) == "day"))
int day = Convert.ToInt32(values["day"]);
if (day < 1)
switch (month)
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
if (day <= 31) return true;
break;
case 2:
if (day <= 28) return true;
case 4:
case 6:
case 9:
case 11:
if (day <= 30) return true;
DayRouteConstraint checks the value of day parameter based on the value of month, so it can’t exceed the valid value of the number of days in that months. To do this, it extracts the value of month from the RouteValueDictionary along with the value of day.
The last step is to connect all these things together so ASP.NET MVC can run the application. This step is nothing but defining the routes.
using RouteConstraintSample.Routes;
namespace RouteConstraintSample
public class MvcApplication : System.Web.HttpApplication
public static void RegisterRoutes(RouteCollection routes)
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
"Archive",
"archive/{year}/{month}/{day}",
new
controller = "Archive",
action = "Index",
year = "",
month = "",
day = ""
},
year = new YearRouteConstraint(),
month = new MonthRouteConstraint(),
day = new DayRouteConstraint()
protected void Application_Start()
RegisterRoutes(RouteTable.Routes);
I add a new route to my route collection called Archive with “archive/{year}/{month}/{day}” pattern that everybody can understand. Like the regular routes I also set the default values for the route, but add a constraint object to it. This constraint object maps each parameter in the pattern to its constraint instance, so these values can be validated.
Now I run the application with a valid request pattern to get the result.
I also can send a request for the 31st day of April that is not valid and I get an error. Obviously, the better implementation had to display a custom page with appropriate error message, but this is just a sample application!
As the last note, you may wonder why I wrote three separate constraints for these three values while I could extract all the values in a single constraint and validate them. While this is possible, I avoided it because it’s not the proposed practice and usage, and also it’s not always the case.
You can download the sample code package for this post from here.
Original Post: Custom Route Constraint in ASP.NET MVC
The content of the postings is owned by the respective author. CSharpFeeds is not responsible for the contents of the postings. This site is automatically generated and cannot be reviewed for abusive content. If you find abusive content on CSharpFeeds, please contact us. Designated trademarks and brands are the property of their respective owners. All rights reserved.