Rewrite and Redirect for IIS – Sort of

Last week at work, we unveiled one of the new Web sites I’ve been working on for a while.  In doing so, we removed the old Web site completely from the server and replaced it with the new site.

Of course, when we did so, a lot of our old links became obsolete, so I had to find an effective solution to keep our regular visitors from going bananas when they tried to access some of their favorite pages.  That’s when I began to look into some options for redirecting without having to create placeholder pages just for those redirects.  After quite a few fruitless searches, I was able to piece together a fairly good solution, and also discovered an easy way to achieve URL rewriting on an IIS server (similar to the way you would with htaccess on an apache server).

First, you need to set up your “404” so that it points to a VBScript page.  That’s the most important part.  Once you’ve got that done, navigate to a page that you know does not exist on your server, just so you can test the behavior of your 404 page.

Now, add some code to your 404 page to print out the address that was used to get there.  In my case, the query string of the redirected URL ended up looking something like “notfound=404?404;http://www.example.com/”.  Of course, in my case, that wasn’t visible in the address bar of my browser, but that is what comes up as the address of my script when I use the Request.QueryString server variable.

Knowing that, I was able to extract the path information of just the page that the user was trying to access.  That’s going to be the important part.  You will need a way to get that information into your VBScript.  In my case, I did it something like this:

Fig. 1.0

myStr = lcase(Request.QueryString) 
myStr = Replace(myStr,"notfound=404?404;http://www.example.com/","")

That first part in my query string (“notfound=404”) is something that I had added, as I am using my site map as my 404 page, so I needed a way to determine whether a user was accessing the site map strictly because they wanted to view it, or if they ended up there automatically because of a 404 error.  Therefore, I put some code in my site map that generates the standard “This page does not exist” type of error and prints it out at the top of my site map if the query string starts with “notfound=404”.

Anyway, you can see the meat of what I am doing in the code above.  I’m basically removing my domain information and the “404” from the query string, so that I’m left with just the directory and page information of the page the user was trying to access.

Then, I created a dictionary object that I called “redirects”.  For those of you new to VBScript, a dictionary object is essentially an associative array. Since you cannot use associative arrays in VBScript, you have to create a dictionary object instead.

Within that dictionary object, I defined my old locations as the “keys” of my dictionary and I used the new locations as the “values” of my dictionary.  Therefore, my dictionary looks something like:

Fig. 2.0

Set redirects = Server.CreateObject("Scripting.Dictionary") 
With redirects 
	.Add "oldpage.html","newdir/newpage.html" 
	.Add "oldpage2.html","otherdir/newpage2.html" 
End With

Once I finished making my list of redirects, I simply put in some code to check to see if the page that caused the 404 exists as a key in my redirects dictionary.  If it does, then I redirect the user to the new location.  That code looks something like:

Fig. 3.0

If(redirects.Exists(myStr)) Then 
	Response.Status = "301 Moved Permanently" 
	Response.Redirect(redirects(myStr)) 
End If

With those simple lines of code, I’ve created a redirect that will function very similarly to the redirects you might set up in your htaccess or httpd.conf file on an apache server.  What makes it even a little nicer is that I’ve actually generated my dictionary in an external file, so that it’s easier to keep track of that list.  Then, in order to pull that dictionary definition into my redirect page, my code actually looks like this:

Fig. 4.0

Set redirects = Server.CreateObject("Scripting.Dictionary") 
With redirects 
%> 
<!-- #include file="redirectlist.asp" --> 
<% 
End With

Then, the file “redirectlist.asp” actually includes my list of old locations and new locations.

URL Rewriting

So, now you’re probably saying: “Well, that’s nice and all, but you said you figured out how to do URL rewriting, too.  What about that?”

Well, the process is almost identical.  However, rather than inserting the lines that begin with “Response.Status” and “Response.Redirect”, you use “Server.Transfer” instead.  The transfer directive is kind of a strange one on an IIS server.

Basically, it says “Stop processing this file right now, and start processing the other file instead at this point”.  It doesn’t process any new headers, it just uses the ones that it’s already processed.  What that means for you is, your URL stays exactly the same as the user typed it.  Instead, however, the content of the file you assigned the transfer directive is processed and printed.

Some caveats when using this method:

  1. Server.Transfer only works if you are referring to a file that exists on the same server.  It is a server-level directive, and cannot point to any files that exist outside of the server.
  2. You will probably need to add some extra checks into your file somewhere to be absolutely certain you’re pointing to a file that actually exists.  I have a feeling, although I haven’t tested it, that you could end up in an endless loop if you point this to another file that’s going to return a 404 error (since you’re using your 404 page to process these directives).
  3. Apparently this method is only available in ASP 3.0 and newer.

Anyway, the code looks exactly the same as described above, except that you use the following code in place of the code shown in Fig 3.0:

Fig. 5.0

If(redirects.Exists(myStr)) Then 
	Server.Transfer(redirects(myStr)) 
End If

I’m sure there are other ways of handling this situation, but I think this is a pretty good solution.  I tried a few other recommendations I found on the Web, but I wasn’t able to get any of them to work properly.

You can read a little more about the problems you may encounter with the Server.Transfer method, and a comparison of Response.Redirect and Server.Transfer in an article on 15 seconds.