Как изящно вернуть исключение из обработчика сообщений WebApi
У меня есть глобальный ExceptionFilter в моем приложении Mvc\WebApi, зарегистрированном как:
public virtual void RegisterHttpFilters(HttpConfiguration config)
{
config.Filters.Add(new MyExceptionFilter(_exceptionHandler));
}
где MyExceptionFilter:
public class MyExceptionFilter : ExceptionFilterAttribute
{
private readonly IMyExceptionHandler m_exceptionHandler;
public MyExceptionFilter(IMyExceptionHandler exceptionHandler)
{
m_exceptionHandler = exceptionHandler;
}
public override void OnException(HttpActionExecutedContext context)
{
Exception ex = context.Exception;
if (ex != null)
{
object response = null;
HttpStatusCode statusCode = m_exceptionHandler != null
? m_exceptionHandler.HandleException(ex, out response)
: HttpStatusCode.InternalServerError;
context.Response = context.Request.CreateResponse(statusCode, response ?? ex);
}
base.OnException(context);
}
}
Этот фильтр возвращает все исключения в виде json-объектов и позволяет некоторым IMyExceptionHandler-реализации настраивать возвращаемый объект.
Все это хорошо работает. Пока у меня есть исключения в некоторых обработчиках сообщений:
public class FooMessageHandler : DelegatingHandler
{
private readonly Func<IBar> _barFactory;
public FooMessageHandler(Func<IBar> barFactory)
{
_barFactory = varFactory;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Properties["MY_BAR"] = _barFactory();
return base.SendAsync(request, cancellationToken);
}
}
Как вы видите, этот обработчик создает какой-то компонент и помещает его в текущее сообщение HTTP-запроса.
Когда в FuncOfIBar возникает исключение, я получаю желтый экран смерти. Мой ExceptionFilter не вызывается.
Я попытался специально поймать исключение в обработчике сообщений и вернуть исключение HttpResponseException, но он ничего не меняет - все равно получается YSOD:
public class XApplicationInitializerMessageHandler : DelegatingHandler
{
private readonly Func<IXCoreApplication> _appFactory;
private readonly IXExceptionHandler m_exceptionHandler;
public XApplicationInitializerMessageHandler(Func<IXCoreApplication> appFactory, IXExceptionHandler exceptionHandler)
{
ArgumentValidator.EnsureArgumentNotNull(appFactory, "appFactory");
_appFactory = appFactory;
m_exceptionHandler = exceptionHandler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
request.SetApplication(_appFactory());
}
catch (Exception ex)
{
object resultObject = null;
HttpStatusCode statusCode = m_exceptionHandler != null
? m_exceptionHandler.HandleException(ex, out resultObject)
: HttpStatusCode.InternalServerError;
HttpResponseMessage responseMessage = request.CreateResponse(statusCode, resultObject ?? ex);
throw new HttpResponseException(responseMessage);
}
return base.SendAsync(request, cancellationToken);
}
}
}
Я хочу, чтобы поведение моего приложения было таким же, независимо от того, где происходит исключение: в ApiController или обработчике сообщений.
Как это сделать?
Я знаю о Application_Error, но я бы хотел, чтобы настройка HttpApplication была неприкосновенной.
Ответы
Ответ 1
Вместо того, чтобы бросать HttpResponseException
, я должен просто вернуть HttpResponseMessage
:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
request.SetApplication(_appFactory());
}
catch (Exception ex)
{
object resultObject = null;
HttpStatusCode statusCode = m_exceptionHandler != null
? m_exceptionHandler.HandleException(ex, out resultObject)
: HttpStatusCode.InternalServerError;
HttpResponseMessage responseMessage = request.CreateResponse(statusCode, resultObject ?? ex);
return Task<HttpResponseMessage>.Factory.StartNew(() => responseMessage);
}
return base.SendAsync(request, cancellationToken);
}
Ответ 2
То, что я сделал с помощью succes, - это подход, основанный на атрибутах. Вы размещаете собственный атрибут для каждого из действий, которые должны использовать обработку исключений:
[ExceptionHandling]
public IEnumerable<string> Get()
Ваш настраиваемый атрибут обработки исключений выполняется следующим образом:
public class ExceptionHandlingAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{}
Как вы можете видеть, пользовательский атрибут унаследован от ExceptionFilterAttribute. Затем вы просто переопределяете метод OnException. То, что вы тогда можете сделать, это посмотреть на тип исключения и обработать его в соответствии с бизнес-правилами или тем, что когда-либо соответствует вашим потребностям. В некоторых случаях вы можете просто усвоить исключение на сервере. Если вы хотите уведомить клиента, вы можете создать исключение HttpResponseException, которое может содержать всю соответствующую информацию, такую как сообщение, стек вызовов, внутреннее исключение и т.д.
Для большего вдохновения взгляните на это замечательное сообщение в блоге: Обработка исключений веб-API ASP.NET
Ответ 3
У меня была та же проблема, что и глобальные фильтры исключений. Решение заключалось в том, что фильтры исключений веб-API должны быть настроены в другой глобальной коллекции, чем фильтры исключений ASP.NET MVC.
Итак, чтобы настроить обработку ошибок ASP.NET MVC, вы должны запустить:
System.Web.Mvc.GlobalFilters.Filters.Add(new MyMvcExceptionFilter());
Но для обработки ошибок веб-API вы должны запускать:
System.Web.Http.GlobalConfiguration.Configuration.Filters.Add(new MyWebApiExceptionFilter());
Из моего ограниченного опыта работы с Web API этот тип ситуации типичен: на самом деле используемые шаблоны будут очень похожими на ASP.NET MVC и поэтому мгновенно знакомы. Это дает иллюзию, что написание одного фрагмента кода будет влиять на обе структуры, но на самом деле их реализация под ним все в значительной степени или полностью раздельна, и вам нужно будет написать две версии кода, которые очень похожи, чтобы сделать все это работа.
Отличная ссылка на обработку исключений веб-API: http://www.asp.net/web-api/overview/web-api-routing-and-actions/exception-handling
Ответ 4
В startup.cs:
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
Я обнаружил, что использование GlobalExceptionHandler может перехватывать исключение в делегировании обработчика и возвращать пользовательские объекты JSON.
Ответ 5
Я нашел этот блог с очень хорошим примером:
http://nodogmablog.bryanhogan.net/2016/07/getting-web-api-exception-details-from-a-httpresponsemessage/
Я принял код с некоторыми обновлениями:
if ((int)response.StatusCode >= 400)
{
exceptionResponse = JsonConvert.DeserializeObject<ExceptionResponse>(LogRequisicao.CorpoResposta);
LogRequisicao.CorpoResposta = exceptionResponse.ToString() ;
if (exceptionResponse.InnerException != null)
LogRequisicao.CorpoResposta += "\r\n InnerException: " + exceptionResponse.ToString();
}
с использованием объекта:
public class ExceptionResponse
{
public string Message { get; set; }
public string ExceptionMessage { get; set; }
public string ExceptionType { get; set; }
public string StackTrace { get; set; }
public ExceptionResponse InnerException { get; set; }
public override String ToString()
{
return "Message: " + Message + "\r\n "
+ "ExceptionMessage: " + ExceptionMessage + "\r\n "
+ "ExceptionType: " + ExceptionType + " \r\n "
+ "StackTrace: " + StackTrace + " \r\n ";
}
}