¿Lanzar HttpResponseException o devolver Request.CreateErrorResponse?

172

Después de revisar un artículo Manejo de excepciones en la API web de ASP.NET , estoy un poco confundido sobre cuándo lanzar una excepción y devolver una respuesta de error. También me pregunto si es posible modificar la respuesta cuando su método devuelve un modelo específico de dominio en lugar de HttpResponseMessage...

Entonces, para recapitular aquí están mis preguntas seguidas de un código con los casos #:

Preguntas

Preguntas sobre el caso n. ° 1

  1. ¿Debo usar siempre HttpResponseMessageun modelo de dominio concreto para poder personalizar el mensaje?
  2. ¿Se puede personalizar el mensaje si está devolviendo un modelo de dominio concreto?

Preguntas sobre el caso # 2,3,4

  1. ¿Debo lanzar una excepción o devolver una respuesta de error? Si la respuesta es "depende", ¿puede dar situaciones / ejemplos sobre cuándo usar uno frente al otro?
  2. ¿Cuál es la diferencia entre tirar HttpResponseExceptionvs Request.CreateErrorResponse? La salida al cliente parece idéntica ...
  3. ¿Debo usar siempre HttpErrorpara "ajustar" los mensajes de respuesta en errores (ya sea que se lance la excepción o se devuelva la respuesta de error)?

Muestras de casos

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

Actualizar

Para ayudar a demostrar los casos # 2,3,4, el siguiente fragmento de código destaca varias opciones que "pueden suceder" cuando no se encuentra un cliente ...

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);
}
zam6ak
fuente
66
@ Mike Wasson Como autor del artículo vinculado, ¿qué enfoque adoptarías?
zam6ak

Respuestas:

102

El enfoque que he tomado es simplemente lanzar excepciones de las acciones del controlador api y tener un filtro de excepción registrado que procese la excepción y establezca una respuesta adecuada en el contexto de ejecución de la acción.

El filtro expone una interfaz fluida que proporciona un medio para registrar controladores para tipos específicos de excepciones antes de registrar el filtro con configuración global.

El uso de este filtro permite el manejo centralizado de excepciones en lugar de distribuirlo entre las acciones del controlador. Sin embargo, hay casos en los que detectaré excepciones dentro de la acción del controlador y devolveré una respuesta específica si no tiene sentido centralizar el manejo de esa excepción en particular.

Ejemplo de registro de filtro:

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

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

El código fuente también se puede encontrar aquí .

Opuesta
fuente
¡Guauu! :) Esto puede ser un poco demasiado para proyectos más pequeños, pero aún así es muy agradable ... Por cierto, ¿por qué CreateResponse en lugar de CreateErrorResponse en DefaultHandler?
zam6ak
Intenté separar los detalles del error (serializados en el cuerpo) de la frase de razón; pero ciertamente podría usar CreateErrorResponse si eso tuviera más sentido como en el caso del enlace de modelos.
Opuesto
1
Como puede registrar el filtro con solo una línea de código, creo que es adecuado para casi cualquier tipo de proyecto. Tenemos el filtro en una biblioteca de clases que se publica en nuestro feed NuGet interno, por lo que es fácil de usar para los desarrolladores.
Oposición
¿Qué estás usando para los guardias (de cosecha propia o de terceros)?
zam6ak
Hoemgrown, eliminé su uso en el ejemplo anterior. La clase Guard ofrece un conjunto de métodos estáticos que protegen o verifican que se haya cumplido una afirmación. Consulte codepaste.net/5oc1if (Guard) y codepaste.net/nsrsei (DelegateInfo) si desea la implementación.
Oposición
23

Si no está devolviendo HttpResponseMessage y en su lugar está devolviendo clases de entidad / modelo directamente, un enfoque que he encontrado útil es agregar la siguiente función de utilidad a mi controlador

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

y simplemente llámelo con el código de estado y mensaje apropiados

Joe King
fuente
44
Esta es la respuesta correcta, viene con el formato "Mensaje" como un par de valores clave en el cuerpo. Por lo general, así es como veo que otros marcos e idiomas lo hacen
MobileMon
Tengo una pregunta menor sobre este enfoque. Estoy consumiendo el mensaje usando la sintaxis {{}} en la página angularJS. Si dejo retornos de carro, aparecen como n \ r \ en el mensaje. ¿Cuál es la forma correcta de preservarlos?
Naomi
Intenté este enfoque. Lo hice throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!")), pero en Fiddler, muestra el estado 500 (no 400). ¿Alguna idea de por qué?
Sam
La diferencia es la función principal del error. Aquí está la excepción ThrowResponseException para cualquier excepción en la aplicación. Pero debería ser la verdadera función lanzando la excepción ...
Serge
15

Caso 1

  1. No necesariamente, hay otros lugares en la tubería para modificar la respuesta (filtros de acción, manejadores de mensajes).
  2. Consulte más arriba, pero si la acción devuelve un modelo de dominio, no puede modificar la respuesta dentro de la acción.

