nav-left cat-right
cat-right

ASP.Net MVC – How to route to images or other file types

A recent question on Stack Overflow (and subsequent answer that I wrote for it) inspired this post. I had recently been discussing URL rewriting in depth with my brother, and have also been doing some introductory work with the routing engine in ASP.Net MVC, and the question piqued my interest since I had been meaning to look at this more closely for some time.

The question on Stack Overflow is titled "How do I route images with ASP.Net MVC", but fundamentally the question is really asking "how can I use ASP.Net MVC to re-route URL's to actual physical files, rather than methods of a controller?"

To be clear, lets address the conceptual differences between routing and url rewriting.  Url rewriting takes the requested URL and modifies it before your code ever sees it.  As far as your application is concerned, the client requested the rewritten URL.  All that URL rewriting does is to change one URL into another URL, based on pattern matching.

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.

So where does that leave physical files?  If a request is always parsed by the routing engine and then handed off to some function somewhere, how can we ever route a request for an image to actually return the physical image?

Well, it takes a tiny bit of legwork, but once we're through it, I'm confident you will see the huge advantages that routing has over simple url-rewriting.  We will show the equivalent of url-rewriting by handling a request for an image using an URL that doesn't map to a physical path, but be able to return the image anyway.

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.

The end result of MvcRouteHandler is not what we desire. We want to return an image, not instantiate a controller and run a method.   We want to skip dealing with controllers altogether in this case.  So lets create our own route handler that we'll use instead.

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 -- we're inserting our own handling module into the ASP.Net pipeline, that will handle the request much closer to the webserver/http level, rather than at the ASP.Net application level.

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("~/test.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 "test.jpg", and stream it to the browser.

Clearly, this should be adapted to your needs by actually using the filename parameter to find the physical files on your system.   But moving on -- how do we invoke this from our MVC app?  And how do we pass in the filename parameter, of which we'd like to reroute to some other physical path?

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.

And that's pretty much it!  You might be thinking, "this seems like a lot of extra work just to re-route a URL to a physical file that already existed in my web app!".   If you take a step back though, you'll see the power of this approach.  What if you wanted to log every request to the original URL to a special log file?  What if you wanted to also transform the image before returning it?  Perhaps launch a system executable or asynchronously hit a web service?  What if you wanted to…?

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.

kick it on DotNetKicks.com

Be Sociable, Share!

11 Responses to “ASP.Net MVC – How to route to images or other file types”

  1. Really nice and quite simple. Thanks for sharing.

  2. Brian says:

    Pretty cool, thanks for posting.

    I wonder what you would do if you had a folder in your /graphics directory, like:
    /graphics/icons/add.gif

    I believe it will not match the route (because it will split {filename} once it encounters the "/" between "icons" and "add".

    Is there a way to get the URL to ignore the slashes, so that it is essentially
    "graphics/{filepath}"?

  3. morgan says:

    Hey Brian,

    Absolutely – you can use the catch-all parameter in ASP.Net MVC for this. Define your route like:

    "graphics/{filepath*}"

    and it will capture the rest of the URL string without splitting it.

    (Apologies for the late reply… I didn't seem to get a notification about this comment…)

  4. chad says:

    Morgan i think you meant "graphics/{*filepath}" . right?

  5. Dinesh says:

    I dont think the implementation in GetHttpHandler should be writing to the Response. It is a Get method and should not have a state change. Ideally a seperate class which implements IHttpHandler interface should take care of writing to the Reponse. and the main Route Handler should just create an instance of it and return in GetHttpHandler. I implemented something similar when i had to serve static content (css,js,images)

  6. Erwin1441 says:

    Hi morgan,

    Thanks for this great guide! It's been most helpful. I've got everything to work fine in my local test environment where my application runs in the root of the development webserver.

    I'm having a problem however when uploading my application to my production server, where the application resides within a subdir of the root. How am I supposed to acces my images in this way?

    Kind regards,
    Erwin

  7. Erwin1441 says:

    I managed to solve this problem by writing an ImageHelper class and access my images like this:

    <img src="" alt="">

    My class looks like this:

    public static class ImageHelper
    {
    public static string ImgUrl(string applicationPath, string filename)
    {
    if (applicationPath == "/")
    {
    return "/Images/" + filename;
    }
    else
    {
    return applicationPath + "/Images/" + filename;
    }
    }
    }

  8. Mark says:

    Great post, simple yet powerful. thanks.

  9. Pawan Bhise says:

    Hi!
    Should i add a new class to implement IRouteHandler or in global.asax(in MVC).IF yes where will it be in Model folder or outside.plz suugest m confused.

  10. [...] I use Routing to re-route URL's to actual physical files. My code is pretty identical to this one. [...]

  11. embarus says:

    Hello morgan.

    Thank you for your useful post.
    I've tried to implement custom route myself.
    However, it does not work.

    My custom route is OggHandler and it is not called.

    I have posted the question at stackoverflow

    http://stackoverflow.com/questions/8550911/custom-route-is-not-called-for-asp-net-mvc-3-application-net-4

    and can download link at

    http://codesanook.com/shared/MvcOgg.zip

  12. Bibhu says:

    I am newbie to MVC, just wanted to clarify one thing, can't we ignore the .png files using IgnoreRoute(), so that the request is handled by ASP.NET routing engine, instead of handling the request and then routing to the files in the physical location.

Leave a Reply