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

xLibrary - Using a custom IHttpHandler to access embedded JavaScript

0.00/5 (No votes)
28 Feb 2009 1  
This article covers how to make a custom IHttpHandler to use the X Library from www.cross-browser.com embedded in an assembly.

Table of Contents

Introduction

This article covers how to make a custom IHttpHandler to use the X Library from www.cross-browser.com embedded in an assembly.

Key Technologies Used

  • Embedding resources in an assembly.
  • Using IHttpHandler for custom processing of requests.

Features

All JavaScript and XML documentation for the X library will be embedded into the assembly.

The custom IHttpHandler will return all necessary methods to use the X Library function you require.

The assembly is easy to update. As long as the format of the XML documentation files do not change, to update to a new version of the X Library, all you have to do is update the .js/.xml files in the project and recompile.

Problem

The X Library from www.cross-browser.com is a great tool to have. It standardizes many JavaScript functions for use across different browsers. The best part of the X Library is that it is modular. Each function is in its own .js file, so you don't have to send the browser more than it needs. This is great for keeping your traffic to a minimum. To keep us from having to reference each script in a separate <script> block, it has a nice tool called XC that compiles all the scripts you use into one .js file. This is a great tool, but I'm lazy and I don't want to run it every time I update my pages.

Solution

I decided to come up with an ASP.NET wrapper for this library that would serve only the scripts I needed without having to compile them with the XC tool described above, and without having to use a separate <script> block for each function. All the scripts and documentation will be embedded in this new xLibrary assembly. It will have a class that implements the IHttpHandler interface, and will serve only the scripts needed for each request.

The Code

Overview

The xLibrary assembly contains three classes and the embedded JavaScript and XML. The classes are as follows:

  • xFunction
  • A simple object to hold the function's name, source, and dependencies.

  • xLibraryHttpHandler
  • An implementation of the IHttpHandler interface that will handle the requests for the X functions.

  • xLibraryLoader
  • A helper object that loads the embedded JavaScript into memory to speed up the request of X functions.

Embedding the JavaScript and XML in the Assembly

The first thing that you'll do is take all the .xml and .js files from the x\lib folder in the .zip file that was downloaded from www.cross-browser.com, and add them to a lib folder in the project. Select them all and in the "Properties" window, and change the "Build Action" property to "Embedded Resource". When Visual Studio builds the assembly, this setting will cause the compiler to embed all of these files in the assembly. Each of these embedded resources will be named as follows:

[assemblyName].[relative-path-to-file].[filename].[extn]

In this case, the assembly name is xLibrary and the path is lib. So, the xaddclass.js file in the lib folder will be given the embedded name xLibrary.lib.xaddclass.js. If the files were in separate folders, such as "lib\xml" and "lib\js", then xaddclass.js would be named xLibrary.lib.js.xaddclass.js, and xaddclass.xml would be named xLibrary.lib.xml.xaddclass.xml.

Implementation

Once they are embedded in the assembly, we need to be able to access them from the code. This is where the xLibraryLoader class comes in. The first method I will talk about is GetResourceStream.

private Stream GetResourceStream( string resourceName )
{
   return Assembly.GetExecutingAssembly().GetManifestResourceStream( resourceName );
}

This is a pretty simple method used to retrieve the embedded resource as a Stream. Pass in a resource name like xLibrary.lib.xaddclass.js, and it will return a stream for that file.

The next method is GetResource.

private string GetResource( string resourceName )
{
   return new StreamReader( GetResourceStream( resourceName ) ).ReadToEnd();
}

This one is similar to GetResourceStream, but instead of returning the Stream, it converts the entire Stream into a string to return to the caller.

In order to save time when a request is made (note: this is at the expense of memory usage), when we initialize the xLibraryLoader class, it loads a Hashtable with all the functions it can find embedded in the assembly. Then, the Hashtable can be used to return the script needed instead of relying on Reflection. To accomplish this, we use FillHashtable.

This method starts off by creating the Hashtable and getting a reference to the current assembly. If we wanted, we could embed the resources in another assembly and modify this and the GetResource methods. Once it has the reference to the necessary assembly, it gets a list of all the resources embedded in that assembly. Then, we start to loop through all the resources it found.

