15

I'm currently developing custom error pages in my error handling code for my MVC application. But I'm unclear as to which HTTP status codes I'm meant to cover.

Question: is there a typical list of HTTP status codes that should be catered for?

Alot articles which explain how to do MVC error handling and custom error pages but appear to only show several of the HTTP Status Codes: 403, 404, and 500 in their error handling code. What about HTTP Status Code: 408 as an example? Should this be covered? What about the tonne of other status codes - HTTP status codes on wiki

This may sound like a dumb question, but I really don't know the answer and can't find an information on this. Am I missing something here, i.e. should only a subset of status codes be covered?

If it helps, below is what I've done for my MVC error handling. This code (so far with the little testing that I've done) covers 404, and all 50x type exceptions:

1 In web.config, and entry for each HTTP status code I want to cover

<httpErrors errorMode="Custom" existingResponse="Replace" >
  <remove statusCode="403" />
  <remove statusCode="404" />
  <remove statusCode="500" />
  <error statusCode="403" responseMode="ExecuteURL" path="/Error/Forbidden" />
  <error statusCode="404" responseMode="ExecuteURL" path="/Error/NotFound" />
  <error statusCode="500" responseMode="ExecuteURL" path="/Error" />
</httpErrors>  

2 An error controller

    namespace MyApp.Controllers
    {
      public class ErrorController : Controller
      {
      public ActionResult Index()
      {
        return View();
      }
      public ActionResult Forbidden()
      {
        return View();
      }
      public ActionResult NotFound()
      {
        return View();
      }

3 User friendly error pages:

/Views/Shared/Index.cshtml
/Views/Shared/Forbidden.cshtml
/Views/Shared/NotFound.cshtml

4 ELMAH for logging

Further findings at 2 Nov 2015

Something I've just discovered that has been staring me in the face which I've missed... In IIS, the default Error pages covered are:

  • 401 – Unauthorized
  • 403 – Forbidden
  • 404 – Not Found
  • 405 – Method Not Allowed
  • 406 – Not Acceptable
  • 412 – Precondition Failed
  • 500 – Internal Server Error
  • 501 – Not Implemented
  • 502 – Bad Gateway

If this is good range Microsoft have set, then I will go by this as a guide going forwards!

OpcodePete
  • 887
  • 1
  • 14
  • 28
  • 1
    Excellent question. I'm always amazed at how so many web developers know and care so little about HTTP so it's great to see such questions here. A while writing ago, I started writing a trivial HTTP client as a learning project and ended up spending a couple of weekends learning about HTTP/1.1 caching directives. – Anthony Geoghegan Mar 28 '15 at 18:18
  • 2
    Just go with 402. Always good. `402: Payment Required` :-) – Paolo Apr 02 '15 at 15:59
  • 1
    LOL, if only :) @Paolo – OpcodePete Apr 05 '15 at 01:26

4 Answers4

5

There may be another way: this solution uses 1 custom-error page to handle all types (I think?)

[1]: Remove all 'customErrors' & 'httpErrors' from Web.config

[2]: Check 'App_Start/FilterConfig.cs' looks like this:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
    }
}

[3]: in 'Global.asax' add this method:

public void Application_Error(Object sender, EventArgs e)
{
    Exception exception = Server.GetLastError();
    Server.ClearError();

    var routeData = new RouteData();
    routeData.Values.Add("controller", "ErrorPage");
    routeData.Values.Add("action", "Error");
    routeData.Values.Add("exception", exception);

    if (exception.GetType() == typeof(HttpException))
    {
        routeData.Values.Add("statusCode", ((HttpException)exception).GetHttpCode());
    }
    else
    {
        routeData.Values.Add("statusCode", 500);
    }

    Response.TrySkipIisCustomErrors = true;
    IController controller = new ErrorPageController();
    controller.Execute(new RequestContext(new HttpContextWrapper(Context), routeData));
    Response.End();
}

[4]: Add 'Controllers/ErrorPageController.cs'

