내가 취한 접근법은 API 컨트롤러 작업에서 예외를 throw하고 예외를 처리하고 작업 실행 컨텍스트에 적절한 응답을 설정하는 예외 필터를 등록하는 것입니다.
필터는 전역 구성으로 필터를 등록하기 전에 특정 유형의 예외에 대한 핸들러를 등록하는 수단을 제공하는 유연한 인터페이스를 제공합니다.
이 필터를 사용하면 컨트롤러 조치에 분산시키는 대신 중앙 집중식 예외 처리가 가능합니다. 그러나 컨트롤러 작업 내에서 예외를 포착하고 특정 예외 처리를 중앙 집중화하는 것이 적합하지 않은 경우 특정 응답을 반환하는 경우가 있습니다.
필터 등록 예 :
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
}
}
소스 코드는 여기 에서도 찾을 수 있습니다 .