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

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

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

1 Ответ

12 голосов
/ 07 июля 2011

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

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.

...