如何覆盖Web API中的所有标准错误页面

时间:2013-01-25 21:05:08

标签: iis error-handling asp.net-web-api

我漂亮的REST网络服务很棒。除非我访问~/之类的页面,它返回默认的IIS 403 Forbidden页面(即使使用Fiddler并仅指定Accept: application/json)。我只想要JSON或XML错误。有没有办法用自定义异常处理程序覆盖所有异常?或者一个默认的控制器来处理所有未知请求?什么是最简单,最正确(如果不同)的方法来处理这个问题,以便客户端只需要解析REST API友好的XML数据报或JSON blob?

示例请求:

GET http://localhost:7414/ HTTP/1.1
User-Agent: Fiddler
Host: localhost:7414
Accept: application/json, text/json, text/xml

回复:(我不喜欢,请注意text/html不是可接受的响应类型之一)

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"> 
<!-- 
...

回复(我更喜欢):

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."
}

3 个答案:

答案 0 :(得分:11)

修改1/26/14:微软刚刚添加了&#34; Global Error Handling&#34;到最新的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: "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);
    }
}

接下来,我创建了一个GenericExceptionFilterAttribute,它检查是否填充了HttpActionExecutedContext.Exception以及响应是否仍为空。如果两种情况都属实,则会生成响应。

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

就我而言,我选择了一个通用处理程序,我可以为每个主要的异常类型注册支持,并将每个异常类型映射到特定的HTTP响应代码。现在,在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)

接下来,创建一个catchall路由,将所有未知请求发送到新的Error处理程序:

config.Routes.MapHttpRoute(
    name: "DefaultCatchall",
    routeTemplate: "{*url}",
    defaults: new {
        controller = "Error",
        action = "404"
    }
);

并且,为了将其全部包装起来,让IIS通过将其添加到您的web.config来处理所有请求:

<configuration>
    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
    </system.webServer>
</configuration>

或者,您也可以使用customErrors的{​​{1}}部分将所有错误重定向到新的错误处理程序。

答案 1 :(得分:0)

在IIS管理器中,您可以edit the custom errors

  

打开IIS管理器并导航到要管理的级别。对于   有关打开IIS管理器的信息,请参阅打开IIS管理器(IIS 7)。   有关在UI中导航到位置的信息,请参阅   在IIS管理器(IIS 7)中导航。

     

在“功能视图”中,双击“错误页面”。

     

在“错误页面”页面上,单击以选择要更改的错误。

     

在“操作”窗格中,单击“编辑”。

     

在“编辑自定义错误页面”对话框中,选择以下选项之一:     如果您的错误,请将静态文件中的内容插入错误响应中   内容是静态的,例如.html文件。

     

如果您的错误内容是动态的,请在此网站上执行网址,例如   一个.asp文件。

     

如果要重定向客户端浏览器,则使用302重定向进行响应   到不同的网址。

     

在“文件路径”文本框中,键入自定义错误页面的路径   将静态文件中的内容插入到错误响应中   路径类型。如果使用此站点上的“执行URL”或“响应”   使用302重定向路径类型,键入自定义的URL   错误页面。单击“确定”。

另请参阅Rick Strahl's writeup,其中包含一些屏幕截图。

但是,我不认为这会解决响应中的内容类型标题 - 它只是概述了如何更改内容部分,您可以将其更改为JSON格式。我不知道如何在不做更多自定义的情况下改变它,但是如果内容仍然是JSON,一些客户端将能够处理错误的内容类型,所以这可能就足够了(如果是这样,可能是最简单的选项)。所以这不是一个完整的答案,但它可能对你有帮助。

还有其他更多代码密集型选项 - 如自定义HTTP模块或使用服务器端代码/配置(如.Net)来处理所有请求并构建正确的响应+标头(请参阅ASP.NET rewritten custom errors do not send content-type headerhttp://www.iis.net/learn/develop/runtime-extensibility/developing-iis-modules-and-handlers-with-the-net-framework)。

答案 2 :(得分:0)

它可能不适用于所有情况,但大多数情况 一个更简单的解决方案:

<system.webServer>
    <httpErrors errorMode="Detailed" />
</system.webServer>

我们的代码管理的404(真正获得不存在的REST实体)可以使用该配置。

您还可以在IIS管理控制台的“错误页面”区域右侧的“编辑功能设置...”链接中编辑该设置。 enter image description here