ASP.NET MVC - Prevent Image Leeching with a Custom RouteHandler

4.67 (18 votes)

Have you ever noticed an unusually high number of requests in your web server log files for image files? It may well be that someone is linking to your images from their own site, and basically stealing your bandwidth. Here's how to implement a custom RouteHandler within an ASP.NET MVC application to prevent people leeching your images.

To begin with, I'll start by reviewing the flow of execution when a request comes into a typical ASP.NET MVC site. When IIS receives a request, it passes the request to ASP.NET, based on the url, or more specifically, the file extension. With IIS 7 in integrated mode (the default), all requests are mapped to ASP.NET, while with IIS 6, you can set up a wildcard mapping to cause this to happen too.

MVC Routing Overview

The first component that gets invoked within an ASP.NET MVC application is UrlRoutingModule which is in fact part of System.Web.Routing. Its job is to first check the incoming url to see if there is a matching file on disk. If there is, it passes the request back to IIS to serve the file directly.  If there is no matching file on disk, the module gets to work by examining its RouteCollection structure to determine where to send the request next. It will invoke the RouteHandler associated with the matching route's entry (MvcRouteHandler by default), which in turn invokes the appropriate HttpHandler to process the remaining logic associated with the request. Again, by default, this will be MvcHandler. With image files, which do exist in a subfolder somewhere within the application, the core routing module doesn't get that far as control is passed back to IIS prior to a RouteHandler being invoked.

Usually, the fact that ASP.NET bails out of processing requests for static files is desirable behaviour. However, if you want to perform some logic prior to serving these types of requests, you need to get involved programmatically at some point.  You could get round the default behaviour for static files by setting RouteTable.Routes.RouteExistingFiles = true. Phil Haack (Senior Program Manager for ASP.NET MVC) rightly describes this as the "nuclear option" in that every file - css, js, doc, pdf, xml etc will have to be handled by Routing. So the key to this is to ensure that requests for static files do not match existing files on disk, which will force the routing module to perform its lookup against the RouteTable (and then invoke a RouteHandler etc). This is simple to do, just by ensuring that your <img> elements reference a fictitious directory. For example, if your images are located in a subfolder called "images", img tags that reference a "graphics" folder will not find existing matching files.

Having done this, the next steps are to:

  1. Register a Route for image file requests
  2. Create a RouteHandler to handle those requests
  3. Create an HttpHandler to process the actual requests

We'll start with Step 2. There's not much point in starting with Step 1 - if you try to register a route and specify a custom RouteHandler for that Route without creating one first, you will not be able to compile.

The RouteHandler is simple. It has to implement IRouteHandler, which only specifies one method - IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext):

public class ImageRouteHandler : IRouteHandler
  public IHttpHandler GetHttpHandler(RequestContext requestContext)
    return new ImageHandler(requestContext);

Moving quickly on to the actual HttpHandler - ImageHandler that this method returns:

public class ImageHandler : IHttpHandler
  public ImageHandler(RequestContext context)

  private static void ProcessRequest(RequestContext requestContext)
    var response = requestContext.HttpContext.Response;
    var request = requestContext.HttpContext.Request;
    var server = requestContext.HttpContext.Server;
    var validRequestFile = requestContext.RouteData.Values["filename"].ToString();
    const string invalidRequestFile = "thief.gif";
    var path = server.MapPath("~/graphics/");

    response.ContentType = GetContentType(request.Url.ToString());

    if (request.ServerVariables["HTTP_REFERER"] != null &&
      response.TransmitFile(path + validRequestFile);
      response.TransmitFile(path + invalidRequestFile);

  private static string GetContentType(string url)
    switch (Path.GetExtension(url))
      case ".gif":
        return "Image/gif";
      case ".jpg":
        return "Image/jpeg";
      case ".png":
        return "Image/png";
    return null;

  public void ProcessRequest(HttpContext context)

  public bool IsReusable
    get { return false; }

At the bottom are the two methods that the IHttpHandler interface insists are implemented. The first of these, public void ProcessRequest(HttpContext context) is ignored. With an MVC application, we pass in a RequestContext object, not an HttpContext object, so an overload is provided that is MVC friendly, and it is this overload that does all the work. It is called within the ImageHandler's class constructor. In this case, ProcessRequest checks to see if my domain is part of the referer (which it should be if the image request is being made from a page in my site, and not someone else's site). If it isn't, I am returning an alternative image in the response. What you decide to return is up to you. I have seen all sorts of things returned by other people, including replacement images containing "nudies", or a simple 1 pixel transparent gif, or a plain and simple "404 Not Found".

There are other actions you could take at this point. For example, it may be that you are happy for Google to index your images, so you might want to check to see if the referer contains "". You may also want to log the referer if the condition fails, indicating a potential leecher.

Now we need to register the requests for images in the RouteTable, and to indicate that ImageRouteHandler should take care of them. In global.asax, this is added:

                 new Route("images/{filename}", new ImageRouteHandler()));

Hopefully, not only should this article have helped you to protect against image leeching, but should also have given you enough of the basics behind ASP.NET MVC Routing so that you can extend it as you need for other purposes.

Date Posted:
Last Updated:
Posted by:
Total Views to date: 48628


- Troels Thomsen

