« Trigger workflow engines from Sitecore | HomePage | Sitecores abstract data layer »

Wednesday, 17 October 2007

Modifying the HttpRequest pipeline, custom 404 handler

Today, during level 2 training I got an idea when describing the Sitecore HttpRequest pipeline. This pipeline is executed every time a request is made: If it’s a web page, if it’s web services or if it’s the AJAX layer.

The purpose of the request pipeline is to populate the Sitecore context with information on the current request, - such as visiting device, security settings and the item requested.

 

I decided to do a small example on pipeline usage, while the participants were working hard to complete a lab. In this example I’m inserting a class right after the pipeline has checked if the current requested item exists. If not, - I will redirect to another Sitecore item which is the “page not found” page. As a small addition, I will parse the requested document and search the content structure for matching alternative items and display a description, something like:

 

Page not found.

The page you were looking for “xyz” was not found. However, you may want to visit the following topics:

  • Topic1
  • :

This example is nowhere near perfect: You should rather use a real search engine to find the item, - and this is also something I will recommend that you do if you decide to use this approach.

 

HttpRequest pipeline

As described above, the HttpRequest pipeline is executed on every single request for Sitecore, - and it populates the Sitecore.Context objects with information ranging from the current user, security, devices, databases and the requested item. The following entry in the pipeline finds the requested item, and if the item does not exists, instead of returning the normal 404 page (404 page not found), my class will set the new item:


<httpRequestBegin>
  :
  <processor type="Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel" />       
  <processor type="Sitecore.Examples.PageNotFound, PageNotFound" method="ProcessCustomPageNotFound" />
  :
</httpRequestBegin>

 

The Sitecore.Examples.PageNotFound class contains the following code:

 

using Sitecore.Data;
using Sitecore.Data.Items;

namespace Sitecore.Examples {
  public class PageNotFound {
    public void ProcessCustomPageNotFound(
      Pipelines.HttpRequest.HttpRequestArgs args) {

      if (Context.Item == null) {

        if (Context.Database.Name == "web") {         

          Item PageNotFoundPage =
            Context.Database.GetItem(
            "/sitecore/content/home/global/pagenotfound");

          if (PageNotFoundPage!=null){
            Context.Item = PageNotFoundPage;

          }
        }
      }
    }
  }
}

 

As you can see, I simply check if Sitecore has found an item (if the context item is null), and if not, do a quick check if the database is the web database (we don’t want to show this page for internal Sitecore items). I also try to identify the page not found item in the Sitecore content structure, - that’s the page that actually displays the error code: Make sure you do have a Sitecore item (with an assigned layout) in the structure. The item is, in this example, located under /home/global/pagenotfound.

 

As a small treat, - I have also created a sub layout that searches the Sitecore data structure for items that may match the URL the user has entered. Now, I realize this way of searching may be nowhere optimal, - and I would therefore recommend you to refine it should you choose this approach and use this code.

 

The sublayout contains a single label, looking something like this:

 

<%@ Control Language="C#" AutoEventWireup="true"
    CodeBehind="PageNotFoundDetails.ascx.cs"
    Inherits="PageNotFound.layouts.WebUserControl1" %>

<%@ register TagPrefix="sc"
    Namespace="Sitecore.Web.UI.WebControls"
    Assembly="Sitecore.Kernel" %>

<asp:Label ID="lblAbstract" CssClass="normal"
    runat="server">
</asp:Label>

 

With this code-behind:

 

using System;
using System.Text;
using Sitecore.Data.Items;
using Sitecore.Web;

namespace PageNotFound.layouts {

  public partial class WebUserControl1 :
    System.Web.UI.UserControl {

    protected void Page_Load(object sender, EventArgs e) {

      Response.StatusCode = 404;
      Response.Status = "404 Page not found";

      string searchedfor = WebUtil.GetUrlName(0);

      string sPath =
        "/sitecore/content/home//*[startswith(@@key,'" +
        searchedfor.Substring(0, 2) + "')]";

      Item[] found =
        Sitecore.Context.Database.SelectItems(sPath);

      StringBuilder stringBuilder = new StringBuilder();

      stringBuilder.Append(
        "<h1>The requested page, '" +
        searchedfor +
        "' could not be found.</h1>");

      if (found.Length>0){
        stringBuilder.Append(
          "<h3>However, you might find these interesting:</h3>");

        stringBuilder.Append("<ul>");

        foreach (Item item in found) {
          stringBuilder.Append("<li><a href='"+
            item.Paths.GetFriendlyUrl()
            + "'>" + item.Name + "</a></li>");

        }

        stringBuilder.Append("</ul>");

      }

      lblAbstract.Text = stringBuilder.ToString();

    }
  }
}

 

A single comment to this code:

 

You may notice that I set the Response.StatusCode and Response.Status to 404 and a matching description.

      Response.StatusCode = 404;
      Response.Status = "404 Page not found";

 

This is naturally because is a visiting spider identifies a link to a non-matching page, - and are redirected to this page, - it should be mapped as a page that does not exists, -and therefore should be removed from the index.
The following line extracts the last part of the requested URL:

      string searchedfor = WebUtil.GetUrlName(0);

 

For example, it would return apple from this URL request: http://localhost/products/apple.aspx.

 

I also utilize the Sitecore path search to find a matching document in the entire content structure, by extracting the first 2 letters from the above request part, and attempts matching items with that name:

      string sPath =
        "/sitecore/content/home//*[startswith(@@key,'" +
        searchedfor.Substring(0, 2) + "')]";

 

Download file (Sitecore package with project file and installation instructions):

PageNotFound-v.1.zip.

12:52 Posted in Sitecore | Permalink | Email this

Comments

He's finally back to business :).
Keep on doing courses, Lars. By the end of the month you'll be filled full of ideas. Don't enlarge the Sitecore vNext Roadmap to much...

Posted by: Alex de Groot | Wednesday, 17 October 2007

cool!

when i was in copenhagen i had a discussion with Kim about this exact scenario - great to see actual code for it (since i guess i've been to busy/lazy to write my own version.. and, you versions rock more than mine anyway so..).

downloading and trying is a must :)

P.

Posted by: Peter | Thursday, 18 October 2007

Great stuff Lars. Short & Sweet :o)

Posted by: Anders Dreyer | Friday, 26 October 2007