如何覆盖WebAPI中的所有标准错误页面 [英] How to override all standard error pages in WebAPI

查看:127
本文介绍了如何覆盖WebAPI中的所有标准错误页面的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的美丽的REST webservice工作得很好。除非我访问页面,如〜/ ,返回默认的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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