Click here to Skip to main content
15,908,015 members
Articles / Web Development / ASP.NET

URL Mapper Handler Factory

Rate me:
Please Sign up or sign in to vote.
4.17/5 (8 votes)
15 Apr 2010CPOL2 min read 34.9K   385   49  
An IHttpHandler/IHttpHandlerFactory to map requests.

Introduction

In most enterprise environments, there are usually lots of web applications from various origins (bought as a ready-made product or built for a specific purpose) that are published in most different mediums (URLs sent by e-mail, enterprise portals, rich client applications hosting web browsers, etc.).

In such an environment, coupling every caller with a specific entry point that can change with time and versions leads to a maintenance nightmare.

Solution

The solution to this problem is using a smart URL mapper that is used as the entry point and maps the first request to the real entry point of the application.

In order to make this mapper versatile, it should have a way to transform the request to the mapper into a request to the application. Such transformations should be configurable and allow some logic in its working, and the best technology to accomplish that is using XSLT.

Implementation

The mapper is implemented as an HTTP Handler that is created by an HTTP Handler Factory.

The URL Mapper Handler Factory

This is the class responsible to provide the ASP.NET engine with the HTTP Handler that will handle the request.

To enhance performance, the handler factory keeps a stack of idle handles for each URL.

C#
private readonly Dictionary<string, Stack<UrlMapperHandler>> 
        globalCache = new Dictionary<string, Stack<UrlMapperHandler>>();

When a handler is requested, the stack for the corresponding URL is checked for an available instance. If one exists, it's returned; otherwise, a new one is created and returned.

C#
public System.Web.IHttpHandler GetHandler(System.Web.HttpContext context, 
       string requestType, string url, string pathTranslated)
{
    lock (globalCache)
    {
        Stack<UrlMapperHandler> factoryCache;
        if (globalCache.TryGetValue(pathTranslated, out factoryCache))
        {
            if (factoryCache.Count != 0)
            {
                return factoryCache.Pop();
            }
        }
    }
    return new UrlMapperHandler(pathTranslated);
}

When the handler is done with the request, if it's a reusable handler, it's pushed into the idle stack.

C#
public void ReleaseHandler(System.Web.IHttpHandler handler)
{
    if (handler.IsReusable)
    {
        UrlMapperHandler urlMapperHandler = (UrlMapperHandler)handler;
        lock (globalCache)
        {
            Stack<UrlMapperHandler> factoryCache;
            if (!globalCache.TryGetValue(pathTranslated, out factoryCache))
            {
                globalCache.Add(factoryCache = new Stack<UrlMapperHandler>());
            }
            factoryCache.Push(urlMapperHandler);
        }
    }
}

The URL Mapper Handler

This is the class that handles the request.

When an instance is created, an instance of a XslCompiledTransform is created and the transformation document is loaded into it.

C#
public UrlMapperHandler(string pathTranslated)
{
    this.pathTranslated = pathTranslated;
    this.xslt = new XslCompiledTransform();
    this.xslt.Load(this.pathTranslated);
}

When the request processing starts, an XML Document is created and loaded with the request information. The query string items are loaded into the QueryString element and the form items are loaded into the Form element according to the following schema.

Request Schema

C#
public void ProcessRequest(HttpContext context)
{
    // Create the request document
    XmlDocument requestDocument = new XmlDocument();
    requestDocument.AppendChild(requestDocument.CreateElement("Request"));
    FillItems(requestDocument, "QueryString", context.Request.QueryString);
    FillItems(requestDocument, "Form", context.Request.Form);
  
    ...
}

The request is then transformed using the previously created XslCompiledTransform instance, providing a XsltArgumentList that will add request specific parameters to the transformation. The transformed document will have the following schema:

MappedRequest Schema

C#
public void ProcessRequest(HttpContext context)
{
    ...
  
    // Load the transformation parameters
    XsltArgumentList arguments = new XsltArgumentList();
    arguments.AddParam("UserName", string.Empty, 
        (context.User == null) ? string.Empty : context.User.Identity.Name);
    arguments.AddParam("ApplicationPath", 
                       string.Empty, context.Request.ApplicationPath);
    arguments.AddParam("AppRelativeCurrentExecutionFilePath", 
                       string.Empty, 
                       context.Request.AppRelativeCurrentExecutionFilePath);
  
    // Transform the request
    byte[] buffer;
    using (MemoryStream mappedRequestStream = new MemoryStream())
    {
        using (XmlTextWriter mappedRequestWriter = 
               new XmlTextWriter(mappedRequestStream, Encoding.UTF8))
        {
            this.xslt.Transform(requestDocument, arguments, 
                                mappedRequestWriter);
        }
        buffer = mappedRequestStream.GetBuffer();
    }
  
    // Load the mapped request document
    XPathDocument mappedRequest;
    using (MemoryStream mappedRequestStream = new MemoryStream(buffer))
    {
        mappedRequest = new XPathDocument(mappedRequestStream);
    }
  
    ...
}

Finally, the mapped request is created and executed based on the mapped request document.

C#
public void ProcessRequest(HttpContext context)
{
    ...
  
    // Build and execute the mapped request
    XPathNavigator navigator = 
      mappedRequest.CreateNavigator().SelectSingleNode("/MappedRequest");
    string path = navigator.GetAttribute("path", string.Empty);
    if (!string.IsNullOrEmpty(path))
    {
        StringBuilder sbPath = new StringBuilder(path);
  
        // Build the query string
        bool pathHasQuery = path.IndexOf('?') >= 0;
        XPathNodeIterator queryStringNodes = 
           navigator.Select("/MappedRequest/QueryString/Item");
        while (queryStringNodes.MoveNext())
        {
            string item = 
              queryStringNodes.Current.GetAttribute("name", string.Empty);
            if (!string.IsNullOrEmpty(item))
            {
                if (pathHasQuery)
                {
                    sbPath.Append('&');
                }
                else
                {
                    sbPath.Append('?');
                    pathHasQuery = true;
                }
                sbPath.Append(HttpUtility.UrlEncode(item));
                sbPath.Append('=');
                sbPath.Append(HttpUtility.UrlEncode(queryStringNodes.Current.Value));
            }
        }
  
        // Set the HttpContext items
        XPathNodeIterator httpContextNodes = 
           navigator.Select("/MappedRequest/HttpContext/Item");
        while (httpContextNodes.MoveNext())
        {
            string item = httpContextNodes.Current.GetAttribute("name", string.Empty);
            if (!string.IsNullOrEmpty(item))
            {
                context.Items[item] = httpContextNodes.Current.Value;
            }
        }
  
        // Execute the mapped request
        context.Server.Execute(sbPath.ToString(), false);
    }
}

Usage and Configuration

In order to use this URL mapper, the HTTP Handler Factory must be registered in the web.config of the site in the httpHandlers section and in the IIS mappings.

XML
<configuration>
  <system.web>
    <httpHandlers>
      <add verb="GET" path="*.srvx" 
         type="Pajocomo.UrlMapper.UrlMapperHandlerFactory, Pajocomo.UrlMapper"/>
    </httpHandlers>
  </system.web>
</configuration>

Improvements

To maintain performance in the creation of handler instances and to reduce memory usage, a better cache mechanism should be used to expire instances based on idle time and changes in the mapper definition file.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paulo Morgado
Portugal Portugal

Comments and Discussions

 
-- There are no messages in this forum --