Кинути HttpResponseException або повернути Request.CreateErrorResponse?


172

Переглянувши статтю Обробка винятків у веб-API ASP.NET, я трохи заплутався, коли кинути виняток проти повернути відповідь на помилку. Мені також залишається цікаво, чи можна змінити відповідь, коли ваш метод повертає модель, доменну замість HttpResponseMessage...

Отже, для резюме моїх запитань, а також код з регістром №s:

Запитання

Питання щодо справи №1

  1. Чи слід завжди використовувати HttpResponseMessageзамість конкретної доменної моделі, щоб повідомлення можна було налаштувати?
  2. Чи можна налаштувати повідомлення, якщо ви повертаєте конкретну модель домену?

Питання щодо справи № 2,3,4

  1. Чи потрібно кидати виняток або повертати відповідь про помилку? Якщо відповідь "це залежить", чи можете ви навести ситуації / приклади, коли використовувати один проти іншого.
  2. Яка різниця між метанням HttpResponseExceptionvs 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
@Mike Wasson Як автор зв'язаної статті, який би ви підходили?
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
    }
}

Вихідний код можна також знайти тут .


Оце Так! :) Це може бути трохи більше для менших проектів, але все-таки дуже приємно ... BTW, чому CreateResponse замість CreateErrorResponse в DefaultHandler?
zam6ak

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

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

Що ви використовуєте для охоронців (доморощених чи сторонніх)?
zam6ak

Зрозуміло, я застосував її в наведеному вище прикладі. Клас Guard надає набір статичних методів, які захищають від або підтверджують, що твердження виконано. Дивіться codepaste.net/5oc1if (Guard) та codepaste.net/nsrsei (DelegateInfo), якщо ви хочете впровадження.
Опозиційний

23

Якщо ви не повертаєте HttpResponseMessage, а замість цього повертаєте об'єкти / моделі класів безпосередньо, підхід, який я вважав корисним, - це додати наступну функцію утиліти до мого контролера

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

і просто зателефонуйте йому за допомогою відповідного коду статусу та повідомлення


4
Це правильна відповідь, воно поставляється з форматом "Повідомлення" як пара ключових значень у тілі. Як правило, я бачу, як це роблять інші структури та мови
MobileMon

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

Я спробував такий підхід. Я це зробив throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), але у Фіддлера він показує статус 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 / тощо, але це не потрібно. наприклад, ви можете не хочете включати суб'єкт-орган у відповідь, або ви хочете отримати інший формат.


Я взяв підхід - просто викидати винятки з дій контролера api, і я зареєстрував фільтр винятків, який обробляє виняток і встановлює відповідну відповідь у контексті виконання дій. Фільтр "підключається" таким чином, що я можу зареєструвати обробники для певних типів винятків перед реєстрацією фільтра в глобальній конфігурації. Це дозволяє мені робити централізовану обробку винятків, а не поширювати її на контролерах.
Опозиційний

@Oppositional Будь-який шанс ви могли б поділитися своїм фільтром виключень? Можливо, як Gist або на такому веб-сайті, як CodePaste?
Пейдж Кук

@Mike Wasson Ви б сказали, що "відповідь на помилку повернення" є більш поширеним підходом проти "кидання виключення"? Я функціонально розумію, що кінцевий результат може бути (є?) Однаковим, але мені цікаво, чому б просто не охопити всю логіку контролера у спробі / ловити та повернути відповідь на помилку як потрібно?
zam6ak

15

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

HttpResponseException не обробляються так само, як інші винятки . Вони не потрапляють у фільтри винятків . Вони не потрапляють у обробник винятків . Вони є хитрим способом ковзання в HttpResponseMessage, припиняючи поточний процес виконання поточного коду.

Якщо код не є інфраструктурним кодом, що покладається на цю спеціальну обробку, уникайте використання типу HttpResponseException!