public class ErrorPageController : Controller
{
    public ActionResult Error(int statusCode, Exception exception)
    {
         Response.StatusCode = statusCode;
         ViewBag.StatusCode = statusCode + " Error";
         return View();
    }
}

[5]: in 'Views/Shared/Error.cshtml'

@model System.Web.Mvc.HandleErrorInfo
@{
    ViewBag.Title = (!String.IsNullOrEmpty(ViewBag.StatusCode)) ? ViewBag.StatusCode : "500 Error";
}

<h1 class="error">@(!String.IsNullOrEmpty(ViewBag.StatusCode) ? ViewBag.StatusCode : "500 Error"):</h1>

//@Model.ActionName
//@Model.ContollerName
//@Model.Exception.Message
//@Model.Exception.StackTrace
ubik404
  • 621
  • 1
  • 6
  • 5
2

An interesting question, IMHO.

These three errors (403, 404 and 500) are the most common errors that can happen to the real user accessing your site with a standard browser.

On other hand, the HTTP standard was written for both server and agent developers in order to define how both sides should operate. Naturally, the standard browsers like IE, Chrome, Firefox, etc. as well as the standard robots like Google or Bing bots correctly fulfill the requirements, but some proprietary written agent may send a malformed request, and the standard provides the set of codes the server should send in this situation. For example, if the Content-Length field is missed the server returns the error code 411. However, you shouldn't provide user-friendly pages for such a situation.

The code 408 (Request timeout) is explained in the standard as following:

"The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time."

and it also not a case you should make user-friendly page for.

To make a long story short, don't worry :)

Alexander Dayan
  • 2,846
  • 1
  • 17
  • 30
  • Agree with this. I would personally cover the three you've mentioned plus 401's where applicable. I think this is just a personal preference. As a thought though, why not cover the common ones to start with but perhaps can you `globally` track any others via the logs you have in place? That way you can see over time and introduce `as and when` you need to customer errors to the site. Just a thought! – scgough Mar 27 '15 at 15:02
  • @scgough It would be great if someone has already done this and can share their information with the community. – Muhammad Rehan Saeed Mar 30 '15 at 08:37
  • 1
    @RehanSaeed I think with the use of ELMAH you can log everything and from that discover if anything else needs to be covered that isn't already. Job done! – scgough Apr 02 '15 at 10:29
2

I'm trying to find out the answer also. My code looks scarily like yours. This is a great question with so few views, I've set a bounty on this question. I myself have handled the following codes so far:

<system.webServer>
  <!-- Custom error pages -->
  <httpErrors errorMode="Custom" existingResponse="Replace">
    <!-- Redirect IIS 400 Bad Request responses to the error controllers bad request action. -->
    <remove statusCode="400" />
    <error statusCode="400" responseMode="ExecuteURL" path="/error/badrequest" />
    <!-- Redirect IIS 401 Unauthorized responses to the error controllers unauthorized action. -->
    <remove statusCode="401" />
    <error statusCode="401" responseMode="ExecuteURL" path="/error/unauthorized" />
    <!-- Redirect IIS 403.14 Forbidden responses to the error controllers not found action. 
         A 403.14 happens when navigating to an empty folder like /Content and directory browsing is turned off
         See http://rehansaeed.co.uk/securing-the-aspnet-mvc-web-config/ and http://www.troyhunt.com/2014/09/solving-tyranny-of-http-403-responses.html -->
    <error statusCode="403" subStatusCode="14" responseMode="ExecuteURL" path="/error/notfound" />
    <!-- Redirect IIS 404 Not Found responses to the error controllers not found action. -->
    <remove statusCode="404" />
    <error statusCode="404" responseMode="ExecuteURL" path="/error/notfound" />
    <!-- Redirect IIS 500 Internal Server Error responses to the error controllers internal server error action. -->
    <remove statusCode="500" />
    <error statusCode="500" responseMode="ExecuteURL" path="/error" />
  </httpErrors>
</system.webServer>

