Выбросить HttpResponseException или вернуть Request.CreateErrorResponse?

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

Итак, чтобы повторить здесь, мои вопросы сопровождаются некоторым кодом с аргументом #s:

Вопросов

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

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

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

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

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

Подход, который я принял, – это просто исключить исключения из действий controllerа api и зарегистрировать фильтр исключения, который обрабатывает исключение и устанавливает соответствующий ответ в контексте выполнения действия.

Фильтр предоставляет свободный интерфейс, который обеспечивает средство регистрации обработчиков для определенных типов исключений до регистрации фильтра с глобальной конфигурацией.

Использование этого фильтра позволяет централизовать обработку исключений, а не распространять его на действия controllerа. Однако есть случаи, когда я буду перехватывать исключения в действии controllerа и возвращать конкретный ответ, если нет смысла централизовать обработку этого конкретного исключения.

Пример регистрации фильтра:

 GlobalConfiguration.Configuration.Filters.Add( new UnhandledExceptionFilterAttribute() .Register(HttpStatusCode.NotFound) .Register(HttpStatusCode.Forbidden) .Register( (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 { ///  /// Represents the an attribute that provides a filter for unhandled exceptions. ///  public class UnhandledExceptionFilterAttribute : ExceptionFilterAttribute { #region UnhandledExceptionFilterAttribute() ///  /// Initializes a new instance of the  class. ///  public UnhandledExceptionFilterAttribute() : base() { } #endregion #region DefaultHandler ///  /// Gets a delegate method that returns an  /// that describes the supplied exception. ///  ///  /// A  delegate method that returns /// an  that describes the supplied exception. ///  private static Func DefaultHandler = (exception, request) => { if(exception == null) { return null; } var response = request.CreateResponse( HttpStatusCode.InternalServerError, GetContentOf(exception) ); response.ReasonPhrase = exception.Message.Replace(Environment.NewLine, String.Empty); return response; }; #endregion #region GetContentOf ///  /// Gets a delegate method that extracts information from the specified exception. ///  ///  /// A  delegate method that extracts information /// from the specified exception. ///  private static Func 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 ///  /// Gets the exception handlers registered with this filter. ///  ///  /// A  collection that contains /// the exception handlers registered with this filter. ///  protected ConcurrentDictionary>> Handlers { get { return _filterHandlers; } } private readonly ConcurrentDictionary>> _filterHandlers = new ConcurrentDictionary>>(); #endregion #region OnException(HttpActionExecutedContext actionExecutedContext) ///  /// Raises the exception event. ///  /// The context for the action. public override void OnException(HttpActionExecutedContext actionExecutedContext) { if(actionExecutedContext == null || actionExecutedContext.Exception == null) { return; } var type = actionExecutedContext.Exception.GetType(); Tuple> 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(HttpStatusCode statusCode) ///  /// Registers an exception handler that returns the specified status code for exceptions of type . ///  /// The type of exception to register a handler for. /// The HTTP status code to return for exceptions of type . ///  /// This  after the exception handler has been added. ///  public UnhandledExceptionFilterAttribute Register(HttpStatusCode statusCode) where TException : Exception { var type = typeof(TException); var item = new Tuple>( statusCode, DefaultHandler ); if (!this.Handlers.TryAdd(type, item)) { Tuple> oldItem = null; if (this.Handlers.TryRemove(type, out oldItem)) { this.Handlers.TryAdd(type, item); } } return this; } #endregion #region Register(Func handler) ///  /// Registers the specified exception  for exceptions of type . ///  /// The type of exception to register the  for. /// The exception handler responsible for exceptions of type . ///  /// This  after the exception  /// has been added. ///  /// The  is . public UnhandledExceptionFilterAttribute Register(Func handler) where TException : Exception { if(handler == null) { throw new ArgumentNullException("handler"); } var type = typeof(TException); var item = new Tuple>( null, handler ); if (!this.Handlers.TryAdd(type, item)) { Tuple> oldItem = null; if (this.Handlers.TryRemove(type, out oldItem)) { this.Handlers.TryAdd(type, item); } } return this; } #endregion #region Unregister() ///  /// Unregisters the exception handler for exceptions of type . ///  /// The type of exception to unregister handlers for. ///  /// This  after the exception handler /// for exceptions of type  has been removed. ///  public UnhandledExceptionFilterAttribute Unregister() where TException : Exception { Tuple> item = null; this.Handlers.TryRemove(typeof(TException), out item); return this; } #endregion } } 

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

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

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

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

Дело 1

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

Случаи № 2-4

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

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

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

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

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

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

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


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

Когда есть случай с исключительной / ошибочной ситуацией, идите в обычное исключение .NET или с помощью специального типа исключения приложения ( не из HttpResponseException) с требуемыми свойствами «HTTP error / response», такими как код состояния, обработка .

Используйте Exception Filters / Exception Handlers / Exception Loggers, чтобы сделать что-то подходящее в этих исключительных случаях: изменить / добавить коды состояния? добавить идентификаторы отслеживания? включить трассировки стека? журнал?

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

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

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

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

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

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

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

атрибут метода, поэтому помощь генерируется правильно. затем

 return Request.CreateResponse(objCustomeReturnedType) 

или по ошибке

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

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

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

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

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

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

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

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

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

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

Result – это class, содержащий сведения об ошибке, а ProviderCollection – результат моего счастливого пути.

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

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

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

 public override void OnException(HttpActionExecutedContext actionExecutedContext) { if (actionExecutedContext == null || actionExecutedContext.Exception == null) { return; } var type = actionExecutedContext.Exception.GetType(); Tuple> 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 ); } } 

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

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

my2cents

  • Будут ли исключения C ++ безопасно распространяться через C-код?
  • SqlException улов и обработка
  • Оскорбительно ли использовать IDisposable и «использование» в качестве средства для получения «видимого поведения» для безопасности исключений?
  • Interesting Posts

    Почему я не могу написать себя по адресу: [email protected]?

    Есть ли известная проблема, связанная с символами ядра Windows 7?

    Ось метки X в графике временных рядов с использованием R

    Извлечение фабрики COM-classа для компонента с CLSID {XXXX} не удалось из-за следующей ошибки: 80040154

    Как определить шаблон для экспорта в режиме org-mode?

    форматируйте число с запятыми и десятичными знаками в C # (asp.net MVC3)

    Как мне выполнить слово Stemming или Lemmatization?

    Последняя дата доступа не изменяется даже после чтения файла в Windows 7

    Предварительный просмотр фотографий в Windows 10

    Захват HTTP 401 с помощью перехватчика Angular.js

    Если я использую «компактный» для сжатия диска NTFS, как я могу избежать сжатия сжатых файлов?

    Peer не аутентифицирован при импорте проекта Gradle в eclipse

    Putty 0.61: почему я вижу сообщение «Access Denied» после ввода моего идентификатора входа?

    (-2147483648> 0) возвращает true в C ++?

    Как добавить новую строку (разрыв строки) в файле XML?

    Давайте будем гением компьютера.