Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

There are several problems with ASP.NET MVC application when deployed on IIS 6.0:

  • Extensionless URLs give 404 unless some URL Rewrite module is used or wildcard mapping is enabled
  • IIS 6.0 built-in compression does not work for dynamic requests. As a result, ASP.NET pages are served uncompressed resulting in poor site load speed.
  • Mapping wildcard extension to ASP.NET introduces the following problems:
    • Slow performance as all static files get handled by ASP.NET and ASP.NET reads the file from file system on every call
    • Expires headers doesn't work for static content as IIS does not serve them anymore. Learn about benefits of expires header from here. ASP.NET serves a fixed expires header that makes content expire in a day.
    • Cache-Control header does not produce max-age properly and thus caching does not work as expected. Learn about caching best practices from here.
  • After deploying on a domain as the root site, the homepage produces HTTP 404.

Problem 1: Visiting your website's homepage gives 404 when hosted on a domain

You have done the wildcard mapping, mapped .mvc extention to ASP.NET ISAPI handler, written the route mapping for Default.aspx or default.aspx (lowercase), but still when you visit your homepage after deployment, you get:

image

You will find people banging their heads on the wall here:

Solution is to capture hits going to "/" and then rewrite it to Default.aspx:

image

You can apply this approach to any URL that ASP.NET MVC is not handling for you and it should handle. Just see the URL reported on the 404 error page and then rewrite it to a proper URL.

Problem 2: IIS 6 compression is no longer working after wildcard mapping

When you enable wildcard mapping, IIS 6 compression no longer works for extensionless URL because IIS 6 does not see any extension which is defined in IIS Metabase. You can learn about IIS 6 compression feature and how to configure it properly from my earlier post.

Solution is to use an HttpModule to do the compression for dynamic requests.

Problem 3: ASP.NET ISAPI does not cache Static Files

When ASP.NET's DefaultHttpHandler serves static files, it does not cache the files in-memory or in ASP.NET cache. As a result, every hit to static file results in a File read. Below is the decompiled code in DefaultHttpHandler when it handles a static file. As you see here, it makes a file read on every hit and it only set the expiration to one day in future. Moreover, it generates ETag for each file based on file's modified date. For best caching efficiency, we need to get rid of that ETag, produce an expiry date on far future (like 30 days), and produce Cache-Control header which offers better control over caching.

image

So, we need to write a custom static file handler that will cache small files like images, Javascripts, CSS, HTML and so on in ASP.NET cache and serve the files directly from cache instead of hitting the disk. Here are the steps:

  • Install an HttpModule that installs a Compression Stream on Response.Filter so that anything written on Response gets compressed. This serves dynamic requests.
  • Replace ASP.NET's DefaultHttpHandler that listens on *.* for static files.
  • Write our own Http Handler that will deliver compressed response for static resources like Javascript, CSS, and HTML.

image

Here's the mapping in ASP.NET's web.config for the DefaultHttpHandler. You will have to replace this with your own handler in order to serve static files compressed and cached.

Solution 1: An Http Module to compress dynamic requests

First, you need to serve compressed responses that are served by the MvcHandler or ASP.NET's default Page Handler. The following HttpCompressionModule hooks on the Response.Filter and installs a GZipStream or DeflateStream on it so that whatever is written on the Response stream, it gets compressed.

image

These are formalities for a regular HttpModule. The real hook is installed as below:

image

Here you see we ignore requests that are handled by ASP.NET's DefaultHttpHandler and our own StaticFileHandler that you will see in next section. After that, it checks whether the request allows content to be compressed. Accept-Encoding header contains "gzip" or "deflate" or both when browser supports compressed content. So, when browser supports compressed content, a Response Filter is installed to compress the output.

Solution 2: An Http Module to compress and cache static file requests

Here's how the handler works:

  • Hooks on *.* so that all unhandled requests get served by the handler
  • Handles some specific files like js, css, html, graphics files. Anything else, it lets ASP.NET transmit it
  • The extensions it handles itself, it caches the file content so that subsequent requests are served from cache
  • It allows compression of some specific extensions like js, css, html. It does not compress graphics files or any other extension.

Let's start with the handler code:

image

Here you will find the extensions the handler handles and the extensions it compresses. You should only put files that are text files in the COMPRESS_FILE_TYPES.

Now start handling each request from BeginProcessRequest.

image

Here you decide the compression mode based on Accept-Encoding header. If browser does not support compression, do not perform any compression. Then check if the file being requested falls in one of the extensions that we support. If not, let ASP.NET handle it. You will see soon how.

image

Calculate the cache key based on the compression mode and the physical path of the file. This ensures that no matter what the URL requested, we have one cache entry for one physical file. Physical file path won't be different for the same file. Compression mode is used in the cache key because we need to store different copy of the file's content in ASP.NET cache based on Compression Mode. So, there will be one uncompressed version, a gzip compressed version and a deflate compressed version.

