抛出 HttpResponseException 还是返回 Request.CreateErrorResponse? [英] Throw HttpResponseException or return Request.CreateErrorResponse?

查看:31
本文介绍了抛出 HttpResponseException 还是返回 Request.CreateErrorResponse?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

查看文章后异常处理在 ASP.NET Web API 中 我对何时抛出异常与返回错误响应有点困惑.我还想知道当您的方法返回特定于域的模型而不是 HttpResponseMessage...

After reviewing an article Exception Handling in ASP.NET Web API I am a bit confused as to when to throw an exception vs return an error response. I am also left wondering whether it is possible to modify the response when your method returns a domain specific model instead of HttpResponseMessage...

所以,回顾一下我的问题,然后是一些带有 case #s 的代码:

So, to recap here are my questions followed by some code with case #s:

  1. 我是否应该始终使用 HttpResponseMessage 而不是具体的域模型,以便可以自定义消息?
  2. 如果您要返回具体的域模型,是否可以自定义消息?
  1. Should I always use HttpResponseMessage instead of a concrete domain model, so that the message can be customized?
  2. Can the message be customized if you are returning concrete domain model?

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

  1. 我应该抛出异常还是返回错误响应?如果答案是视情况而定",您能否提供有关何时使用一种与另一种的情况/示例.
  2. 抛出 HttpResponseExceptionRequest.CreateErrorResponse 有什么区别?客户端的输出似乎相同...
  3. 我是否应该始终使用 HttpError 将响应消息包装"在错误中(无论是抛出异常还是返回错误响应)?
  1. Should I be throwing an exception or returning error response? If the answer is "it depends", can you give situations/examples on when to use one vs the other.
  2. What is the difference between throwing HttpResponseException vs Request.CreateErrorResponse ? The output to client seems identical...
  3. Should I always use HttpError to "wrap" response messages in errors (whether the exception is thrown or error response returned)?

案例样本

// 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,以下代码片段突出显示了在找不到客户时可能发生"的几个选项......

Update

To help further demonstrate cases #2,3,4 the following code snippet highlights several options that "can happen" when a customer is not found...

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

推荐答案

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

The approach I have taken is to just throw exceptions from the api controller actions and have an exception filter registered that processes the exception and sets an appropriate response on the action execution context.

过滤器公开了一个流畅的接口,该接口提供了一种在使用全局配置注册过滤器之前为特定类型的异常注册处理程序的方法.

The filter exposes a fluent interface that provides a means of registering handlers for specific types of exceptions prior to registering the filter with global configuration.

使用此过滤器可以实现集中式异常处理,而不是将其分散到控制器操作中.然而,在某些情况下,如果集中处理该特定异常没有意义,我会在控制器操作中捕获异常并返回特定响应.

The use of this filter enables centralized exception handling instead of spreading it across the controller actions. There are however cases where I will catch exceptions within the controller action and return a specific response if it does not make sense to centralize the handling of that particular exception.

过滤器注册示例:

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

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

这篇关于抛出 HttpResponseException 还是返回 Request.CreateErrorResponse?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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