Saturday, February 27, 2016

HTTPHandler and HTTPModule in ASP.NET

ASP.NET handles all the HTTP requests coming from the user and generates the appropriate response for it. ASP.NET framework knows how to process different kind of requests based on extension, for example, It can handle request for .aspx.ascx and .txt files, etc. When it receives any request, it checks the extension to see if it can handle that request and performs some predefined steps to serve that request.
Now as a developer, we might want to have some of our own functionality plugged in. We might want to handle some new kind of requests or perhaps we want to handle an existing request ourselves to have more control on the generated response, for example, we may want to decide how the request for .jpg or .gif files will be handled. Here, we will need an HTTPHandler to have our functionality in place.
There are also some scenarios where we are ok with the way ASP.NET is handling the requests but we want to perform some additional tasks on each request, i.e., we want to have our tasks execute along with the predefined steps ASP.NET is taking on each request. If we want to do this, we can have HTTPModule in place to achieve that.
So from the above discussion, it is clear that HTTPHandlers are used by ASP.NET to handle the specific requests based on extensions. HTTPModule, on the other hand, is used if we want to have our own functionality working along with the default ASP.NET functionality. There is one Handler for a specific request but there could be N number of modules for that.

Using the Code

Let us try to understand these two concepts by writing a small application for each. What we will do is we will try to have a mechanism where we can process the web pages with extension like .bspx and .cspx. Although this is a very unrealistic scenario, a similar concept is used to have search engine friendly URLs so perhaps it's not that realistic either.
Note: The HTTPHandler example here is just for demonstration purpose, I am not recommending the use ofHTTPHandlers for something that I am about to do now. HTTPHandlers should ideally be used to customize the handling of existing MIME types and not for serving search engine friendly URLs or non standard URLs.

Implementing the HTTPHandler

So with our problem definition, let us try to see how we can handle the request for .cspx pages usingHTTPHandlers. First we need to have the handler class with us, so let us create the handler class.
public class CspxHandler :IHttpHandler
{
    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {

    }
}
The class should have a method ProcessRequest and a property called IsReusable. The property tells whether this handler can be reused or not and the method will be called whenever a request for that type comes. But wait, Where have we defined the type of request where this handler should be invoked? This can be defined either in IIS, if we have a handler common to all the sites running on that server or we can configure it inweb.config file, if the handler is specific for a website. Let's do that in web.config file for now.
<httpHandlers>
    <add verb="*" path="*.cspx" type="CspxHandler"/>
</httpHandlers>
Here we registered our handler to specify that if any request for .cspx file comes, it should be forwarded to our handler.
Now, since we don't have any "real" files with .cspx extension, what we will do is we will handle the request for.cspx and in turn push the user to the corresponding .aspx file.
public class CspxHandler :IHttpHandler
{
    public bool IsReusable
    {
        get { return false; }
    }

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/plain";

        if (context.Request.RawUrl.Contains(".cspx"))
        {
            string newUrl = context.Request.RawUrl.Replace(".cspx", ".aspx");
            context.Server.Transfer(newUrl);
        }
    }
}
Whenever a request for .cspx file comes, we will handle it in our handler and show the corresponding .aspx file instead. Let's see how it works.
Note: I have also changed the startup page name to Default.cspx but there is no page like that. I want my handler to handle that and show me the actual default page.

Important: I reiterate, This example is just for illustration. This is not how HTTPHandlers should be used at all.HTTPHandlers should ideally be used to customize the handling of existing MIME types.
Well the pages seems to be working fine and the user will see .cspx URL for his request. But there is one problem. The way we wrote our handler is not good to handle the postback. If I add a button on any of these pages and do a postback, the original URLs will be visible. So it is not a good solution to the problem but it sure demonstrated the way Handlers can be used.

Implementing the HTTPModule

How do we solve the problem we just saw. Well, our application needed URL rewriting and HTTPHandlers are a bad solution for that and should never be used for that. So perhaps the guys using this technique to have search friendly URLs should rethink their strategy. SO how can we solve this problem really.
Let us look at the requirement again, All we needed was to show the user URLs which are different than the real URLs and process the real URLs internally. So we don't need custom handlers, we are ok with the way ASP.NET engine is handling these requests but we need custom activities to be done during the processing phase. So it looks like we can solve it using HTTPModule.
So let's go ahead and write an HttpModule that will:
  1. Check for file extension on request.
  2. If it finds a .bspx extension it changes it to .aspx (or find real URLS if we are implementing search friendly URLs)
  3. It will pass the request to the default handler, since the page is still aspx.
  4. Once the response is generated, it will write back the original .bspx URL to users browser.
public class MyBModule : IHttpModule
{
    public void Dispose()
    {

    }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
        context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
        context.EndRequest += new EventHandler(context_EndRequest);
        context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest);
    }

    void context_AuthorizeRequest(object sender, EventArgs e)
    {
        //We change uri for invoking correct handler
        HttpContext context = ((HttpApplication)sender).Context;

        if (context.Request.RawUrl.Contains(".bspx"))
        {
            string url = context.Request.RawUrl.Replace(".bspx", ".aspx");
            context.RewritePath(url);
        }
    }

    void context_PreRequestHandlerExecute(object sender, EventArgs e)
    {
        //We set back the original url on browser
        HttpContext context = ((HttpApplication)sender).Context;

        if (context.Items["originalUrl"] != null)
        {
            context.RewritePath((string)context.Items["originalUrl"]);
        }
    }

    void context_EndRequest(object sender, EventArgs e)
    {
        //We processed the request
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        //We received a request, so we save the original URL here
        HttpContext context = ((HttpApplication)sender).Context;

        if (context.Request.RawUrl.Contains(".bspx"))
        {
            context.Items["originalUrl"] = context.Request.RawUrl;
        }
    }
}
Also we need to register our module so that it can be invoked, we will do that in our web.config file.
<httpModules>
    <add name="MyBModule" type="MyBModule" />
</httpModules>
And now let's run the application:

So here we solved the problem of URL reverting back to the original on postback. This is also the ideal way of doing that.

Points of Interest

In this article, we saw how we can implement a basic HTTPHandler and HTTPModule. We saw each of their roles in page processing frameworks. We worked on an example that tried to solve the URL rewriting first the wrong way by using HTTPHandler (but we understood how to write HTTPhandler) and then the right way of doing URL rewriting using HTTPModule (we got to understand that too.
The emphasis of this article was solely on understanding how we can have HTTPHandlers and HTTPModulesworking. The example is a little unrealistic and perhaps a little misleading too but since I made that point really clear, it shouldn't be a problem.
Before wrapping up, there is one last thing that we should know about handlers. It is also possible to handle the request asynchronously. ASP.NET provides a mechanism for creating asynchronous handler and then increases the performance of a web page (implements the IHttpAsyncHandler do that).

No comments:

Post a Comment