Thursday, June 3, 2010

SharePoint 2010 Custom Error Messages for Public Facing Deployments

It's a fairly common requirement when building a public facing SharePoint site to make sure all pages share branding elements. This includes the SharePoint error pages. While working on a publishing site, you've probably encountered an article that touches on one of the error pages, but for some reason I haven't seen one that tries to cover all of them. So hopefully this will do the job!

Error Pages

If you are just looking to control the basic error pages, then look no further than the SPWebApplication.SPCustomPage enumeration, which provides a list of commonly updated pages. You use the SPWebApplication.UpdateMappedPage() method to set which pages SharePoint should serve up for each of the values within the enumeration. For a good example of this, see this post.

HTTP 404
But what about other HTTP status codes? Well the easiest would be the 404. SharePoint already has support for a custom 404 page, as long as it is pure HTML. The default 404 is located in 14\TEMPLATE\LAYOUTS\1033\sps404.html. The quickest way to override this is to use a feature to deploy your own HTML page into the same folder. A feature receiver can then update the SPWebApplication.FileNotFound property with a relative path to your file. There are just a couple points to keep in mind when you do this:
  1. Your custom page should be greater than 512 bytes. This is because the default feature in Internet Explorer to show "friendly" error messages will sometimes ignore pages smaller than this. You can read more about this problem at Microsoft Support.
  2. Your custom page, if encoded as UTF-8, should not have a BOM. Even if you save your file as UTF-8 without BOM, editing it later in Visual Studio will add the BOM back, so be careful. You can read more about that problem here.

If you'd like to have a custom 404 that performs server side code, such as to include web parts, you'll need to get a little more creative. There are two common ways to handle this. The first would be to have a static html page that performs a client side redirect with either a META tag or javascript. If you'd like to see that approach in action, here is an example.

A more robust solution would be to use an HttpModule. With the HttpModule, you have two more possibilities for how you serve your page. You can use a Response.Redirect, which is what the linked article suggests. The only downside to this is that the URL changes, which may or may not be desired. Alternatively, to keep the URL, you would need to use Response.Write.

HTTP 401
Now that you have custom error pages for the SPCustomPage enumered pages as well as a 404 page. But what about a 401? The simplest way you might think to do this would be to edit the web.config CustomErrors element. In any normal ASP.NET application, that would have been sufficient. Unfortunately it doesn't work in SharePoint.

Your next instinct might be to try IIS, as it provides the ability to set custom error pages. If you edit the property for the 401.2 status code and point it to a custom HTML page on your site, it may or may not work. In testing, it turns out that having Anonymous Access enabled in SharePoint (which then sets it in IIS) prevents a custom 401.2 page from being used. However, if Anonymous Access is disabled, such as with an Intranet site, then the custom page will show just fine. Since we are talking about a public facing deployment you are probably using the Publishing template, so you're going to need a different approach.

The trick for a custom 401 message when you have Anonymous Access enabled is using a custom http module, much like you could have done for the 404.

Here is an example:

public class CustomPageMappingsHttpModule : IHttpModule
{
 private const string Custom401File = "/_layouts/1033/Custom401_CSS.htm";

 private HttpApplication _application;

 public void Dispose()
 {
 }

 public void Init(HttpApplication context)
 {
  this._application = context;
  this._application.PreSendRequestHeaders += new EventHandler(_application_PreSendRequestHeaders);
 }

 protected void _application_PreSendRequestHeaders(object sender, EventArgs e)
 {
  HttpResponse response = this._application.Response;

  if (response.ContentType.Equals("text/html", StringComparison.CurrentCultureIgnoreCase))
  {
   string message = string.Empty;

   switch (response.StatusCode)
   {
    case 401:
     message = File.ReadAllText(this._application.Server.MapPath(Custom401File));
     this._application.Response.Clear();
     this._application.Response.Write(message);
     break;
   }
  }
 }
}

Once you hook that up in your web.config, go ahead and try to load http://YourSite/_layouts/settings.aspx. You should get the same authentication prompt you'd expect for an anonymous user, but if you hit cancel you'll now see your custom 401 message. Note that the way you register an HttpModule in 2010 is slightly different. If you add the module to the httpModules element in web.config, it may not work. In testing, I was only able to get my module to work by adding it to the modules element with the other SharePoint HttpModules.

As a final level of customization, you could consider having no prompt at all on your public site when users request restricted content. The way to do this is to extend the site and have a private site as well. Since we are just talking about two different web sites in IIS (with corresponding settings and web.config files), you can control their authentication and error settings independently while still serving up the same content. The public site could then have Anonymous Access enabled and Windows Authentication disabled. This removes the login prompt when a 401 occurs on the public site. The private site would have Anonymous Access disabled but Windows Authentication enabled, allowing users to log in and manage content as needed.

Quirks
Since you are probably using the Publishing template, it's worth mentioning the Lockdown feature. This feature stops users from being able to see your Form pages. It also locks down lists and libraries, such as the Style Library. So make sure if you use CSS in your custom error pages that you know whether you have lockdown enabled and plan locations for your assets appropriately.

3 comments:

  1. Hi

    I have been battling with trying to set up a custom 401.htm page via HttpErrors section in my IIS 7 web.config. Then I came accross your post.
    Yep sure enough I have anonymous enabled in IIS 7 ... and all because I have to, at the web app level before delegated this access to a list or sub site.

    I just wondered does the feature work for non-anonymous sites as well?

    ReplyDelete
  2. Hi, great post, but what would you do if you have anonymous access disabled and want codebehind on the custom 401 page?

    ReplyDelete