Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

A dynamic class loading HttpHandlerFactory


:P
On this page:

I mentioned a few days ago that I was working on a Web application that's dealing with custom extensions to route requests to a very large number of custom 'script' pages. Basically I have a custom IHttpHandler based base class and custom subclasses that implement the actual handler logic which is essentially a script like implementation to call business objects and return an XML response to the client.

I love using HttpHandlers in general because of the high performance and simple implementation model - they are just awesome for custom architectures that need to manage Web requests in very specific ways - it's been a perfect fit for this project which is a distributed Flash application with an XML based data backend.

One of the issues I've been fighting with is that we use custom extensions for our requests (and this had to be done for backwards compatibility reasons). Athough I can map an HttpHandler to the the SimpleHandlerFactory in web.config:

<httpHandlers>
  <add path="*.xrns" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="false"/>
</httpHandlers>

which is all it takes to get things to execute just fine, the results in Visual Studio are less than optimal. As mentioned in my last post one issue with this scenario is that Visual Studio doesn't display this combination very nicely:

which is actually  problem in this project as we'll be ending up with close to 100 script requests.

There are a couple of solutions I came up to get around this issue:

  • Using .ashx in the project and rewriting the Url
  • Creating a class factory HttpHandlerFactory

Rewriting Urls from .ashx to .xrns

The former is pretty easy to do - a quick module implementation can simply rewrite URLs ending in .xrns and route them to the appropriate .ashx scripts. Scott Guthrie recently posted a nice summary of Rewriting options for ASP.NET that point out ways that this can be done the best of which are a couple of module that you can use to use RegEx expressions to re-write Urls by way of web.config entires.

If the needs are simple though it's just as easy to create a quick module of your own and add it to your project so there's no need for external dependencies. Here's what I used:

public class xrnsToashxMappingModule : IHttpModule 
 {
 
     public void Init(HttpApplication context)
     {
         context.BeginRequest += new EventHandler(context_BeginRequest);
     }
 
     void context_BeginRequest(object sender, EventArgs e)
     {
         HttpContext Context = HttpContext.Current;
         string Url = Context.Request.RawUrl;
 
         string LowerUrl = Url.ToLower();
 
 
         // *** ScriptExtension: ".xrns"
         if ( !LowerUrl.Contains(App.Configuration.ScriptExtension) )
             return;
 
         Url = Regex.Replace(Url, App.Configuration.ScriptExtension, ".ashx", 
                             RegexOptions.IgnoreCase);            
 
         Context.RewritePath(Url);                       
     }
 
     public void Dispose() { }
 }
 

It's hooked up in web.config like this:

<httpModules>
   <add name="xrnsToashxMappingHandler" type="SummaLPManager.xrnsToashxMappingModule,SummaLPManager" />
   <add name="wwScriptCompression" type="Westwind.Web.Controls.wwScriptCompressionModule,westwind.web.controls" />
</httpModules>

The module basically looks for any .xrns extension entries and routes them to .ashx. So now in our project I can simply create and store .ashx handler files while inbound requests actually fire .xrns requests.

The reason .ashx is important is because I created a custom Item Template for these new handlers and by using ashx as the extension I can create both .ashx and .ashx.cs template files and Visual Studio will properly drop these files as associated files into the project. It will also allow double clicking on the .ashx file and open the source file automatically.

Direct class Routing: HttpHandlerFactory to a class

But then I got to thinking: Why bother with a .ashx file at all? Why not have the request automatically instantiate the requested class based on the filename of the inbound request? This would allow instantiation of the class purely with a .cs file in the project.

This can be accomplished with an HttpHandlerFactory which is an abstraction layer that sits in front of the handler creation. In Web.Config you can specify a handler hookup either by IHttpHandler or by IHttpHandlerFactory. A factory basically allows you to make decisions about which class to instantiate as a handler in the GetHandler() method.

So in my scenario I want to be able to look at the filename and based on that create instantiate the matching handler that has the same classname. Here's the code I used to do this:

 

/// <summary>
/// Handler Factory that allows executing classes that match a filename only
/// and dynamically instantiates a matching class.
/// 
/// This version specifically checks for the ISummaLPHandler interface on
/// the instantiated handler to avoid executing malicious code.
/// 
/// Use this handler factory only if you don't want to map to phsical
/// .ashx files. In the SummaLP system mapping .xrns to the SimpleHandlerFactory
/// works if .ashx files are to be used.
/// </summary>
public class xrnsHandlerFactory : IHttpHandlerFactory
{
    /// <summary>
    /// Internal static hashtable that caches constructors for dynamic activation
    /// </summary>
    private static Hashtable constructorList = new Hashtable();
    private static object SyncLock = 0;
 