private void FillHashtable ()
{
   xLibraryData = new Hashtable();

   Assembly      asm         = Assembly.GetExecutingAssembly();
   string[]      resources   = asm.GetManifestResourceNames();

   foreach ( string resource in resources )
   {

The first thing we do is make sure we are on an XML file and not a JavaScript file. We only have to worry about these two, because that's all we have in the assembly. If we had XML files other that the ones we use here, we would need additional checking to verify we had an X Library XML documentation file. We then set up the variables that will hold our information about the function specified by the XML documentation file.

if ( String.Compare( Path.GetExtension( resource ), ".xml", true ) == 0 )
{
    string    id            = string.Empty;
    string    source        = string.Empty;
    ArrayList dependencyIds = new ArrayList();

Now that we know we have an XML documentation file, we need to parse it to get the information we need. First, we create an instance of System.Xml.XmlDocument and load into it the Stream of the embedded file. Next, using XPath, we select the ID attribute (/@id) of the root element x_symbol (/x_symbol). We assign the value of this attribute to the id variable we set up earlier.

Next, we select the /x_symbol/sources/src/file element. There should only be one of these per XML file, but if there are more than one, it will only select the first instance. Because it's an element, it doesn't have a Value property, but does have an InnerXml property. We use string.Format to get the file name into a format that GetResource and GetResourceStream can use to find our JavaScript file.

Finally, we get a list of all the dependencies for this X function. The SelectNodes method returns an XmlNodeList of all the nodes that match the XPath expression /x_symbol/dependencies/dep. We go through each of them and add the name of the X function this depends on to the ArrayList.

XmlDocument doc = new XmlDocument();
doc.Load( GetResourceStream( resource ) );

XmlNode x_symbolId = doc.SelectSingleNode( "/x_symbol/@id" );
id                 = x_symbolId.Value;
x_symbolId         = null;

XmlNode srcFile    = doc.SelectSingleNode( "/x_symbol/sources/src/file" );
source             = GetResource(
                         string.Format( "xLibrary.lib.{0}", srcFile.InnerXml )
                     );
srcFile            = null;

XmlNodeList deps = doc.SelectNodes( "/x_symbol/dependencies/dep" );
foreach( XmlNode dep in deps )
{
    dependencyIds.Add( dep.InnerXml );
}
deps = null;

Now, we take the id, source, and dependencyIds we collected, and put them in a simple object to hold them. If you've never used the ToArray method of the ArrayList class, it's handy, but it is a one-to-one copy. Remember that if you are dealing with extremely large arrays (which we are not here), it can take some time to copy it over.

Then, we add the xFunction we created to the Hashtable and index it by its name. Finally, complete the loop and do the next one.

         xFunction function =
            new xFunction( id, source, dependencyIds.ToArray( typeof(string) ) as string[] );

         xLibraryData.Add( function.Name.ToLower(), function );
      }
   }
}

IHttpHandler

We have two more methods here, but before I get to them, I want to talk about the IHttpHandler interface and the xLibraryHttpHandler class.