Please bear in mind that the `HTTP_REFERER` is not required and may be missing from the request. I understand that your code is only to educate and serve as an example, but if someone were to use it directly they would block a lot of legit requests from visitors having some kind of internet security application installed (many of these remove this header).

- Mike


It's a reasonable point that you make. In that case, you might decide to send the genuine image when the HTTP_REFERER is null.

- CareySon

hi mike,i have a question to ask...
if every image is come through the HttpHandler instead of download directly...if the website is about picture album or something like that...
is there any performance issue we should take into account?

- Mike


I very much doubt it. The Handler requires less resources than a regular web page.

- Mujahid Khaleel

The handler becomes more generic if the content type return mime is moved to a config file. This was you can control not only images, but pdf, doc, docx....

Mike, Thankyou for this, this article helped me quickly solve a problem, my handler also validates session for certain documents before serving.

- Tommy Carlier

The HttpRequest-class has a property UrlReferrer that contains the parsed URI of the referrer. I think that might be a better option than to use ServerVariables["HTTP_REFERER"].

- Denis

A bit off topic but looking at that flow diagram I wonder if it would be possible somewhere along the line on the server to decide to send back an image placeholder instead of a 404 for certain images. Doing it on the client has serveral cons.

- Mike


Both methods point to the same thing. The only advantage UrlReferrer has is that it is strongly typed. Therefore, it may be a better option for those who are not used to dealing with Request and Response objects in the raw.

- Mike


I'm uncertain as to what you are talking about. What kind of place holder do you have in mind and what purpose should it serve?

- Dubb

how does this tie into search engines? would the image be able to be retrieved by Google, Yahoo, or Bing?

- Denis


I'm think of a scenario where you can't be sure of the quality of the data for a listing object that has images. In some cases image uri come across the wire that don't actually exist. Covering this with JS requires either a block at the top of the page or a block that doesn't execute till after page load. The former slows down the page rendering and the later mean the user can see broken images till the JS gets around to executing.

- Mike


I discuss that in the third paragraph from the end. You can add urls to the check within the 1st if statement in ProcessRequest, or you can store them in an XML file, for instance, and check against that.

- Mike


Yes, you are right. That's wildly off-topic. And still unclear to me.

- tadyzzz

Why not just add handler directly to web.config? Just curious ;]

- alefi

tadyzzz - you don't need to regiter it in web.config, so why would you?

- Mik Robbins

Tried to implement this using VWD2010 but it does recognise the ROUTES, IRouteHandler. Am I missing a reference or something? Where does the line of code for Global.asax go exactly?

- Mike


The code goes in the RegisterRoutes method within global.asax.

- Rathinavelan

would like to know, how you implemented Globalization & Localization in your website, good job on this one.

thank you

- Mike


Ermmmm.. I haven't implemented that at all.

- Paul

Having some trouble getting this working with mvc3 on .net 4 framework. The images are just displaying in the browser as a broken img link. I'm kind of a noob to .net so please bear with me. I've created my images folder and put my images in there. Added the above scripts in my Globals.asax.cs, the new route declaration in the registerRoutes method and created the other classes in the same file below those already in there. I am using <img src="@Url.Content("~/graphics/header.gif")"> in my cshtml file. I'm testing this in the visual studio server. I noticed that the server.MapPath says it maps to a virtual directory in its rollover help. Do I need to set up my graphics folder as a virtual directory in IIS to get this going. It looks like it's not calling the new route at all as my breakpoint in the script is not getting hit. Is this something to do with the Razor views @Url pattern? Any ideas or hints here would be appreciated.

Recent Comments

Thomas 05/03/2018 00:59
In response to I'm Not Writing A Book On Razor Pages
There's a typo on this page: = true)] should be [BindProperty(SupportsGet = true)]...

Rolf Herbert 04/03/2018 19:25
In response to I'm Not Writing A Book On Razor Pages
So is MS deprecating razor Web Pages..? Is it dead..? I wish they would stop killing things so its...

Borut 17/02/2018 12:59
In response to I'm Not Writing A Book On Razor Pages
Mike, is it possible that Web Pages and Razor Pages "live" together in one web application? I a I...

hrboyce 09/02/2018 04:44
In response to I'm Not Writing A Book On Razor Pages
Mike, First thanks for doing this but I have to ask, any chance you would consider converting one of...

aziz sallam 07/02/2018 10:18
In response to I'm Not Writing A Book On Razor Pages
u are a great man...

Satyabrata Mohapatra 31/01/2018 11:36
In response to I'm Not Writing A Book On Razor Pages
This is a great news!!!! Thanks...

tangdf 30/01/2018 07:25
In response to I'm Not Writing A Book On Razor Pages
=> { o.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()); }); The extension method does...

Obinna Okafor 30/01/2018 04:02
In response to I'm Not Writing A Book On Razor Pages
Thank you very much. I would like to see a project built from scratch using Razor Pages. And it show...

rachida Dukes 31/10/2017 13:52
In response to Customising Identity in Razor Pages
Thanks again for this wonderful tutorial. I followed all the steps in this section called: Adding...

Rachida 31/10/2017 12:06
In response to Customising Identity in Razor Pages
Thanks very much for this wonderful tutorial, it helped a lot....