Подтвердить что ты не робот

Как изящно вернуть исключение из обработчика сообщений 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 была неприкосновенной.

4b9b3361

Ответ 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 ";           
        }  
    }