EPiServer - how to return 404 for expired pages

Introduction

When site visitors try to open a page that has expired, they'll get a login page instead of 404 page.

That's by design, but we can easily change the default behavior and throw 404 instead.

EDIT 26.10.2016: 404 is the default behavior in Episerver 10.

Solution 1

If you're starting a new project from scratch, you can add the following code in your base controller:

protected override void OnAuthorization(AuthorizationContext filterContext)
{
    // don't throw 404 in edit mode
    if (!PageEditing.PageIsInEditMode)
    {
        if (PageContext?.Page != null && PageContext.Page.StopPublish <= DateTime.Now)
        {
            filterContext.Result = new HttpStatusCodeResult(404, "Not found");
            return;
        }
    }

    base.OnAuthorization(filterContext);
}

Important note: page should not throw 404 in edit mode because site editors should be able to see / modify the page although it expired.

Solution 2

If you're working on existing project that doesn't have a base controller, or you're simply not a fan of base controllers, you can create a global filter:

Update on 2016-07-21: In some versions of EPiServer, users will be prompted to log in on any page, even if Everyone has Read access and the page has not even expired. To fix this, just remove all the instances of base.OnAuthorization(filterContent) from ExpiredContentFilter class. 

public class ExpiredContentFilter : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        // skip the execution for controllers
        // that don't derive from PageController<>
        if (!IsPageController(filterContext.Controller.GetType()))
        {
            base.OnAuthorization(filterContext);
            return;
        }

        // don't throw 404 in edit mode
        if (!PageEditing.PageIsInEditMode)
        {
            var pageContext = ServiceLocator.Current.GetInstance<PageRouteHelper>();
            if (pageContext?.Page != null && pageContext.Page.StopPublish <= DateTime.Now)
            {
                filterContext.Result = new HttpStatusCodeResult(404, "Not found");
                return;
            }
        }

        base.OnAuthorization(filterContext);
    }

    private bool IsPageController(Type type)
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(PageController<>))
        {
            return true;
        }

        return type.BaseType != null && IsPageController(type.BaseType);
    }
}

Namespaces to include:

using System;
using System.Web.Mvc;
using EPiServer.Editor;
using EPiServer.ServiceLocation;
using EPiServer.Web.Mvc;
using EPiServer.Web.Routing;

To register a filter as global, open Global.asax.cs and add the following code in Application_Start()

protected void Application_Start()
{
    GlobalFilters.Filters.Add(new ExpiredContentFilter());
    // ...
}

Solution 3

Sometimes you want to make a nuget package and you cannot modify the content of Application_Start() method in order to register a global filter.

In that case, you can create a new initialization module:

[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ExpiredContentModule : IInitializableModule
{
    public void Initialize(InitializationEngine context)
    {
        GlobalFilters.Filters.Add(new ExpiredContentFilter());
    }

    public void Preload(string[] parameters) { }

    public void Uninitialize(InitializationEngine context) { }
}

Namespaces to include:

using System.Web.Mvc;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
comments powered by Disqus