Next check if the file exits. If not, throw HTTP 404. Then create a memory stream that will hold the bytes for the file or the compressed content. Then read the file and write in the memory stream either directly or via a GZip or Deflate stream. Then cache the bytes in the memory stream and deliver to response. You will see the ReadFileData and CacheAndDeliver functions soon.

image

This function delivers content directly from ASP.NET cache. The code is simple, read from cache and write to the response.

When the content is not available in cache, read the file bytes and store in a memory stream either as it is or compressed based on what compression mode you decided before:

image

Here bytes are read in chunk in order to avoid large amount of memory allocation. You could read the whole file in one shot and store in a byte array same as the size of the file length. But I wanted to save memory allocation. Do a performance test to figure out if reading in 8K chunk is not the best approach for you.

Now you have the bytes to write to the response. Next step is to cache it and then deliver it.

image

Now the two functions that you have seen several times and have been wondering what they do. Here they are:

image

WriteResponse has no tricks, but ProduceResponseHeader has much wisdom in it. First it turns off response buffering so that ASP.NET does not store the written bytes in any internal buffer. This saves some memory allocation. Then it produces proper cache headers to cache the file in browser and proxy for 30 days, ensures proxy revalidate the file after the expiry date and also produces the Last-Modified date from the file's last write time in UTC.

How to use it

Get the HttpCompressionModule and StaticFileHandler from:

http://code.msdn.microsoft.com/fastmvc

Then install them in web.config. First you install the StaticFileHandler by removing the existing mapping for path="*" and then you install the HttpCompressionModule.

image

That's it! Enjoy a faster and more responsive ASP.NET MVC website deployed on IIS 6.0.

Share this post : del.icio.us digg dotnetkicks furl live reddit spurl technorati yahoo

kick it on DotNetKicks.com

Published Mon, Jun 30 2008 9:12 by omar

Comments

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Tuesday, July 01, 2008 1:53 AM by Mehfuz Hossain

Can you please tell me what is the problem with ActionFilter?

ActionFilter attribute gives a way to to  easily compress and cache your response.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Wednesday, July 02, 2008 12:06 AM by Scott Hanselman

Fantastic post!

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Wednesday, July 02, 2008 1:52 AM by omar

Hi Mehfuz,

ActionFilter is a great way to selectively enable compression on some requests. My solution turns on compression for all requests. This way you don't need to include the filter attribute on all action methods.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Friday, July 04, 2008 7:56 AM by Kazi Manzur Rashid

Hi Omar, as Mehfuz said I see a number of benifits of Action Filters over HttpModules, but before that just want to correct that You can apply the action filter on the Controller which in turn applies to all the action/public methods, it is not required to decorate method.

1. I do not have to stick with a Fixed Cache Duration.

2. Unable to exclude or need to have some kind of rule engine to exclude it from caching.

3. One of the main reason of MVC becoming so poplular is TDD, with HTTPModules it is harder and sometime impossible to fit in TDD.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Friday, July 04, 2008 8:30 AM by omar

Hi Kazi,

I believe there's a confusion here. I haven't done caching for dynamic requests. My module is only for enabling compression site wide which does not have Action/Controller specific requirement.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Friday, July 11, 2008 10:57 AM by Steve Sanderson

Hi Omar

> Mapping wildcard extension to ASP.NET introduces the following problems...

I don't think this is always true. If you avoid interfering with the request, then DefaultHttpHandler leaves the request unhandled, which means IIS comes back in and serves the file natively (and then you'll get all the expiration headers etc.). The problems you describe only apply when DefaultHttpHandler serves the request through StaticFileHandler.

For details of how it chooses what to do, see sticklebackplastic.com/.../The+continuing+saga+of+StaticFileHandler++meet+DefaultHttpHandler.aspx

Thanks for writing this blog post though; it has lots of nice ideas.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Friday, July 11, 2008 12:28 PM by omar

Hi Steve,

Thanks for the pointer. As I use a Response Filter to compress dynamic page output, so in my case, the DefaultHttpHandler calls StaticFielHandler to handle it. So, IIS 6 does not handle the file. This is why I made my custom Static File handler.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Friday, August 15, 2008 5:16 AM by Niran

Doesn't applying url format  like

"{controller}/{action}/{id}.aspx"

solve such issues?

I am still very new to asp.net mvc. And is using this type of url format. This solution will work in shared hosts also.

Nirandas

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Wednesday, November 12, 2008 12:53 PM by Ricardo Cavalcanti

How do I set StaticFileHandler up in IIS 7?

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Monday, November 24, 2008 7:27 AM by jorgebg

Hi Omar,

Great job!

One small detail, I think there is a mistake on the method you use for "non-handled" extensions:

