Кинуть HttpResponseException или вернуть Request.CreateErrorResponse?


172

После просмотра статьи Обработка исключений в ASP.NET Web API я немного озадачен тем, когда выдавать исключение и возвращать ответ об ошибке. Меня также интересует, возможно ли изменить ответ, когда ваш метод возвращает специфичную для домена модель вместо HttpResponseMessage...

Итак, подведем итоги. Мои вопросы сопровождаются кодом с делом №:

Вопросы

Вопросы по делу № 1

  1. Должен ли я всегда использовать HttpResponseMessageвместо конкретной модели предметной области, чтобы сообщение можно было настроить?
  2. Можно ли настроить сообщение, если вы возвращаете конкретную модель домена?

Вопросы по делу № 2,3,4

  1. Должен ли я генерировать исключение или возвращать ответ об ошибке? Если ответ «это зависит», можете ли вы привести ситуации / примеры того, когда использовать один против другого.
  2. Какая разница между броском HttpResponseExceptionпротив Request.CreateErrorResponse? Вывод клиенту кажется идентичным ...
  3. Должен ли я всегда использовать, 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;
}

Обновить

Для дальнейшей демонстрации случаев № 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);
}

6
@ Майк Уоссон Как автор связанной статьи, какой подход вы бы выбрали?
zam6ak

Ответы:


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

Класс 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
    }
}

Исходный код также можно найти здесь .


Вот Это Да! :) Это может быть немного для небольших проектов, но все же очень приятно ... Кстати, почему CreateResponse вместо CreateErrorResponse в DefaultHandler?
zam6ak

Я пытался отделить детали ошибки (сериализованные в теле) от фразы причины; но вы, безусловно, можете использовать CreateErrorResponse, если это имеет больше смысла, как в случае с привязкой модели.
Оппозиционный

1
Поскольку вы можете зарегистрировать фильтр только с одной строкой кода, я думаю, что он подходит практически для любого типа проекта. У нас есть фильтр в библиотеке классов, который публикуется во внутреннем фиде NuGet, поэтому разработчикам его легко использовать.
Оппозиция

Что вы используете для охраны (доморощенные или сторонние)?
zam6ak

Hoemgrown, я удалил его использование в примере выше. Класс Guard предоставляет набор статических методов, которые защищают или проверяют соответствие утверждению. См. Codepaste.net/5oc1if (Guard) и codepaste.net/nsrsei (DelegateInfo), если вы хотите реализацию.
Оппозиционный

24

Если вы не возвращаете HttpResponseMessage и вместо этого возвращаете классы сущностей / моделей напрямую, я нашел полезный подход - добавить следующую функцию в мой контроллер

private void ThrowResponseException(HttpStatusCode statusCode, string message)
{
    var errorResponse = Request.CreateErrorResponse(statusCode, message);
    throw new HttpResponseException(errorResponse);
}

и просто позвоните с соответствующим кодом статуса и сообщением


4
Это правильный ответ, он поставляется с форматом «Сообщение» в виде пары «ключ-значение» в теле. Обычно я вижу, как это делают другие фреймворки и языки
MobileMon 22.10.15

У меня есть небольшой вопрос об этом подходе. Я использую сообщение, используя синтаксис {{}} на странице angularJS. Если я оставляю возврат каретки, они приходят как n \ r \ в сообщении. Как правильно их сохранить?
Наоми

Я попробовал этот подход. Я сделал throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), но в Fiddler, он показывает статус 500 (не 400). Есть идеи почему?
Сэм

разница является родительской функцией ошибки. Вот ThrowResponseException для любого исключения в приложении. Но должна быть реальная функция,
Серж

15

Случай 1

  1. Не обязательно, есть другие места в конвейере для изменения ответа (фильтры действий, обработчики сообщений).
  2. См. Выше - но если действие возвращает модель предметной области, то вы не можете изменить ответ внутри действия.

Случаи № 2-4

  1. Основными причинами для исключения HttpResponseException являются:
    • если вы возвращаете модель домена, но вам нужно обрабатывать ошибки,
    • упростить логику вашего контроллера, рассматривая ошибки как исключения
  2. Они должны быть эквивалентны; 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;
    }

    ... но если логика вашего контроллера более сложная, создание исключения может упростить поток кода.

  3. HttpError предоставляет вам согласованный формат тела ответа и может быть сериализован в JSON / XML / etc, но это не обязательно. Например, вы можете не захотеть включать тело-сущность в ответ, или вам может потребоваться другой формат.


Подход, который я выбрал, состоит в том, чтобы просто генерировать исключения из действий контроллера API, и я зарегистрировал фильтр исключений, который обрабатывает исключение и устанавливает соответствующий ответ в контексте выполнения действия. Фильтр «подключаемый», так что я могу зарегистрировать обработчики для определенных типов исключений до регистрации фильтра в глобальной конфигурации. Это позволяет мне выполнять централизованную обработку исключений вместо того, чтобы распространять ее по контроллерам.
Оппозиция

