ASP.NET MVC Custom Routing Logic

I’ve been working with the new ASP.NET MVC framework since the first CTP back in December. I love it and it keeps getting better.

The application I’m working on uses custom Session management and so I have to manage the SessionId in the url myself. So I wanted to be able to make sure that all Redirects, Links and Urls had the SessionId. But I didn’t want to have to add it myself (maintenance nightmare) and end up with having to track down errors from missing it.

Because the routing framework builds the urls for you I wanted to interject my logic into one place where I could intercept each request to build a url and add the SessionId.

Before the March CTP (aka the MIX ‘08 CTP) I had to add extensions to all the classes which constructed urls. Here’s a link to a post of mine at the time: http://forums.asp.net/p/1216840/2159920.aspx#2159920

But now with the recent CTP refresh it has become very easy to manage the session id from one place. I just create a class which inherits from System.Web.Routing.Route (or you can implement it from scratch by inheriting from System.Web.Routing.RouteBase). Here’s how I did it.

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

using DevelopmentalMadness.Web.Mvc;

namespace DevelopmentalMadness.Web.MvcExtensions
{
    public class SessionRoute : Route
    {
        public SessionRoute(String url, IRouteHandler routeHandler) : base(url, routeHandler)
        {

        }

        public SessionRoute(String url, RouteValueDictionary defaults, IRouteHandler routeHandler)
            : base(url, defaults, routeHandler)
        {

        }

        public SessionRoute(String url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
            : base(url, defaults, constraints, routeHandler)
        {

        }

        public SessionRoute(String url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
            : base(url, defaults, constraints, dataTokens, routeHandler)
        {

        }

        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
        {
            //how can I get the view context or controller context ?????
            if (requestContext is ControllerContext)
            {
                ControllerContext cCtx = (ControllerContext)requestContext;

                if (cCtx.Controller is SessionBaseController)
                {
                    SessionBaseController sCtl = (SessionBaseController)cCtx.Controller;
                    if (values.ContainsKey("sid") == false && sCtl.ServerCookie != null)
                        values.Add("sid", sCtl.ServerCookie.Ticket);
                }
            }

            VirtualPathData virtualPath = base.GetVirtualPath(requestContext, values);

            return virtualPath;
        }
    }
}

SessionBaseController and ServerCookie are two custom classes of mine. ServerCookie is my session state management class and SessionBaseController is the class which exposes and maintains it. So I am able to access it through the RequestContext.

I haven’t fully tested this yet, so I’m not sure if RequestContext will ever be something other than ControllerContext. I’m assuming that it can be, especially in the context of a unit test. Because they didn’t explicitly declare ControllerContext it would be correct to assume that it won’t always be ControllerContext, I just haven’t discovered yet what the other possible values could be. Maybe for my next post.

The only other requirement is to design your routes so that they have a parameter for the values you are adding to the url, like this:

namespace MvcApplication
{
    public class Global : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            // Note: Change Url= to Url="[controller].mvc/[action]/[id]" to enable
            //       automatic support on IIS6

            routes.Add(new Route(
                "Login.mvc/Index/{result}",
                new RouteValueDictionary(new
                {
                    controller = "Login",
                    action = "Index",
                    result = (String)null
                }),
                new MvcRouteHandler()
                )
            );

            routes.Add(new SessionRoute(
                "Login.mvc/{action}/{sid}",
                new RouteValueDictionary(new
                {
                    controller = "Login",
                    action = "Index",
                    sid = (String)null
                }),
                new MvcRouteHandler()
                )
            );

            routes.Add(new SessionRoute(
                "{controller}.mvc/{action}/{sid}",
                new RouteValueDictionary(new
                {
                    action = "Index",
                    sid = (String)null
                }),
                new MvcRouteHandler()
                )
            );

            routes.Add(new Route(
                "Default.aspx",
                new RouteValueDictionary(new
                {
                    controller = "Home",
                    action = "Index"
                }),
                new MvcRouteHandler()
                )
            );
        }

        protected void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes(RouteTable.Routes);
        }
    }
}

Each route which requires my custom session management uses SessionRoute instead of Route. Also, the route will include the “sid” argument in the route definition.

Now I can manage the routes I need to by simply updating my Routes defined in global.asax instead of worring wither or not every url in my application was written correctly.