如何覆盖WebAPI中的所有标准错误页面 [英] How to override all standard error pages in WebAPI
问题描述
〜/
,返回默认的IIS 403 Forbidden页面(即使使用Fiddler并仅指定 Accept:application / json
)。我只想要JSON或XML错误。有没有办法用自定义异常处理程序来覆盖所有异常?或默认控制器来处理所有未知请求?最简单,最正确(如果不同)的方法来处理这个问题,以便客户端只需要解析REST API友好的XML数据报或JSON Blob? 示例请求:
GET http:// localhost:7414 / HTTP / 1.1
用户代理:Fiddler
主机:localhost:7414
接受:application / json,text / json,text / xml
回应:(我不喜欢,注意 text / html
不是接受的回应类型之一)
HTTP / 1.1 403 Forbidden
缓存控制:private
内容类型:text / html; charset = utf-8
服务器:Microsoft-IIS / 8.0
X-SourceFiles:=?UTF-8?B?QzpcaWNhcm9sXENoYXJpdHlMb2dpYy5pQ2Fyb2wuQXBp?=
X-Powered By:ASP.NET
日期:Fri,25 Jan 2013 21:06:21 GMT
内容长度:5396
<!DOCTYPE html PUBLIC - // W3C // DTD XHTML 1.0严格/ / ENhttp://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">
< html xmlns =http://www.w3.org/1999/xhtml>
< head>
< title> IIS 8.0详细错误 - 403.14 - 禁止< / title>
< style type =text / css>
<! -
...
回应(我会喜欢):
HTTP / 1.1 403禁止
缓存控制:私人
内容类型:应用程序/ JSON; charset = utf-8
日期:...
内容长度:...
{
错误:禁止,
状态:403,
error_description:不允许列表列表。
}
编辑1 / 26/14:微软刚刚添加了全局错误处理到最新的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:没有资源匹配指定的URL。);
}
[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,内部服务器错误,服务器上出现的未检测到的错误,异常:ex);
}
只读字典< Type,Func< HttpActionExecutedContext,Exception,HttpResponseMessage>> exceptionHandlers = new Dictionary< Type,Func< HttpActionExecutedContext,Exception,HttpResponseMessage>>();
public Func< HttpActionExecutedContext,Exception,HttpResponseMessage> DefaultHandler {get;组; }
public void AddExceptionHandler< T>(Func< HttpActionExecutedContext,Exception,HttpResponseMessage> handler)其中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:构建异常响应时出错,异常:ex);
}
}
}
在我的情况下,我去了使用单个通用处理程序,我可以为每个主要异常类型注册支持,并将每个异常类型映射到特定的HTTP响应代码。现在在您的 global.asax.cs
中注册您的异常类型和处理程序这个过滤器:
//这些过滤器覆盖了默认的ASP.NET异常处理,以创建REST-Friendly错误响应。
var exceptionFormatter = new GenericExceptionFilterAttribute();
exceptionFormatter.AddExceptionHandler< NotImplementedException>((context,ex)=> context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.InternalServerError,Not Implemented,此方法尚未实现请尝试稍后再次请求,例外: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,不支持,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
:
;结构>
< system.webServer>
< modules runAllManagedModulesForAllRequests =true/>
< /system.webServer>
< / configuration>您可以使用 customErrors
web.config
的部分将所有错误重定向到新的错误处理程序。
My beautiful REST webservice works great. Except if I visit pages like ~/
, which returns the default IIS 403 Forbidden page (even using Fiddler and specifying only Accept: application/json
). I want nothing but JSON or XML errors. Is there a way to override ALL exceptions with a custom exception handler? or a default controller to handle all unknown requests? What's the simplest, and the most correct (if different), way to handle this so that clients need only parse REST API-friendly XML datagrams or JSON blobs?
Example Request:
GET http://localhost:7414/ HTTP/1.1
User-Agent: Fiddler
Host: localhost:7414
Accept: application/json, text/json, text/xml
Response: (that I don't like, notice that text/html
wasn't one of the accepted response types)
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">
<!--
...
Response (that I would prefer):
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."
}
Edit 1/26/14: Microsoft just added "Global Error Handling" to the latest WebAPI 2.1 update.
Ok, I think I've got it. There's a few parts to it.
First: Create a controller for your errors. I named my actions according to the HTTP error codes.
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);
}
}
Next, I created a GenericExceptionFilterAttribute
that checks to see if the HttpActionExecutedContext.Exception is populated and if the response is still empty. If both cases are true, then it generates a response.
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);
}
}
}
In my case, I went with a single generic handler that I could register support for each of the main exception types and map each of those exception types to specific HTTP response codes. Now register your exception types and handlers this filter globally in your 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)
Next, create a catchall route to send all unknown requests to your new Error handler:
config.Routes.MapHttpRoute(
name: "DefaultCatchall",
routeTemplate: "{*url}",
defaults: new {
controller = "Error",
action = "404"
}
);
And, to wrap it all up, let IIS process all requests through ASP.NET by adding this to your web.config
:
<configuration>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
</configuration>
Optionally, you could also use the customErrors
section of the web.config
to redirect all errors to your new error handler.
这篇关于如何覆盖WebAPI中的所有标准错误页面的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!