Scott Hanselman

Redirecting ASP.NET Legacy URLs to Extensionless with the IIS Rewrite Module

May 10, '13 Comments [19] Posted in ASP.NET | IIS
Sponsored By

ASP.NET has included support for "friendly URLs" for a while now. ASP.NET MVC has always supported friendly URLs and more recently, so has Web Forms. That means if you don't want to have the .aspx extension, you certainly don't have to.

However, there's a LOT of existing legacy apps out there as well as apps that you may not have full control over. For example, there's a site that I want to influence but it's got dozens (hundreds) of links to foo.html and bar.html existing pages.

Legacy (n): A super-fancy way of saying "already exists."

What I want to do is kind of sloppy and I'm doing it for aesthetic reasons. I'll hopefully get around to updating the site's links later and know that future links will be extensionless. But look at me, I'm justifying why I'm doing this, Dear Reader. You of all people know that sometimes you just gotta do something just because ya gotta Get It Done™©.

I want to:

  • redirect existing GETs to a /foo.html to /foo
    • Redirects are external
  • but, keep rewriting /foo to the underlying /foo.html so it handles the request
    • Rewrites are internal

I can do all this within my web.config using the IIS Url Rewrite Module.  I can do this with ANY file type that IIS can handle, meaning this isn't an ASP.NET-specific thing. This all happens well before your application gets involved. You'll note I did a similar thing with a PHP app running under IIS just last month.

Here's what my web.config looks like. Note that since I have Azure (or in just IIS 7+ and the Rewrite module) I just added this file. There was no configuration needed. The same would apply to any existing site. Be aware that sometimes super-"greedy" rewrite or redirect rules can end up grabbing ahold of your CSS or JS so you'll want to be aware if something odd happens.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="extensionless" stopProcessing="true">
<match url="(.*)\.html$" />
<action type="Redirect" url="{R:1}" redirectType="Permanent" />
</rule>
<rule name="removeextension" enabled="true">
<match url=".*" negate="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
<add input="{URL}" pattern="(.*)\.(.*)" negate="true" />
</conditions>
<action type="Rewrite" url="{R:0}.html" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>

This stuff is hard to write, though.

SIDE NOTE: My RegEx expert Ruslan points out that the final Action could be simplified slightly like this:

<add input="{URL}" pattern="\." negate="true" />

This stuff is also hard to test. The IIS Rewrite module has a great UI for IIS that will write a lot of these rules for you and let you test them interactively:

URL Rewrite module in IIS

Hope this helps. I wrote this post (and bookmarked it) for myself because I am always googling around for this particular rule to remind myself. Now I'll search my own blog. ;) Reason #64 to blog, friends.


SPONSOR: Big thanks to the feed sponsor this past week, Ext.NET (seriously, check out their demos, really amazing stuff!) - Quickly build modern WebForm and MVC (including RAZOR) Apps for ASP.NET. Free pancake breakfast with all purchases!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by ORCS Web
Friday, May 10, 2013 11:16:07 PM UTC
From my experience:

Legacy(n): An educated way of saying: Cr*** that we didn't write, is very bad code, are not responsable for and want out the door asap, but the manager doesn't let us rewrite it.

Friday, May 10, 2013 11:27:19 PM UTC
I was seriously just looking for this! Thanks for all the work you put into this blog. I hope you know your it's appreciated!
Friday, May 10, 2013 11:52:03 PM UTC
Great post!
Saturday, May 11, 2013 2:01:27 AM UTC
What are the SEO implications of such a rewrite? Does the redirect respond with the correct headers for a permanent redirect?
Scott
Saturday, May 11, 2013 4:26:30 AM UTC
Scott - it's the HTTP response code that used used. So 301 Permanent rather than 302 Object Moved.

This line -> action type="Redirect" url="{R:1}" redirectType="Permanent"
Saturday, May 11, 2013 11:37:52 AM UTC
One thing - that IIS Url Rewrite editor is very nice. But, these days, few of my sites run locally on IIS, most now use IIS Express. That means I cannot use this editor directly. And Visual Studio doesn't even have Intellisense for URL Rewrite. Currently, I have a dummy site in IIS that I use to set up rules and then I copy the XML over (or I try to write it by hand, if I can remember the correct syntax). Surely there has to be a better way?
Asbjørn
Saturday, May 11, 2013 12:02:38 PM UTC
Are there solutions for those of us still supporting IIS 6?
Nick Delany
Saturday, May 11, 2013 7:52:03 PM UTC
Maybe it's just me, but your rules seemed to be named backwards. I would expect the "extensionless" rule to do the rewrite to the file with the extension and the "removeextension" rule to, well, remove the extension and do a redirect.
Sunday, May 12, 2013 11:38:59 AM UTC
Another useful tool when debugging rewrites is the failed request tracing in IIS. You can see each rewrite rule as it processed. Very useful.
Jeff Cuscutis
Sunday, May 12, 2013 2:59:59 PM UTC
Nick, if you're using II6 then check out Ionic's IIRF ( http://www.codeplex.com/IIRF ). I wrote an article a few years ago with a working example of IIRF in action... http://swortham.blogspot.com/2008/12/redirecting-default-page-defaultaspx-to.html
Monday, May 13, 2013 1:41:01 PM UTC
Your blog is never fit for a mobile device. any code section added always gets cut. hope u will do sumthing about it soon.
Naveed Butt
Monday, May 13, 2013 2:10:57 PM UTC
Naveed - it seems to be fine on my IE on WP8; the code section does cut off on the right, but I can scroll inside the code section as needed. YMMV, of course.
Monday, May 13, 2013 4:44:21 PM UTC
Naveed and Joe - Yes, I designed this with @jzy to absolutely look great on mobile. You can touch scroll horizontally within code blocks.
Monday, May 13, 2013 4:55:17 PM UTC
I was initially using my Nokia E-500 and now I am using Samsung Galaxy Y (Android) mobile. Same result on both the code sections are always cut and I can not scroll horizontally on my mobile device...
Naveed Butt
Tuesday, May 14, 2013 6:50:31 PM UTC
Great post and it works great on my Samsung Galaxy Nexus running Android 4.2.2, code scrolling and all.
Wade
Tuesday, May 14, 2013 8:34:03 PM UTC
Thanks for this article, good knowledge. I hope the irony isn't lost on you that this post is delivered with an "aspx" extension! .LOLz!
.LAG
Tuesday, May 14, 2013 10:41:38 PM UTC
.LAG - It's not. But this site is under .NET 2.0 and it's worked great for years. I don't want to change it now.
Thursday, May 23, 2013 9:47:04 PM UTC
Nice Post, Scott. I have one question, if you type foo1 or foo1.html and the file does not exist I get a 500 Internal server error, instead of 404. Any ideas how to prevent that.
Rakesh
Thursday, August 08, 2013 7:37:58 PM UTC
Legacy (n): A super-fancy way of saying "currently works"...
Mark Cooper
Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.