Sitecore

Updating the response headers on your 404 Page in Sitecore

A few weeks ago I blogged about how to create a custom 404 Page in Sitecore. Following on from that, one thing you may notice in the response header of your 404 Page is the status code is 200 Ok, rather than 404 Page not found.

When Sitecore can't find a page what actually happens is a 302 redirect is issued to the page not found page, which as its an ordinary page will return a 200 Ok. Thankfully Google is actually quite good at detecting pages a being 404's even when they return the wrong status code, but it would be better if our sites issues the correct headers.

Method 1

The simplest solution is to create a view rendering with the following logic and place it somewhere on your page not found page. This will update the response headers with the correct values.

1@{
2 Response.TrySkipIisCustomErrors = true;
3 Response.StatusCode = 404;
4 Response.StatusDescription = "Page not found";
5}

However personally I don't think this a particularly neat solution. The contents of a view should really be left for what's going in a page rather than interfering with its headers, even if it does have access to the Response object.

Method 2

Rather than using a view my solution is to add some code to the httpRequestEnd pipeline that will check the context items Id against a setting where we will store the Id of the 404 page item in Sitecore and if the two match then update the response header.

The solution will look like this

Pipeline logic

1using Sitecore.Configuration;
2using Sitecore.Data;
3using Sitecore.Pipelines.HttpRequest;
4
5namespace Pipelines.HttpRequest
6{
7 public class PageNotFoundResponseHeader : HttpRequestProcessor
8 {
9 private static readonly string PageNotFoundID = Settings.GetSetting("PageNotFound");
10
11 public override void Process(HttpRequestArgs args)
12 {
13 if (Sitecore.Context.Item != null && Sitecore.Context.Item.ID == new ID(PageNotFoundID))
14 {
15 args.Context.Response.TrySkipIisCustomErrors = true;
16 args.Context.Response.StatusCode = 404;
17 args.Context.Response.StatusDescription = "Page not found";
18 }
19 }
20 }
21}

Patch config file

1<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
2 <sitecore>
3 <pipelines>
4 <httpRequestEnd>
5 <processor
6 patch:after="processor[@type='Sitecore.Pipelines.PreprocessRequest.CheckIgnoreFlag, Sitecore.Kernel']"
7 type="Pipelines.HttpRequest.PageNotFoundResponseHeader, MyProjectName" />
8 </httpRequestEnd>
9 </pipelines>
10 <settings>
11 <!-- Page Not Found Item Id -->
12 <setting name="PageNotFound" value="ID of 404 Page" />
13 </settings>
14 </sitecore>
15</configuration>

What's the TrySkipIisCustomErrors property

Quite simply this stops a scenario where you end up on IIS's 404 page rather than your own. If you don't set this, when you update the header status code to 404, IIS likes to return the page from it's settings rather than continuing with your own.

Sitecore Search and Indexing: Creating a simple search

With Sitecore 7, Sitecore introduced the new Sitecore.ContentSearch API which out of the box can query Lucene and SOLR based index's.

Searching the index's has been made easier through Linq to Sitecore that allows you to construct a query using Linq, the same as you would use with things like Entity Framework or Linq to SQL.

To do a query you first need a search context. Here I'm getting the a context on one of the default index's:

1using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext()) { ... }

Next a simple query would look like this. Here I'm doing a where parameter on the "body" field:

1using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
2{
3 IQueryable<SearchResultItem> searchQuery = context.GetQueryable<SearchResultItem>().Where(item => item["body"] == “Sitecore”)
4}

But what if you want to add a search to your site. Typically you would want to filter on more than one field, what the user enters may be a collection of words rather than an exact phrase and you'd also like some intelligent ordering to your results.

Here I am splitting the search term on spaces and then building a predicate that has an "or" between each of its conditions. For each condition rather than doing a .Contains on a specific field, I'm doing it on a content field that will contain data for all fields in the item.

