Think things through, or: Simple way to 404 Not Found instead of EPiServer login screen for unpublished pages

I spent some time today attempting to make visitors land on a custom 404 Not Found error page when surfing to EPiServer pages that are no longer published; i.e. pages where the StopPublish date has passed (click here to skip my rant and go directly to the solution). The behaviour that we were experiencing on our site was an automatic redirect to EPiServer’s /Util/login.aspx with a ReturnUrl parameter, which was not desirable. Having recently written a redirect module for a custom login functionality, I first failed trying a similar approach to this problem.

Failing 404 module

public class MyModule : IHttpModule
{
  public void Init(HttpApplication context)
  {
    context.BeginRequest += My404Handler;
  }

The brilliant idea to attach a new event handler to the BeginRequest event, catching things early on, turned out to work a little bit too well. At this point, EPiServer’s authentication code has not been run yet, resulting in no way of knowing whether the user is logged in or not. Hence, an editor surfing to the page in EPiServer’s Edit Mode ended up getting the 404 Not Found page as well.

Failing 404 module, revisited

public class MyModule : IHttpModule
{
  public void Init(HttpApplication context)
  {
    context.PostAuthenticateRequest += My404Handler;
  }

  private void My404Handler(object sender, EventArgs e)
  {
    var application = sender as HttpApplication;
    var context = new HttpContextWrapper(application.Context);
..
    var url = new UrlBuilder(context.Request.Url);

So, after reattaching the handler to the PostAuthenticationRequest event, the context.User.Identity.IsAuthenticated gave a much better result. The only problem now was that during the PostAuthenticationRequest event, unauthorized visitors have already had their context.Request.Url:s redirected to EPiServer’s /Util/login.aspx. So. Fail. Again.

Eleven lines of code to properly 404 Not Found on unpublished EPiServer pages

Doing what I should have done from the start, I had a look in the EPiServer documentation. It seems like there is already a brilliant way of handling this problem in just a few lines of code. By overriding the AccessDenied method in our page base (inheriting from the EPiServer.TemplatePage; the virtual AccessDenied method is located on the EPiServer.PageBase class), I was able to do my date check rather easily.

TemplatePageBase.cs

public abstract class TemplatePageBase<TPage> : EPiServer.TemplatePage, IStandardPage
    where TPage : PageData
{

public override void AccessDenied()
{
// Don’t use CurrentPage directly, see update below.
var currentPage = EPiServer.DataFactory.Instance.GetPage(CurrentPageLink);
if (currentPage.StopPublish <= DateTime.Now) { Context.Response.Clear(); Context.Response.Status = "404 Not Found"; Context.Response.StatusCode = (int)HttpStatusCode.NotFound; Context.Response.End(); return; } base.AccessDenied(); } } [/csharp]

AccessDenied is run whenever EPiServer decides that a visitor is not authenticated, giving you all the opportunities in the world to do whatever you want about it.

Note: Perhaps a 404 Not Found page is not what you should be showing at this point; perhaps what you really are looking for is a 410 Gone page. What do unpublished pages mean on your site?

Update: Don’t use CurrentPage directly in the AccessDenied override. As Niel states in his comment below there will be an infinite loop crashing the application pool if using CurrentPage directly in the AccessDenied override. This is basically because the current user do not have access to CurrentPage (which is why we are in AccessDenied in the first place); when trying to access CurrentPage again we get redirected to the same method over and over. It is possible to bypass this by calling the DataFactory’s GetPage with the current page link. Thanks Niel for the comment, and thank you Fredrik Haglund for explaining. I have updated the code sample accordingly.

11 Comments

  1. Mikael Jönsson April 3, 2012
    • Mathias Kunto April 3, 2012
  2. Chetan April 4, 2012
    • Mathias Kunto April 4, 2012
  3. Chetan April 5, 2012
    • Mathias Kunto April 5, 2012
  4. Neil May 30, 2012
    • Neil May 30, 2012
      • Neil May 30, 2012
    • Mathias Kunto June 8, 2012
      • Neil July 4, 2012