@Oppositional Есть ли шанс, что вы захотите поделиться своим фильтром исключений? Возможно, как Gist или на сайте обмена кодами, например CodePaste?
Пейдж Кук

@ Майк Уоссон, не могли бы вы сказать, что «ответ об ошибке возврата» является более распространенным подходом по сравнению с «выбросить исключение»? Я понимаю, что функционально конечный результат может быть (есть?) Одинаковым, но мне интересно, почему бы просто не охватить всю логику контроллера при попытке / перехвате и вернуть ответ об ошибке в зависимости от ситуации?
zam6ak

15

Не бросайте HttpResponseException и не возвращайте HttpResponesMessage для ошибок - кроме случаев, когда намерение завершить запрос с таким точным результатом .

HttpResponseException не обрабатываются так же, как другие исключения . Они не попадают в фильтры исключений . Они не попадают в обработчик исключений . Они являются хитрым способом проскользнуть в HttpResponseMessage при завершении потока выполнения текущего кода.

Если код не является кодом инфраструктуры, основанным на этой особой необработанности, избегайте использования типа HttpResponseException!

HttpResponseMessage's не являются исключениями. Они не завершают текущий поток выполнения кода. Они не могут быть отфильтрованы как исключения. Они не могут быть зарегистрированы как исключения. Они представляют действительный результат - даже ответ 500 является «действительным ответом без исключения»!


Сделай жизнь проще:

При наличии исключительного случая / ошибки, продолжайте и генерируйте нормальное исключение .NET - или настраиваемый тип исключения приложения ( не производный от HttpResponseException) с желаемыми свойствами http error / response, такими как код состояния - согласно обычному исключению обработка .

Используйте Фильтры исключений / Обработчики исключений / Регистраторы исключений, чтобы сделать что-то уместное в этих исключительных случаях: изменить / добавить коды состояния? добавить идентификаторы отслеживания? включить следы стека? журнал?

Избегая HttpResponseException, обработка «исключительного случая» делается равномерной и может быть обработана как часть открытого конвейера! Например, можно легко и равномерно превратить NotFound в 404, а ArgumentException - в 400, а NullReference - в 500 с исключениями на уровне приложения, допуская расширяемость для предоставления «основ», таких как регистрация ошибок.


2
Я понимаю, почему ArgumentExceptions в контроллере логически будет 400, но как насчет ArgumentExceptions глубже в стеке? Необязательно было бы корректно превращать их в 400, но если у вас есть фильтр, который полностью преобразует все ArgumentExceptions в 400, единственный способ избежать этого - перехватить исключение в контроллере и повторно выдать что-то еще, что кажется чтобы победить цель равномерной обработки исключений в фильтре или аналогичном.
17

@cmeeren В коде, которым я занимался, большинство из них поймало исключение и превратило его в HttpResponse [Exception / Message] в каждом веб-методе. Оба случая одинаковы в том смысле, что если задача состоит в том, чтобы сделать что-то другое с внутренними исключениями, то есть сделать «что-то» с перехваченным внутренним исключением: я рекомендую получить соответствующее исключение обертывания, которое все еще обрабатывается далее вверх стек.
user2864740

@cmeeren После обновлений большинство наших веб-точек входа генерируют специальные производные (не-HttpResponseException, которые имеют или отображаются в соответствующие коды ответов) для ошибок использования. Универсальный обработчик может выполнить некоторую проверку стека (icky, но он работает с некоторой осторожностью), чтобы определить, на каком уровне произошло исключение - т.е. охватите 99% случаев, в которых нет более совершенной обработки, или просто укажите 500 для внутренних ошибок. Суть HttpResponseException в том, что он обходит полезную конвейерную обработку.
user2864740

9

Другой случай использования HttpResponseExceptionвместо Response.CreateResponse(HttpStatusCode.NotFound)или другого кода состояния ошибки - это если у вас есть транзакции в фильтрах действий, и вы хотите, чтобы транзакции откатывались при возврате клиенту ответа об ошибке.

Использование Response.CreateResponseне откатит транзакцию назад, тогда как исключение будет.


3

Я хочу отметить, что у меня был опыт, что, если выбрасывать исключение HttpResponseException вместо возврата HttpResponseMessage в методе webapi 2, то, если вызов будет сделан немедленно в IIS Express, он выдаст таймаут или вернет 200, но с ошибкой html в ответ. Самый простой способ проверить это - вызвать $ .ajax для метода, который выдает HttpResponseException, а в errorCallBack в ajax немедленно вызвать другой метод или даже простую страницу http. Вы заметите, что немедленный вызов не удастся. Если вы добавите точку останова или settimeout () в вызове ошибки, чтобы отложить второй вызов на секунду или две, предоставляя серверу время для восстановления, он работает правильно.

