Wednesday, January 15, 2014

Routing Constraint



Routing Constraint – By Narendra Shrestha

Introduction
There is no doubt regarding the default functionality supplied by MVC. It is single handedly capable of leveraging the whole project. Moreover, we can add our own customizing feature to enhance the default one, or implement our own. Due to these features, it has gathered such an enormous fame in such a short span. Even though we are not bound to implement these customizing features, at least for the sake of understanding the real deal of MVC, it is worthy to explore these aspects.
The MVC Routing System is responsible for validating the incoming URL, inferring the name of the controllers and action based on the registered routes, and sending those requests down to the Request Pipeline to further perform the action designated in action methods of controllers. In case for non-existent action and controller, the unpopular error page is displayed. Remember that routing system is unaware whether the controller, action or other parameters are existent or not. Its job is to only provide the first matching URL and throttle request down the pipe line. When I refer it as the First, it means the first, not the best!!!
For your total recall, the routes are registered in the Global.asax.cs file of the MVC web application’s root folder. One of the common ways to register a Route is specified by the code listed as below:-
routes.MapRoute(
       "",
       "{Controller}/{action}",
       new { controller = "Home", action = "Index" }
);

Listing 1:-Registering Routes

Default Constraints

We can add constrictions to our route in several different ways. For example as in case below by Regular Expression:-

routes.MapRoute(
       "",
       "{controller}/{action}/{id}",
       new { controller = "Home", action = "Index", id = UrlParameter.Optional },
       new { Controller = "^H.*",action="^Index$|^About$" }
);

Listing 2:-Default Constraint by Regular Expression


The above specified route causes the routing system to only accept those URL whose controller name begins with H. Also, it limits the possible actions down to “Index” & “About”. These kinds of constriction come into play when there is any repair/maintenance work going on. We strictly confide the periphery of the user.  The method prevents users from accessing other actions in controllers whose name begins with H (including “Home” controller).

Customizing Constraints

These are default constrictions. The flexibility of the MVC is only realized when we can apply our own self defined constrictions. In order to customize the constrictions, we need to create a customizing class which implements IRouteCosntraint Interface.  This interface defines a Method named Match whose signature is:-

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

Inside the Match method, we then implement rest of our own logic.

We get access to various part of the application through the supplied parameters.

HttpContextBase httpContext
Provide access to the various ASP.NET functionality such as user request, response, sessions, services etc.
Route route

Provide access to the current Route in which we have specified our customized constrictions
string parameterName
The value of the constructor Parameter for the class that Implements IRouteCosntraint interface.
RouteValueDictionary values
Provides access to the various segment of the URL such as controller, action, area and others.
RouteDirection routeDirection
Specify whether we are dealing with incoming URL or outgoing URL


Example

To demonstrate the use of IRouteCosntraint interface, let us assume that we display separate views for different languages based on the user browser language settings. We have added “Nepali” language as one of the language preferences in the Chrome (or any browser).

Add new controller to our project called “Home” controller. Change the listing of the Home controller as shown below

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace RouteConstraint.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/

        public ActionResult Index()
        {
            return View();
        }
        public ActionResult Nepali()
        {
            return View();
        }
    }
}

Listing 3:-Home Controller


For an action Index, add a View “Index” and change the listing as following

@{
    ViewBag.Title = "Index";
}

<h2>Default</h2>

Listing 4:-Index View Razor file


Similarly, for an action Nepali, add a View “Nepali” and change the listing as following

@{
    ViewBag.Title = "Nepali";
}

<h2>नेपाली</h2>

Listing 5:-Nepali View Razor file


Now, that we have everything in right place, it’s time to bring out IRouteCosntraint from our arsenal.
To do so, right click the MVC project in the solution explorer, and add a new folder called “infrastructure”. It’s not a hard and fast rule to give the same name to your folder. Since, all of our interfaces and customizing codes sit in this folder, it’s an appropriate name.  Add a class MyConstraint.cs inside this folder that implements the interface IRouteCosntraint. The listing of this class is as shown below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Routing;

namespace RouteConstraint.Infrastructure
{
    public class MyConstraint : IRouteConstraint
    {
        #region IRouteConstraint Members

        public bool Match(HttpContextBase httpContext, Route route, string parameterName,
 RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (httpContext.Request.UserLanguages.Contains("ne;q=0.8"))
            {
                return true;
            }
            return false;
        }

        #endregion
    }
}

Listing 6:-Custom Constraint


Inside the “Match” method, we have tried to access the browser languages by “httpContext.Request.UserLanguages” which is of type “string[]”. Hence, whatever language preferences that are added in the browser will fill up this array. We check if it contains “Nepali” and act accordingly.

If we try to run our project at this point, we will not get the desired outcome. There is still one glitch left. We have not yet instructed “MVC” that we are going to make use of our custom constraint MyConstraint.cs.

We have to make changes in “RegisterRoutes()” method in “Global.asax.cs”. Hence, we change the listing of this method as follows

public static void RegisterRoutes(RouteCollection routes)
{   
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
           
routes.MapRoute("", "{controller}/{action}", new { controller = "Home",
              action = "Nepali" }, new { contraint = new MyConstraint() });
           
routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional }
 // Parameter defaults
       );
}

Listing 7:-Registering route

Here in portion

routes.MapRoute("", "{controller}/{action}", new { controller = "Home",
              action = "Nepali" }, new { contraint = new MyConstraint() });
                    
We haven’t provided the name for the route. Also, we accept the URL of form “{controller}/{action}”. By the help of anonymous type,

new { controller = "Home", action = "Nepali" }

we set the default controller to be “Home” and default action to be “Nepali”.

For the last anonymous type, 
new { contraint = new MyConstraint() }

we have told the routing engine that we prefer the custom routing constraint of type “IRouteConstraint” i.e. “MyConstraint”. Thus, our constraint is validated according to the “Match” method inside the “MyConstraint”.

If method “Match” in custom routing code returns false, routing engine falls down to route named “default”. Otherwise, the routing engine happily puts this particular unmanned route in request pipeline.

Run the project and see its effect in the all possible browsers (especially chrome in our case).

First we run the project without adding “Nepali” language in the browser setting. We can see the effect as in the figure below.





Figure 1:-Without adding Nepali language in browser language setting



Now let us add “Nepali” in the browser language setting. Thus, we can see the result as below.




Figure 2:-With Nepali language added in browser language setting

We have demonstrated the use of customizing routing constraint. In our example we have used customizing routing constraint to display the user an appropriate language based on their browser language settings. However, you shouldn’t rely on the routing behavior for this purpose. Instead you must use the globalization and the localization feature. The routes may vary along with changes in the requirements as the project proceeds. So, relying on this behavior may later result in several hours of debugging and headache. We have used this example as only mean of explaining the customizing routing constraint and nothing more.

No comments:

Post a Comment