2011-07-06 12 views
18

Tengo un filtro de acción que se encarga de colocar información común en el ViewBag para que lo utilicen todas las vistas en el archivo _Layout.cshtml compartido.¿Cómo puedo usar los datos colocados en un ViewBag por un filtro en mi vista Error.cshtml?

public class ProductInfoFilterAttribute : ActionFilterAttribute 
{ 
    public override void 
    OnActionExecuting(ActionExecutingContext filterContext) 
    { 
     // build product info 
     // ... (code omitted) 

     dynamic viewBag = filterContext.Controller.ViewBag; 
     viewBag.ProductInfo = info; 
    } 
} 

En el archivo _Layout.cshtml compartida, uso la información que se ha puesto en la ViewBag.

... 
@ViewBag.ProductInfo.Name 
... 

Si se produce una excepción al procesar una acción del controlador, el estándar HandleErrorAttribute debería mostrar mi punto de vista Error.cshtml compartida, y esto trabajado antes introduje el filtro de la acción por encima y empecé a usar los nuevos valores de ViewBag en _Layout. cshtml. Ahora lo que obtengo es la página estándar de error de tiempo de ejecución de ASP.Net en lugar de mi vista personalizada Error.cshtml.

He rastreado esto hasta el hecho de que al representar la vista de error, se arroja una RuntimeBinderException ("No se puede ejecutar el enlace de tiempo de ejecución en una referencia nula") en el uso de ViewBag.ProductInfo.Name en _Layout.cshtml.

Parece que, aunque mi filtro de acción ha establecido correctamente el valor en el ViewBag antes de que se lanzara la excepción original, se usa un nuevo contexto con un ViewBag vacío al renderizar mi vista Error.cshtml.

¿Hay alguna forma de que los datos creados por un filtro de acción estén disponibles para una vista de error personalizada?

Respuesta

12

He encontrado mi propia solución mediante la adición de otro filtro.

public class PreserveViewDataOnExceptionFilter : IExceptionFilter 
{ 
    public void 
    OnException(ExceptionContext filterContext) 
    { 
     // copy view data contents from controller to result view 
     ViewResult viewResult = filterContext.Result as ViewResult; 
     if (viewResult != null) 
     { 
      foreach (var value in filterContext.Controller.ViewData) 
      { 
       if (! viewResult.ViewData.ContainsKey(value.Key)) 
       { 
        viewResult.ViewData[value.Key] = value.Value; 
       } 
      } 
     } 
    } 

    public static void 
    Register() 
    { 
     FilterProviders.Providers.Add(new FilterProvider()); 
    } 

    private class FilterProvider : IFilterProvider 
    { 
     public IEnumerable<Filter> 
     GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) 
     { 
      // attach filter as "first" for all controllers/actions; note: exception filters run in reverse order 
      // so this really causes the filter to be the last filter to execute 
      yield return new Filter(new PreserveViewDataOnExceptionFilter(), FilterScope.First, null); 
     } 
    } 
} 

Este filtro tiene que ser enganchado en todo el mundo en el Application_Start() método Global.asax.cs llamando PreserveViewDataOnExceptionFilter.Register().

Lo que he hecho aquí es configurar un nuevo filtro de excepción que se ejecute en último lugar, después de ejecutar el filtro HandleErrorAttribute y copie los contenidos de la colección ViewData que estaba disponible para el controlador que lanzó la excepción en el resultado creado por el filtro HandleErrorAttribute.

+0

Esto funciona muy bien para ViewData y me gusta el código, pero ¿tiene un equivalente para ViewBag? –

Cuestiones relacionadas