来自ASP.Net Web API 2的统一,一致的错误响应

时间:2014-06-13 14:21:27

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

我正在开发一个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 inherting ExceptionFilterAttribute
  • 自定义ControllerActionInvoker inherting ApiControllerActionInvoker
  • IExceptionHandler(Web API 2.1中新的全局错误处理功能)

然而,这些方法都没有能够捕获这些类型的错误(404,405等)。关于如何实现这一目标的任何想法?

......或者,我是否采取了错误的方式?我是否应该仅针对应用程序/用户级别错误格式化我的特定样式的错误响应,并依赖404之类的默认错误响应?

2 个答案:

答案 0 :(得分:9)

您可以覆盖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在评论中提到的那样,您可以使用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";
        }
    }
}

答案 1 :(得分: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

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