zam*_*6ak 171 c# exception-handling http-status-codes asp.net-web-api
在查看ASP.NET Web API中的文章异常处理之后,我对于何时抛出异常vs返回错误响应感到困惑.我还想知道当你的方法返回特定于域的模型而不是HttpResponseMessage... 时是否可以修改响应
所以,在这里回顾一下我的问题,然后是一些带有#s的代码:
HttpResponseMessage而不是具体的域模型,以便可以自定义消息吗?HttpResponseExceptionVS有Request.CreateErrorResponse什么区别?输出到客户端似乎相同......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;
}
Run Code Online (Sandbox Code Playgroud)
为了帮助进一步演示案例#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);
}
Run Code Online (Sandbox Code Playgroud)
Opp*_*nal 102
我采用的方法是从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);
}
}
)
);
Run Code Online (Sandbox Code Playgroud)
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
}
}
Run Code Online (Sandbox Code Playgroud)
源代码也可以在这里找到.
Joe*_*ing 23
如果你没有返回HttpResponseMessage而是直接返回实体/模型类,我发现有用的方法是将以下实用程序函数添加到我的控制器
private void ThrowResponseException(HttpStatusCode statusCode, string message)
{
var errorResponse = Request.CreateErrorResponse(statusCode, message);
throw new HttpResponseException(errorResponse);
}
Run Code Online (Sandbox Code Playgroud)
并简单地使用适当的状态代码和消息调用它
Mik*_*son 15
情况1
案例#2-4
这些应该是等价的; 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;
}
Run Code Online (Sandbox Code Playgroud)
...但是如果您的控制器逻辑更复杂,抛出异常可能会简化代码流.
HttpError为响应主体提供了一致的格式,可以序列化为JSON/XML/etc,但不是必需的.例如,您可能不希望在响应中包含实体主体,或者您可能需要其他格式.
use*_*740 15
不要抛出HttpResponseException或返回HttpResponesMessage以查找错误 - 除非意图是使用该确切结果结束请求.
HttpResponseException的处理方式与其他异常不同.它们不会被异常过滤器捕获.它们不会被异常处理程序捕获.在终止当前代码的执行流程时,它们是在HttpResponseMessage中滑动的狡猾方式.
除非代码是依赖于此特殊取消处理的基础结构代码,否则请避免使用HttpResponseException类型!
HttpResponseMessage也不例外.它们不会终止当前代码的执行流程.它们不能作为例外进行过滤.它们不能作为例外记录.它们代表了有效的结果 - 即使500响应也是"有效的非异常响应"!
让生活更简单:
当存在异常/错误情况时,继续并抛出正常的.NET异常 - 或者使用所需的"http错误/响应"属性(例如状态代码)的自定义应用程序异常类型(不是从HttpResponseException派生) - 按照正常异常处理.
使用异常过滤器/异常处理程序/异常记录器来执行适合这些特殊情况的操作:更改/添加状态代码?添加跟踪标识符?包括堆栈跟踪?登录?
通过避免HttpResponseException ,"异常情况"处理变得统一,并且可以作为暴露管道的一部分进行处理!例如,可以将"NotFound"转换为404,将"ArgumentException"转换为400,将"NullReference"转换为500容易且统一的应用程序级异常 - 同时允许可扩展性提供"基础知识",例如错误记录.
何时使用HttpResponseException代替Response.CreateResponse(HttpStatusCode.NotFound)或其他错误状态代码的另一种情况是,如果您在操作过滤器中有事务,并且您希望在向客户端返回错误响应时回滚事务.
使用Response.CreateResponse不会回滚事务,而抛出异常会.
| 归档时间: |
|
| 查看次数: |
134689 次 |
| 最近记录: |