Routing is a different and much more powerful beast. The ASP.Net routing engine maps an URL to a “resource”, based on a set of routes. The first route to match the requested URL wins the prize, and sends the request off to the resource it chooses. For the ASP.Net MVC framework (which uses System.Web.Routing under the hood), a resource is something that can handle the request object, which is always a piece of code.
Handling the Request
First off, we need to handle the request that we want to re-route to a physical file. Out of the box, ASP.Net MVC uses an instance of the MvcRouteHandler object to handle every request. MvcRouteHandler hides all the complexities of taking the requested URL, breaking it down into parts, finding the right controller in your application, instantiating it and passing it all the data it needs.
To do so, we simply implement IRouteHandler, an interface exposed by ASP.Net MVC that actually inherits from IHttpHandler. This means that what we’re writing is the ASP.Net MVC equivalent of an .ashx file for a webforms app.
IRouteHandler only has one method that we need to implement, which is GetHttpHandler().
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web; using System.Web.Compilation; using System.Web.Routing; using System.Web.UI; namespace MvcApplication1 { public class ImageRouteHandler : IRouteHandler { public IHttpHandler GetHttpHandler(RequestContext requestContext) { string filename = requestContext.RouteData.Values["filename"] as string; if (string.IsNullOrEmpty(filename)) { requestContext.HttpContext.Response.Clear(); requestContext.HttpContext.Response.StatusCode = 404; requestContext.HttpContext.Response.End(); } else { requestContext.HttpContext.Response.Clear(); requestContext.HttpContext.Response.ContentType = GetContentType(requestContext.HttpContext.Request.Url.ToString()); // find physical path to image here. string filepath = requestContext.HttpContext.Server.MapPath("~/default.jpg"); requestContext.HttpContext.Response.WriteFile(filepath); requestContext.HttpContext.Response.End(); } return null; } private static string GetContentType(String path) { switch (Path.GetExtension(path)) { case ".bmp": return "Image/bmp"; case ".gif": return "Image/gif"; case ".jpg": return "Image/jpeg"; case ".png": return "Image/png"; default: break; } return ""; } } }
The above IRouteHandler is pretty simple. Ignoring the GetContentType helper method, there’s really only two things happening. First, we check for a “filename” parameter that got passed in to our handler (more on that in a second). If it’s not there, we return a 404 response. Otherwise, we attempt to open up the physical file “default.jpg”, and stream it to the browser.
Routing the Request to the Custom Handler
Well, this is the easy part. Where you’d normally define your routes in Global.asax, simply use routes.Add(), instead of routes.MapRoute(). Just like this:
routes.Add("ImagesRoute", new Route("graphics/{filename}", new ImageRouteHandler()));
This method of adding our route allows us to specify our custom IRouteHandler, rather than routes.MapRoute(), which by default uses an instance of MvcRouteHandler. So now, we’ve defined a route that matches against any requested URL containing “graphics/”, and puts the rest of the URL into the “filename” bucket of the RouteDataDictionary, and hands it off to our IRouteHandler. This is how we pass the filename parameter into our custom route handler — basically the same way we pass things into controllers, by defining the variables in the route pattern.
We’ve successfully routed all URL’s containing “graphics/”, which doesn’t physically exist in our web application, and returning “temp.jpg”, which could exist anywhere. With a bit of coding around the file IO, you could return files from anywhere.
In a nutshell, by inserting your own HttpHandlers into the ASP.Net pipeline to handle routed requests, you can code anything that you’d like to happen when a request comes in, rather than just rewriting it to some other URL.