1using (var context = ContentSearchManager.GetIndex("sitecore_web_index").CreateSearchContext())
2{
3 IQueryable<SearchResultItem> query = context.GetQueryable<SearchResultItem>();
4
5 var predicate = PredicateBuilder.True<SearchResultItem>();
6
7 foreach (string term in criteria.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
8 {
9 predicate = predicate.Or(p => p.Content.Contains(searchTerm.Trim()));
10 }
11
12 SearchResults<SearchResultItem> searchResults = query.Where(predicate).GetResults();
13
14 results = (from hit in searchResults.Hits
15 select hit.Document).ToList();
16}

The intelligent ordering of results you will get for free based on what was search for.

SEO Friendly URL's in Sitecore

As silly as it seams to the average developer, URL's are an important factor when it comes to SEO. As ridiculous as some of the requirements are (like not having spaces in a URL) they are unfortunately requirements that we have to live with. Here's how to deal with not having upper case letters in a URL, replacing spaces with hyphens and getting rid of that pesky aspx extension.

Removing Spaces from a URL

A space in a URL actually gets translated to %20, which for the average user isn't very readable. I would argue that as well as doing the translation, browsers could actually just hide this fact from users, and Google could also hide it as well. But alas that hasn't happened and the accepted solution is to replace spaces with a hyphen.

Within a patch config file add the following:

1<encodeNameReplacements>
2 <replace mode="on" find=" " replaceWith="-" />
3</encodeNameReplacements>

What this will do is replace every space in an items name with a hyphen as links are rendered. When a request comes into Sitecore it will also replace all hyphens with a space, so that the URL's still resolve.

However this does cause a problem for any items you have that already had a hyphen in them. Sadly the best we can do for this is to stop content editors including hyphens in item names with the following config patch:

1<settings>
2 <setting name="InvalidItemNameChars">
3 <patch:attribute name="value">\/:?&amp;quot;&amp;lt;&amp;gt;|[]-</patch:attribute>
4 </setting>
5</settings>

Make URLs all lower case

Believe it or not URL's are actually case sensitive. Maybe not in the Windows / IIS world, but with Linux and the rest of the web they are. So the simplest solution is to make all the URL's on the site lower case.

Sitecore 6.6 and above

If your using Sitecore 6.6 or above then you in luck, there's a config setting on the linkManager to set all urls to lower case

1<linkManager defaultProvider="sitecore">
2 <providers>
3 <add name="sitecore">
4 <patch:attribute name="lowercaseUrls">true</patch:attribute>
5 </add>
6 </providers>
7</linkManager>

Sitecore 6.5 and below

If you using 6.5 or below you need to do a little more work.

One way is using the same encodeNameReplacements config as we used before and replace every upper case letter in the alphabet with the lower case equivalent.

1<encodeNameReplacements>
2 <replace mode="on" find="A" replaceWith="a" />
3 <replace mode="on" find="B" replaceWith="b" />
4 <replace mode="on" find="C" replaceWith="c" />
5 <replace mode="on" find="D" replaceWith="d" />
6</encodeNameReplacements>

Personally this doesn't seen the nicest solution and I expect will lead to a lot of replace functions being called.

Another solution is to create a class that overrides the Sitecores LinkProvider and simply makes the result of GetItemUrl lowercase

1namespace YourNamespace.Providers
2{
3 public class LinkProvider : Sitecore.Links.LinkProvider
4 {
5 public override string GetItemUrl(Sitecore.Data.Items.Item item, Sitecore.Links.UrlOptions urlOptions)
6 {
7 return base.GetItemUrl(item, urlOptions).ToLower();
8 }
9 }
10}

And then add a patch config to tell Sitecore to use your LinkManager rather than the default.

1<linkManager defaultProvider="sitecore">
2 <providers>
3 <add name="sitecore">
4 <patch:attribute name="type">YourNamespace.Providers.LinkProvider, YourProjectName</patch:attribute>
5 </add>
6 </providers>
7</linkManager>

Getting rid of the aspx extension

By default Sitecore puts a .aspx extension on the end of a url. Changing it is just a config setting:

1<linkManager defaultProvider="sitecore">
2 <providers>
3 <add name="sitecore">
4 <patch:attribute name="addAspxExtension">false</patch:attribute>
5 </add>
6 </providers>
7</linkManager>

Creating a custom 404 Page in Sitecore

Nobody wants to see the standard Sitecore 404 page. Thankfully it's really easy to change the error pages a user is redirected to through some config settings.

Your error pages can be pages in Sitecore or static HTML files. For 404 pages I would normally use a Sitecore page, that way content authors can still manage its content. For an Error Page I would recommend a static html file to avoid issues with the Error page potentially error-ing.

Add these to a patch file and update the urls accordingly:

1<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
2 <sitecore>
3 <settings>
4 <!-- ITEM NOT FOUND HANDLER
5 Url of page handling 'Item not found' errors
6 -->
7 <setting name="ItemNotFoundUrl">
8 <patch:attribute name="value">/ErrorPages/404.html</patch:attribute>
9 </setting>
10 <!-- LINK ITEM NOT FOUND HANDLER
11 Url of page handling 'Link item not found' errors
12 -->
13 <setting name="LinkItemNotFoundUrl">
14 <patch:attribute name="value">/ErrorPages/404.html</patch:attribute>
15 </setting>
16 <!-- LAYOUT NOT FOUND HANDLER
17 Url of page handling 'Layout not found' errors
18 -->
19 <setting name="LayoutNotFoundUrl">
20 <patch:attribute name="value">/ErrorPages/404.html</patch:attribute>
21 </setting>
22 <!-- ERROR HANDLER
23 Url of page handling generic errors
24 -->
25 <setting name="ErrorPage">
26 <patch:attribute name="value">/ErrorPages/Error.html</patch:attribute>
27 </setting>
28 </settings>
29 </sitecore>
30</configuration>

These settings are already defined in the web.config file and changing them here will have the same effect, but I recommend adding patch config files as it will make your solution easier to update in the future.