18

我有一个动作过滤器,负责将一些通用信息放入 ViewBag 中,以供共享 _Layout.cshtml 文件中的所有视图使用。

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

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

在共享的 _Layout.cshtml 文件中,我使用了已放入 ViewBag 的信息。

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

如果在处理控制器操作时发生异常,标准的 HandleErrorAttribute 应该显示我共享的 Error.cshtml 视图,这在我引入上面的操作过滤器并开始使用 _Layout.cshtml 中的 ViewBag 的新值之前有效。现在我得到的是标准的 ASP.Net 运行时错误页面,而不是我的自定义 Error.cshtml 视图。

我已经追踪到这样一个事实,即在呈现错误视图时,在 _Layout.cshtml 中使用 ViewBag.ProductInfo.Name 时会引发 RuntimeBinderException(“无法对空引用执行运行时绑定”)。

看来,即使我的操作过滤器在引发原始异常之前已成功设置 ViewBag 中的值,在呈现我的 Error.cshtml 视图时仍会使用具有空 ViewBag 的新上下文。

有什么方法可以让操作过滤器创建的数据可用于自定义错误视图?

4

1 回答 1

12

我通过添加另一个过滤器提出了自己的解决方案。

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);
        }
    }
}

这个过滤器需要Application_Start()通过调用全局挂接到 Global.asax.cs 方法中PreserveViewDataOnExceptionFilter.Register()

What I've done here is to set up a new exception filter that runs last, after the HandleErrorAttribute filter runs, and copies the contents of the ViewData collection that was available to the controller that threw the exception into the result created by the HandleErrorAttribute filter.

于 2011-07-06T23:30:15.603 回答