Table of Contents
This article covers how to make a custom IHttpHandler
to use the X Library from www.cross-browser.com embedded in an assembly.
- Embedding resources in an assembly.
- Using
IHttpHandler
for custom processing of requests.
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.
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.
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 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.
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.
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 );
}
}
}
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 function
s, 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.
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.
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>
-->
<add verb="GET" path="*.xlib" type="xLibrary.xLibraryHttpHandler,xLibrary" />
-->
<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".
From here, click on the "Home Directory" tab and select "Configuration".
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.
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).
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.
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.
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>
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.
- 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
- 4 July 2007
Initial version.
- 14 July 2007
Updated to X 4.17.