asp.net-mvc - mvc - viewbag razor
¿Cómo puedo usar los datos colocados en un ViewBag por un filtro en mi vista Error.cshtml? (1)
Tengo un filtro de acción que es responsable de colocar cierta información común en ViewBag para que todas las vistas lo utilicen en el archivo _Layout.cshtml compartido.
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 compartido, utilizo la información que se ha colocado en el ViewBag.
...
@ViewBag.ProductInfo.Name
...
Si ocurre una excepción al procesar una acción del controlador, el HandleErrorAttribute estándar debería mostrar mi vista compartida Error.cshtml, y esto funcionó antes de que introdujera el filtro de acción anterior y comencé a usar los nuevos valores de ViewBag en _Layout.cshtml. Ahora lo que obtengo es la página de error de tiempo de ejecución de ASP.Net estándar en lugar de mi vista personalizada Error.cshtml.
He rastreado esto hasta el hecho de que al representar la vista de error, se emite una excepción RuntimeBinderException ("No se puede realizar el enlace de tiempo de ejecución en una referencia nula") sobre el uso de ViewBag.ProductInfo.Name en _Layout.cshtml.
Parece que aunque mi filtro de acción haya 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 representar 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?
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 debe estar conectado globalmente en el método Global.asax.cs Application_Start()
llamando a PreserveViewDataOnExceptionFilter.Register()
.
Lo que he hecho aquí es configurar un nuevo filtro de excepción que se ejecute en último lugar, después de que se ejecute el filtro HandleErrorAttribute, y copie el contenido de la colección ViewData que estaba disponible para el controlador que arrojó la excepción al resultado creado por el filtro HandleErrorAttribute .