Единообразные, последовательные ответы об ошибках от ASP.Net Web API 2

Я разрабатываю приложение Web API 2, и в настоящее время я пытаюсь форматировать ошибки resposnes единым способом (чтобы потребитель также знал, какой объект/структура данных они могут проверять, чтобы получить больше информации об ошибках), Это то, что у меня есть до сих пор:

{   
    "Errors":
    [
        {
            "ErrorType":5003,
            "Message":"Error summary here",
            "DeveloperAction":"Some more detail for API consumers (in some cases)",
            "HelpUrl":"link to the docs etc."
        }
    ]
}

Это отлично подходит для исключений, создаваемых самим приложением (то есть внутри контроллеров). Однако, если пользователь запрашивает плохой URI (и получает 404) или использует неправильный глагол (и получает 405) и т.д., Web Api 2 выплескивает сообщение об ошибке по умолчанию, например.

{
     Message: "No HTTP resource was found that matches the request URI 'http://localhost/abc'."
}

Есть ли способ улавливать эти ошибки (404, 405 и т.д.) и форматировать их в ответ на ошибку в первом примере выше?

До сих пор я пробовал:

  • Настраивание пользовательских исключенийAttribute ExceptionFilterAttribute
  • Пользовательский ControllerActionInvoker inherting ApiControllerActionInvoker
  • IExceptionHandler (новая функция глобальной обработки ошибок из Web API 2.1)

Однако ни один из этих подходов не способен поймать такие ошибки (404, 405 и т.д.). Любые идеи о том, как/если это может быть достигнуто?

... или я об этом неправильно? Должен ли я форматировать ответы об ошибках в моем конкретном стиле для ошибок приложения/уровня пользователя и полагаться на ответы об ошибках по умолчанию для таких вещей, как 404?

Ответы

Ответ 1

Вы можете переопределить абстрактный класс DelegatingHandler и перехватить ответ клиенту. Это даст вам возможность вернуть то, что вы хотите.

Вот некоторая информация об этом. http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler(v = vs .118).aspx

Здесь представлен плакат веб-конвейера Api, который показывает, что можно переоценить. http://www.asp.net/posters/web-api/asp.net-web-api-poster.pdf

Создайте класс Handler, подобный этому, чтобы переопределить ответ

public class MessageHandler1 : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Debug.WriteLine("Process request");
        // Call the inner handler.
        var response = base.SendAsync(request, cancellationToken);

        Debug.WriteLine("Process response");
        if (response.Result.StatusCode == HttpStatusCode.NotFound)
        {
            //Create new HttpResponseMessage message
        }
        ;
        return response;
    }
}

В вашем классе WebApiConfig.cs добавьте обработчик.

config.MessageHandlers.Add(new MessageHandler1());

UPDATE Как упоминает Киран в комментариях, вы можете использовать OwinMiddleware для перехвата ответа, возвращаемого клиенту. Это будет работать для MVC и Web Api, работающих на любом хосте.

Вот пример того, как получить ответ и изменить его, как он идет на клиента.

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use(typeof(MyMiddleware)); 
    }
}

public class MyMiddleware : OwinMiddleware
{
    public MyMiddleware(OwinMiddleware next) : base(next) { }

    public override async Task Invoke(IOwinContext context)
    {
        await Next.Invoke(context);
        if(context.Response.StatusCode== 404)
        {
            context.Response.StatusCode = 403;
            context.Response.ReasonPhrase = "Blah";
        }
    }
}

Ответ 2

Я сделал так же, как @Dan H, упомянутый

 public class ApiGatewayHandler : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        try
        {
            var response = await base.SendAsync(request, cancellationToken);
            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                var objectContent = response.Content as ObjectContent;
                return await Task.FromResult(new ApiResult(HttpStatusCode.NotFound, VmsStatusCodes.RouteNotFound, "", objectContent == null ? null : objectContent.Value).Response());
            }
            return response;
        }
        catch (System.Exception ex)
        {
            return await Task.FromResult(new ApiResult(HttpStatusCode.BadRequest, VmsStatusCodes.UnHandledError, ex.Message, "").Response());
        }

    }
}

Добавлена ​​маршрутизация, как показано ниже, и теперь она попадает в try catch для недопустимого URL

  config.Routes.MapHttpRoute(name: "DefaultApi",routeTemplate: "api/{controller}/{id}",defaults: new { id = RouteParameter.Optional });
        config.Routes.MapHttpRoute(name: "NotFound", routeTemplate: "api/{*paths}", defaults: new { controller = "ApiError", action = "NotFound" });