抛出HttpResponseException还是返回Request.CreateErrorResponse?

185

阅读了一个文章ASP.NET Web API异常处理后,我对何时抛出异常与返回错误响应感到有些困惑。当方法返回特定领域模型而非HttpResponseMessage时,是否可以修改响应?

回顾一下,以下是我的问题和几个案例:

问题

关于案例#1的问题

  1. 是否应始终使用HttpResponseMessage而不是具体的领域模型,以便可以自定义消息?
  2. 如果返回具体的领域模型,是否仍然可以自定义消息?

关于案例#2、3、4的问题

  1. 是否应该抛出异常或返回错误响应?如果答案是“取决于情况”,请举例说明何时使用其中之一。
  2. 抛出HttpResponseExceptionRequest.CreateErrorResponse有什么区别?对客户端的输出似乎相同...
  3. 是否始终应使用HttpError将响应消息包装在错误中(无论是抛出异常还是返回错误响应)?

案例样本

// CASE #1
public Customer Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
        throw new HttpResponseException(notFoundResponse);
    }
    //var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    //response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return customer;
}        

// CASE #2
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var notFoundResponse = new HttpResponseMessage(HttpStatusCode.NotFound);
        throw new HttpResponseException(notFoundResponse);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

// CASE #3
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var message = String.Format("customer with id: {0} was not found", id);
        var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
        throw new HttpResponseException(errorResponse);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

// CASE #4
public HttpResponseMessage Get(string id)
{
    var customer = _customerService.GetById(id);
    if (customer == null)
    {
        var message = String.Format("customer with id: {0} was not found", id);
        var httpError = new HttpError(message);
        return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
    }
    var response = Request.CreateResponse(HttpStatusCode.OK, customer);
    response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
    return response;
}

更新

为了更好地演示情况#2、3和4,以下代码片段突出几个可能发生的选项,当客户没有找到时......

if (customer == null)
{
    // which of these 4 options is the best strategy for Web API?

    // option 1 (throw)
    var notFoundMessage = new HttpResponseMessage(HttpStatusCode.NotFound);
    throw new HttpResponseException(notFoundMessage);

    // option 2 (throw w/ HttpError)
    var message = String.Format("Customer with id: {0} was not found", id);
    var httpError = new HttpError(message);
    var errorResponse = Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
    throw new HttpResponseException(errorResponse);

    // option 3 (return)
    var message = String.Format("Customer with id: {0} was not found", id);
    return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
    // option 4 (return w/ HttpError)
    var message = String.Format("Customer with id: {0} was not found", id);
    var httpError = new HttpError(message);
    return Request.CreateErrorResponse(HttpStatusCode.NotFound, httpError);
}

6
作为链接文章的作者,你会采取哪种方法? - zam6ak
9个回答

110

我采取的方法是从api控制器操作中抛出异常,并注册一个异常过滤器来处理异常并在操作执行上下文中设置适当的响应。

该过滤器提供了流畅的接口,可以在将过滤器注册到全局配置之前为特定类型的异常注册处理程序。

使用此过滤器可以实现异常处理的集中化,而不是将其分散在控制器操作中。然而,在某些情况下,如果不合适将特定异常的处理集中化,我会在控制器操作中捕获异常并返回特定响应。

过滤器的示例注册:

GlobalConfiguration.Configuration.Filters.Add(
    new UnhandledExceptionFilterAttribute()
    .Register<KeyNotFoundException>(HttpStatusCode.NotFound)

    .Register<SecurityException>(HttpStatusCode.Forbidden)

    .Register<SqlException>(
        (exception, request) =>
        {
            var sqlException = exception as SqlException;

            if (sqlException.Number > 50000)
            {
                var response            = request.CreateResponse(HttpStatusCode.BadRequest);
                response.ReasonPhrase   = sqlException.Message.Replace(Environment.NewLine, String.Empty);

                return response;
            }
            else
            {
                return request.CreateResponse(HttpStatusCode.InternalServerError);
            }
        }
    )
);

UnhandledExceptionFilterAttribute类:

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http.Filters;

namespace Sample
{
    /// <summary>
    /// Represents the an attribute that provides a filter for unhandled exceptions.
    /// </summary>
    public class UnhandledExceptionFilterAttribute : ExceptionFilterAttribute
    {
        #region UnhandledExceptionFilterAttribute()
        /// <summary>
        /// Initializes a new instance of the <see cref="UnhandledExceptionFilterAttribute"/> class.
        /// </summary>
        public UnhandledExceptionFilterAttribute() : base()
        {

        }
        #endregion

