Introduction

In this article, I will show you how you can effectively protect a file or folder by using the IHttpHandler. From this, I hope that people will learn a bit more about HttpHandlers and why they should/should not use them. I also hope that people will get a better understanding of how to implement their own handler. I shall write the code in both C# (article and source code) and VB.NET (source code only).

This article will cover the following:

What is an HttpHandler

ASP.NET uses HttpHandlers to interact with requests before a web page is served. An HttpHandler must implement the two methods of the IHttpHandler interface. These methods are:

  • processRequest - This contains the logic required for processing a request.
  • IsReusable - This tells the .NET runtime whether or not the HttpHandler can be used again for requests of a similar type.

Now that we know what HttpHandlers are, let's see how to implement them.

Creating an HttpHandler

To create an HttpHandler, start by opening up a new or existing web site in the language of your choice. After you get yourself the setup, add a class to your project and name it FileprotectionHandler. (Note: you will be asked to put it in the app_code folder, it is fine to do so.)

You should then see the following output:

C#
using System;
using System.Collections.Generic;
using System.linq;
using System.Web;

/// <summary> 
/// Summary description for FileprotectionHandler
/// </summary> 
public class FileprotectionHandler
{
    public FileprotectionHandler()
    {
        //
        // TODO: Add constructor logic here
        //
    }
}

Next, we need to implement the IHttpHandler interface before we can add our logic. You should change the class decoration to the following:

C#
public class FileprotectionHandler: IHttpHandler

Then, if you press Shift+Alt+F10 with your cursor on the IHttpHandler, you will see that Visual Studio will auto implement the required methods for you (or you could type them in yourself). Making your code look like:

C#
using System;
using System.Collections.Generic;
using System.linq;
using System.Web;

public class FileprotectionHandler : IHttpHandler
{
    public FileprotectionHandler()
    {
        //
        // TODO: Add constructor logic here
        //
    }

    bool IHttpHandler.IsReusable
    {
        get { throw new NotImplementedException(); }
    }

    void IHttpHandler.processRequest(HttpContext context)
    {
        throw new NotImplementedException();
    }
}

If you want to use this handler for every request, then you have to change the implementation in the IsReusable method to this:

C#
bool IHttpHandler.IsReusable
{
    get { return true; }
}

Alternatively, just change the true to false. We also need to add some variables that we will need in the next section. These are a domain name, a static folder, and the relative path to the image itself. These are all strings.

Getting the domain name automagically

In order to get the domain name automatically, you need to add a new Global Application Class to your project. We need this as we are interested in the Session_Start event.

In this event, we get the URL that is requested and set Session["DomainName"].