Casos # 2-4

  1. Las principales razones para lanzar HttpResponseException son:
    • si está devolviendo un modelo de dominio pero necesita manejar casos de error,
    • para simplificar la lógica de su controlador tratando los errores como excepciones
  2. Estos deberían ser equivalentes; HttpResponseException encapsula un HttpResponseMessage, que es lo que se devuelve como la respuesta HTTP.

    por ejemplo, el caso # 2 podría reescribirse como

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

    ... pero si la lógica de su controlador es más complicada, lanzar una excepción podría simplificar el flujo de código.

  3. HttpError le ofrece un formato coherente para el cuerpo de respuesta y se puede serializar a JSON / XML / etc., pero no es obligatorio. por ejemplo, es posible que no desee incluir un cuerpo de entidad en la respuesta, o puede que desee algún otro formato.

Mike Wasson
fuente
El enfoque que he tomado es simplemente lanzar excepciones de las acciones del controlador api y he registrado un filtro de excepción que procesa la excepción y establece una respuesta adecuada en el contexto de ejecución de la acción. El filtro es 'conectable', de modo que puedo registrar controladores para tipos específicos de excepciones antes de registrar el filtro con la configuración global. Esto me permite hacer un manejo centralizado de excepciones en lugar de distribuirlo entre los controladores.
Oposición
@Oppositional ¿Hay alguna posibilidad de que esté dispuesto a compartir su filtro de excepción? ¿Quizás como Gist o en un sitio de código compartido como CodePaste?
Paige Cook
@ Mike Wasson, ¿diría que la "respuesta de error de retorno" es un enfoque más común frente a la "excepción de lanzamiento"? Entiendo funcionalmente que el resultado final puede ser (¿es?) El mismo, pero me pregunto por qué no abarcar toda la lógica del controlador en la respuesta de error try / catch y return según corresponda.
zam6ak
15

No arroje una HttpResponseException ni devuelva un HttpResponesMessage por errores, excepto si la intención es finalizar la solicitud con ese resultado exacto .

Las HttpResponseException no se manejan de la misma manera que otras excepciones . No están atrapados en los filtros de excepción . No están atrapados en el controlador de excepciones . Son una forma astuta de deslizarse en un HttpResponseMessage mientras se termina el flujo de ejecución del código actual.

A menos que el código sea un código de infraestructura que se base en este des-manejo especial, ¡ evite usar el tipo HttpResponseException!

HttpResponseMessage's no son excepciones. No terminan el flujo de ejecución del código actual. Pueden no ser filtrados como excepciones. Pueden no ser registrados como excepciones. Representan un resultado válido, ¡incluso una respuesta 500 es "una respuesta válida sin excepción"!


Haz la vida más simple:

Cuando hay un caso excepcional / de error, continúe y arroje una excepción .NET normal, o un tipo de excepción de aplicación personalizada (que no se derive de HttpResponseException) con las propiedades deseadas de 'error / respuesta http', como un código de estado, según la excepción normal manejo .

Use los filtros de excepción / controladores de excepción / registradores de excepción para hacer algo apropiado con estos casos excepcionales: ¿cambiar / agregar códigos de estado? agregar identificadores de seguimiento? incluir rastros de pila? ¿Iniciar sesión?

Al evitar HttpResponseException, el manejo de "casos excepcionales" se hace uniforme y se puede manejar como parte de la tubería expuesta. Por ejemplo, uno puede convertir un 'NotFound' en un 404 y una 'ArgumentException' en un 400 y una 'NullReference' en un 500 de manera fácil y uniforme con excepciones a nivel de aplicación, al tiempo que permite la extensibilidad para proporcionar "elementos básicos" como el registro de errores.

usuario2864740
fuente
2
Entiendo por qué ArgumentExceptions en un controlador lógicamente sería un 400, pero ¿qué pasa con ArgumentExceptions más profundo en la pila? No sería necesariamente correcto convertirlos en 400, sin embargo, si tiene un filtro que convierte todos los ArgumentExceptions a 400, la única forma de evitarlo es atrapar la excepción en el controlador y volver a lanzar algo más, lo que parece para anular el propósito del manejo uniforme de excepciones en un filtro o similar.
cmeeren
@cmeeren En el código que estaba tratando, la mayoría captó la excepción y la convirtió en HttpResponse [Exception / Message] en cada método web. Ambos casos son iguales , ya que si la preocupación es hacer algo diferente con excepciones internas que se hace haciendo "algo" con la excepción interna atrapada: recomiendo que el resultado sea lanzar una excepción de envoltura apropiada que aún se maneja más arriba apilar.
user2864740
@cmeeren Después de las actualizaciones, la mayoría de nuestros puntos de entrada web arrojan una excepción especial (no HttpResponseException, que tiene o está asignada a los códigos de respuesta apropiados) a errores de uso. El manipulador de uniformes podría hacer una inspección de la pila (asqueroso, pero funciona con cuidado) para determinar a qué nivel vino la excepción, es decir. cubren el 99% de los casos que no tienen un manejo más refinado, o simplemente responden con un 500 por errores internos. El quid de la cuestión con HttpResponseException es que omite el procesamiento de canalización útil.
user2864740
9