Обновить:Основная причина тайм-аута странного Ajax-соединения заключается в том, что если ajax-вызов выполняется достаточно быстро, используется то же самое tcp-соединение. Я поднимал ошибку 401 эфира, возвращая HttpResonseMessage или выдавая исключение HTTPResponseException, которое было возвращено вызову ajax браузера. Но вместе с этим вызовом MS возвращала ошибку «Объект не найден», потому что в Startup.Auth.vb была включена app.UserCookieAuthentication, поэтому он пытался вернуть перехватить ответ и добавить перенаправление, но при этом возникла ошибка с «Объектом, а не экземпляром объекта». Эта ошибка была html, но была добавлена ​​к ответу после факта, поэтому только если ajax-вызов был сделан достаточно быстро, и то же самое используемое tcp-соединение вернулось в браузер, а затем было добавлено в начало следующего вызова. По какой-то причине Chrome просто тайм-аут, fiddler потрудился из-за сочетания json и htm, но firefox вернул настоящую ошибку. Так странно, но перехватчик пакетов или firefox был единственным способом отследить это.

Также следует отметить, что если вы используете справку Web API для создания автоматической справки и возвращаете HttpResponseMessage, то вам следует добавить

[System.Web.Http.Description.ResponseType(typeof(CustomReturnedType))] 

атрибут метода, чтобы справка генерировалась правильно. затем

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

или по ошибке

return Request.CreateErrorResponse( System.Net.HttpStatusCode.InternalServerError, new Exception("An Error Ocurred"));

Надеюсь, что это поможет кому-то еще, кто может получить случайный тайм-аут или сервер недоступен сразу после выброса HttpResponseException.

Кроме того, возвращение HttpResponseException имеет дополнительное преимущество, заключающееся в том, что Visual Studio не прерывается на необработанное исключение, что полезно, когда возвращается ошибка, когда AuthToken необходимо обновить в одностраничном приложении.

Обновление: я отказываюсь от своего утверждения о превышении времени ожидания IIS Express, это была ошибка в моем клиентском обратном вызове ajax, оказывается, поскольку Ajax 1.8 возвращает $ .ajax () и возвращает $ .ajax. (). Then () оба возвращают обещание, но не одно и то же связанное обещание then () возвращает новое обещание, из-за которого порядок выполнения был неверным. Поэтому, когда обещание then () завершилось, наступил тайм-аут сценария. Странная ошибка, но не проблема IIS Express - проблема между клавиатурой и стулом.


0

Насколько я могу сказать, генерируете ли вы исключение или возвращаете Request.CreateErrorResponse, результат один и тот же. Если вы посмотрите на исходный код для System.Web.Http.dll, вы увидите столько же. Взгляните на это общее резюме и очень похожее решение, которое я принял: Web Api, HttpError и поведение исключений.


0

В ситуациях с ошибками я хотел вернуть определенный класс сведений об ошибке в любом формате, который запрашивал клиент, вместо объекта «счастливый путь».

Я хочу, чтобы мои методы контроллера возвращали объект «счастливый путь» для конкретного домена и в противном случае выдали бы исключение.

Проблема, с которой я столкнулся, заключалась в том, что конструкторы HttpResponseException не разрешают доменные объекты.

Это то, что я в конце концов придумал

public ProviderCollection GetProviders(string providerName)
{
   try
   {
      return _providerPresenter.GetProviders(providerName);
   }
   catch (BadInputValidationException badInputValidationException)
   {
     throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.BadRequest,
                                          badInputValidationException.Result));
   }
}

Resultэто класс, который содержит подробности об ошибке, а ProviderCollectionмой счастливый путь.


0

Мне нравится оппозиционный ответ

В любом случае, мне нужен был способ унаследовать унаследованное исключение, и это решение не удовлетворяет всем моим потребностям.

Так что я закончил тем, что изменил, как он обрабатывает OnException, и это моя версия

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)) {
      //tento di vedere se ho registrato qualche eccezione che eredita dal tipo di eccezione sollevata (in ordine di registrazione)
      foreach (var item in this.Handlers.Keys) {
         if (type.IsSubclassOf(item)) {
            registration = this.Handlers[item];
            break;
         }
      }
   }

   //se ho trovato un tipo compatibile, uso la sua gestione
   if (registration != null) {
      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
      );
   }
}

Суть этого цикла, где я проверяю, является ли тип исключения подклассом зарегистрированного типа.

foreach (var item in this.Handlers.Keys) {
    if (type.IsSubclassOf(item)) {
        registration = this.Handlers[item];
        break;
    }
}

my2cents

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.