MVC - Routing to a static file

I have been following the .Net MVC project since very early on. There are lots of things I like about. I like the testability. I like the control over the final html. But one of the things that I’ve really come to appreciate after watching the project evolve over the last year is its extensibility. It seems to me that Phil Haack and his team have done a great job of allowing developers (i.e. me) to plug in our own code at almost every point in the process. This week I had a new feature request at work that was easily solved due to that extensibility.
I have an application that I built using MVC. It’s sort of a CMS system with some specialized logic for allowing our users to download large files (i.e. video games) using a 3rd party downloader. Well, this week management decided they’d like to try out a different downloader to see how it affects conversions. This would be a short term test on 2-3 off the more popular files on the site. The problem is there’s not really a clean way under the current architecture to use a different downloader on only a select number of pages. It also doesn’t really make sense to make extensive code changes in order to support a 4 day test.
After thinking about the problem for a while I came up with a fairly clean, low impact (code wise) solution. Why not use the MVC routing mechanism to route 2-3 specific urls to a temporary hardcoded page? That way, I won’t have to change any logic in the underlying system to handle the handful of temporary edge cases. It also means I won’t have to update any “url” logic in my system. In fact, the urls of the pages won’t change at all. I’ll just intercept calls to the specific pages before they’re handed off to the current controller and instead route them to a static html file. Best of all, the only change to the code base would be to add a couple of new routing rules in the global.asax file (a solution that required zero code changes would be even better, but this still seems pretty reasonable to me).
However, when I did a little searching on how to route to a static file in MVC I couldn’t really find a solution. I did find people adding an “Ignore” route to allow requests to static files to bypass the MVC engine, but that wasn’t really what I was after. What I did find was a handy little class called RouteBase which is a base class that you can inherit in order to create specialized routing rules. Perfect! I experimented for about an hour and came up with the following…
public class StaticRoute : RouteBase, IRouteHandler, IHttpHandler
{
private string _url;
private string _filePath;
public StaticRoute(string url, string filePath)
{
_url = url;
_filePath = filePath;
}
public override RouteData GetRouteData
(HttpContextBase httpContext)
{
if (httpContext.Request.Url.AbsolutePath == _url)
{
return new RouteData(this, this);
}
else
{
return null;
}
}
public override VirtualPathData GetVirtualPath
(RequestContext requestContext,
RouteValueDictionary values)
{
return null;
}
public IHttpHandler GetHttpHandler
(RequestContext requestContext)
{
return this;
}
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
context.Response.WriteFile(_filePath);
}
}
Not to scary really. Now all I have to do is use it. Here’s an example…
routes.Add(new StaticRoute("/some/url", "~/static_file.html"));
Basically, my StaticRoute class takes two arguments in the constructor, the incoming url to watch for and the static file to return when a request to that url is made. RouteBase has two abstract functions, GetRouteData and GetVirtualPath. The second, GetVirtualPath, is used to create a url based on a given controller, action, parameters, etc. which isn’t really important here so I return null (i.e. “I can’t help you, go ask some other route what the url should be”). GetRouteData is the interesting function. Here I check if the requested url matches the url we’re trying to route to a static file. If it does I return an IRouteHandler that should handle the request. For simplicity, I’ve made my StaticRoute class also implement IRouteHandler. IRouteHandler has one function, GetHttpHandler, which returns the IHttpHandler that is responsible for actually writing content to the response stream. Again for simplicity I’ve made my StaticRoute class implement IHttpHandler as well, so i just return “this”. IHttpHandler has a function called ProcessRequest which passes an HttpContext as a parameter. I simply pass the path to a static file and that files contents get written to the response stream. Simple and easy.
As I said at the beginning, one of my favorite parts of the MVC framework is its extensibility. In this case I was able to provide custom logic to process a very edge case scenario with only a few lines of code. You gotta love that!