Using ASP.NET MVC on IIS 6 without the .MVC Extension
Tuesday, May 27 2008 7 Comments
One awesome features of ASP.NET MVC is the clean URLs. If you aren't familiar with them, here is a sample:
/products/list
(versus a typical /ProductList.aspx)
The default route for ASP.NET MVC looks like this:
/{controller}/{action}
Using this route you can map most of the URLs that your application will need. There is a problem, however, when running ASP.NET MVC on IIS6 (and 5). The URL above doesn't have an extension, so IIS will assume that it is a virtual directory on the server. We have a couple of options we can do to get around this:
- We could create wild card mappings and pass every request through ASP.NET, however this is not recommended for performance reasons
- We can add an extension that maps to ASP.NET (.mvc)
In the latter option your routes must change to something like this:
/{controller}.mvc/{action}
So URLs generated with this route will look like:
/products.mvc/list
...which basically reverts our clean urls into, well... less clean ones.
So if you want to deploy to Windows Server 2003 & IIS 6, you're out of luck. Well, mostly...
URL Rewriting to the Rescue
We can leverage an ISAPI filter to rewrite URLs which will allow us to have the nice URLs while still on IIS 6. There are 2 major products that do this for IIS:
- ISAPI Rewrite ($99)
- Ionic's ISAPI Rewrite Filter (Free)
ISAPI Rewrite is the more mature of the two, however it's not free. They do provide a free version, but it doesn't support regular expressions, so it's pretty useless.
Jeff Atwood has a nice write-up of these two utilities. In it he notes that the syntax for Ionic's ISAPI Rewrite is a little stranger and doesn't support the regex that we're all used to (since it uses a C regex library, not a .NET one).
I decided to choose Ionic's because it was free, however there was a lack of information on how to structure URLs, and it required a lot of trial and error to get it working. Luckily they have an automatic logging facility that tells you how the rules are matching up.
For ASP.NET MVC, I needed to cover these cases:
- A request for / should be redirected to /home
This isn't really required, as .NET can do this for me, however I wanted to ensure that all entry points are at the same URL to avoid Page Rank issues. (See the same Atwood post for information on how important this is) - A request for /something should be rewritten to /something.mvc
That is, the user will request it without an extension, but the filter will rewrite it without the user's address bar ever changing. - A request for /something/index should be rewritten to /something.mvc/index
Just making sure that URLs with actions get the extension only on the first part - The content directory that contains our javascript, CSS, and images should be excluded from the above rule
Otherwise we'd have /content.mvc/styles.css which would be interpreted as a controller, rather than a direct file request.
I found a good resource that outlined how to do this with ISAPI Rewrite, however the rules were quite different with Ionic's.
Here are my rules for Ionic's ISAPI Rewrite that works for ASP.NET MVC:
# empty URL gets mapped to home controller
RewriteRule ^/$ /home [R] # map controller parts of urls to .mvc, ignoring the content directory
RewriteRule ^(?!/Content)(/[A-Za-z0-9_-]+)(/.*)?$ $1.mvc$2 [I]
It turns out that I only need 2 rules to satisfy the above requirements. The first rule forces the browser to redirect, which will aid in making sure that I only have 1 entry point to my website.
The 2nd rule takes the first part of the ULR and adds .mvc to it, appending the remaining verbatim (if any). It also excludes anything beginning with "content," so I'm free to put images, javascript, css, and other literal file resources there.
The last requirement is to define my routes with both .mvc and regular formats. The trick is to define them in the right order.
routes.MapRoute("basic", "{controller}/{action}", new { ... } );
routes.MapRoute("basic_mvc", "{controller}.mvc/{action}", new {...});
Doing this ensures that our .MVC routes will actually function, but when we ask the framework for a URL (such as with Url.Action() or Html.ActionLink) we are handed the extension-less route (since it is defined first).
Route Testing is IMPORTANT
I've said it before and I'll say it again. Route testing is important. A single tiny change can break an entire application. Applying automated tests is critical for any MVC application. Since deployment of an ASP.NET MVC application needs to be flexible, your application should function with either type of route (extensions or not) so that you have flexibility of deployment.
Hard coding a route in 1 single location will prevent you from doing this. Did I mention route testing is important?


Tim
6.07.2008
11:58 AM
Which release of Ionic's ISAPI Rewrite did you use to do this?I've been trying to do the same but no matter what version i've tried, i've recieved a heap corruption error when i've configured the ISAPI Filter in IIS.