How to do URL rewriting on IIS 7 properly

One of my earlier blog posts, and the all time most popular one, was about how to make URL rewriting on IIS 7 work like IIS 6. While my method did provide a means to the goal, it's humiliatingly far from what I should've done. Since the old post is still the most visited post on my blog I feel obligated to write a followup on how to do proper url rewriting in IIS 7.

The scenario


I'll assume a completely vanilla IIS 7 setup, contrary to the old post, there's no IIS tampering required.

I've setup a simple web application solution structure like so:

Image: iis7urlrewritingdoneproperly_1
As in the original post my goal is to accept a URL like http://localhost/blog/2006/12/08/missing-windows-mobile-device-center and map it to the BlogPost.aspx file in the root of my application. During the rewrite process I want to make the year, month, day and title available for the BlogPost.aspx file in an easily accessible way.

Rewriting using Global.asax


The easiest way of rewriting URL's is to add a new Global.asax file to the root of your solution. Now paste in the following code:

using System;
using System.Text.RegularExpressions;
using System.Web;

namespace IIS7UrlRewritingDoneProperly
{
	public class Global : HttpApplication
	{
		// Runs at the beginning of each request to the server
		protected void Application_BeginRequest(object sender, EventArgs e)
		{
			// Match the specific blog post URL path as well as pull out variables in regex groups
			Match m = Regex.Match(Request.Url.LocalPath, @"^/blog/(?<year>\d{4})/(?<month>\d{2})/(?<day>\d{2})/(?<title>.*)/?$");

			// If we match a blog posts URL, save the URL variables in Context.Items and rewrite to /BlogPost.aspx
			if (m.Success)
			{
				Context.Items["Title"] = m.Groups["title"].Value;
				Context.Items["Year"] = m.Groups["year"].Value;
				Context.Items["Month"] = m.Groups["month"].Value;
				Context.Items["Day"] = m.Groups["day"].Value;

				HttpContext.Current.RewritePath("/BlogPost.aspx");
			}
		}
	}
}

Now all you need is a single change in your web.config file:

<configuration>
	<system.webServer>
		<modules runAllManagedModulesForAllRequests="true">
	</system.webServer>
</configuration>

The web.config change basically does the same as adding the wildcard map in IIS6. It ensures ASP.NET will run our Application_BeginRequest function for all requests - both those that match .aspx files as well as those for static files.

Rewriting using an HttpModule


As an alternative to putting the rewriting logic into Global.asax, you might want to write it into a distributable HttpModule. If your URL rewriting functionality is common for multiple sites, generic or for any other reason may be usable on multiple sites, we don't want to replicate the functionality in Global.asax.

If you added the Global.asax file from before, make sure you remove it again so it doesn't conflict with the HttpModule we're about to write. Add a new class project to the solution - I've called mine MyUrlRewriter. Add a reference to System.Web and add a single new class file to the project called UrlRewriter. Your solution should look like this:

Image: iis7urlrewritingdoneproperly_2
Now paste the following code into the UrlRewriter.cs class file:

using System;
using System.Text.RegularExpressions;
using System.Web;

namespace MyUrlRewriter
{
	public class UrlRewriter : IHttpModule
	{
		// We've got nothing to dispose in this module
		public void Dispose()
		{ }

		// In here we can hook up to any of the ASP.NET events we use in Global.asax
		public void Init(HttpApplication context)
		{
			context.BeginRequest += new EventHandler(context_BeginRequest);
		}

		// This method does exactly the same as in Global.asax
		private void context_BeginRequest(object sender, EventArgs e)
		{
			// Match the specific blog post URL path as well as pull out variables in regex groups
			Match m = Regex.Match(HttpContext.Current.Request.Url.LocalPath, @"^/blog/(?<year>\d{4})/(?<month>\d{2})/(?<day>\d{2})/(?<title>.*)/?$");

			// If we match a blog posts URL, save the URL variables in Context.Items and rewrite to /BlogPost.aspx
			if (m.Success)
			{
				HttpContext.Current.Items["Title"] = m.Groups["title"].Value;
				HttpContext.Current.Items["Year"] = m.Groups["year"].Value;
				HttpContext.Current.Items["Month"] = m.Groups["month"].Value;
				HttpContext.Current.Items["Day"] = m.Groups["day"].Value;

				HttpContext.Current.RewritePath("/BlogPost.aspx");
			}
		}
	}
}

Notice that the context_BeginRequest function is identical to the one we had in Global.asax, except we have to reference HttpContext.Current explicitly since it's not implicitly available as in Global.asax.

Now add a reference from the original web application project to the MyUrlRewriter class project. Once this is done we just need to ensure our HttpModule is included in our web application by modifying the web.config:

