2

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 ?

Veverke
  • 9,208
  • 4
  • 51
  • 95
  • You can override the response in the global application error event so it doesn't result in a redirect - see the global asax snippet here: http://stackoverflow.com/questions/25844627/custom-error-page-when-http-error-occured-without-changing-url/25844913#25844913 – Carl Mar 11 '15 at 14:28
  • @Carl: does that beat a bit the flexibility we acquired by being provided a solution where any chances you have to do take place at the web.config (which are transparent to a running application) ? Your solution requires me to wire code in the global.asax. – Veverke Mar 11 '15 at 14:31
  • Yes, but how often do you change the error page path? If you want to preserve flexibility, you could always add AppSettings keys to control custom pages on/off, target action results etc. As far as I am aware, you cannot return the correct status code usign the webconfig custom errors alone as it uses server.transfer as I've explained here: http://www.trycatchexfinally.co.uk/posts/handling-custom-errors-in-aspnet-mvc. As I say, not that I've ever come across a way myself but if you do find a way I'd be interested to know about it :) – Carl Mar 11 '15 at 14:36
  • @Carl: "you cannot return the correct status code using web config custom errors alone" - this is the sort of answer am I looking for :-). Let's see if anyone else does know a way. Otherwise... the answer is no and I will happily shut this door for once (and consider solutions like the ones you suggest). Thanks anyway. – Veverke Mar 11 '15 at 14:39
  • @Carl: by the way your blog is a very good idea, I have been thinking of doing the same for a long time (keeping track of solutions you used...) but there is one thing that separates us 2, and it is called: laziness... – Veverke Mar 11 '15 at 14:41
  • No problem! hope you find something that works for you :-) – Carl Mar 11 '15 at 15:06
  • the following discussion might turn out mandatory to answer the question: http://stackoverflow.com/questions/2480006/what-is-the-difference-between-customerrors-and-httperrors – Veverke Mar 11 '15 at 16:25

2 Answers2

1

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.

scgough
  • 5,099
  • 3
  • 30
  • 48
  • well, this has no advantage over the standard way, since here you are specifying different paths for each distinct http status code. I can do the same with "customerrors". What I want... is a single view to handle different http status codes. – Veverke Mar 11 '15 at 16:07
  • edited - you can just add a default path to the "httpErrors" node. Also "httperrrors" is the correct one to use for current IIS releases. – scgough Mar 11 '15 at 16:09
  • the same can be done with "customerrors" :-) and yet... the view will not know what was the http status code of the errored response. – Veverke Mar 11 '15 at 16:10
  • customerrors is a legacy element. You're right though...no way of knowing which code is relevant. Couldn't you do it as I 1st said and in the controller handle the code and return a single error View for all controller Actions?? – scgough Mar 11 '15 at 16:12
  • you mean... in the controller handle each different path/error code - but use the same view... hmm, well that's what I was trying to do with "custom errors", but the http code was always 200 because I was always "inside" a new redirect request, not in my erroneous request anymore. If you are saying that httpErrors will not make redirects... than it might be an option. I will check it and let you know. – Veverke Mar 11 '15 at 16:15
  • I've added a PPS @Veverke – scgough Mar 11 '15 at 16:15
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/72776/discussion-between-scgough-and-veverke). – scgough Mar 11 '15 at 16:16
  • Your answer is, in the end, what I ended up using. I am, however, using IIS 7, and I did not have to work using httpErrors, rather I kept working with customErrors and it works fine. The main problem was that redirecting to a custom page for 404 is pretty straightforward, whilst for 500 it is the exact opposite. – Veverke Mar 29 '15 at 09:18
1

The solution I was looking for (after being on the verge of giving up) is described here.

Summing up the steps:

  1. Set your web.config to redirect to a controller action for each error you want to handle.
  2. Set TryToSkipCustomErrors property of the Response object to true, in each action defined in 1.
  3. Comment out the line filters.Add(new HandleErrorAttribute()); from your filterConfig.cs file.

Using this approach I finally succeeded in having a redirect to a custom page for error 500. There seems to be some sort of default skip by IIS for 500, because setting a custom page for error 404 does not require neither steps 2 nor 3.

So, in the end, for each http status code I want to render a custom page, I create a controller action, set the described properties accordingly, and then redirect to the same view, for all actions - where I display @Response.Status, making it enough a generic solution for me.

Veverke
  • 9,208
  • 4
  • 51
  • 95