11
votes

I want to display different error messages for each status code e.g:

  • 400 Bad Request
  • 403 Forbidden
  • 500 Internal Server Error
  • 404 Not Found
  • 401 Unauthorized

How can I achieve this in the new ASP.NET MVC 6 applications? Can I do this using the built in UseErrorHandler method?

application.UseErrorHandler("/error");

Also, I noticed that even with the above handler, entering a non-existent URL e.g. /this-page-does-not-exist, causes an ugly 404 Not Found error page from IIS. How can this also be handled?

In MVC 5 we have had to use the system.web customerrors section for ASP.NET and the system.webServer httpErrors section in the web.config file but it was difficult to work with an unwieldy, with lots of very strange behaviour. Does MVC 6 make this a lot simpler?

3

3 Answers

10
votes

You could use the StatusCodePagesMiddleware for this. Following is an example:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    app.UseStatusCodePagesWithReExecute("/StatusCodes/StatusCode{0}");

    app.UseMvcWithDefaultRoute();

Controller which handles the status code requests:

public class StatusCodesController : Controller
{
    public IActionResult StatusCode404()
    {
        return View(viewName: "NotFound"); // you have a view called NotFound.cshtml
    }

    ... more actions here to handle other status codes
}

Some Notes:

  • Check other extension methods like UseStatusCodePagesWithRedirects and UseStatusCodePages for other capabilities.
  • I tried having StatusCode as a query string in my example, but looks like this middleware doesn't handle query strings, but you can take a look at this code and fix this issue.
3
votes

How can I achieve this in the new ASP.NET MVC 6 applications? Can I do this using the built in UseErrorHandler method?

Quick answer: Not in an elegant fashion.

Explanation/Alternative: To start lets first look at what the UseErrorHandler method is actually doing: https://github.com/aspnet/Diagnostics/blob/6dbbe831c493e6e7259de81f83a04d1654170137/src/Microsoft.AspNet.Diagnostics/ErrorHandlerExtensions.cs#L25 which adds the following middleware: https://github.com/aspnet/Diagnostics/blob/6dbbe831c493e6e7259de81f83a04d1654170137/src/Microsoft.AspNet.Diagnostics/ErrorHandlerMiddleware.cs Note lines 29-78 (the invoke method)

The invoke method is executed whenever a request comes in (controlled by the location of your application.UseErrorHandler("...") in your Startup.cs). So the UseErrorHandler is a glorified way of adding a custom middleware: middleware = component that can act on an http request.

Now with that background, if we wanted to add our own error middleware that differentiated requests. We could do this by adding a similar middleware that's like the default ErrorHandlerMiddleware by modifying these lines: https://github.com/aspnet/Diagnostics/blob/6dbbe831c493e6e7259de81f83a04d1654170137/src/Microsoft.AspNet.Diagnostics/ErrorHandlerMiddleware.cs#L48-L51 With that approach we could control the redirect path based on the status code.

In MVC 5 we have had to use the system.web customerrors section for ASP.NET and the system.webServer httpErrors section in the web.config file but it was difficult to work with an unwieldy, with lots of very strange behaviour. Does MVC 6 make this a lot simpler?

Answer: It sure does :). Just like the above answer the fix lies in adding middleware. There's a shortcut to adding simple middleware via the IApplicationBuilder in your Startup.cs; at the end of your Configure method you can add the following:

app.Run(async (context) =>
{
    await context.Response.WriteAsync("Could not handle the request.");

    // Nothing else will run after this middleware.
});

This will work because it means that you reached the end of your http pipeline without the request being handled (since it's at the end of your Configure method in Startup.cs). If you want to add this middleware (in the quick fashion) with the option to execute middleware after you, here's how:

app.Use(async (context, next) =>
{
    await context.Response.WriteAsync("Could not handle the request.");

    // This ensures that any other middelware added after you runs.
    await next();
});

Hope this helps!

0
votes

Works with various status codes without specifying each one individually in the controller.

Startup.cs:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    app.UseStatusCodePagesWithRedirects("/StatusCodes?statusCode={0}");
    app.UseMvcWithDefaultRoute();

Controller:

  public class StatusCodesController : Controller
  {
    public IActionResult Index(string statusCode)
    {
      if(statusCode == null) statusCode = "";
      if(statusCode == "404") return View("Error404");
      return View("Index",statusCode);
    }

    public IActionResult Test404() { return StatusCode(404); }
    public IActionResult Test500() { return StatusCode(500); }

View:

@model string
@{ ViewData["Title"] = Model + " Oops!"; }

<style>
  .error-template {
    padding: 40px 15px;
    text-align: center;
  }

  .error-actions {
    margin-bottom: 15px;
    margin-top: 15px;
  }

    .error-actions .btn {
      margin-right: 10px;
    }
</style>

<div class="container">
  <div class="row">
    <div class="col-md-12">
      <div class="error-template">
        <h2>Oops!</h2>
        <h2>@Model Error</h2>
        <div class="error-details">Sorry, an error has occurred!</div>
        <div class="error-actions">
          <a class="btn btn-primary btn-lg" href="/"><span class="glyphicon glyphicon-home"></span> Take Me Home </a>
          <a class="btn btn-default btn-lg" href="/Home/ContactUs"><span class="glyphicon glyphicon-envelope"></span> Contact Support </a>
        </div>
      </div>
    </div>
  </div>
</div>