<configuration>
	<system.webServer>
		<modules runAllManagedModulesForAllRequests="true">
			<add name="UrlRewriter" type="MyUrlRewriter.UrlRewriter, MyUrlRewriter"/>
		</modules>
	</system.webServer>
</configuration>

At this point you should be able to run the website with the exact same URL rewriting functionality as we had before - though this time in a redistributable assembly called MyUrlRewriter.dll which can easily be included into any website by adding a single line to the section of the web.config file.

Not Invented Here Syndrome


If you have basic requirements to your URL rewriting solution you may often be able to settle with one of the many readymade HttpModules that you can simply plug into your application. IIS 7 also has a URL Rewrite Module that you can install and easily configure through the IIS manager.

kick it on DotNetKicks.com


Comments

Brian Holmgård Kristensen | Oct 19th, 2009, 3:50 PM

Mark, you ROCK!

Setting runAllManagedModulesForAllRequests="true" solved all my issues with NHibernate and Unit Of Work :-)

Chris | Dec 19th, 2009, 12:58 AM

IS there any IIS 7.0 setup required here? I've tried both methods you mentioned and can't get either working. I have URL Rewriting working in IIS 6.0 but our production server just switched to 7.0.

Thanks,
Chris

Mark S. Rasmussen | Dec 21st, 2009, 9:20 AM

@Chris

There shouldn't be any IIS7 setup required as all is configured through the web.config file. Has there been any IIS7 configurations on the machine level that might affect your particular site?

Is the BeginRequest method being fired in either method?

/ Mark

Andrew | Jan 14th, 2010, 11:23 PM

Excellent follow up article. Great info.

Thanks,
Andrew

Andrew | Jan 14th, 2010, 11:37 PM

This method available in IIS7 is easy to implement. But I don't believe it is as efficient as the "hack" method you outlined in:
http://www.improve.dk/blog/2006/12/11/making-url-rewriting-on-iis7-work-like-iis6

The reason is, I noticed that global.asax is called for every request on the page - i.e. .net code is run for request for .css, .js, .gif, .png files etc which is not as good as having the final "catch all" method you outlined in the previous article.

Using the old method, you can set up the wildcard to use a Managed Handler: System.Web.UI.PageHandlerFactory which allows us to keep IIS7 in Integrated mode. The only catch, which I am trying to find a simple solution for, is the default pages for folders will have to be managed by the handler instead of relying on the Default Pages specified in IIS7, which gets overridden by System.Web.UI.PageHandlerFactory.

Thanks,
Andrew

Mark | Mar 19th, 2010, 10:51 PM

You are my new god Mark. You totally just saved my ass - My URL rewriting was set up fine and working on my localhost, but porting to IIS 7 presented an issue. The URL rewriting can also get screwed up if you have your application pool in integrated mode rather than classic - but that was not the case for me this time around - setting runAllManagedModulesForAllRequests="true" worked beautifully.

Interestingly enough actually, my problems with my URL rewriting module came up today after I implemented a custom 404 error page. It worked fine before hand. Not sure why that made a difference though??

Ophedian | Mar 31st, 2010, 8:23 PM

Dude, after 6 hours trying to figure out what the heck is going on, finding this answer was like finding a chilled Deer Park water bottle after wondering in the Sahara desert. You Rock!!!

Kvarc | May 9th, 2010, 7:34 PM

Thanks for the info, this enabled my UrlRewriter to work in Windows 7.

Only two changes were necessary:
<modules> -> <modules runAllManagedModulesForAllRequests="true">
and adding:
<add name="UrlRewriter" type="Intelligencia.UrlRewriter.RewriterHttpModule, Intelligencia.UrlRewriter" />

Regards!

P90X | Jun 7th, 2010, 7:30 AM

Hi..Your post is amazing. From long time I search for this information. But I didn’t get right thing. Thanks to you I got stuff which I am looking for. I would like to read more from you.

natural peanut butter | Jun 17th, 2010, 4:17 AM

I have never thought that surfing online can be so much beneficial and having found your blog, I feel really happy and grateful for providing me with such priceless information.

jouer aux casinos de jeux en ligne | Jun 21st, 2010, 11:10 AM

I am sure the same concept can be used in a varity of way to get lot of real time information.The person who create this post he is a great human..thanks for shared this with us.

Tech Blog | Jul 14th, 2010, 1:09 AM

you wrote very charming article i loved your tutorial and want to thank you for this tutorial

carpet cleaner Monterey | Aug 7th, 2010, 7:04 AM

This asax project is killing me this semester. I just figured out you and get huge relief from you. Thank you man.

Add comment

After you have posted a comment, an email will be sent to the provided email address. Before your comment is activated, you will have to click the confirmation link within the email.

Name:

Email (only used for validation):

Website (optional):

Message:

Notify me when new comments are added:

Please type the following letters into the box below:  

Post!