A simple solution for custom 404 pages and permanent redirects

by: Joel Abrahamsson

Yesterday me and my colleague Henrik Kraft where working on the final touches before the release of a new site. The site we where working on is a new version of a site that has quite a few inbound links and quite a few pages with good rankings in search engines. So we needed to set up a good 404 page and set up permanent redirects for the most important old URLs.

Our goals where:

  • A general error page in static HTML
  • A custom 404 page editable in EPiServer
  • Permanent redirects for some URLs
  • The editor should be able to specify which URLs should be permanently redirected and to where
  • Visitors that found the 404 by clicking on a link from a search engine should be assisted with performing a new search on the site for the same query as they had searched for with the search engine

We took a look at several good solutions for custom error pages that we found on EPiServer World and epicode, but none satisfied our specific goals, so in the end we worked out our own, pretty simple, solution.

Setting up custom error pages

Setting up custom error pages actually turned out to be trickier than we had thought. Me being an avid Windows XP user I started out by editing the custom errors section in web.config but was surprised when absolutely nothing happened, whatever I did. Finally it turned out that in IIS 7 the customerrors node has been replaced, or at least overridden, by the system.webServer/httpErrors node which specifies custom error pages for IIS.

<httpErrors defaultResponseMode="ExecuteURL" 
    defaultPath="/error.html">
  <clear/>
  <error statusCode="404" responseMode="ExecuteURL" 
    path="/Page-not-found/"/>
</httpErrors>

The above setting did the trick. It did however require a change in the IIS configuration as the default configuration doesn’t allow individual websites to override the httpErrors node.

FeatureDelegation

Creating the 404 page

With the httpErrors node set up all requests for resources that can’t be found will be transferred to a page located at http://customerdomain/pagenotfound/. This is a page with three properties, a headline, a main body and a link collection.

The two first are pretty self explanatory. The third, the link collection is what we use to allow the editor to specify certain old URLs that should be redirected and to where. The editor specifies the the old URL in the Information/Clickable text field and the new URL in the Link target/Address field. It may not be the most intuitive solution but it works and allows the editor to edit something that would normally only be editable in configuration files without having us develop a custom property.

permanentRedirect

In the page load event handler in the 404 page we compare the requested URL with the items in the link collection. If the requested URL is found we modify the response to create a permanent redirect.

private const string REQUESTED_URL_QUERYSTRING_PREFIX = "?404;";
 
protected void Page_Load(object sender, EventArgs e)
{
    string redirectUrl = GetRedirectUrl();
    if(!string.IsNullOrEmpty(redirectUrl))
    {
        Response.Clear();
        Response.Status = "301 Moved Permanently";
        Response.AddHeader("Location", redirectUrl);
        Response.End();
    }
}
 
private string GetRedirectUrl()
{
    LinkItemCollection permanentRedirects = 
        CurrentPage.GetValue("PermanentRedirectLinks") 
        as LinkItemCollection;
 
    if(permanentRedirects == null)
        return null;
 
    if(!Request.RawUrl.Contains(REQUESTED_URL_QUERYSTRING_PREFIX))
        return null;
 
    string requestedUrl = Request.RawUrl.Substring(
        Request.RawUrl.IndexOf(REQUESTED_URL_QUERYSTRING_PREFIX) 
        + REQUESTED_URL_QUERYSTRING_PREFIX.Length);
    requestedUrl = requestedUrl.Replace(":80/", "/");
    requestedUrl = requestedUrl.Replace(":443/", "/");
 
    string redirectUrl = null;
    foreach (LinkItem permanentRedirect in permanentRedirects)
    {
        if (permanentRedirect.Text.ToLower().Trim() 
            == requestedUrl.ToLower())
        {
            PermanentLinkMapStore.TryToMapped(
                permanentRedirect.Href, out redirectUrl);
        }
    }
 
    return redirectUrl;
}

If the requested URL is not specified for permanent redirection we move on to display the 404 page. We begin by modifying the response status code to 404.

protected void Page_Load(object sender, EventArgs e)
{
    string redirectUrl = GetRedirectUrl();
    if (!string.IsNullOrEmpty(redirectUrl))
    {
        ...
    }
    else
    {
        Display404Page();
    }
}
 
private void Display404Page()
{
    Response.StatusCode = 404;
}

Then we check if  the referring URL contains a querystring key matching the ones used by Google (“q”), Live Search (“q”) or Yahoo (“p”). If it does we set the default value of the textbox in our search control to the value of that querystring key. We may of course also suggest some pages that match this search query to further assist the user.

private const string GOOGLE_QUERYSTRING_KEY = "q";
private const string YAHOO_QUERYSTRING_KEY = "p";
 
private void Display404Page()
{
    Response.StatusCode = 404;
 
    string searchQuery = GetSearchQuery();
    if (!string.IsNullOrEmpty(searchQuery))
        QuickSearch.SearchQuery = searchQuery;
 
    //Some code to set header and body text
    //from EPiServer properties
}
 
private string GetSearchQuery()
{
    if (Request.UrlReferrer == null)
        return null;
 
    string searchQuery = null;
 
    if (!UrlUtility.UrlContainsQueryStringParameter(
        Request.UrlReferrer.ToString(), GOOGLE_QUERYSTRING_KEY))
    {
        searchQuery = UrlUtility.GetQueryStringParameterValue(
            Request.UrlReferrer.ToString(), GOOGLE_QUERYSTRING_KEY);
    }
 
    if (string.IsNullOrEmpty(searchQuery) 
        && !UrlUtility.UrlContainsQueryStringParameter(
        Request.UrlReferrer.ToString(), YAHOO_QUERYSTRING_KEY))
    {
        searchQuery = UrlUtility.GetQueryStringParameterValue(
            Request.UrlReferrer.ToString(), YAHOO_QUERYSTRING_KEY);
    }
 
    return searchQuery;
}

Results and request for feedback

Any feedback would of course be greatly appreciated!

We’ll follow up on the results (traffic saved etc) on Nansenbloggen.

21 May 2009


Comments

  1. I like it! I think your approach beats having the redirects in XML files, unless these can be generated automatically. I have another idea on how to make a "smart" 404 page in EPiServer, I'll try and get it out in a post soon!
  2. Looking forward to it!
  3. This is a great solution to a common problem, but how would you expand it to handle requests for friendly urls? Such as http://customerdomain/afriendlypage/ ? Good stuff! Thanks
Post a comment    
User verification Image for user verification  
EPiTrace logger