        #region DefaultHandler
        /// <summary>
        /// Gets a delegate method that returns an <see cref="HttpResponseMessage"/> 
        /// that describes the supplied exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, HttpRequestMessage, HttpResponseMessage}"/> delegate method that returns 
        /// an <see cref="HttpResponseMessage"/> that describes the supplied exception.
        /// </value>
        private static Func<Exception, HttpRequestMessage, HttpResponseMessage> DefaultHandler = (exception, request) =>
        {
            if(exception == null)
            {
                return null;
            }

            var response            = request.CreateResponse<string>(
                HttpStatusCode.InternalServerError, GetContentOf(exception)
            );
            response.ReasonPhrase   = exception.Message.Replace(Environment.NewLine, String.Empty);

            return response;
        };
        #endregion

        #region GetContentOf
        /// <summary>
        /// Gets a delegate method that extracts information from the specified exception.
        /// </summary>
        /// <value>
        /// A <see cref="Func{Exception, String}"/> delegate method that extracts information 
        /// from the specified exception.
        /// </value>
        private static Func<Exception, string> GetContentOf = (exception) =>
        {
            if (exception == null)
            {
                return String.Empty;
            }

            var result  = new StringBuilder();

            result.AppendLine(exception.Message);
            result.AppendLine();

            Exception innerException = exception.InnerException;
            while (innerException != null)
            {
                result.AppendLine(innerException.Message);
                result.AppendLine();
                innerException = innerException.InnerException;
            }

            #if DEBUG
            result.AppendLine(exception.StackTrace);
            #endif

            return result.ToString();
        };
        #endregion

        #region Handlers
        /// <summary>
        /// Gets the exception handlers registered with this filter.
        /// </summary>
        /// <value>
        /// A <see cref="ConcurrentDictionary{Type, Tuple}"/> collection that contains 
        /// the exception handlers registered with this filter.
        /// </value>
        protected ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> Handlers
        {
            get
            {
                return _filterHandlers;
            }
        }
        private readonly ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>> _filterHandlers = new ConcurrentDictionary<Type, Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>>();
        #endregion

        #region OnException(HttpActionExecutedContext actionExecutedContext)
        /// <summary>
        /// Raises the exception event.
        /// </summary>
        /// <param name="actionExecutedContext">The context for the action.</param>
        public override void OnException(HttpActionExecutedContext actionExecutedContext)
        {
            if(actionExecutedContext == null || actionExecutedContext.Exception == null)
            {
                return;
            }

            var type    = actionExecutedContext.Exception.GetType();

            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

            if (this.Handlers.TryGetValue(type, out registration))
            {
                var statusCode  = registration.Item1;
                var handler     = registration.Item2;

                var response    = handler(
                    actionExecutedContext.Exception.GetBaseException(), 
                    actionExecutedContext.Request
                );

                // Use registered status code if available
                if (statusCode.HasValue)
                {
                    response.StatusCode = statusCode.Value;
                }

                actionExecutedContext.Response  = response;
            }
            else
            {
                // If no exception handler registered for the exception type, fallback to default handler
                actionExecutedContext.Response  = DefaultHandler(
                    actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
                );
            }
        }
        #endregion

        #region Register<TException>(HttpStatusCode statusCode)
        /// <summary>
        /// Registers an exception handler that returns the specified status code for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register a handler for.</typeparam>
        /// <param name="statusCode">The HTTP status code to return for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler has been added.
        /// </returns>
        public UnhandledExceptionFilterAttribute Register<TException>(HttpStatusCode statusCode) 
            where TException : Exception
        {

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                statusCode, DefaultHandler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler)
        /// <summary>
        /// Registers the specified exception <paramref name="handler"/> for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to register the <paramref name="handler"/> for.</typeparam>
        /// <param name="handler">The exception handler responsible for exceptions of type <typeparamref name="TException"/>.</param>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception <paramref name="handler"/> 
        /// has been added.
        /// </returns>
        /// <exception cref="ArgumentNullException">The <paramref name="handler"/> is <see langword="null"/>.</exception>
        public UnhandledExceptionFilterAttribute Register<TException>(Func<Exception, HttpRequestMessage, HttpResponseMessage> handler) 
            where TException : Exception
        {
            if(handler == null)
            {
              throw new ArgumentNullException("handler");
            }

            var type    = typeof(TException);
            var item    = new Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>>(
                null, handler
            );

            if (!this.Handlers.TryAdd(type, item))
            {
                Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> oldItem = null;

                if (this.Handlers.TryRemove(type, out oldItem))
                {
                    this.Handlers.TryAdd(type, item);
                }
            }

            return this;
        }
        #endregion