Global.asax (C#)
void Session_Start(object sender, EventArgs e) 

void Session_Start(object sender, EventArgs e) 
{
    Session["DomainName"] = 
        String.Concat(HttpContext.Current.Request.Url.ToString().Replace(
        HttpContext.Current.Request.Url.PathAndQuery.ToString(), "") , "/");
}

Now we can set the variables that we will need for the rest of the program. These will be set in the IHttpHandler.processRequest method, as shown below:

C#
void IHttpHandler.processRequest(HttpContext context)
{
    // Define your Domain Name Here
    String strDomainName = context.Session["DomainName"].ToString(); 
    // Add the RELATIVE folder where you keep your stuff here
    String strFolder = "~/static/";
    // Add the RELATIVE pATH of your "no" image
    String strNoImage = "~/static/no.jpg";
}

Dealing with HttpMethods

Sometimes we may want to execute different code based on what the request is. The two main types of requests we will deal with are HTTP GET and HTTP pOST. Setting up the framework for this is pretty straightforward. As with many things in computer programming, there are many different ways to do things. In this article, I will use a switch statement, which should keep our code a little bit cleaner. To set the switch up to execute different code on events, use the following code:

C#
void IHttpHandler.processRequest(HttpContext context)
{
    // Define your Domain Name Here
    String strDomainName = context.Session["DomainName"].ToString(); 
    // Add the RELATIVE folder where you keep your stuff here
    String strFolder = "~/static/";
    // Add the RELATIVE pATH of your "no" image
    String strNoImage = "~/static/no.jpg";
    switch (context.Request.HttpMethod)
    {
        case "GET":
            // TODO :: Implement code for GET method.
            break;
        case "pOST":
            // TODO :: Implement code for pOST method.
            break;
    }
}

As you can see, we have deleted the not-implemented exception code. Request.HttpMethod returns a string which we can compare against. In this instance, the HttpMethod is already in uppercase. Before we go any further, I would like to show you part of the code that will be used to return the content type to the browser.

C#
public string GetContentType(string filename)
{
    // used to set the encoding for the reponse stream
    string res = null;

    FileInfo fileinfo = new System.IO.FileInfo(filename);

    if (fileinfo.Exists)
    {
        switch (fileinfo.Extension.Remove(0, 1).ToLower())
        {
            case "png":
                res = "image/png";
                break;
            case "jpeg":
                res = "image/jpg";
                break;
            case "jpg":
                res = "image/jpg";
                break;
            case "js":
                res = "application/javascript";
                break;
            case "css":
                res = "text/css";
                break;
        }
        return res;
    }
    return null;
}

I have already added the five main file extensions that I protect. It will check to see if the file exists. If it does not, then the function will automatically return null. We are now ready to implement the code for the POST method. In this article, I never want a user to POST to my files in my folder. So I will set the ContentType and send the image back to the user in the following function:

C#
HttpContext SendContentTypeAndFile(HttpContext context, String strFile)
{
    if (String.IsNullOrEmpty(strFile))
    {
        return null;
    }
    else
    {
        context.Response.ContentType = GetContentType(strFile);
        context.Response.TransmitFile(strFile);
        context.Response.End();
        return context;
    }
}

Now we have the basis to complete the rest of the method, checking to see if the URL the request comes from is ours or not. It is completed like so:

C#
void IHttpHandler.processRequest(HttpContext context)
{
    // Define your Domain Name Here
    String strDomainName = context.Session["DomainName"].ToString(); 
    // Add the RELATIVE folder where you keep your stuff here
    String strFolder = "~/static/";
    // Add the RELATIVE pATH of your "no" image
    String strNoImage = "~/static/no.jpg";

    switch (context.Request.HttpMethod)
    {
        case "GET":
            String strRequestedFile = context.Server.Mappath(context.Request.Filepath);
            if (context.Request.UrlReferrer != null)
            { 
                String strUrlRef = context.Request.UrlReferrer.ToString();
                String strUrlimageFull = ResolveUrl(strFolder);

                if (strUrlRef.Contains(strUrlimageFull))
                {
                    context = SendContentTypeAndFile(context, strNoImage);
                }
                else if(strUrlRef.StartsWith(strDomainName))
                {
                    context = SendContentTypeAndFile(context, strRequestedFile);
                }
                else
                {
                    context = SendContentTypeAndFile(context, strNoImage);
                }
            }
            else
            {
                context = SendContentTypeAndFile(context, strNoImage);
            }
            break;
        case "pOST":
            context = SendContentTypeAndFile(context, strNoImage);
            break;
    }
}

Adding an HttpHandler to Web.config

To get the ASP.NET runtime to recognize that you have an HttpHandler you wish to implement, you will need to add the following line to your web.config:

<add verb="*" path="*/static/*" validate="true"  type="FileprotectionHandler"/>

It needs to go in under the "HttpHandlers" section and should be placed underneath the second line. Following this, test your application and all should be well :)

Adding sessions to an HttpHandler

Adding session state to the HttpHandler is easier than you may think. First of all, you need to add the following statement to your using statements section:

C#
using System.Web.SessionState;

Following that, you need to change the class declaration to the following:

C#
public class FileprotectionHandler : IHttpHandler, IRequiresSessionState

Then, in the "IHttpHandler.processRequest" method, you can access the Session state in two ways:

C#
// 1) you can use this way so you do not have to write context.Session constantly
HttpSessionState Session = context.Session;
Session["SomeKey"].ToString();

// 2) or you can just type it out in full
context.Session["SomeKey"].ToString();

Protecting sub-folders

To protect sub-folders, simply add a new web.config file to the sub folder and ensure that the contents of the file look like:

<?xml version="1.0"?>
<configuration>
    <appSettings/>
    <connectionStrings/>
    <system.web>
      <httpHandlers>
        <add verb="*" path="*" validate="true" type="FileprotectionHandler"/>
      </httpHandlers>
    </system.web>
</configuration>

Hopefully you now understand HttpHandlers a bit more. You can use the code in any shape or form. All I ask is that you link me to your site so I can see it in action :)

References

To-do list

  • Add ability to use web.config instead of hard coded paths
  • Clean up code
  • Add better handling of URLs

History

  • 08/05/11: Changed some source code, uploaded to github for easier code changes.
  • 20/08/09: Added source code download as I forgot that originally.
  • 05/10/09: Changed to make more dynamic. Improved several code features. Added automatic domain finding.
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"