How to enable pretty urls with Asp.Net MVC and IIS6

I’ve been working with the asp.net mvc bits lately and everything has been going smooth.  That is until I tried to deploy to a Windows 2003 server.  All of a sudden my pretty urls looked like crap.  When it comes to deploying an Asp.Net MVC app to IIS6, you have two options. 

  1. You can either setup a wildcard mapping
  2. You can add an isapi mapping and have an extension (.mvc) use the asp.net runtime.  

Both techniques have their good and bad points.  The wildcard mapping gives you nice looking urls, but all requests (including images, css, scripts, etc) go through the asp.net pipeline.  If performance is a concern, and generally it should be, you’ll want to avoid needlessly mapping all requests through the asp.net pipeline.  Alternatively, mapping just an extension to the asp.net runtime gives you the best performance but the urls are less desirable (Eg. http://localhost/Home.mvc/About).

My solution is to use Isapi_rewrite to transparently rewrite all nice urls to *.mvc file extension requests.  So, the user sees http://localhost/Home/About, while the webserver (after the isapi_rewrite filter has been applied) sees http://localhost/Home.mvc/About.  Isapi_rewrite is fast (written in c/c++), so you don’t have to worry about the performance, and your static files (scripts, images, etc) do not go through the pipeline.  Best of all, your users (and Google) see pretty urls.  It’s a win win setup, for IIS6.

On to the good part…

  1. Setup your .mvc extension map with IIS
    1. Open IIS Mangaement Console
    2. Expand your computer
    3. Expand Websites
    4. Right-click the website you’d like to edit (Most times it’ll be called "Default Web Site") and click Properties
    5. Click the Home Directory tab
    6. Click Configuration…
    7. Click Add
    8. Executable: c:windowsmicrosoft.netframeworkv2.0.50727aspnet_isapi.dll
    9. Extension: .mvc
    10. Verbs: Limit to: GET,HEAD,POST,DEBUG
    11. Un-check Verify that file exists
    12. Click Ok
  2. Install isapi_rewrite
  3. Put an .htaccess file in your website directory and edit as follows (make note of how I ignore the Content directory):
    RewriteEngine on
    RewriteRule ^Home(/)?$ $9 [NC,R=301]
    RewriteRule ^$ Home [NC]
    RewriteRule ^([w]+)$ $1.mvc [NC]
    RewriteRule ^(?!Content)([w]*)/(.*) $1.mvc/$2 [NC]
    
  4. Modify your routes to include the routes with and without the .mvc extension.  My routes look like:
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
    "Default",                                              // Route name
    "{controller}/{action}/{id}",                           // URL with parameters
    new { controller = "Home", action = "Index", id = "" }, // Parameter defaults
    new { controller = @"[^.]*" }                          // Parameter constraints - Do not allow dots in the controller name
    );
    routes.MapRoute(
    "Default.mvc",                                          // Route name
    "{controller}.mvc/{action}/{id}",                       // URL with parameters
    new { controller = "Home", action = "Index", id = "" }, // Parameter defaults
    new { controller = @"[^.]*" }                          // Parameter constraints - Do not allow dots in the controller name
    );
    

Using both routes with and without the .mvc extension gives you an added bonus.  The built in helper functions for generating routes use the first route found, so it will not generate urls with the .mvc extension.  The mvc framework does recognize and process both forms of urls, though.  So defining routes this way preserves debugging with cassini as well as deployment scenarios.

One other thing to note.  The "RewriteRule ^Home(/)?$ $9 [NC,R=301]" line in the .htaccess file is there to redirect requests to /Home and /Home/ back to the base url.  "RewriteRule ^$ Home [NC]" then redirects requests to the base url to the home controller (transparent to the user).  The final two lines transparently map controllers to urls with the .mvc extension, allowing the request to be processed by asp.net (via the IIS .mvc extension map setup on step 1).

Everything seems to be working great.  Hope this helps anyone in the same position.

** Updated for Preview 4