Redirects Using 404 Error Handling in ASP.NET 2.0 on Shared Hosting (Part 4)

This is the fourth and last in a series of blog entries on using 404 error handling to redirect obsolete HTML pages to newly created ASPX pages.  You can read the earlier posts in the series at the links below:

In the third post in this series, I added ASP.NET custom error page handling to the IIS custom error page handling for HTTP 404 errors.  To understand why I needed both IIS and ASP.NET custom error page handling, read through the earlier blog entries in this series.   

Redirects from IIS and ASP.NET Custom Error Pages

Now that I’ve got all 404 errors for all file-types redirected to my custom error page, it’s time to set up redirection for those obsolete pages that have been replaced. To do this, I’ll need to obtain the originally requested page from the query string passed to my custom error page.

IIS custom error page redirection passes a query string that looks like this:

404;http://www.yourserver.com:80/SomeBogusPage.html

ASP.NET custom error page redirection works a bit differently, and passes a query string that looks like this:

aspxerrorpath=/SomeBogusPage.aspx

Since the one error page is being used by both the IIS custom error redirection and the ASP.NET custom error redirection, the logic for determining if an old-to-new page URL mapping exists would have to accommodate the both methods of passing information on the original requested resource.

Here’s some code that does just that:

   private string GetRequestedPath()
   {
      string path = "unknown";
      string qstr = HttpUtility.UrlDecode(Request.QueryString.ToString());
      if (!string.IsNullOrEmpty(qstr))
      {
         path = Request.QueryString["aspxerrorpath"]; // try to get asp.net error info
         if (string.IsNullOrEmpty(path))                         // if none, must be IIS error
         {
            if (qstr.StartsWith("404"))
            {
               int start = qstr.IndexOf(":80");
               if (start != -1)
               {
                  path = qstr.Substring(start + 3);
               }
            }
         }
      }
      return path;
   }

The first thing the method does is decode the query string to make it easier to work with. Next, we see if the request is from ASP.NET, in which case there will be an aspxerrorpath query string parameter. If that doesn’t exist, the redirect came from IIS so we do a little work to separate out the requested path.

An incoming URL like this: http://www.myserver.com/SomeBogusPage.html, results in a return value of “/SomeBogusPage.html”. A URL pointing to a page in a sub-folder like http://www.myserver.com/prds/oldPrd.html would return “/prds/oldPrd.html”.

Now that we have the visitors requested path (typically a web page), we can see if it’s a page for which we have replacement. There are any number of ways to represent mappings from old paths to new URLs. You can chain if-then-else string comparisons, set up a static array or load the mappings from the database into a DataSet. Do whatever works best for you. For my purposes, since I had only a few pages to redirect, I just used a static array.

 

Making the Redirect SEO Friendly

ASP.NET provides the Response.Redirect method for easily redirecting to a specified URL on your domain. There’s a problem with that Redirect method, however, in that it sets an HTTP response code of 302 (found) which basically says that the resource has moved temporarily and that a substitute page was found. In general, I prefer to either return an HTTP response code of 404 indicating that the resource simply doesn’t exist or return 301 indicating that the resource has moved permanently.

Human visitors to your web site don’t care what you return. But search engine spiders do. 301 and 404 give pretty clear instructions to a search engine spider, essentially telling it “use this new page in place of that old page” or “this page no longer exists, remove it from your index” respectively. 302 sends a kind of mixed message, sort of “hey, the page isn’t here right now; it might be back later; it might not, or whatever”.  Search engine's like Google do not seem to like this indecision.

When an incoming URL path is an old page that has a newer replacement page, I want to return 301. I do that by calling the following utility method instead of Response.Redirect:

      public static void Redirect301(string url, bool endResponse)
      {
         System.Web.HttpContext context = System.Web.HttpContext.Current;
         if (context != null)
         {
            context.Response.Status = "301 Moved Permanently";
            context.Response.AddHeader("Location", url);
            if (endResponse)
            {
               context.Response.End();
            }
         }
      }

