Skip to content

Instantly share code, notes, and snippets.

@stefanolsen
Last active August 27, 2017 11:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stefanolsen/0dcca8643dd80200615a707df8811be6 to your computer and use it in GitHub Desktop.
Save stefanolsen/0dcca8643dd80200615a707df8811be6 to your computer and use it in GitHub Desktop.
Code listings for blog post about generically handling exceptions in EPiServer, showing stack trace to those allowed to see them. Read about it here: https://stefanolsen.com/posts/a-custom-exception-handler-for-episerver-pages/
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public sealed class HandleErrorFriendlyAttribute : HandleErrorAttribute
{
internal Injected<LocalizationService> LocalizationService;
internal Injected<IPageRouteHelper> PageRouteHelper;
internal Injected<PageViewContextFactory> PageViewContextFactory;
internal Injected<PermissionService> PermissionService;
public string ErrorMessageTranslationKey { get; set; }
public override void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException(nameof(filterContext));
}
if (filterContext.IsChildAction ||
filterContext.ExceptionHandled)
{
// Skip handling if exception was caused by a child action or if it was already handled.
return;
}
var httpContext = filterContext.HttpContext;
if (httpContext.IsDebuggingEnabled)
{
// Skip handling if site is running in debug mode (<compilation debug="true"> in web.config).
return;
}
if (httpContext.IsCustomErrorEnabled)
{
// Skip handling if site has custom errors enabled (in web.config).
return;
}
Exception exception = filterContext.Exception;
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500)
{
// Skip handling if the error is not really translatable to a 500 status code.
// Maybe a 401 or 404 exception was thrown, or something else.
return;
}
if (!ExceptionType.IsInstanceOfType(exception))
{
// Skip handling if the exception type of this attribute does not relate to the thrown exception (or if is not set).
// An Exception type would cover everything. More specific types would only cover itself or derived exception types.
return;
}
// Mark the exception as handled.
filterContext.ExceptionHandled = true;
httpContext.Response.Clear();
httpContext.Response.StatusCode = 500;
httpContext.Response.TrySkipIisCustomErrors = true;
bool isDetailsAllowed = PermissionService.Service.IsPermitted(httpContext.User, SystemPermissions.DetailedErrorMessage);
var httpRequest = httpContext.Request;
if (httpRequest.IsAjaxRequest())
{
// If the request is an AJAX request.
HandleErrorAjax(filterContext, exception, isDetailsAllowed);
}
else
{
// If the request is a regular request.
HandleErrorPage(filterContext, exception, isDetailsAllowed);
}
}
private void HandleErrorAjax(ExceptionContext filterContext, Exception exception, bool showDetails)
{
// Return a JSON object with the error message.
// If the user is allowed to see detailed error messages, include the full exception text.
var localizationService = LocalizationService.Service;
string errorMessage = localizationService.GetString(ErrorMessageTranslationKey, FallbackBehaviors.Echo);
// TODO: You might want to use real types here.
object error = new
{
Message = errorMessage,
Exception = showDetails ? exception.ToString() : null
};
filterContext.Result = new JsonResult { Data = error, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
filterContext.ExceptionHandled = true;
}
private void HandleErrorPage(ExceptionContext filterContext, Exception exception, bool showDetails)
{
// Skip handling if the user has permission to see detailed error messages (an ASP.Net YSOD).
// By now we know that EPiServer will not render its simple error page when we skip out
if (showDetails)
{
return;
}
// TODO: Handle the rendering of the error page on your own.
// The following code works for EPiServer's Alloy Tech sample site.
var pageData = PageRouteHelper.Service.Page as SitePageData;
if (pageData == null)
{
// If the current page does not work, give up and let EPiServer show its simple error message.
return;
}
// If the user is not allowed to see that kind of details, render a friendly error page.
var localizationService = LocalizationService.Service;
string errorMessage = localizationService.GetString(ErrorMessageTranslationKey, FallbackBehaviors.Echo);
var viewModel = new ErrorViewModel();
viewModel.CurrentPage = pageData;
viewModel.Layout = PageViewContextFactory.Service.CreateLayoutModel(pageData.ContentLink, filterContext.RequestContext);
viewModel.ErrorMessage = errorMessage;
filterContext.Result = new ViewResult
{
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary(viewModel),
TempData = filterContext.Controller.TempData
};
}
}
[HandleErrorFriendly(ErrorMessageTranslationKey = "/errormessages/generalerrormessage/heading")]
public abstract class PageControllerBase<T> : PageController<T>, IModifyLayout
where T : SitePageData
{
// Common controller code goes here...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment