Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

URL Mapper Handler Factory

0.00/5 (No votes)
15 Apr 2010 1  
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.

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.

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.

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.

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

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

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.

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.

<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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here