2012-07-12 22 views
10

En una aplicación MVC3 web que estaba usandoMVC [HandleError] HandleErrorAttribute llamado dos veces al utilizar registro global

public static void RegisterGlobalFilters(GlobalFilterCollection filters) 
{ 
    filters.Add(new HandleErrorAttribute()); 
} 

de aplicar el tratamiento de errores global donde el usuario se muestra la vista 'error' si una excepción no controlada se produjo.

Para una vista particular, también quería que se mostrara una vista de error diferente si se producía una excepción no controlada decorando el método con [HandleError(View = "SpecialError")]. Esto funcionó bien.

Luego quería agregar el registro global de excepciones no controladas. He creado un atributo personalizado HandleError con código de registro:

public class MyHandleErrorAttribute : HandleErrorAttribute 
    { 
     public override void OnException(ExceptionContext context) 
     { 
      // Write to log code 
      base.OnException(context); 
     } 
    } 

y actualizada RegisterGlobalFilters y la decoración método para utilizar este nombre de atributo en su lugar. Esto funciona en general, pero cuando ocurre una excepción dentro del método que está decorado con MyHandleError(View = "SpecialError")], el método OnException se llama dos veces. Originalmente asumí que decorar el método con este atributo reemplazó al controlador global, pero parece que simplemente se agrega (lo cual tiene más sentido, pero no es lo que quiero). Al llamar a OnException dos veces, la misma excepción se registra dos veces, lo que no debe suceder. No creo que OnException se llame dos veces porque es un atributo personalizado: creo que esto también ocurre con el atributo HandleError estándar, ahora es simplemente visible ya que estoy creando un registro de él.

En última instancia, deseo registrar todas las excepciones no controladas (una vez), conservando las funciones ofrecidas por [HandleError], en particular estableciendo diferentes vistas para excepciones de métodos particulares. ¿Hay una manera limpia de hacer esto?

Respuesta

9

creo que he encontrado una solución limpia para esto mismo. La extensión de HandleError me pareció una buena idea, pero ahora creo que fue un paso en la dirección equivocada. No quería manejar ningún error de forma diferente, solo escribir excepciones para iniciar sesión antes de que HandleError los recogiera. Debido a esto, HandleError predeterminado puede dejarse en su lugar tal como está. Aunque OnException se puede llamar varias veces, parece ser completamente benigno en la implementación estándar de HandleErrorAttribute.

En lugar de eso creó un filtro de registro de excepción:

public class LoggedExceptionFilter : IExceptionFilter 
    { 
     public void OnException(ExceptionContext filterContext) 
     { 
      // logging code 
     } 
    } 

No necesita demasiado heredar de FilterAttribute ya que se acaba de registrar una vez dentro de RegisterGlobalFilters junto HandleErrorAttribute.

public static void RegisterGlobalFilters(GlobalFilterCollection filters) 
    { 
     filters.Add(new LoggedExceptionFilter()); 
     filters.Add(new HandleErrorAttribute()); 
    } 

Esto permite excepciones a registrar ordenadamente sin cambiar las características estándar [HandleError]

+1

¡Gracias! Esto funcionó para mí también. – abjbhat

+0

Hola, gran solución, pero ¿cómo puedes obtener más información del origen de la excepción como el nombre de clase/método? – Patrick

2

Se puede crear una costumbre IFilterProvider que comprobará si el filtro se ha aplicado ya a esa acción:

public class MyFilterProvider : IFilterProvider 
{ 
    public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 
    { 
     if (!actionDescriptor.GetFilterAttributes(true).Any(a => a.GetType() == typeof(MyHandleErrorAttribute))) 
     { 
      yield return new Filter(new MyHandleErrorAttribute(), FilterScope.Global, null); 
     } 
    } 
} 

Entonces, en lugar de registrar su filtro con el GlobalFilterCollection, se registraría su proveedor de filtro en Application_Start()

FilterProviders.Providers.Add(new MyFilterProvider()); 

Alternativamente (similar a lo propuesto @ Marcos) se podría establecer explícitamente la ExceptionHandled propiedad de ExceptionContext

public class MyHandleErrorAttribute : HandleErrorAttribute 
{ 
    public override void OnException(ExceptionContext context) 
    { 
     if(context.ExceptionHandled) return; 

     // Write to log code 
     base.OnException(context); 
     context.ExceptionHandled = true; 
    } 
} 
3

Prueba esto,

public class MyHandleErrorAttribute : HandleErrorAttribute 
{ 
    public override void OnException(ExceptionContext context) 
    { 
     var exceptionHandled = context.ExceptionHandled; 

     base.OnException(context);       

     if(!exceptionHandled && context.ExceptionHandled) 
      // log the error. 
    } 
} 
+0

¡Qué maravilla! Gracias funciona bien para mí. Gracias – mayk

-1

De hecho, me encontré la solución para mantener el método OnException de disparar dos veces. Si está utilizando En los FilterConfig.RegisterGlobalFilters() método, comentar de salida del registro de la HandleErrorAttribute:

public class FilterConfig 
{ 
    public static void RegisterGlobalFilters(GlobalFilterCollection filters) 
    { 
     //filters.Add(new HandleErrorAttribute()); 
    } 
} 

De hecho, solía también la incorporada en HandleErrorAttribute sin registrarlo, y funcionó bien. Solo necesito tener activados los errores personalizados:

<system.web> 
    <customErrors mode="On" /> 
</system.web> 
+0

Quizás debería volver a leer la pregunta. Él quiere usar ambos al mismo tiempo. –

Cuestiones relacionadas