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.
Nice post, but wouldn’t it be more EPiServer best practice to use CurrentPage.CheckPublishedStatus(PagePublishedStatus.Published) instead of checking the StopPublish property?
Thanks Mikael. It’s really motivating getting comments :)
In my opinion it all depends on the circumstances, often boiling down to readability and clean, working code. I always get a bit uneasy as soon as I hear the words best practice as I believe that every situation is different. Even though both ways would probably work, in this particular case I failed to see the gain of using CheckPublishedStatus as it would only add a few extra if-statements checking statuses and StartPublish dates whilest making the code slightly harder to read; when all I really wanted to do was check if the StopPublish date had passed.
Where i can find EPiServer.TemplatePage and EPiServer.PageBase class file to work this issue
EPiServer.TemplatePage and EPiServer.PageBase are both base classes in the EPiServer.dll along with a few others (see picture below). They all add different things for you to work with; TemplatePage will for instance give you right mouse click administration and on-page editing.
One way of working with them is to inherit from them in your page template (aspx) files.
Login.aspx.cs
This would only work for EPiServer page types pointing to the login template though; so you would probably have to create some sort of base class instead, using it on your templates.
There are of course other ways of working with this as well, like our abstract page base using generics shown in the post for instance, but I hope this answered your question.
Hello Mathias Kunto,
Thanks for the reply, we have class named
i try to write above code as
but when i clicked on the link of unpublished page, i found there is error for CurrentPage.StopPublish at the time of debugging, why this is so?
Please help me out.
Hi Chetan,
I’ve sent you an E-mail. Hope it helps, and let me know if it doesn’t.
Cheers!
Hi – If I try to use this when I hit a page that has Anonymous permissions removed, the process appears to go into a loop and crashes the app pool. Any ideas?
Update to previous post – the crash occurs as within a page with no anon access as soon as I try to reference CurrentPage
Here’s the answer – don’t reference CurrentPage directly get it from CurrentPageLink, i.e.
PageData currPage = EPiServer.DataFactory.Instance.GetPage(CurrentPageLink);
Sorry for not getting back to you sooner, have had a lot on my plate the last couple of. Happy to hear you got it working. Where did you need to use the CurrentPageLink?
Hi, my apologies now, just instead of referencing CurrentPage directly I needed to use:
public override void AccessDenied()
{
PageData currPage = EPiServer.DataFactory.Instance.GetPage(CurrentPageLink);
if (!currPage.CheckPublishedStatus(PagePublishedStatus.Published) || currPage.IsDeleted)
{
Context.Response.Status = "404 Not Found";
Context.Response.StatusCode = (int)HttpStatusCode.NotFound;
Context.Response.End();
return;
}
base.AccessDenied();
}