One of the more interesting challenges I've encountered with Sitefinity has been how to handle error pages, specifically the 404 error that should be returned if a page is not found. Sitefinity uses the built in ASP.NET error handling mechanism, which allows you to redirect to a specific page based on the error code returned by the server. This means if a page is not found, you can catch that 404 error, and redirect to a custom 404 page informing the user of the error while avoiding the dreaded yellow screen of death.
Unfortunately, since the 404 error was trapped, and a page was served, the resulting response is 200 (or "ok") instead of the 404 "not found" that it should be. That means if someone goes to, say yoursite.com/news/defalut.aspx instead of yoursite.com/news/default.aspx, they will be redirected to an error page such as yoursite.com/404.aspx?aspxerrorpath=/news/defalut.aspx. As you can see, it did "find" a page, so technically it's not a 404 error!
In addition to that, I want my error page to be more helpful than just telling the user there was an error. I want them to be able to see a list of possible reslated links that the user MAY have been looking for. While Google provides such a tool in their Webmaster Tools suite, it unfortunately requires that you do NOT redirect to a generic page. Since asp.net redirects to 404.aspx, the google tool will always try to search for that term instead.
![Google's 404 Widget](/images/google404widget.gif)
Fortunately, through the use of Sitefinity's search API, we can search our own index for possible links and display them to the user on the 404 page! It's actually very simple so let's go right to the code.
404 Status
The first thing we need to do is change the response code so that the page returns the correct 404 NOT FOUND error code instead of the 200 OK code that it returns by default. This turns out to be as simple as chaging the Response.StatusCode in the Code-behind for the 404 page. However, while researching this, I discovered that there is a problem if your 404 page is using a Master Page (which mine is) that requires an extra step. Instead of just changing the Response.StatusCode in the Page_Load, we have to actually override the Render method:
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
Response.StatusCode = 404;
}
Now the 404.aspx page will correctly return the 404 NOT FOUND error code, ensuring that search engines do not crawl invalid pages!
Displaying Related Page Links
Now that we're returning the correct response code to the search engines, we also want to show some more helpful information to our visitors. This is especially helpful in cases where you've renamed a page or news item, since in Sitefinity, these titles are what form the urls for accessing the content.
Fortunately this is made simple due to the fact that our custom error page traps the original request in the aspxerrorpath querystring field. All we have to do is get that parameter, parse through it and create a new search query to pass to the Sitefinity Search Service.
// attempt to locate desired page if (string.IsNullOrEmpty(Request.QueryString["aspxerrorpath"].ToLower())) return;
string[] req = Request.QueryString["aspxerrorpath"].Split('/');
if (req.Length == 0) return;
string q = req[req.Length - 1].Replace('_', ' ');
if (q.Contains("default.aspx") && req.Length > 1)
q = req[req.Length - 2];
Notice I used the / character as a delimiter, then replaced the _ with blank spaces, and use the text after the last slash (/) as my query. If that happens to be "default.aspx" (which is likely to return hundreds of results), I grab the next previous item in the url. Feel free to experiment with different logic to suit your site's URL structure and piece together a valid search.
Now we need to add a repeater to write our results to the page, and fortunately, we can copy and paste this straight from an existing search results control located in /Sitefinity/ControlTemplates/SearchResult.ascx.
<asp:Repeater ID="rptResults" runat="server">
<HeaderTemplate>
<h2>Possible Related Links</h2>
<dl class="searchResults">
</HeaderTemplate>
<ItemTemplate>
<dt>
<strong><a href='<%#DataBinder.Eval(Container.DataItem, "Url")%>'> <%#DataBinder.Eval(Container.DataItem, "Title")%></a></strong>
</dt>
<dd>
<%#DataBinder.Eval(Container.DataItem, "Snippet")%>
</dd>
<dd>
<em><a href='<%#DataBinder.Eval(Container.DataItem, "Url")%>'> <%#DataBinder.Eval(Container.DataItem, "Url")%></a> </em>
</dd>
</ItemTemplate>
<FooterTemplate>
</dl>
</FooterTemplate>
</asp:Repeater>
Finally, all we do is use the Search Manager to issue a query, and bind the results to our repeater. There are several overloads to the search method you can use, but I used one that is actually not documented that takes the paramters (string SearchQuery, string SearchIndexName, int StartIndex, int MaxResults, string SearchMode, out int ResultCount).
int count;
IList<Telerik.Search.Engine.ResultItem> results = Telerik.Search.Engine.SearchManager.Search(q, "McAllen-NET", 0, 5, "AnyWord", out count);
if (count == 0)
rptResults.Visible = false;
else
{
rptResults.DataSource = results;
rptResults.DataBind();
}
And that's all there is to it! Take a look at this in action by visiting this link: http://www.mcallen.net/news/default/2009-01-05/28th_annual_golden_age_olympics.aspx. The event is actually supposed to be the 29th Annual Golden Age Olympics, but since it was entered incorrectly, the link is now broken since the title was changed. However, if you click the link, you'll see that the first result is the correct one to the news item. Not too shabby!
I hope that this article was helpful. Feel free to submit your comments and criticisms (I can take it!) As always, thanks for reading!
Enjoyed this post and/or found it useful?