Otro caso sobre cuándo usar en HttpResponseExceptionlugar de Response.CreateResponse(HttpStatusCode.NotFound), u otro código de estado de error, es si tiene transacciones en los filtros de acción y desea que las transacciones se reviertan al devolver una respuesta de error al cliente.

El uso Response.CreateResponseno revertirá la transacción, mientras que lanzar una excepción lo hará.

Rob Gray
fuente
3

Quiero señalar que, según mi experiencia, si arroja una HttpResponseException en lugar de devolver un HttpResponseMessage en un método webapi 2, que si se realiza una llamada inmediata a IIS Express, se agotará el tiempo o devolverá un 200 pero con un error html en la respuesta. La forma más fácil de probar esto es hacer una llamada $ .ajax a un método que arroje una HttpResponseException y en el errorCallBack en ajax realice una llamada inmediata a otro método o incluso a una simple página http. Notará que la llamada inmediata fallará. Si agrega un punto de interrupción o un tiempo de espera establecido () en la llamada de error para retrasar la segunda llamada un segundo o dos, dando tiempo al servidor para recuperarse, funciona correctamente.

Actualizar:La causa raíz de la extraña conexión Ajax Timeout es que si una llamada ajax se realiza con la suficiente rapidez, se usa la misma conexión tcp. Estaba generando un error de error 401 devolviendo un mensaje HttpResonseMessage o lanzando una HTTPResponseException que se devolvió a la llamada ajax del navegador. Pero junto con esa llamada, MS devolvió un error de Objeto no encontrado porque en la aplicación Startup.Auth.vb.UserCookieAuthentication estaba habilitado, por lo que estaba tratando de regresar, interceptar la respuesta y agregar una redirección pero se produjo un error con el objeto, no la instancia de un objeto. Este error fue html pero se agregó a la respuesta después del hecho, por lo que solo si la llamada ajax se realizó lo suficientemente rápido y la misma conexión TCP utilizada se devolvió al navegador y luego se agregó al frente de la siguiente llamada. Por alguna razón, Chrome acaba de expirar, Fiddler vomitó debido a la mezcla de json y htm pero firefox arrojó el error real. Tan extraño pero sniffer de paquetes o firefox era la única forma de rastrearlo.

También debe tenerse en cuenta que si está utilizando la ayuda de la API web para generar ayuda automática y devuelve HttpResponseMessage, debe agregar un

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

atribuir al método para que la ayuda se genere correctamente. Luego

return Request.CreateResponse<CustomReturnedType>(objCustomeReturnedType) 

o por error

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

Espero que esto ayude a alguien más que puede estar obteniendo un tiempo de espera aleatorio o un servidor no disponible inmediatamente después de lanzar una HttpResponseException.

También devolver una HttpResponseException tiene el beneficio adicional de no hacer que Visual Studio se rompa en una excepción no manejada útil cuando el error que se devuelve es que AuthToken necesita actualizarse en una sola aplicación de página.

Actualización: estoy retractando mi declaración sobre el tiempo de espera de IIS Express, esto resultó ser un error en la llamada ajax de mi cliente, ya que resulta que Ajax 1.8 devuelve $ .ajax () y devuelve $ .ajax. (). Then () ambos devuelven la promesa, pero no la misma promesa encadenada, entonces () devuelve una nueva promesa que causó que el orden de ejecución fuera incorrecto. Entonces, cuando la promesa then () se completó, fue un tiempo de espera de script. Un problema extraño, pero no un problema expreso de IIS, es un problema entre el teclado y la silla.

Andrew DeVries
fuente
0

Por lo que puedo decir, si lanzas una excepción o devuelves Request.CreateErrorResponse, el resultado es el mismo. Si observa el código fuente de System.Web.Http.dll, verá tanto. Eche un vistazo a este resumen general y una solución muy similar que he hecho: Web Api, HttpError y el comportamiento de las excepciones.

Andy Cohen
fuente
0

En situaciones de error, quería devolver una clase específica de detalles de error, en cualquier formato que el cliente solicitara en lugar del objeto de ruta feliz.

Quiero que mis métodos de controlador devuelvan el objeto de ruta feliz específico del dominio y, de lo contrario, arroje una excepción.

El problema que tuve fue que los constructores HttpResponseException no permiten objetos de dominio.

Esto es lo que finalmente se me ocurrió

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

Resultes una clase que contiene detalles de error, mientras que ProviderCollectiones mi resultado de ruta feliz.

NickBeaugié
fuente
0

me gusta respuesta oposicionista

De todos modos, necesitaba una forma de detectar la excepción heredada y esa solución no satisface todas mis necesidades.

Así que terminé cambiando cómo maneja OnException y esta es mi versión

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

El núcleo es este bucle donde verifico si el tipo de excepción es una subclase de un tipo registrado.

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

my2cents

No importante
fuente