12

我漂亮的 REST Web 服务运行良好。除非我访问类似的页面~/,它会返回默认的 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."
}
4

3 回答 3

12

编辑 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: "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)

接下来,创建一个包罗万象的路由,将所有未知请求发送到新的错误处理程序:

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

并且,总结一下,让 IIS 通过 ASP.NET 处理所有请求,方法是将其添加到您的web.config

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

或者,您还可以使用 的customErrors部分web.config将所有错误重定向到新的错误处理程序。

于 2013-02-01T22:43:26.080 回答
0

在 IIS 管理器中,您可以编辑自定义错误

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

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

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

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

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

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

如果您将客户端浏览器重定向到不同的 URL,请使用 302 重定向进行响应。

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

另请参阅Rick Strahl 的文章,其中包含一些屏幕截图。

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

还有其他更多代码密集型选项——比如自定义 HTTP 模块或使用服务器端代码/配置(例如 .Net)来处理所有请求并构建正确的响应 + 标头(请参阅ASP.NET 重写自定义错误不发送内容类型标头http://www.iis.net/learn/develop/runtime-extensibility/developing-iis-modules-and-handlers-with-the-net-framework)。

于 2013-01-26T00:41:37.093 回答
0

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

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

由我们的代码管理的 404(对不存在的 REST 实体的真正访问)适用于该配置。

您还可以从 IIS 管理控制台中编辑该设置,在“错误页面”区域右侧的“编辑功能设置...”链接中。 在此处输入图像描述

于 2017-04-06T15:08:10.403 回答