Catch all routes for ASP.Net MVC

21 May 2016

Most of the content management system offers users with the ability to have their own custom URL paths or structures. Have you wondered how this is done.

1) Webform application:

You can configure the http runtime of webapplication in web.config to run managed modules for all requests (RMMFAR). That way you can write and wireup a http module to get the current URL path and serve the content appropriately. But configuring RMFAR will mean a huge performance hit, as every requests even the ones to the static resources like images, documents, JS and css etc., will be handled by ASPNET process.

2) Using a unique identifiers :

Most of the CMS systems or applications will nest the content id in the URL string, through which it can identify and serve the correct content. Most websites does this through the query strings (/products.php?id=12345). There are other interesting way to implement the same

Example: See how StackOverflow.com employs the same concept in SEO friendly way

http://stackoverflow.com/questions/37368722/how-to-disallow-authenticated-but-unverified-users
The contents that you see after the numbers are just for SEO purpose, you can reach the exact page by just typing in following URL
http://stackoverflow.com/questions/37368722

Advantage : This method is scalable solution which will not be affected even when the site grows too huge.

Limitation : Your URL structure should be in a fixed format, so that your application could recognize and serve the correct content.

3) Using route constraint in MVC application

This can be achieved by writing a custom route constraint and adding it to the route collection during application startup. So, for each page request this route constraint will validate the current requested path against a URL cache base and if a valid entry is found then it routes the request to the custom controller that we have written which serves up the requested content.

public static void RegisterRoutes(RouteCollection routes)
{
	routes.MapRoute(name: "CmsRoute",
        url: "{*permalink}",
        defaults: new
        {
            controller = "CustomPage",
            action = "Index"
        },
        constraints: new
        {
            permalink = new CmsUrlConstraint()
    });
}

public class CmsUrlConstraint : IRouteConstraint
{
    public CmsUrlConstraint(){ }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        string strPath = values[parameterName] + "";
        var permalink = string.Format("/{0}", strPath.TrimEnd(new char[] { '/' }));

        if (CmsUrls.PageList.ContainsKey(permalink)) // Check in the inmemory dictionary
        {
            HttpContext.Current.Items["cmspage"] = CmsUrls.PageList[permalink];
            return true;
        }
        //Go for checking page existence in database
        using (var db = new ApplicationDbContext())
        {
            var page = db.Cms_PageProperties.Where(p => p.UrlSlug.Equals(permalink,
                StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
            if (page != null)
            {
                CmsUrls.PageList.AddOrUpdate(permalink, page, (key, oldValue) => page);
                HttpContext.Current.Items["cmspage"] = page;
                return true;
            }
        }
        //Not in database
        return false;
    }
}

This above method works in a sexy way and this is being used in this website wink.

Advantage : You can literally have any URL structure or URLs with no actual parent folder structures. Even the initial application startup will not be slow, because the required links are verified and loaded on demand. Most importantly cached.

Gotcha: but only thing to consider :: IF YOUR application is going to be very huge with more than 10,000 pages or so, I would suggest you to go for unique identifiers in URL just like how stackoverflow.com does it. otherwise your server memory would be under crunch by growing URL in the in-memory dictionary.