iis - practice - Cómo anular todas las páginas de error estándar en WebAPI
web api error handling (3)
Editar 26/01/14: Microsoft acaba de agregar " Global Error Handling " a la última actualización de WebAPI 2.1.
Ok, creo que lo tengo. Hay algunas partes.
Primero: crea un controlador para tus errores. Nombré mis acciones de acuerdo con los códigos de error de HTTP.
public class ErrorController : ApiController {
[AllowAnonymous]
[ActionName("Get")]
public HttpResponseMessage Get() {
return Request.CreateErrorInfoResponse(HttpStatusCode.InternalServerError, title: "Unknown Error");
}
[AllowAnonymous]
[ActionName("404")]
[HttpGet]
public HttpResponseMessage Status404() {
return Request.CreateErrorInfoResponse(HttpStatusCode.NotFound, description: "No resource matches the URL specified.");
}
[AllowAnonymous]
[ActionName("400")]
[HttpGet]
public HttpResponseMessage Status400() {
return Request.CreateErrorInfoResponse(HttpStatusCode.BadRequest);
}
[AllowAnonymous]
[ActionName("500")]
[HttpGet]
public HttpResponseMessage Status500() {
return Request.CreateErrorInfoResponse(HttpStatusCode.InternalServerError);
}
}
Luego, creé un GenericExceptionFilterAttribute
que verifica si HttpActionExecutedContext.Exception está lleno y si la respuesta aún está vacía. Si ambos casos son verdaderos, entonces genera una respuesta.
public class GenericExceptionFilterAttribute : ExceptionFilterAttribute {
public GenericExceptionFilterAttribute()
: base() {
DefaultHandler = (context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.InternalServerError, "Internal Server Error", "An unepected error occoured on the server.", exception: ex);
}
readonly Dictionary<Type, Func<HttpActionExecutedContext, Exception, HttpResponseMessage>> exceptionHandlers = new Dictionary<Type, Func<HttpActionExecutedContext, Exception, HttpResponseMessage>>();
public Func<HttpActionExecutedContext, Exception, HttpResponseMessage> DefaultHandler { get; set; }
public void AddExceptionHandler<T>(Func<HttpActionExecutedContext, Exception, HttpResponseMessage> handler) where T : Exception {
exceptionHandlers.Add(typeof(T), handler);
}
public override void OnException(HttpActionExecutedContext context) {
if (context.Exception == null) return;
try {
var exType = context.Exception.GetType();
if (exceptionHandlers.ContainsKey(exType))
context.Response = exceptionHandlers[exType](context, context.Exception);
if(context.Response == null && DefaultHandler != null)
context.Response = DefaultHandler(context, context.Exception);
}
catch (Exception ex) {
context.Response = context.Request.CreateErrorInfoResponse(HttpStatusCode.InternalServerError, description: "Error while building the exception response.", exception: ex);
}
}
}
En mi caso, fui con un único controlador genérico que podría registrar soporte para cada uno de los principales tipos de excepción y correlacionar cada uno de esos tipos de excepción con códigos de respuesta HTTP específicos. Ahora registre sus tipos de excepción y controladores este filtro globalmente en su global.asax.cs
:
// These filters override the default ASP.NET exception handling to create REST-Friendly error responses.
var exceptionFormatter = new GenericExceptionFilterAttribute();
exceptionFormatter.AddExceptionHandler<NotImplementedException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.InternalServerError, "Not Implemented", "This method has not yet been implemented. Please try your request again at a later date.", exception: ex));
exceptionFormatter.AddExceptionHandler<ArgumentException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, exception: ex));
exceptionFormatter.AddExceptionHandler<ArgumentNullException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, exception: ex));
exceptionFormatter.AddExceptionHandler<ArgumentOutOfRangeException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, exception: ex));
exceptionFormatter.AddExceptionHandler<FormatException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, exception: ex));
exceptionFormatter.AddExceptionHandler<NotSupportedException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, "Not Supported", exception: ex));
exceptionFormatter.AddExceptionHandler<InvalidOperationException>((context, ex) => context.Request.CreateErrorInfoResponse(System.Net.HttpStatusCode.BadRequest, "Invalid Operation", exception: ex));
GlobalConfiguration.Filters.Add(exceptionFormatter)
A continuación, cree una ruta catchall para enviar todas las solicitudes desconocidas a su nuevo manejador de errores:
config.Routes.MapHttpRoute(
name: "DefaultCatchall",
routeTemplate: "{*url}",
defaults: new {
controller = "Error",
action = "404"
}
);
Y, para terminar, deje que IIS procese todas las solicitudes a través de ASP.NET agregando esto a su web.config
:
<configuration>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
</configuration>
Opcionalmente, también puede usar la sección customErrors
de web.config
para redirigir todos los errores a su nuevo manejador de errores.
Mi hermoso servicio web REST funciona muy bien. Excepto si visito páginas como ~/
, que devuelve la página predeterminada IIS 403 prohibida (incluso utilizando Fiddler y especificando solo Accept: application/json
). No quiero más que errores JSON o XML. ¿Hay alguna manera de anular TODAS las excepciones con un controlador de excepciones personalizado? o un controlador predeterminado para manejar todas las solicitudes desconocidas? ¿Cuál es la forma más sencilla y más correcta (si es diferente) de manejar esto para que los clientes solo necesiten analizar los datagramas XML compatibles con API REST o los blobs JSON?
Solicitud de ejemplo:
GET http://localhost:7414/ HTTP/1.1
User-Agent: Fiddler
Host: localhost:7414
Accept: application/json, text/json, text/xml
Respuesta: (que no me gusta, observe que text/html
no era uno de los tipos de respuesta aceptados)
HTTP/1.1 403 Forbidden
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/8.0
X-SourceFiles: =?UTF-8?B?QzpcaWNhcm9sXENoYXJpdHlMb2dpYy5pQ2Fyb2wuQXBp?=
X-Powered-By: ASP.NET
Date: Fri, 25 Jan 2013 21:06:21 GMT
Content-Length: 5396
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>IIS 8.0 Detailed Error - 403.14 - Forbidden</title>
<style type="text/css">
<!--
...
Respuesta (que preferiría):
HTTP/1.1 403 Forbidden
Cache-Control: private
Content-Type: application/json; charset=utf-8
Date: ...
Content-Length: ....
{
"error":"forbidden",
"status":403,
"error_description":"Directory listing not allowed."
}
Puede que no funcione en todos los casos, pero la mayoría.
Una solución más fácil:
<system.webServer>
<httpErrors errorMode="Detailed" />
</system.webServer>
Nuestro 404 administrado por nuestro código (un acceso real a una entidad REST que no existe) funciona con esa configuración.
También puede editar esa configuración desde la consola de administración de IIS, en la zona "Páginas de error", a la derecha, en el vínculo "Editar configuración de funciones ...".
En el Administrador de IIS, puede editar los errores personalizados :
Abra el Administrador de IIS y navegue hasta el nivel que desea administrar. Para obtener información sobre cómo abrir el Administrador de IIS, vea Abrir el Administrador de IIS (IIS 7). Para obtener información sobre cómo navegar a ubicaciones en la interfaz de usuario, vea Navegación en el Administrador de IIS (IIS 7).
En la Vista de características, haga doble clic en Páginas de error.
En la página de páginas de error, haga clic para seleccionar el error que desea cambiar.
En el panel Acciones, haga clic en Editar.
En el cuadro de diálogo Editar página de error personalizado, seleccione una de las siguientes opciones: Insertar contenido del archivo estático en la respuesta de error si el contenido de error es estático, como un archivo .html.
Ejecute una URL en este sitio si su contenido de error es dinámico, como un archivo .asp.
Responda con un redireccionamiento 302 si está redirigiendo un navegador cliente a una URL diferente.
En el cuadro de texto Ruta del archivo, escriba la ruta de acceso de la página de error personalizada si Insertar contenido del archivo estático en la respuesta de error es el tipo de ruta elegida. Si usa Ejecutar una URL en este sitio o Responder con un tipo de ruta de redireccionamiento 302, escriba, en su lugar, la URL de la página de error personalizada. Haga clic en Aceptar.
Ver también el escrito de Rick Strahl, que tiene algunas capturas de pantalla.
Sin embargo, no creo que esto aborde el encabezado de tipo de contenido en la respuesta; solo describe cómo cambiar la porción de contenido , que puede cambiar al formato JSON. No sé cómo modificar eso sin hacer algo más personalizado, pero algunos clientes podrán manejar el tipo de contenido incorrecto si el contenido sigue siendo JSON, por lo que esto puede ser suficiente (y de ser así, puede ser la opción más simple) . Entonces, esta no es una respuesta completa, pero puede ser útil.
Hay otras opciones más intensivas en cuanto a códigos, como un módulo HTTP personalizado o el uso de código / configuración del servidor (como .Net) para manejar todas las solicitudes y generar los encabezados de respuesta + correctos. (Ver errores personalizados reescritos de ASP.NET no enviar encabezado de tipo de contenido o http://www.iis.net/learn/develop/runtime-extensibility/developing-iis-modules-and-handlers-with-the-net-framework ).