Как переопределить все стандартные страницы ошибок в WebAPI

Мой красивый веб-сервис REST отлично работает. За исключением случаев, когда я посещаю такие страницы, как ~/, который возвращает запретную страницу IIS 403 по умолчанию (даже используя Fiddler и указывая только Accept: application/json). Я не хочу ничего, кроме ошибок JSON или XML. Есть ли способ переопределить ВСЕ исключения с помощью специального обработчика исключений? или контроллер по умолчанию для обработки всех неизвестных запросов? Какой самый простой и самый правильный (если он отличается) способ справиться с этим, так что клиентам нужно просто анализировать REST API-дружественные XML-дейтаграммы или JOSON-капли?

Пример запроса:

GET http://localhost:7414/ HTTP/1.1
User-Agent: Fiddler
Host: localhost:7414
Accept: application/json, text/json, text/xml

Ответ: (что мне не нравится, обратите внимание, что text/html не был одним из принятых типов ответов)

HTTP/1.1 403 Forbidden
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/8.0
X-SourceFiles: =?UTF-8?B?QzpcaWNhcm9sXENoYXJpdHlMb2dpYy5pQ2Fyb2wuQXBp?=
X-Powered-By: ASP.NET
Date: Fri, 25 Jan 2013 21:06:21 GMT
Content-Length: 5396

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
<title>IIS 8.0 Detailed Error - 403.14 - Forbidden</title> 
<style type="text/css"> 
<!-- 
...

Ответ (который я бы предпочел):

HTTP/1.1 403 Forbidden
Cache-Control: private
Content-Type: application/json; charset=utf-8
Date: ...
Content-Length: ....

{
  "error":"forbidden",
  "status":403,
  "error_description":"Directory listing not allowed."
}

Ответ 1

Редактировать 1/26/14: Microsoft просто добавила " Глобальная обработка ошибок к последнему обновлению WebAPI 2.1.


Хорошо, я думаю, у меня это есть. Там несколько частей.

Сначала: создайте контроллер для своих ошибок. Я назвал свои действия в соответствии с кодами ошибок HTTP.

public class ErrorController : ApiController {
    [AllowAnonymous]
    [ActionName("Get")]
    public HttpResponseMessage Get() {
        return Request.CreateErrorInfoResponse(HttpStatusCode.InternalServerError, title: "Unknown Error");
    }

    [AllowAnonymous]
    [ActionName("404")]
    [HttpGet]
    public HttpResponseMessage Status404() {
        return Request.CreateErrorInfoResponse(HttpStatusCode.NotFound, description: "No resource matches the URL specified.");
    }

    [AllowAnonymous]
    [ActionName("400")]
    [HttpGet]
    public HttpResponseMessage Status400() {
        return Request.CreateErrorInfoResponse(HttpStatusCode.BadRequest);
    }

    [AllowAnonymous]
    [ActionName("500")]
    [HttpGet]
    public HttpResponseMessage Status500() {
        return Request.CreateErrorInfoResponse(HttpStatusCode.InternalServerError);
    }
}

Затем я создал GenericExceptionFilterAttribute, который проверяет, заполнен ли HttpActionExecutedContext.Exception и если ответ по-прежнему пуст. Если оба случая истинны, тогда он генерирует ответ.

public class GenericExceptionFilterAttribute : ExceptionFilterAttribute {
    public GenericExceptionFilterAttribute()
        : base() {
        DefaultHandler = (context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.InternalServerError, "Internal Server Error", "An unepected error occoured on the server.", exception: ex);
    }

    readonly Dictionary<Type, Func<HttpActionExecutedContext, Exception, HttpResponseMessage>> exceptionHandlers = new Dictionary<Type, Func<HttpActionExecutedContext, Exception, HttpResponseMessage>>();

    public Func<HttpActionExecutedContext, Exception, HttpResponseMessage> DefaultHandler { get; set; }

    public void AddExceptionHandler<T>(Func<HttpActionExecutedContext, Exception, HttpResponseMessage> handler) where T : Exception {
        exceptionHandlers.Add(typeof(T), handler);
    }

    public override void OnException(HttpActionExecutedContext context) {
        if (context.Exception == null) return;

        try {
            var exType = context.Exception.GetType();
            if (exceptionHandlers.ContainsKey(exType))
                context.Response = exceptionHandlers[exType](context, context.Exception);

            if(context.Response == null && DefaultHandler != null)
                context.Response = DefaultHandler(context, context.Exception);
        }
        catch (Exception ex) {
            context.Response = context.Request.CreateErrorInfoResponse(HttpStatusCode.InternalServerError, description: "Error while building the exception response.", exception: ex);
        }
    }
}