My reasoning is as follows:

  • 400 - Controllers have the BadRequest() method built in and you may want to return this when a parameter passed to the action is invalid.
  • 401 - Applying the Authorize attribute to a controller or action causes a 401 Unauthorized response. Controllers also have the Unauthorized() method built in.
  • 403.14 - Redirect these to 404 Not Found responses as a Forbidden is just plain wrong (See Securing your web.config and Troy Hunt's blog for more information).
  • 404 - Thrown when the user browses to a page not found.
  • 500 - Thrown when something goes catastrophically wrong.

Overall I feel you should handle those codes that you yourself are going to use. The problem is that IIS does all kinds of strange things and we need to handle some of it's incorrect or invalid responses such as the 403.14 I listed above.

Here is a complete list of IIS HTTP Status Codes and Sub-Status Codes which might be useful to our cause. I have a feeling the 403 Forbidden response should also be supported as it seems to be a fairly prominent response thrown by IIS.

One interesting thing I discovered while Googling is that navigating to:

yoursite/<script></script>

Returns a 500 Internal Server from IIS. I feel this should return a 404. The IIS error page does not tell us what the Sub-Status Code is and I would be interested to know how we can find out, so that we can redirect the 500.Something to a 404 Not Found page.

Here is a link to the GitHub page for the ASP.NET MVC Boilerplate project, for which I am doing this research and where you can look at my code.

Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311
  • 1
    I'm currently handling the following HTTP Status codes: 403, 404, 500, 503, & 504. I have no "real" answer why I'm handing 503 & 504, just a gut feeling as I read something somewhere. So a definitive answer to this topic would be great! (and thanks for the bounty). And thanks for your list also, now looking to add 400 & 401 to my list. – OpcodePete Mar 26 '15 at 22:15
  • I'd be interested to know about 403, 503 and 504. 503 and 504 seem quite fundamental though, I'm not sure if you would ever see these pages because the server is unavailable. – Muhammad Rehan Saeed Mar 27 '15 at 08:52
  • 1
    After a bit more thought, you would probably handle a 503 or 504 using static html files rather than routing to a dynamic MVC controller action like we are doing for the other error codes. I would like someone to confirm. – Muhammad Rehan Saeed Mar 27 '15 at 15:13
  • I don't think you're doing anything wrong @ThomasVeil so don't worry about a 'definitive' answer...I don't think there is one. You have ELMAH installed so I would have that logging all errors (maybe just for a set amount of time) and you can then see if anything else needs to be covered that you aren't already. – scgough Apr 02 '15 at 10:27
  • 1
    If you include an App_Offline.htm file with your MVC project, the server will return a 503 status... I've also seen 503 messages when there's a server/IIS issue, so I typically handle 503 as well. – disappointed in SO leadership Apr 02 '15 at 15:59
  • @silencedmessage Thanks for that. I suspected that. What about 504? Handle that with a static file also? – Muhammad Rehan Saeed Apr 03 '15 at 17:20
  • I don't know as much about how servers process and serve the 504 message, but I'd recommend always using static files for your error pages. From http://benfoster.io/blog/aspnet-mvc-custom-error-pages : `Ideally you should always use simple static files for your error pages. This way if there's something wrong with ASP.NET you should still be able to display your custom error pages.` – disappointed in SO leadership Apr 03 '15 at 18:24
1

Don't rely too much on http status codes.

I have worked with a few bad web developers over the last couple of years that have incorrectly used them in their responses.

I may look for codes within 200-299 for an indication of success. I may look for codes >500 to indicate a server failure.

Beyond that, I use a selfish approach i.e. if you are making a request that your are expecting to have a package of data returned to you, then inspect the data. If there is no data or if the data is bad then I know for certain that there was a problem, because I didn't get what I needed to continue running my application in a nominal way.

  • Yep. If there's a legitimate use case where your clients are not plain browsers but could be apps, as a rule - don't mess with status code pages. The app may fail to function, as it could expect a string to go along with 501, when you override it and it gets some unexpected HTML. But, since this is YOUR app, I suppose you'd pretty much know if this applies to you :) – Lucius Apr 02 '15 at 15:30
  • Statement about nothing. – Sergey Kravchenko Feb 14 '18 at 11:32