        #region Unregister<TException>()
        /// <summary>
        /// Unregisters the exception handler for exceptions of type <typeparamref name="TException"/>.
        /// </summary>
        /// <typeparam name="TException">The type of exception to unregister handlers for.</typeparam>
        /// <returns>
        /// This <see cref="UnhandledExceptionFilterAttribute"/> after the exception handler 
        /// for exceptions of type <typeparamref name="TException"/> has been removed.
        /// </returns>
        public UnhandledExceptionFilterAttribute Unregister<TException>()
            where TException : Exception
        {
            Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> item = null;

            this.Handlers.TryRemove(typeof(TException), out item);

            return this;
        }
        #endregion
    }
}

源代码也可以在此处找到。


1
由于您只需使用一行代码即可注册过滤器,因此我认为它适用于几乎任何项目类型。我们将过滤器放在一个类库中,并发布到我们的内部 NuGet 源中,因此开发人员可以轻松使用它。 - Oppositional
你在使用什么保护措施(自制或第三方)? - zam6ak
如果有其他全局过滤器,那么这应该是第一个添加的,对吧?此外,什么时候注销异常?我一直在测试这个,到目前为止它表现得很好...顺便说一下,在您的DefaultHandler中,您可以使用var includeErrorDetail = false; #if DEBUG includeErrorDetail = true; #endif var httpError = new HttpError(exception, includeErrorDetail); return request.CreateErrorResponse(HttpStatusCode.InternalServerError, httpError); - zam6ak
点赞,无论如何我都会发布另一个扩展你的解决方案的答案(我这里没有足够的字符可用) - Not Important
如果您使过滤器仅在启动时配置,那么您可以使用非并发集合来减少性能成本(或者使用ImmutableCollections)。我没有看到很多涉及在应用程序运行时注册新过滤器的情况。我很欣赏注册语法的简单性;也许可以使用params[] Func进行初始化?只是提供一些想法;我目前执行的操作与此类似,但更加简单。回答很棒,就这样吧。 - TheXenocide
显示剩余7条评论

30

如果您没有返回HttpResponseMessage,而是直接返回实体/模型类,我发现一种有用的方法是将以下实用程序函数添加到我的控制器中:

private void ThrowResponseException(HttpStatusCode statusCode, string message)
{
    var errorResponse = Request.CreateErrorResponse(statusCode, message);
    throw new HttpResponseException(errorResponse);
}

只需使用适当的状态码和消息调用它即可


4
这是正确的答案,它以"Message"作为键值对的格式出现在主体中。这通常是我看到其他框架和语言使用的方式。 - MobileMon
我有一个关于这种方法的小问题。我正在使用AngularJS页面中的{{}}语法来消费消息。如果我留下回车符,它们会以n\r\的形式出现在消息中。保留它们的正确方式是什么? - Naomi
我尝试了这种方法。我使用了 throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "无效的请求格式!")),但在 Fiddler 中,它显示状态码为 500(而不是 400)。请问有什么想法吗? - Sam
区别在于错误的父函数。这里是为应用程序中的任何异常抛出ThrowResponseException。但实际上应该是抛出异常的真正函数... - serge

21

不要为错误抛出HttpResponseException或返回HttpResponesMessage - 除非意图以该确切结果结束请求。

HttpResponseException与其他异常不同,它们不会在异常过滤器中被捕获,也不会在异常处理程序中被捕获。它是一种巧妙的方法,可以在终止当前代码执行流程的同时插入HttpResponseMessage。

除非代码是依赖于此特殊未处理的基础设施代码,否则应避免使用HttpResponseException类型!

HttpResponseMessage不是异常。它们不会终止当前代码的执行流程。无法将其作为异常进行筛选或记录。它们代表一个有效的结果-即使500响应也是“有效的非异常响应”!


让生活更简单:

当出现异常/错误情况时,请继续抛出普通的.NET异常-或自定义的应用程序异常类型(不派生自HttpResponseException),并具有所需的“http错误/响应”属性,例如状态代码-根据正常异常处理。

使用异常过滤器/异常处理程序/异常记录器来适当地处理这些异常情况:更改/添加状态码?添加跟踪标识符?包括堆栈跟踪?日志?

通过避免HttpResponseException,“异常情况”处理变得统一,可以作为公开管道的一部分处理!例如,可以使用应用程序级别的异常将“NotFound”转换为404,“ArgumentException”转换为400,并且可以轻松地使用基本错误日志提供可扩展性。


