About the author

Miron Abramson
Software Engineer,
CTO at PixeliT
and .NET addicted for long time.
Open source projects:
MbCompression - Compression library

Recent comments



The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2018

Creative Commons License

Blog Flux Directory
Technology Blogs - Blog Top Sites

New & Shiny WebResource.axd compression Module

This project is now an Open source project on  CodePlex web site. Latest code and updates can be found at  http://www.codeplex.com/MbCompression


Running after the best performance for your web application, one of the biggest improvement you can do is to use compression. Page, Stylesheet & Javascript files (images are already compressed format). There are few compression modules out there, all you needs to do is just 'goolge' for it, and the implementation is really very simple. Not a lot of changes in your code are needed.

ASP.NET bring us a new way to integrate resources into our site - using WebResource.axd handler. Those are like a 'Virtual containers' for other file that embedded into a DLL file. It can be Css, Javascript, Images... Actually this is an handler that load the resource from the DLL file. 

The Problem

The pages, .css & .js files can be easily compress with one of the modules on the net. The basic technique is to pass your response through 'Compression Filter'.

WebResource.axd, that became very large (specially when using AJAX) are break down for unknown reason when you try to compress them that way. That result is you will not compress them, and your client will download big files that contains js or css (sometimes of 70kb and even more)

The Challenge

I couldn't find on the net a compression module that compress this type of file. I decided I will try to do it by myself.

First Try.  Working, but bad performance. ( Compress pages and WebResource.axd files in ASP.NET )

I started to make tests and to play with WebResources files. Only WebResources that contains 'text' content (css, js, text) can be consider as compressible. Images are not.

I was thinking, maybe I can load the WebResource, get it's content as string, compress the string and send it to the response as string (or as byte[]). After some tests, It discovered as true. The WebResource was passing to the client completely and without any missing characters. The problem was to get the WebResource content. For that, I had to make a 'fake' request based on the url, asking again for the WebResource and then compress it.

HttpWebResponse request = (HttpWebRequest)WebRequest.Create(app.Context.Request.Url.OriginalString + "&p=1");
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream responseStream = response.GetResponseStream();

That make every time the client ask for webresource, the server made double HTTP request - what a waste.

This module was working. It was actually compressing the WebResources, But suffer from bad performance.

Second Try. Working better. But not enough. ( Compress your pages, css, js and WebResources.axd files for better performance )

Thanks to Mads Kristensen that point me to the performance issue and offer an idea how to improve it, The second try was better. The first time the WebResource was loading, it was compressed and inserted into the Application object, and next time it was requested, it was served from there directly.

It was actually running on our production servers (Where I work) for a while, and compressed the WebResources. But I was not happy with that solution. The 'fake' request throw an exception once in a while. Maybe one for some thousands of users. Also it was not stable enough on some other servers, and throw 404 errors.

Third Try.  Success! Working as expected. No errors and really WebResource compression module!

The compression part in the first two tries was perfect. compress the content of the webresource as byte[] and serve it to the client. The only problem is to load the actual resource content.

In this time, I decided to 'attack' the problem from another way. .NET using System.Web.Handlers.AssemblyResourceLoader to load and serve WebResource requests. I decided to implement my own AssemblyResourceLoader handler that will support compression. unfortunately,  AssemblyResourceLoader handler is registered into the system, and can't be remove. Any try to do so will lead you to an exception.

So, what I did was to create an HttpModule, using the event PreRequestHandlerExecute that is one step before the handle action, do my stuff here, and then end response and jump over the hander step. That way I can prevented AssemblyResourceLoader handler to do his stuff.

To create my version for the AssemblyResourceLoader, I used the source code of 'ASP.NET 2.0 AJAX Extensions' and a reflector to see the code of the original AssemblyResourceLoader.

There are few steps in that module:

1.  Parsing the request query string. The request for WebResource has an encrypted query string with the the resource data separate with pipes chars ('|'):     Assemby info,  Resource name and a char for the 'assembly type'. The query string is encrypted using the machinekey, and needs to be decrypted using reflection. The problem is, that share hosting servers (such GoDaddy) are not permitting using reflection from within your code. Only from assemblies that are in the GAC. What I had to do is to find a public method from an assembly that in the GAC of every server.

