【问题标题】:Uniform, consistent error responses from ASP.Net Web API 2来自 ASP.Net Web API 2 的统一、一致的错误响应
【发布时间】:2014-08-04 03:31:21
【问题描述】:

我正在开发一个 Web API 2 应用程序,我目前正在尝试以统一的方式格式化错误响应(这样消费者也将知道他们可以检查哪些数据对象/结构以获取有关错误的更多信息) .这是我到目前为止所得到的:

{   
    "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 等)并将它们格式化为上面第一个示例中的错误响应?

到目前为止我已经尝试过:

  • 自定义 ExceptionAttribute 继承 ExceptionFilterAttribute
  • 自定义 ControllerActionInvoker 继承 ApiControllerActionInvoker
  • IExceptionHandler(来自 Web API 2.1 的新全局错误处理功能)

但是,这些方法都无法捕获这些类型的错误(404、405 等)。关于如何/是否可以实现的任何想法?

...或者,我是不是走错了路?我是否应该只针对应用程序/用户级别的错误以我的特定样式格式化错误响应,并依赖默认的错误响应来处理 404 之类的问题?

【问题讨论】:

  • Should I only format error responses in my particular style for application/user level errors and rely on the default error responses for things like 404?...我的意见是肯定的
  • 我也越来越倾向于这种方法。感谢您的出色回答/cmets。

标签: c# asp.net asp.net-web-api asp.net-web-api2


【解决方案1】:

您可以重写 DelegatingHandler 抽象类并拦截对客户端的响应。这将使您能够返回您想要的东西。

这里有一些关于它的信息。 http://msdn.microsoft.com/en-us/library/system.net.http.delegatinghandler(v=vs.118).aspx

这是 Web 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());

更新 正如 Kiran 在 cmets 中提到的,您可以使用 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";
        }
    }
}

【讨论】:

  • 仅此还不够,因为在某些情况下,由于没有 Web API 路由匹配,请求甚至不会进入 Web API。(此问题特定于 IIS 托管方案)。这个问题可以通过在 Web API 路由的末尾设置一个 catch all 路由来规避,该路由可以发送回一个自定义的 404 错误消息......其他可能的解决方案是在管道中较早放置一个 OwinMiddleware,这样你就拥有了所有你的逻辑在一个地方(我自己没有玩过这个,但我猜这应该可行)
  • 即使路由不正确,它也会命中所有 api 调用的 DelegatingHandler,因为 HttpRoutingDispatcher 在 DelegatingHandlers 之后被调用。您是对的,如果请求不是 api 请求,它将不会命中此处理程序,因为非 api 调用由 System.Web.Mvc 命名空间处理程序处理。示例:localhost/WebApplication1/api/invalid 将命中此处理程序,但 localhost/WebApplication1/invalid 不会命中此处理程序...假设所有 api 调用都设置为以 localhost/WebApplication1/api 开头
  • 明确一点...HttpRoutingDispatcher 的路由匹配仅发生在 SelfHost 和 In-Memory 场景中...
  • @Kiran Charla - 你有这方面的文件吗?我的理解是,OWIN 的全部目的是将应用程序与主机分开。这意味着从 DelegatingHandler 开始到应用程序中的所有内容都不关心它的托管方式。这包括 HttpRoutingDispatcher。
  • 实际上您可以使用上面的示例来验证这一点...创建一个基于 IIS 的 Web API 应用程序,没有任何 MVC 相关的东西..现在有模板提供的默认 api 路由..即@987654330 @...现在发出类似localhost/yourwebapplicationname/nonexistingpath的请求...如果路由匹配确实发生在HttpRoutingDispatcher,那么您的委托处理程序应该被调用...但它没有...因为路由匹配发生在请求通过您的委托处理程序...
【解决方案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());
        }

    }
}

添加了如下所示的路由,现在它命中了无效 url 的 try catch

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-01-13
    • 2015-11-27
    • 2018-04-28
    • 1970-01-01
    • 1970-01-01
    • 2019-06-25
    • 2019-01-16
    • 1970-01-01
    相关资源
    最近更新 更多