2
我理解为什么控制器中的 ArgumentException 逻辑上应该是 400,但是如果在堆栈深处出现 ArgumentException 呢?将它们全部转换为 400 并不一定正确,但是如果您有一个过滤器,可以将所有 ArgumentException 都转换为 400,那么避免这种情况的唯一方法就是在控制器中捕获异常并重新抛出其他异常,这似乎违背了在过滤器或类似位置进行统一异常处理的目的。 - cmeeren
@cmeeren 在我处理的代码中,大部分都捕获了异常并在每个 Web 方法中将其转换为 HttpResponse[Exception/Message]。这两种情况是相同的,因为如果关注点是对内部异常执行不同的操作,则可以通过对捕获的内部异常执行“某些”操作来完成:我建议结果是抛出一个适当的包装异常,仍然在堆栈上方进一步处理。 - user2864740
@cmeeren 在更新后,我们的大多数Web入口点都会抛出一个特殊的派生异常(非HttpResponseException,该异常具有或映射到适当的响应代码)以表示使用错误。统一处理程序可以执行一些堆栈检查(有些棘手,但在某些情况下可行),以确定异常来自哪个级别 - 即覆盖99%没有更精细处理的情况 - 或仅对内部错误响应500。 HttpResponseException的关键是它绕过了有用的管道处理。 - user2864740

18

案例 #1

  1. 不一定,可以在管道中的其他位置修改响应(操作过滤器,消息处理程序)。
  2. 如上所述 - 但如果操作返回一个领域模型,则无法在操作内部修改响应。

案例 #2-4

  1. 抛出HttpResponseException的主要原因是:
    • 如果您正在返回一个领域模型但需要处理错误情况,
    • 通过将错误视为异常来简化控制器逻辑。
  2. 这两种方法应该是等价的;HttpResponseException封装了一个HttpResponseMessage,它将作为HTTP响应返回。
    例如,案例#2可以重写为:

    public HttpResponseMessage Get(string id)
    {
        HttpResponseMessage response;
        var customer = _customerService.GetById(id);
        if (customer == null)
        {
            response = new HttpResponseMessage(HttpStatusCode.NotFound);
        }
        else
        {
            response = Request.CreateResponse(HttpStatusCode.OK, customer);
            response.Content.Headers.Expires = new DateTimeOffset(DateTime.Now.AddSeconds(300));
        }
        return response;
    }
    

    ... 但是如果你的控制器逻辑更加复杂,抛出异常可能会简化代码流程。

  3. HttpError为响应体提供了一致的格式,并可序列化为JSON/XML等格式,但这并非必需。例如,您可能不想在响应中包含实体正文,或者希望使用其他格式。


我使用的方法是在 api 控制器操作中仅抛出异常,然后我注册一个异常过滤器来处理异常并在操作执行上下文中设置适当的响应。该过滤器是“可插入式”的,这样我就可以在将过滤器与全局配置注册之前为特定类型的异常注册处理程序。这使我能够集中处理异常,而不是将其分散到控制器中。 - Oppositional
@Oppositional你愿意分享你的异常过滤器吗?或许可以放在Gist上或者类似CodePaste这样的代码分享网站上? - Paige Cook
@Mike Wasson,您认为“返回错误响应”比“抛出异常”更常见吗?我理解在功能上最终结果可能是相同的,但我想知道为什么不只是将整个控制器逻辑包含在try/catch中,并根据需要返回错误响应呢? - zam6ak

9

在处理错误状态码时,如果你在操作过滤器中有事务,并且希望在向客户端返回错误响应时回滚事务,这时候应该使用 HttpResponseException 而不是 Response.CreateResponse(HttpStatusCode.NotFound) 或其他错误状态码。

使用 Response.CreateResponse 不会回滚事务,而抛出异常则会。


3

我想指出,根据我的经验,在webapi 2方法中抛出HttpResponseException而不是返回HttpResponseMessage,如果立即调用IIS Express,则会超时或返回200,但响应中会有一个html错误。 最简单的测试方法是对抛出HttpResponseException的方法进行$.ajax调用,并在ajax的errorCallBack中立即调用另一个方法或甚至是一个简单的http页面。您将注意到即时调用将失败。如果在错误回调中添加断点或settimeout()以延迟第二个调用一两秒钟,从而给服务器时间来恢复,它将正常工作。这毫无意义,但几乎就像抛出HttpResponseException会使服务器端侦听线程退出并重新启动,导致短暂的无服务器接受连接的情况。

