Как я могу использовать данные, помещенные в ViewBag фильтром в моем представлении Error.cshtml?

У меня есть фильтр действий, который отвечает за размещение некоторой общей информации в 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, и это работало до того, как я ввел фильтр действия выше и начал использовать новые значения из ViewBag в _Layout.cshtml. Теперь я получаю стандартную страницу ошибок ASP.Net вместо моего пользовательского представления Error.cshtml.

Я проследил это до того факта, что при отображении представления ошибки RuntimeBinderException ("Невозможно выполнить связывание во время выполнения для пустой ссылки") генерируется при использовании ViewBag.ProductInfo.Name в _Layout.cshtml.

Похоже, что хотя мой фильтр действий успешно установил значение в ViewBag до того, как было сгенерировано исходное исключение, при отображении моего представления Error.cshtml используется новый контекст с пустым ViewBag.

Есть ли способ сделать данные, созданные фильтром действий, доступными для пользовательского представления ошибок?

1 ответ

Решение

Я придумал собственное решение, добавив еще один фильтр.

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

Этот фильтр необходимо подключить глобально в Global.asax.cs Application_Start() метод по вызову PreserveViewDataOnExceptionFilter.Register(),

Здесь я настроил новый фильтр исключений, который запускается последним после запуска фильтра HandleErrorAttribute и копирует содержимое коллекции ViewData, которая была доступна для контроллера, который выбросил исключение в результат, созданный фильтром HandleErrorAttribute.,

Другие вопросы по тегам