HttpResponseMessage не є винятком. Вони не припиняють поточний процес виконання поточного коду. Вони не можуть бути відфільтровані як винятки. Вони не можуть бути зареєстровані як винятки. Вони представляють вагомий результат - навіть відповідь 500 - це "дійсна відповідь про виняток"!


Зробити життя простішим:

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

Використовуйте фільтри виключень / обробники винятків / реєстратори винятків, щоб зробити щось відповідне з цими винятковими випадками: змінити / додати коди статусу? додати ідентифікатори відстеження? включити сліди стека? журнал?

Уникаючи HttpResponseException, обробка "виняткових випадків" стає рівномірною і може бути оброблена як частина відкритого трубопроводу! Наприклад, ви можете перетворити "NotFound" на 404, а "ArgumentException" на 400, а "NullReference" на 500 легко і рівномірно з винятками на рівні додатків - дозволяючи розширюваності надавати "основи", такі як реєстрація помилок.


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

@cmeeren У коді, яким я мав справу, більшість із них потрапила на виняток і перетворила його на HttpResponse [Виняток / Повідомлення] у кожному веб-методі. Обидва випадки однакові , тому що якщо проблема полягає в тому, щоб зробити щось інше за внутрішніми винятками, що робиться, роблячи "щось" із вилученим внутрішнім винятком: я рекомендую результат - перекинути відповідний винятковий обгортковий випадок, який все ще обробляється далі стек.
користувач2864740

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

9

Інший випадок, коли використовувати HttpResponseExceptionзамість цього Response.CreateResponse(HttpStatusCode.NotFound)або інший код статусу помилки, це якщо у вас є транзакції у фільтрах дій, і ви хочете, щоб транзакції були повернуті назад, коли повертаєте відповідь про помилку клієнту.

Використання Response.CreateResponseне поверне транзакцію назад, тоді як викид буде виключенням.


3

Я хочу зазначити, що мій досвід, що якщо викинути HttpResponseException замість повернення HttpResponseMessage методом webapi 2, що якщо виклик буде зроблений негайно в IIS Express, він зупинить або поверне 200, але з помилкою html у відповідь. Найпростіший спосіб перевірити це - здійснити виклик $ .ajax до методу, який передає HttpResponseException, а в errorCallBack в ajax негайно звернеться до іншого методу або навіть простої сторінки http. Ви помітите, що негайний дзвінок не вдасться. Якщо ви додаєте в перерві помилки точку перерви або встановлення timeout (), щоб затримати другий виклик на секунду або два, даючи серверу час на відновлення, він працює правильно.

Оновлення:Першопричиною тайм-ауту підключення Wierd Ajax є те, що якщо виклик Ajax здійснюється досить швидко, використовується те саме tcp-з'єднання. Я піднімав ефір помилки 401, повертаючи HttpResonseMessage або кидаючи HTTPResponseException, який повертався до виклику ajax браузера. Але поряд з цим викликом MS повертала помилку Object Not Found, оскільки в програмі Startup.Auth.vb.UserCookieAuthentication було ввімкнено, тому вона намагалася повернути перехоплення відповіді та додати переспрямування, але вона помилилася з Object not in Instance of Object. Ця помилка була html, але вона була додана до відповіді після факту, тому лише якщо ajax-дзвінок був зроблений досить швидко, і те саме з'єднання tcp, що повернулося, повертається до браузера, а потім додається до передньої частини наступного дзвінка. Чомусь Chrome просто вийшов із часу, Fiddler викреслив суміш json та htm, але firefox відкрив справжню помилку. Тож wierd, але sniffer пакетів або firefox був єдиним способом відстежити це.

Також слід зазначити, що якщо ви використовуєте довідку веб-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. (). Тоді () обидва повертають обіцянку, але не та сама прикована обіцянка, то () повертає нову обіцянку, що спричинило неправильність порядку виконання страти. Тож коли тодішня () обіцянка виконана, це був тайм-аут сценарію. Дивна проблема, але не IIS-експрес, видає проблему між клавіатурою та стільцем.


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

my2cennts

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.