SharePoint out of the box provides only the default 404 page when users attempt to navigate to a page or site that doesn't exist. It has become very common place for users to expect some assistance when entering an invalid URL and also to be forwarded to the proper site when hitting vanity subsite names. For example if you were to have a product called widget whose physical location is referenced at http://www.contoso.com/products/widgets but you would like users who access http://www.contoso.com/widget to be directed to the proper site.
For complete sourcecode and solution package check out the CodePlex project at http://www.codeplex.com/sharepointsmart404 . Special thanks to Chris Regan for some creative assistance on this project.
Lets go ahead and summarize the goals of this feature:
- Provide a friendly 404 message to the user
- Provide some possible best bets on what they may have been attempting to look for based on the URL
- Provide a vanity redirect mechanism
- Implement these features with as little overhead on the general page lifecycle
Before we get started on the implementation lets take a quick peak at the end results:
To implement this feature we'll want to create a new feature that provisions several elements and assemblies to support this functionality.
1) Create a new Feature that will support a simple activation to add this functionality to a site. This feature will include elements to support a custom 404 page, receivers to register the custom 404 page, and finally the support infrastructure for the vanity management:
<Feature xmlns="http://schemas.microsoft.com/sharepoint/"
Id="625D428C-1191-425D-9855-D68335173195"
Title="SharePoint Smart 404"
Description="Provides smart Page Not Found handling."
Scope="Site"
Hidden="False"
Version="1.0.0.0"
ReceiverAssembly="SharePointSmart404, Version=1.0.0.0, Culture=neutral, PublicKeyToken=eaf58ff25bb059f9"
ReceiverClass="SharePointSmart404.SharePointSmartReceiver">
<ElementManifests>
<ElementManifest Location="pages.xml" />
<ElementManifest Location="vanity.xml"/>
<ElementFile Location="Pages\Smart404.aspx"/>
</ElementManifests>
</Feature>
2) Central to the custom 404 functionality is the implementation of a custom 404 html file. Unfortunately the landing page for 404 errors within SharePoint do not support asp.net pages. Instead a HTML page receives the initial 404 error and forwards to a designated site. The custom 404 html file must be registered through the sharepoint API. As you can se we are parsing the url in javascript to obtain the subsite details. We then create a new query string that the SharePoint search webparts will then use to provide some best bets.
<html>
<head>
<meta HTTP-EQUIV="Content-Type" content="text/html; charset=utf-8" />
<meta HTTP-EQUIV="Expires" content="0" />
<noscript>
<meta http-equiv="refresh" content="0; url=/_layouts/spsredirect.aspx?noscript=1" />
</noscript>
<script language="javascript" src="/_layouts/1033/init.js"></script> 1:
2: <script language="javascript" src="/_layouts/1033/core.js">
1: </script>
2: <script language="javascript">
3: var requestedUrl = escapeProperly(window.location.href);
4: var url_array = window.location.href.split("/"); 5: var lastelementindex = url_array.length -1
6: var pagename = url_array[lastelementindex];
7:
8: STSNavigate("/pagenotfound/smart404.aspx?oldUrl=" + requestedUrl +"&k=" + pagename); 9:
</script>
</head>
<body>
</body>
</html>
3) Create a custom 404 page. In our case this page will require a code behind because we need to support some vanity redirect logic. Additionally this page should contain the supporting webparts to provide search results based on query string parameters passed in from the custom 404 page. This page is being provisioned as a site page as opposed to an application page in the layouts folder so that it can support customization per web application. Because this page will need to support cusomtization we do not want to do inline code.
<asp:Content ID="Content1" ContentPlaceholderID="PlaceHolderPageTitle" runat="server">
404 - Page Not Found
</asp:Content>
<asp:Content ID="Content3" ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceholderID="PlaceHolderMain" runat="server">
<div class="smart404">
<h2>
The page you requested could not be found.</h2>
<div class="smart404Notice">
We apologize, but the page you requested is unavailable. It may be temporarily offline
due to maintenance, or it may not exist.
</div>
<div class="smart404Return">
<a href="javascript:history.go(-2)">Return to Previous Page</a>
</div>
<div class="smart404Search">
<h4>Best Bets</h4>
<WebPartPages:WebPartZone runat="server" ID="mainContentZone">
<ZoneTemplate>
<Search:coreresultswebpart runat="server" id="search" DisplayRSSLink="false" DisplayAlertMeLink="false"
ResultsPerPage="10" ChromeType="None" >
</Search:coreresultswebpart>
<br />
<Search:SearchPagingWebPart runat="server" id="searchpaging" ChromeType="None">
</Search:SearchPagingWebPart>
</ZoneTemplate>
</WebPartPages:WebPartZone>
</div>
</div>
</asp:Content>
4) Next we'll need to provision the custom 404 page and the location it will be residing in:
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ListInstance TemplateType="101" RootWebOnly="false"
Id="PageNotFound"
Title="Page Not Found"
Description="Page Not Found Supporting Pages"
Url="PageNotFound"
OnQuickLaunch="False"
FeatureId="00BFEA71-E717-4E80-AA17-D0C71B360101">
</ListInstance>
<Module Url="PageNotFound"
Path="Pages"
RootWebOnly="TRUE">
<File Url="Smart404.aspx"
Name="Smart404.aspx"
Type="GhostableInLibrary"
IgnoreIfAlreadyExists="TRUE">
</File>
</Module>
</Elements>
5) In the feature receiver for our feature we want to make sure that the new HTML form gets registered.
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
string webAppUrl = (properties.Feature.Parent as SPSite).Url;
SPWebApplication webApplication = SPWebApplication.Lookup(new Uri(webAppUrl));
webApplication.FileNotFoundPage = "smart404.html";
webApplication.Update();
}
In part 2 of my next post I'll touch on the implementation of the vanity portion of the smart 404 implementation.