更新:奇怪的Ajax连接超时的根本原因是如果足够快地进行ajax调用,则使用相同的tcp连接。我通过返回HttpResonseMessage或抛出HTTPResponseException引发401错误,这些错误被返回到浏览器的ajax调用中。但与该调用一起,MS还返回了一个对象未找到的错误,因为在Startup.Auth.vb app.UserCookieAuthentication启用了该选项,因此它正在尝试拦截响应并添加重定向,但它报告了对象不存在的错误。这个错误是HTML格式的,但是在事实之后将其附加到了响应中,因此仅当较快地进行ajax调用并使用相同的tcp连接时才会将其返回到浏览器,然后将其附加到下一个调用的前面。由于某种原因,Chrome只是超时了,fiddler则因json和htm混合而弹出,但Firefox返回了真正的错误。如此奇怪,但数据包嗅探器或Firefox是追踪此问题的唯一方法。

还应注意,如果您正在使用Web API帮助程序生成自动帮助并返回HttpResponseMessage,则应添加

[System.Web.Http.Description.ResponseType(typeof(CustomReturnedType))] 

将属性加入到方法中以便帮助正确生成。然后
return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

或者在出现错误时

return Request.CreateErrorResponse( System.Net.HttpStatusCode.InternalServerError, new Exception("An Error Ocurred"));

希望这能帮助到其他可能遇到随机超时或在抛出HttpResponseException后立即出现服务器不可用的人。
另外,返回HttpResponseException的附加好处是不会导致Visual Studio在未处理异常上中断,当返回的错误是需要在单页应用程序中刷新AuthToken时非常有用。
更新:我撤回关于IIS Express超时的说法,这是我的客户端Ajax回调中的错误。事实证明,自从Ajax 1.8版本以来,返回$.ajax()和返回$.ajax().then()都返回Promise,但不是相同的链接Promise,then()返回一个新的Promise,这导致执行顺序错误。因此,当then() Promise完成时,它是脚本超时。奇怪的问题,但不是IIS Express的问题,而是键盘和椅子之间的问题。

0
在错误情况下,我想返回一个特定的错误详情类,以客户端请求的任何格式而不是成功路径对象。
我希望我的控制器方法返回领域特定的成功路径对象,并在其他情况下抛出异常。
我遇到的问题是HttpResponseException构造函数不允许领域对象。
这就是我最终想出来的。
public ProviderCollection GetProviders(string providerName)
{
   try
   {
      return _providerPresenter.GetProviders(providerName);
   }
   catch (BadInputValidationException badInputValidationException)
   {
     throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest,
                                          badInputValidationException.Result));
   }
}

Result 是一个包含错误详情的类,而 ProviderCollection 则是我的正常结果。


0

我喜欢反对性答案

无论如何,我需要一种方法来捕获继承的异常,而那个解决方案并不能满足我所有的需求。

所以我最终改变了他处理OnException的方式,这是我的版本。

public override void OnException(HttpActionExecutedContext actionExecutedContext) {
   if (actionExecutedContext == null || actionExecutedContext.Exception == null) {
      return;
   }

   var type = actionExecutedContext.Exception.GetType();

   Tuple<HttpStatusCode?, Func<Exception, HttpRequestMessage, HttpResponseMessage>> registration = null;

   if (!this.Handlers.TryGetValue(type, out registration)) {
      //tento di vedere se ho registrato qualche eccezione che eredita dal tipo di eccezione sollevata (in ordine di registrazione)
      foreach (var item in this.Handlers.Keys) {
         if (type.IsSubclassOf(item)) {
            registration = this.Handlers[item];
            break;
         }
      }
   }

   //se ho trovato un tipo compatibile, uso la sua gestione
   if (registration != null) {
      var statusCode = registration.Item1;
      var handler = registration.Item2;

      var response = handler(
         actionExecutedContext.Exception.GetBaseException(),
         actionExecutedContext.Request
      );

      // Use registered status code if available
      if (statusCode.HasValue) {
         response.StatusCode = statusCode.Value;
      }

      actionExecutedContext.Response = response;
   }
   else {
      // If no exception handler registered for the exception type, fallback to default handler
      actionExecutedContext.Response = DefaultHandler(actionExecutedContext.Exception.GetBaseException(), actionExecutedContext.Request
      );
   }
}

核心在于这个循环,我检查异常类型是否是已注册类型的子类。

foreach (var item in this.Handlers.Keys) {
    if (type.IsSubclassOf(item)) {
        registration = this.Handlers[item];
        break;
    }
}

我的看法


0
据我所知,无论是抛出异常还是返回Request.CreateErrorResponse,结果都是相同的。如果您查看System.Web.Http.dll的源代码,您会发现这一点。请参阅此通用摘要以及我制作的非常相似的解决方案:Web Api、HttpError和异常行为

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接