Обработка ошибок ASP.NET MVC 6 на основе кода состояния HTTP

Я хочу отображать разные сообщения об ошибках для каждого кода состояния, например:

  • 400 Bad Request
  • 403 Запрещено
  • 500 Внутренняя ошибка сервера
  • 404 Не найдено
  • 401 Несанкционированный

Как я могу добиться этого в новых приложениях ASP.NET MVC 6? Могу ли я сделать это, используя встроенный метод UseErrorHandler?

application.UseErrorHandler("/error");

Кроме того, я заметил, что даже с вышеупомянутым обработчиком, введя несуществующий URL, например. /this -page-does-not-exist, вызывает уристую страницу ошибок 404 Not Found из IIS. Как это можно также обрабатывать?

В MVC 5 нам пришлось использовать раздел system.web customerrors для ASP.NET и раздела system.webServer httpErrors в файле web.config, но было трудно работать с громоздким, с очень странным поведением. Может ли MVC 6 сделать это намного проще?

Ответ 1

Для этого вы можете использовать StatusCodePagesMiddleware. Ниже приведен пример:

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

    app.UseMvcWithDefaultRoute();

Контроллер, который обрабатывает запросы кода состояния:

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
}

Некоторые примечания:

  • Проверьте другие методы расширения, например UseStatusCodePagesWithRedirects и UseStatusCodePages для других возможностей.
  • Я попытался иметь StatusCode в качестве строки запроса в моем примере, но выглядит так: промежуточное ПО не обрабатывает строки запросов, но вы можете взглянуть на этот код и исправить эту проблему.

Ответ 2

Как я могу добиться этого в новых приложениях ASP.NET MVC 6? Могу ли я это сделать, используя встроенный метод UseErrorHandler?

Быстрый ответ: Не в элегантном стиле.

Объяснение/Альтернатива: Чтобы начать сначала, посмотрите, что действительно делает метод UseErrorHandler: https://github.com/aspnet/Diagnostics/blob/6dbbe831c493e6e7259de81f83a04d1654170137/src/Microsoft.AspNet.Diagnostics/ErrorHandlerExtensions.cs#L25, который добавляет следующее промежуточное ПО: https://github.com/aspnet/Diagnostics/blob/6dbbe831c493e6e7259de81f83a04d1654170137/src/Microsoft.AspNet.Diagnostics/ErrorHandlerMiddleware.cs Примечание строки 29-78 (метод вызова)

Метод invoke выполняется всякий раз, когда приходит запрос (контролируется местоположением вашего application.UseErrorHandler("...") в вашем Startup.cs). Таким образом, UseErrorHandler - это прославленный способ добавления специального промежуточного программного обеспечения: middleware = component, который может действовать на HTTP-запрос.

Теперь с этим фоном, если мы хотим добавить наше собственное промежуточное ПО для ошибок, которое будет отличаться от запросов. Мы могли бы сделать это, добавив аналогичное промежуточное программное обеспечение, подобное стандарту ErrorHandlerMiddleware, изменив эти строки: https://github.com/aspnet/Diagnostics/blob/6dbbe831c493e6e7259de81f83a04d1654170137/src/Microsoft.AspNet.Diagnostics/ErrorHandlerMiddleware.cs#L48-L51. При таком подходе мы могли бы контролировать перенаправить путь на основе кода состояния.

В MVC 5 нам нужно было использовать раздел system.web customerrors для ASP.NET и раздел system.webServer httpErrors в файле web.config, но было трудно работать с громоздким, с большим количеством очень странного поведения, Может ли MVC 6 сделать это намного проще?

Ответ: Он уверен, что делает:). Как и вышеприведенный ответ, исправление заключается в добавлении промежуточного программного обеспечения. Там есть ярлык для добавления простого промежуточного программного обеспечения через IApplicationBuilder в Startup.cs; в конце вашего метода Configure вы можете добавить следующее:

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

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

Это будет работать, потому что это означает, что вы достигли конца вашего HTTP-конвейера без обработки запроса (поскольку он в конце вашего метода Configure в Startup.cs). Если вы хотите добавить это промежуточное ПО (быстрым способом) с возможностью выполнения промежуточного программного обеспечения после вас, вот как:

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();
});

Надеюсь, это поможет!

Ответ 3

Работает с различными кодами состояния, не указывая каждый отдельно в контроллере.

Startup.cs:

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

Контроллер:

  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); }

Вид:

@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>