I removed the compressiontype parameter of the TransmitFileUsingHttpResponse in the lines below.

       private void TransmitFileUsingHttpResponse(HttpRequest request, HttpResponse response,

           string physicalFilePath, FileInfo file)

       {

           if (file.Exists)

           {

               // We don't cache/compress such file types. Must be some binary file that's better

               // to let IIS handle

this.ProduceResponseHeader(response, Convert.ToInt32(file.Length), ResponseCompressionType.None,

                   physicalFilePath, file.LastWriteTimeUtc);

               response.TransmitFile(physicalFilePath);

               Debug.WriteLine("TransmitFile: " + request.FilePath);

           }

           else

           {

               throw new HttpException((int)HttpStatusCode.NotFound, request.FilePath + " Not Found");

           }

       }

Kind Regards!

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Wednesday, December 03, 2008 2:44 AM by prabhjot singh

Sir,

I am not using the MVC approach. After deploying the site on the server and implementing everything what you discussed in your earlier article, The "Resource not found" error is thrown. what should i do??

Regards

Prabhjot  

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Tuesday, January 13, 2009 12:33 PM by Vladimir

How I can get it with IIS7? It's doesn't work properly. Please, wrote strings in web.config I must change.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Monday, January 19, 2009 4:06 AM by Meysam

Hi Zabir,

Given we don't want to compress image files, why don't you simply remove ".png", ".jpeg", ".jpg", ".gif" & ".bmp" extensions from FILE_TYPES so that they get bypassed and handled and cached by IIS?

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Friday, February 06, 2009 11:24 AM by Raphael

Hi Omar,

Wonderful tutorial here. Just a doubt: Does it work with all versions of ASP.NET mvc? I have a site developed around the preview 2, so would this work on it?

# IIS7?

Wednesday, March 25, 2009 5:33 AM by Sparhawk

Hi,

I tried the solution. On my development server it works beautifully.

On the production server the compression works for .aspx and .asmx but not for .js and .css.

I am assuming that the StaticFileHandler is not used at all by IIS7.

Any ideas?

Sparhawk

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Wednesday, March 25, 2009 6:06 AM by omar

You can turn on static file compression on IIS 7. I saw there was some config to change on IIS. Forgot.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Thursday, April 02, 2009 3:20 PM by Joel Nylund

Hi, what I am seeing is that compression is working for my static files but caching is not when I have wildcard mapping. If I disable wildcard mapping then everything works.

I tried your approach and it doesnt work for me, I get a bunch of bytes rendered on the html page (I think maybe its being double compressed) Do I need to turn off compression somewhere on IIS to do this?

If I remove the compression module the page gets served but with lots of errors.

Any ideas/suggestions why I am seeing slightly different behavior? Is it possible MS patched something since then that changes this a bit?

# Why no support for "If-Modified-Since" header?

Sunday, April 26, 2009 7:26 AM by sean

Hi Omar,

 Thank you for the nice piece of code. However, how come you did not check for the "If-Modified-Since" header? That way, you just need to send back a 304 to the client instead of the entire file all over again. I've described how to do this in my6solutions.com/.../Enabling-client-side-caching-on-ASP-NET-MVC-on-IIS-60.aspx.

Or is there something I'm missing?

Sean

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Thursday, May 07, 2009 8:18 PM by Sergey

Nice post.

Would it be better to use ASP.NET ISAPI and remove static file directories from wildcard map so static files are not read by ISAPI every time, and then implement your HttpCompressionModule so asp.net mvc files get compressed?

blog.codeville.net/.../overriding-iis6-wildcard-maps-on-individual-directories

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Wednesday, July 08, 2009 1:55 PM by Zulu

I am getting the following error after deploying my site(IIS).

I think problem is with connetionstring. PLEASE HELP..

ERROR ::

Server Error in '/Fed' Application.

Login failed for user 'VOLLER\ASPNET'.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.Data.SqlClient.SqlException: Login failed for user 'VOLLER\ASPNET'.

Source Error:

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Saturday, August 29, 2009 10:21 AM by Atit

Omar, you can remove wildcard mapping on your static files and all such request will be handled by native IIS code..

blog.codeville.net/.../overriding-iis6-wildcard-maps-on-individual-directories

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Monday, December 21, 2009 7:42 AM by Dmitry

Omar, I've used your 404 error approach to URL rewriting (msmvps.com/.../serve-extensionless-url-from-asp-net-without-using-isapi-module-or-iis-6-wildcard-mapping.aspx) to make extensionless URLs in MVC applications.

With postbacks working!

elitebrains.com/.../iis-6-and-asp-net-mvc-extensionless-urls-that-work-even-in-postbacks

# re: Deploy ASP.NET MVC on IIS 6, solve 404, compression and performance problems

Thursday, January 14, 2010 1:00 PM by Kevin

I get the following compile error when implement the HttpCompressionModule.cs:

Compiler Error Message: CS0122: 'System.Web.StaticFileHandler' is inaccessible due to its protection level

Source Error:

HttpContext context = HttpContext.Current;

if (context.Handler is StaticFileHandler

  || context.Handler is DefaultHttpHandler) return;

I am using IIS 6 and ASP.NET 2.x Runtime.

Any help would be appreciated.  Thanks.

Leave a Comment

(required) 
(required) 
(optional)
(required)