If I don’t find a mapping for the requested URL, I just manually set the response status to 404 by doing this:

      System.Web.HttpContext context = System.Web.HttpContext.Current;
      if (context != null)
      {
         context.Response.Status = "404 Not Found";
      }

Conclusion

I hope this series of blog entries helps some of you who are wending your way through the ins and outs of redirecting old pages to new pages, or just providing custom error pages on ASP.NET web sites. The solution I presented is far from perfect, but it seems to be working.

The one major flaw for which I'm seeking a fix is that incoming requests for ASPX resources with no permanent redirect still return 302 errors. Somehow, my manipulation of the current context response status, inserting a 404 or 301 response, does not find its way out of the page processing pipeline and back to the visitor. It works okay for non-ASPX resources. But something ASP.NET is doing is gumming up the works.

Once I figure that out (and suggestions are very welcome), I’ll post the solution.  If someone has an idea, please post a comment.

Redirects Using 404 Error Handling in ASP.NET 2.0 on Shared Hosting (Part 3)

This is the third in a series of blog entries on using 404 error handling to redirect obsolete HTML pages to newly created ASPX pages.  You can read the earlier posts in the series at the links below:

In the second post in this series, I discussed how I implemented an IIS custom error page to handle HTTP 404 errors.  This was a part of my solution for redirecting visitors from obsolete pages that were removed to new replacement pages for the same content. 

I ran into a glitch when I saw that IIS did not forward 404 errors for ASP.NET resources to the custom error page configured in IIS.  So it was clear that I was going to have to do some work on the ASP.NET side to handle any invalid resource requests ignored by IIS. 

 

Implementing an ASP.NET Custom Error Page

You may recall the ASP.NET redirecting options I mentioned from the first blog entry in this series.  Personally, I can’t recall my middle name without a cheat-sheet. So for those of you who are memory-challenged like me, here they are again:

ASP.NET Methods

  • Custom HTTP Handler
  • URL Rewriting
  • ASP.NET URL Mapping
  • ASP.NET Custom Error Pages

Since I already had an approach that leveraged custom error pages, it seemed that a good place to start would be to try to use ASP.NET’s built in support for custom error pages.

You configure ASP.NET custom error handling inside web.config in the customErrors tag.  The customErrors tag has a mode property which can be set to “Off”, “On” and “RemoteOnly”.  Visual Studio .NET, at least the 2005 version, creates web.config’s with the customErrors mode set as follows:

<customErrors mode="RemoteOnly" />

That tells ASP.NET to display custom errors for remote connections and display standard errors for localhost connections.  That set up ensures that remote users see an abbreviated error message without the stack-trace and other detailed information present in the non-custom messages.  For security reasons, not to mention aesthetics, you probably don’t want typical site visitors to see your stack-traces.  So it’s probably a good idea to leave the mode set to either “RemoteOnly” or “On”.

The customErrors tag allows you to map specific error codes to handler pages.  This is similar to how it’s done with IIS custom error pages.  I had already created a custom 404 error page for use with IIS custom error pages, so all I had to do was set up a mapping to the same error page for ASP.NET. Here’s what my customErrors tag looks like:

<customErrors mode="On"  defaultRedirect="~/errors/ServerError.aspx">
    <error statusCode="404"  redirect="~/errors/NotFound.aspx"/>
</customErrors>

The error tag allows you to create a mapping for a specific http error code. The defaultRedirect attribute allows you to map any HTTP error for which you don’t provide an error tag. In my case, I mapped the 404 error to my previously created NotFound.aspx error handler page.

I ran through my series of tests again, trying invalid URLS with HTML extensions, no extensions and ASPX extensions. This time, my custom error page was displayed in each case.

In the next blog post on this series, I'll get into the details of how I performed the redirects for the obsolete pages from the custom error page.