В моем случае я пошел с одним общим обработчиком, чтобы зарегистрировать поддержку для каждого из основных типов исключений и сопоставить каждый из этих типов исключений с конкретными кодами ответов HTTP. Теперь зарегистрируйте свои типы исключений и обработчики этого фильтра по всему миру в global.asax.cs:

// These filters override the default ASP.NET exception handling to create REST-Friendly error responses.
var exceptionFormatter = new GenericExceptionFilterAttribute();
exceptionFormatter.AddExceptionHandler<NotImplementedException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.InternalServerError, "Not Implemented", "This method has not yet been implemented. Please try your request again at a later date.", exception: ex));
exceptionFormatter.AddExceptionHandler<ArgumentException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, exception: ex));
exceptionFormatter.AddExceptionHandler<ArgumentNullException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, exception: ex));
exceptionFormatter.AddExceptionHandler<ArgumentOutOfRangeException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, exception: ex));
exceptionFormatter.AddExceptionHandler<FormatException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, exception: ex));
exceptionFormatter.AddExceptionHandler<NotSupportedException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, "Not Supported", exception: ex));
exceptionFormatter.AddExceptionHandler<InvalidOperationException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, "Invalid Operation", exception: ex));
GlobalConfiguration.Filters.Add(exceptionFormatter)

Затем создайте общий путь для отправки всех неизвестных запросов новому обработчику ошибок:

config.Routes.MapHttpRoute(
    name: "DefaultCatchall",
    routeTemplate: "{*url}",
    defaults: new {
        controller = "Error",
        action = "404"
    }
);

И, чтобы все это сделать, пусть IIS обрабатывает все запросы через ASP.NET, добавляя это к вашему web.config:

<configuration>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
    </system.webServer>
</configuration>

Кроме того, вы также можете использовать раздел customErrors web.config для перенаправления всех ошибок на ваш новый обработчик ошибок.

Ответ 2

В диспетчере IIS вы можете редактировать пользовательские ошибки:

Откройте диспетчер IIS и перейдите на уровень, которым вы хотите управлять. Для информацию об открытии диспетчера IIS см. в разделе "Открыть диспетчер IIS" (IIS 7). Для получения информации о навигации по местоположениям в пользовательском интерфейсе см. Навигация в диспетчере IIS (IIS 7).

В представлении "Возможности" дважды щелкните "Страницы ошибок".

На странице "Страницы ошибок" нажмите, чтобы выбрать ошибку, которую вы хотите изменить.

На панели "Действия" нажмите "Изменить".

В диалоговом окне "Редактировать пользовательскую страницу ошибки" выберите один из следующих вариантов:   Вставьте содержимое из статического файла в ответ об ошибке, если ваша ошибка содержимое является статическим, например .html файл.

Выполните URL-адрес на этом сайте, если ваше содержимое ошибки является динамическим, например файл .asp.

Отвечайте с перенаправлением 302, если вы перенаправляете браузер клиента на другой URL.

В текстовом поле "Путь к файлу" введите путь к странице пользовательской ошибки, если Вставка содержимого из статического файла в ответ на ошибку является выбранной тип пути. Если вы используете либо Выполнить URL-адрес на этом сайте, либо ответьте с типом маршрута перенаправления 302, введите вместо этого URL-адрес пользовательского страница ошибки. Нажмите "ОК".

См. также Запись Рика Страгла, в которой есть несколько скриншотов.

Однако я не думаю, что в ответе будет рассмотрен заголовок типа контента - он просто описывает, как изменить часть контента, которую вы можете изменить в формате JSON. Я не знаю, как изменить это, не делая ничего более привычного, но некоторые клиенты смогут обрабатывать неправильный тип контента, если контент все еще JSON, поэтому этого может быть достаточно (и если это так, может быть, ваш самый простой вариант), Так что это не полный ответ, но он может вам помочь.

Существуют и другие более интенсивные для кода параметры - например, пользовательский HTTP-модуль или серверный код /​​config (например,.Net) для обработки всех запросов и создания правильных заголовков ответов + (см. Переписанные пользовательские ошибки ASP.NET не отправляют заголовок типа контента или http://www.iis.net/learn/develop/runtime-extensibility/developing-iis-modules-and-handlers-with-the-net-framework).

Ответ 3

Он может не работать во всех случаях, но в большинстве.
Более простое решение:

<system.webServer>
    <httpErrors errorMode="Detailed" />
</system.webServer>

Наш 404, управляемый нашим кодом (реальный доступ к объекту REST, который не существует) работает с этой конфигурацией.

Вы также можете отредактировать этот параметр с консоли управления IIS, в зоне "Страницы ошибок", справа, "Изменить настройки параметров...". введите описание изображения здесь