The first time I started messing around with IHttpHandler, I got a little confused on how it works and what it does. So, to get started with it, let's do an example. The most common implementation of IHttpHandler is the System.Web.UI.Page class. The implementation of IHttpHandler is only one method (ProcessRequest) and one property (IsReusable). The IsReusable property is self-explanatory. If it returns true, then the ASP.NET process will reuse the same instance for each request. If IsReusable returns false, it creates a new instance for each request. The ProcessRequest method takes a parameter: an instance of HttpContext. The HttpContext object has references to things like the HttpRequest object (which contains the query string, among other things) and the HttpResponse object (which is what streams information from the server to the client). When a user requests an ASPX page, the ASP.NET process hands off responsibility for the request to an instance of the System.Web.UI.Page class by calling its ProcessRequest method. The Page then determines what page actually exists at the address that is requested. It then parses that page and returns everything on the Response object's stream. (Note: this is a simplified version of what actually happens. For more information, check out the .NET Framework Documentation for ASP.NET Request Processing, or to get a good feel for how Microsoft did it, use Lutz Roeder's Reflector to peer into the System.Web assembly.)

We will do something similiar to the Page object, but much simpler. Because xLibraryHttpHandler implements IHttpHandler, it has the IsReusable property and the ProcessRequest method.

private xLibraryLoader loader;

public xLibraryHttpHandler()
{
   loader = new xLibraryLoader();
}

The first thing we do in the constructor is create a new instance of the xLibraryLoader class. Note that the xLibraryLoader constructor calls the FillHashtable method described above.

public bool IsReusable
{
   get
   {
      return true;
   }
}

Because the xLibraryLoader constructor calls the FillHashtable method, we don't want it to do that parsing on every request, or a busy server would grind to a halt quickly. This is why we set IsReusable to return true. This way, the ASP.NET worker process will reuse the same instance of the xLibraryHttpHandler on each request. (Note: I have not done benchmarks on which is really faster, and I am going on gut instinct. Before putting into production use, I would recommend actually benchmarking the two different ways and deciding which is better for your use.)

public void ProcessRequest ( HttpContext context )
{
   HttpResponse Response = context.Response;
   HttpRequest  Request  = context.Request;

   Response.ContentType  = "application/x-javascript";

   string[] functions    = Request.QueryString["functions"].Split( ',' );
   string   script       = loader.GetScript( functions, true );

   Response.Write( "// Copyright (C) 2007 Brodrick Bassham..." );
   Response.Write( script );
}

The last part of xLibraryHttpHandler is the ProcessRequest method. It starts by making aliases to the Response and Request objects to make them more familiar to use (this is what the Page object does as well). Then, we set the ContentType header to "application/x-javascript". There is much debate on whether to use "application/x-javascript" or "text/javascript" or others. The standard is "application/x-javascript" and Internet Explorer, which doesn't support "application/x-javascript", ignores it anyway, no matter what you set it to. The next line should give you a big hint on how we are going to use this once it's all done. It takes the QueryString and looks for the "functions" part of it (e.g.: http://localhost/xLibrary.xlib?functions=func1,func2 would return "func1,func2") and splits it into an array with the comma as the delimiter. The GetScript method of our instance of xLibraryLoader does just that; it builds the script required to use the functions listed in the array it is given. I will talk about that method below. The last thing the ProcessRequest method does is send the script to the client on the Response stream.

public string GetScript ( string[] functions, bool alertOnError )
{
   ArrayList     added  = new ArrayList();
   StringBuilder output = new StringBuilder();

This is the GetScript method from xLibraryLoader. It takes two parameters, a string array called functions and a boolean called alertOnError. The functions parameter is an array of function names that need to be output. If we have been asked for a function we don't have, the alertOnError parameter is used to display a JavaScript alert box. The ArrayList we create is a list of all the function names we have already added to the output.

foreach( string function in functions )
{
    if ( !added.Contains( function ) )
    {

First check to make sure we haven't already added this function. We don't want duplicates in the output.

xFunction func = xLibraryData[ function.ToLower() ] as xFunction;

Pull the requested function out of the Hashtable.

if ( func != null )
{
    output.Append( func.Source );
    added.Add( func.Name );
    output.Append( GetScript( func.Dependencies, alertOnError, added ) );
}

If we find the function, append the source to the output and then get the source for all its dependencies.

else
{
    output.AppendFormat( "// X Function {0} was not found.\n", function );

    if ( alertOnError )
    {
       output.AppendFormat( "alert('X Function {0} was not found.');\n", function );
    }
}

This code appends a comment to the output to show it couldn't find the function we wanted. If we are supposed to alert the user to an error, then append the code to display a JavaScript alert box with the message.

      }
   }

   return output.ToString();
}

Once we have looped through all of the requested functions, return the generated script.

private string GetScript ( string[] functions, bool alertOnError, ArrayList added )
{
   StringBuilder output = new StringBuilder();

   foreach( string function in functions )
   {
      if ( !added.Contains( function ) )
      {
         xFunction func = xLibraryData[ function.ToLower() ] as xFunction;

         if ( func != null )
         {
            output.Append( func.Source );

            added.Add( func.Name );

            output.Append( GetScript( func.Dependencies, alertOnError, added ) );
         }
         else if ( alertOnError )
         {
            output.AppendFormat( ERROR_ALERT, function );
         }
      }
   }

   return output.ToString();
}

This version of the GetScript method is also in the xLibraryLoader class. This one is a recursive method that keeps calling itself until it has found all the dependencies it needs. It is first called from the non-recursive GetScript method.

How To Use It All

Now that we have gone over what the code actually does, how do we put it to use? Well, the assembly we have built needs to go into the bin folder of the web application we are using. Next, we have two options: we can either modify the web.config file, or we can create a custom .ashx file.

Option 1 - Web.config

We'll start with the web.config file. The changes will be made to the following section (you will have to add this section if it doesn't exist):

<configuration>
   <system.web>
      <httpHandlers>
         <!-- Option 1 - Custom extension -->
         <add verb="GET" path="*.xlib" type="xLibrary.xLibraryHttpHandler,xLibrary" />

         <!-- Option 2 - Specific .ashx file -->
         <add verb="GET" path="xLibrary.ashx" type="xLibrary.xLibraryHttpHandler,xLibrary" />
      </httpHandlers>
   <system.web>
<configuration>

The first web.config option above is a little complicated. Because the IIS process doesn't know anything about a .xlib file; it doesn't know that it needs to process it in any way. If we leave it as is, it will return a 404 (Not found) for any .xlib file that doesn't physically exist on the server. If the file does exist on the server, it will serve that file to the browser and not do the processing we want it to do. To correct this, we need to tell the IIS process about the .xlib file type.

First, open the IIS Management Console by right-clicking on "My Computer" and selecting "Manage". Expand the "Services and Applications" item on the tree. Drill down to your web application; in this instance, "Default Web Site". As shown below, right-click on the web application name and select "Properties".

Computer Management Window

From here, click on the "Home Directory" tab and select "Configuration".

Default Website Properties Window

Next, you will see the "Application Configuration" dialog. Here, you will find what program handles the different files on your server. If an extension is not specified in this list, it will be offered as a regular file. Select the "Add" button to continue.

Application Configuration Window

Finally, we see the "Add/Edit Application Extension Mapping" dialog. Browse to "aspnet_isapi.dll". It should be located in the "C:\Windows\Microsoft.NET\Framework\v1.0.3705\" directory (Note: substitute "Windows" for your Windows install folder and "v1.0.3705" for the version of the .NET Framework you are using).

Add/Edit Application Extension Mapping Window

The "Add/Edit Application Extension Mapping" dialog can be tricky in Windows XP. The Extension field must start with a period. Example: .xlib. If you don't need to post data to any file with this extension, you can limit the verbs to GET. If you don't want to be bothered with actually creating a file (the IHttpHandler implementation will handle all output necessary), then un-check "Check that file exists". Note: In Windows XP, sometimes the "OK" button stays grayed out. If this is the case, make sure everything is selected as you want and click into the text box with the location of "aspnet_isapi.dll". This should expand the text of the box and enable the "OK" button.

This process tells IIS that when a page is requested with this extension -- instead of sending the file's content and letting the browser deal with it -- we want the ASP.NET processor to look at it and figure out what to do. When we add xLibraryHttpHandler into the <HttpHandlers> section of the web.config file, it tells the ASP.NET processor exactly what class to use to handle the request.

So now, when a browser requests any file with the extension .xlib, instead of looking for the file itself, the server will call the ProcessRequest method of the xLibrary.xLibraryHttpHandler class in the xLibrary assembly.

The second web.config option tells the application that every request for "xLibrary.ashx" should be routed through the xLibrary.xLibraryHttpHandler class in the xLibrary assembly. The great thing is that IIS, by default, knows it has to let ASP.NET process .ashx files. With this change to the web.config file, the ASP.NET process knows that if "xLibrary.ashx" is requested, it needs to call xLibraryHttpHandler.

This option is much easier for those without access to the Management Console, such as many shared hosting environments.

Option 2 - Custom .ashx file

The final and perhaps easiest option is creating a custom .ashx file. I said earlier that IIS and ASP.NET by default know they need to process any requests for .ashx files. If we create a file called "xLib.ashx" and leave the file empty, we get an error when the file is requested. This is because ASP.NET knows it needs to process it, but it hasn't been told how. To fix this, we add this line to the file:

<%@ WebHandler Class="xLibrary.xLibraryHttpHandler,xLibrary" %>

This works like the @Page directive in .aspx files. It tells the ASP.NET process that in order to process the request, it has to call the xLibrary.xLibraryHttpHandler class in the xLibrary assembly.

Actual Use

Assuming you created the custom .xlib extension above and used the corresponding web.config entry, you can use the embedded JavaScript as follows:

<script
   type="text/javascript"
   src="xLibrary.xlib?functions=[comma,sparated,list,of,needed,functions]">
</script>

Example:

<script
   type="text/javascript"
   src="xLibrary.xlib?functions=xAddEventListener,xGetElementsByClassName">
</script>

Points of Interest

The .NET framework provides a way to embed all your JavaScript and XML files into an assembly for easy maintenance. I like having all my loose files embedded like this, because it creates a cleaner directory structure and allows you to check custom permissions on files that you set in your application. Because some shared hosting providers make it hard to edit, you can use a custom IHttpHandler to achieve the same goals.

By adding custom extensions and IHttpHandler implementations, you can create your own way to handle files on your server. This includes custom parsers.

The demo website was built using Visual Web Developer Express, and the source solution was built using Visual C# Express, both available for free from Microsoft.

Other Resources

  • www.cross-browser.com
  • The home of X, a Cross-Browser JavaScript Library.

  • W3Schools XPath Tutorial
  • A good overview of XPath.

  • Dean Edwards' Packer
  • A great utility that shrinks and obfuscates JavaScript. The .NET version of this tool could be used to extend this project even further, allowing compressing and obfuscating of the JavaScript on the fly. I may decide to implement this later.

  • Lutz Roeder's Reflector
  • A tool for seeing the source code of non-obfuscated .NET assemblies.

  • The MSDN Library
  • The first place you should always go for information on the .NET Framework.

  • My Blog
  • Code Bassham

History

  • 4 July 2007
  • Initial version.

  • 14 July 2007
  • Updated to X 4.17.

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