The solution I found (if somebody have a better idea, I will be glad to hear) is creating an empty Membership class, that inherits from MembershipProvider, and implement only one public method that using the 'DecryptPassword' method from the  MembershipProvider. it is also using the machinekey.   (To use that decryption, add the attribute reflectionAlloweded="false" to the CompressorSettings section, and machinekey section to the web.config. See Readme.txt file in the source)

private static MethodInfo _decryptString;
private static readonly Object _getMethodLock = new Object();

internal static string DecryptString(string input)
    if (Settings.Instance.ReflectionAlloweded)
        if (_decryptString == null)
            lock (_getMethodLock)
                if (_decryptString == null)
                    _decryptString = typeof(Page).GetMethod("DecryptString", BindingFlags.Static | BindingFlags.NonPublic);
        return (string)_decryptString.Invoke(null, new object[] { input });
        return EmptyMembership.Instance.DecryptString(input);

2.  Load the assembly. The char for the 'assembly type' in the query string indicate if the assembly is the System.Web assembly or the one that encrypted in the query.

3.  Load the resource info. Using the resource name from the query, we need to load the resource info - it's content type, if it exist and if it needs to perform substitution calls      (WebResource within another WebResource. If so, we let the original System.Web.Handlers.AssemblyResourceLoader to handle this WebResource). Of course we check in our cache if that resource already asked for, and load the info from the cache.

4.  Setting the HttpCachePolicy headers to cache the request, and adding a ETag for checking in the next time.

5.  Get the resource stream from the assembly.

    Stream resourceStream = assembly.GetManifestResourceStream(resourceName)

6. Compress the resource stream if needed. If the resource is 'compressable' it can be read as a string, so we load it as StreamReader, read it to the end, convert the string into byte[] and compress it. If not (in case of images) we just load the resource, and serve it to the client.

     public static byte[] Compressor(byte[] buffer, string encodingType)
         using (MemoryStream memStream = new MemoryStream())
             Stream compress = null;

             // Choose the compression type and make the compression
             if (String.Equals(encodingType, "gzip", StringComparison.Ordinal))
                 compress = new GZipStream(memStream, CompressionMode.Compress);
             else if (String.Equals(encodingType, "deflate", StringComparison.Ordinal))
                 compress = new DeflateStream(memStream, CompressionMode.Compress);
                 // No compression
                 return buffer;

             compress.Write(buffer, 0, buffer.Length);

             return memStream.ToArray();

7.  Last thing to do, is to send the byte[] (compressed or not to the response stream, and end the response.

      HttpContext.Response.OutputStream.Write(compressedData, 0, compressedData.Length);

Optional Improvements for the WebResource module (maybe in the feature) 

  • Storing the compressed resource in the HttpContext.Cache
  • Compress using the preferred compression algorithm by the client (parsing the 'Accept-encoding' header)
  • Remove white spaces & comments from css & Javascript resources

Tips when working with WebResources

  • Add to your robots.txt file the following:
    User-agent: *
    Disallow: /*.axd$
  • To prevent the search engines to cache the axd handlers. (not 100% solution, but not harmful Cool)

  • Add a machinekey to your Web.Config. ASP.NET use it for validation and encryption/decryption of the view state & the WebResource query strings. Don't relay on the auto generate key. It can lead you to some Cryptographic Exceptions. Use one of the on-line tools to generate key for you. 


Full instruction can be found in the readme.txt file in the source code.

The Source Code

The source code contain handlers to compress .js & .css files, simple module to compress pages, the module to compress the WebResources and helper classes. Every module/handler is 'stand alone' and can be use independent in case you choose to use your own implementation for the pages of css/js.

The Readme.txt file include all needed info to implement the handlers/modules.

Important: If you are using the WebResource compression module on share hosting thatnow allowed using reflection from your code (as GoDaddy), you must  add the attribute reflectionAlloweded="false" to the CompressorSettings section.

Latest code can be downloaded from: http://www.codeplex.com/MbCompression

See also post:  Update to my compression module to compress third party scripts 

Currently rated 4.5 by 15 people

  • Currently 4.466668/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by Miron on Saturday, October 13, 2007 5:59 AM
Permalink | Comments (137) | Post RSSRSS comment feed