    public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
    {
        string Filename = Path.GetFileNameWithoutExtension(url).ToLower();
        Type PageType = null;
        ConstructorInfo constructor = null;
 
        if (!constructorList.ContainsKey(Filename))
        {
            lock(SyncLock)
            {
                if (!constructorList.ContainsKey(Filename))
                {
                    constructor = constructorList[Filename] as ConstructorInfo;
                    Type[] types = Assembly.GetExecutingAssembly().GetTypes();
                    foreach (Type type in types)
                    {
                        if (type.Name.ToLower() == Filename)
                            PageType = type;
                    }
 
                    if (PageType == null)
                    {
                        context.Response.StatusCode = 404;
                        context.ApplicationInstance.CompleteRequest();
                        return null;
                    }
 
                    constructor = PageType.GetConstructor(new Type[0]);
                    constructorList.Add(Filename, constructor);
 
                }
            }
        }
 
        constructor = constructorList[Filename] as ConstructorInfo;
        IHttpHandler Handler = constructor.Invoke(null) as IHttpHandler;
 
        // *** ensure that we have a valid handler that matches our
        //     execution template classes
        if (Handler is ISummaLPHttpHandler)
            return Handler;
 
        // *** Invalid Handler or couldn't load - return a 404 file not found
        context.Response.StatusCode = 404;
        return null;
    }
 
    public void  ReleaseHandler(IHttpHandler handler)
    {
 
    }
 
}

which is hooked up in web.config like this:

<httpHandlers>
  <add verb="*" path="*.xrns" type="SummaLPManager.xrnsHandlerFactory,SummaLPManager" />
</httpHandlers>

The code is a bit lengthy because it does some caching of the constructors to improve activation speed of the handlers. The code first picks up the filename of the request and then finds a matching class to instantiate. Note that I have to loop through the assembly types to do this since there's no easy way to get just the simple classname through the Assembly interfaces. Once a constructor is found it's cached in a list for fast access.

The constructor is then invoked to create an instance, the instance is checked for a specific interface, and if it doesn't match the request is refused, otherwise the handler is returned.

This works great and allows simply storing .cs files in the project.

In the end...

In the end after a bunch of back and forth with the customer we decided on using the UrlRewriting method and actually keeping the .ashx files in the project, because the .ashx files make it very obvious what type of object you are dealing with. As I mentioned this project would end up with about 100 different script files as well as a large number of fair number of classes and the easier it is to see what the purpose of each file is the better.

Posted in ASP.NET  IIS  

The Voices of Reason


 

Damien Guard
April 03, 2007

# re: A dynamic class loading HttpHandlerFactory

If you just wanted them joined in the solution explorer then the trick is to close the solution and open the .csproj file in notepad, then find the file you want to be shown under another, it will look something like:

<Compile Include="PortfolioInfo.xrns.cs" />

Change this to:

<Compile Include="PortfolioInfo.xrns.cs">
<DependentUpon>PortfolioInfo.xrns</DependentUpon>
</Compile>

That will nest the file - you'll have to do it for each one and for each new file until somebody writes a plug-in to do it - you might be able to also do it with a new template type.

If you were getting problems with VS not understanding what to do syntax-wise with xrns then Tools > Options > Text Editor > File Extension is the place to go and map xrns to the appropriate editor.

Not as clean as your solution admittedly but solves some of the problems without so much work.

[)amien

Rick Strahl
April 03, 2007

# re: A dynamic class loading HttpHandlerFactory

Damien, yeah that works for manually doing this, but it doesn't when you have an item template and you're creating these two files through the template. Could probably be done with an Add-in though but I'm too lazy to go that far <s>...

shrike
April 04, 2007

# re: A dynamic class loading HttpHandlerFactory

Yeah, I use a similar URL rewrite on a site moved from a host that allowed SSI / SHTML to a site that actively does not allow it. I was able to get the new host to point the .shtml extension to the .net isapi, so I wrote a quick console app to roll thru the site and change all the shtml files to .aspx and edit them to replace .shtml links w/ .aspx as well. Then, I just have a custom httphandler for shtml files that swaps the extension to .aspx, thus not breaking external links either. Works great, love it.


Your factory method though...I can't think of a good use for it for anything on my plate, but its definitely cool and Ill store that away mentally against a future need :D

Thanks for posting this kind of thing; love your site!

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024