C# 来自ASP.Net Web API 2的统一、一致的错误响应
我正在开发一个WebAPI2应用程序,目前我正在尝试以统一的方式格式化错误响应(这样消费者也将知道他们可以检查什么数据对象/结构,以获得有关错误的更多信息)。到目前为止,我得到的是:C# 来自ASP.Net Web API 2的统一、一致的错误响应,c#,asp.net,asp.net-web-api,asp.net-web-api2,C#,Asp.net,Asp.net Web Api,Asp.net Web Api2,我正在开发一个WebAPI2应用程序,目前我正在尝试以统一的方式格式化错误响应(这样消费者也将知道他们可以检查什么数据对象/结构,以获得有关错误的更多信息)。到目前为止,我得到的是: { "Errors": [ { "ErrorType":5003, "Message":"Error summary here", "DeveloperAction":"Some more detail f
{
"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'."
}
有没有办法捕获这些类型的错误(404405等)并将它们格式化为上面第一个示例中的错误响应
到目前为止,我已经尝试:
- 自定义例外属性插入
ExceptionFilterAttribute
- 自定义ControllerActionInvoker InHirting
ApiControllerActionInvoker
(来自Web API 2.1的新全局错误处理功能)IEExceptionHandler
…或者,我是不是走错了路?对于应用程序/用户级别的错误,我是否应该仅以我的特定样式格式化错误响应,并依赖404之类的默认错误响应?您可以覆盖DelegatingHandler抽象类并截获对客户端的响应。这将使您能够返回您想要的内容 这里有一些信息。 下面是WebAPI管道的海报,展示了可以覆盖的内容。 创建这样的处理程序类来覆盖响应
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;
}
}
更新
正如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";
}
}
}
我的做法与@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());
}
}
}
对于应用程序/用户级别的错误,我是否应该仅以我的特定风格格式化错误响应,对于404之类的错误,我是否应该依赖默认错误响应?
…我的观点是,我也越来越倾向于这种方法。感谢您的回答/评论。仅此一点是不够的,因为在某些情况下,由于没有Web API路由匹配,请求甚至不会进入Web API。(此问题特定于IIS托管的场景)。这个问题可以通过在Web API路由的末尾设置一个catch-all路由来避免,该路由可以发回一个定制的404错误消息……其他可能的解决方案是使用一个位于管道中较早位置的OwinMiddleware,这样您就可以将所有逻辑放在一个地方(我自己还没有玩过这个,但我猜这个应该有用)即使路由不正确,它也会对所有api调用命中DelegatingHandler,因为HttpRoutingDispatcher在DelegatingHandler之后被调用。您是正确的,如果请求不是api请求,它不会命中此处理程序,因为非api调用由System.Web.Mvc命名空间处理程序处理。示例:将命中此处理程序r但不会命中此处理程序…假设所有api调用都设置为从开始只是为了清除…在httproutindispatcher
处进行路由匹配只在SelfHost和内存中场景下发生…@Kiran Charla-您有关于此的文档吗?我的理解是OWIN的全部目的是将应用程序与主机分离。这意味着从DelegatingHandler开始进入应用程序的所有内容都不关心它的宿主方式。这包括HttpRoutingDispatcher。实际上,您可以使用上面的示例来验证这一点…创建一个基于IIS的Web API应用程序,而不使用任何MVC相关的内容。现在拥有模板提供的默认API路由。即API/{controller}/{id}
…现在发出一个请求,如localhost/yourwebapplicationname/nonexistingpath
…如果路由匹配确实发生在HttpRoutingDispatcher
上,那么应该调用您的委托处理程序…但它没有…因为路由匹配发生在通过委托处理程序的请求之前。。。
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());
}
}
}
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" });