asp.net-mvchandleerror

ASP.NET MVC user friendly 401 error


I have implemented errors handling in ASP.NET MVC site in a way like suggests this post.

With 404 errors all works fine. But how correctly show user friendly screen for a 401 error? They usually do not throw Exception that can be handled inside Application_Error() but rather action returns HttpUnauthorizedResult. One possible way is to add following code to the end of Application_EndRequest() method

if (Context.Response.StatusCode == 401)
{
    throw new HttpException(401, "You are not authorised");
    // or UserFriendlyErrorRedirect(new HttpException(401, "You are not authorised")), witout exception
}

But inside Application_EndRequest() Context.Session == null, errorController.Execute() fails because it cannot use default TempDataProvider.

  // Call target Controller and pass the routeData.
  IController errorController = new ErrorController();
  errorController.Execute(new RequestContext(    
       new HttpContextWrapper(Context), routeData)); // Additional information: The SessionStateTempDataProvider requires SessionState to be enabled.

So, can you suggest some best practices how to 'user friendly handle' 401 in ASP.NET MVC application?

Thanks.


Solution

  • Look at the HandleErrorAttribute. Subclass from it or add your own implementation which will handle all the status codes you're interested in. You can make it to return a separate error view for each error type.

    Here is an idea of how to create your handle error exception filter. I've thrown out most of the stuff to only focus on our essentials. Absolutely have a look at the original implementation to add arguments checks and other important things.

    public class HandleManyErrorsAttribute : FilterAttribute, IExceptionFilter
    {
        public virtual void OnException(ExceptionContext filterContext)
        {
            if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
                return;
    
            Exception exception = filterContext.Exception;
    
            string viewName = string.Empty;
            object viewModel = null;
            int httpCode = new HttpException(null, exception).GetHttpCode();
            if (httpCode == 500)
            {
                viewName = "Error500View";
                viewModel = new Error500Model();
            }
            else if (httpCode == 404)
            {
                viewName = "Error404View";
                viewModel = new Error404Model();
            }
            else if (httpCode == 401)
            {
                viewName = "Error401View";
                viewModel = new Error401Model();
            }
    
            string controllerName = (string)filterContext.RouteData.Values["controller"];
            string actionName = (string)filterContext.RouteData.Values["action"];
            filterContext.Result = new ViewResult
            {
                ViewName = viewName,
                MasterName = Master,
                ViewData = viewModel,
                TempData = filterContext.Controller.TempData
            };
            filterContext.ExceptionHandled = true;
            filterContext.HttpContext.Response.Clear();
            filterContext.HttpContext.Response.StatusCode = httpCode;
    
            filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
        }
    }
    

    Then you "decorate" your controller actions with this attribute:

    [HandleManyErrors]
    public ActionResult DoSomethingBuggy ()
    {
        // ...
    }