Search code examples
asp.net-mvcerror-handlingcustom-errors

How do I catch the http status code of server errors into a generic custom error view in ASP.NET MVC


I am trying to implement a general custom error page in ASP.NET MVC 4. I basically set up an Error Layout, which defines a section for outputting the http status code of the response.

The view that I want my errors to end up at inherits such layout, and simply adds a message that comes from its model, which was instantiated and passed in the call to View() in the Controller (named "Error") I set up to handle custom errors, in the web.config.

<customErrors defaultRedirect="/Error" mode="On"> 
</customErrors>

The custom error controller:

public class ErrorController : Controller
{
    public ActionResult Index()
    {
        return View(new CustomErrorViewModel(Response.Status));
    }
}

The custom error view:

@{
    Layout = "~/Views/Shared/_CustomErrorLayout.cshtml";
}

@using System.Web.Configuration;
@using System.Configuration;
@using MVC_Tests.Models;
@model CustomErrorViewModel

@section HttpCode { @Response.StatusCode }

@Model.Status

The layout is:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title></title>
    <link href="~/Content/CustomError.css" rel="stylesheet" />
</head>
<body>
    <div id="divError">
        <h1>
            <span>Error &nbsp;</span>
            @RenderSection("HttpCode")
        </h1>
        <div id="divErrorMessage">
            <p>@RenderBody()</p>
        </div>
    </div>
</body>
</html>

The error I am trying to handle is a simple division by zero I added to the Home Index action. Note that I want a single view to handle different server http status error codes (500 family). I know having a different view for each distinct http status code works, I am after a single view to handle multiple codes.

The custom error handling is working, I am ending in the desired view - but - as noted above, I expected to output the http status code of the error. Instead, I am always displaying 200 (OK), which, according to my understanding and debugging, happens because:

1. First, upon stumbling with the division by zero, an exception is raised.

2. Now a redirect takes place, due my web.config instructions to handle errors in a customized way (since no specific http status code was specified in the web.config, I handle every http status error code in the same controller/view ("/Error"). What is crucial here is that the redirect is a new request.

3. The redirect from step 2 sends me to the Error Controller, and it then sends me to its view, which is rendered.

4. In rendering the view, the http status code inserted into the layout defined section, my custom message is added, and the output of the http status code part is as I said: 200 (OK). Why, if a 500 server side error was forcibly thrown ? Because, if I am not wrong, the view is rendered within a different request pipe, the redirect one, and in redirecting - no errors occur - and I end up with a 200 (OK) status response.

I went over this post by Dino Esposito about handling errors in asp.net mvc, but I do not want to add filters (OnException and HandleError attribute solutions) to all the app's controllers (I could create a new base controller that has such filter, and make all the others inherit from it, but that also requires changing all controller classes). The last approach he mentions (handling global.asax's Application_Error event) I am not sure does serve this purpose - I have to issue a redirect "Response.Redirect" statement in the handler, which beats the flexibility of being able to set the custom pages in the web.config (I could still redirect to a route defined in the web.config, but I wonder if this is not getting too cumbersome for something that looks so simple).

What is the best way to catch the response's http status code of any server error into my custom error view ?


Solution

  • If you're using IIS7+ I'd disable "customerrors" like so:

     <customErrors mode="Off" />
    

    and use httpErrors like so:

    <system.webServer>
     <httpErrors errorMode="DetailedLocalOnly" existingResponse="Replace" defaultPath="/error">
         <remove statusCode="404" />
         <error statusCode="404" path="/error/notfound" responseMode="ExecuteURL" />
     </httpErrors>
    ....
    

    This allows you to get a non-url changing error page displaying with a 404 status code using the following in your controller:

    public class ErrorController : Controller
    {
    public ActionResult notfound()
        {
            Response.TrySkipIisCustomErrors = true;
            Response.StatusCode = (int)HttpStatusCode.NotFound;
            return View("OneErrorViewToRuleThemAll");
        }
    }
    

    ps. "errorMode" needs to change to "Custom" to see it locally....depending on your local setup (i.e. if you're using IISExpress) you might not see the proper error until you upload to a live server.

    pps. you could have the single view "OneErrorViewToRuleThemAll" to handle the on-screen output of whatever status codes you decide to handle this way. Each ErrorController Action would end up on this view.