DevelopMENTAL Madness

Thursday, March 13, 2008

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.