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
- ¿Debo usar siempre
HttpResponseMessage
un modelo de dominio concreto para poder personalizar el mensaje? - ¿Se puede personalizar el mensaje si está devolviendo un modelo de dominio concreto?
Preguntas sobre el caso # 2,3,4
- ¿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?
- ¿Cuál es la diferencia entre tirar
HttpResponseException
vsRequest.CreateErrorResponse
? La salida al cliente parece idéntica ... - ¿Debo usar siempre
HttpError
para "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);
}
Respuestas:
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:
Clase UnhandledExceptionFilterAttribute:
El código fuente también se puede encontrar aquí .
fuente
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
y simplemente llámelo con el código de estado y mensaje apropiados
fuente
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid Request Format!"))
, pero en Fiddler, muestra el estado 500 (no 400). ¿Alguna idea de por qué?Caso 1
Casos # 2-4
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
... pero si la lógica de su controlador es más complicada, lanzar una excepción podría simplificar el flujo de código.
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.
fuente
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.
fuente
ArgumentException
s en un controlador lógicamente sería un 400, pero ¿qué pasa conArgumentException
s más profundo en la pila? No sería necesariamente correcto convertirlos en 400, sin embargo, si tiene un filtro que convierte todos losArgumentException
s 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.Otro caso sobre cuándo usar en
HttpResponseException
lugar deResponse.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.CreateResponse
no revertirá la transacción, mientras que lanzar una excepción lo hará.fuente
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
atribuir al método para que la ayuda se genere correctamente. Luego
o por error
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.fuente
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.
fuente
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ó
Result
es una clase que contiene detalles de error, mientras queProviderCollection
es mi resultado de ruta feliz.fuente
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
El núcleo es este bucle donde verifico si el tipo de excepción es una subclase de un tipo registrado.
my2cents
fuente