I have an action filter that is responsible for placing some common information into the ViewBag for use by all views in the shared _Layout.cshtml file.
public class ProductInfoFilterAttribute : ActionFilterAttribute
{
public override void
OnActionExecuting(ActionExecutingContext filterContext)
{
// build product info
// ... (code omitted)
dynamic viewBag = filterContext.Controller.ViewBag;
viewBag.ProductInfo = info;
}
}
In the shared _Layout.cshtml file, I use the information that has been put into the ViewBag.
...
@ViewBag.ProductInfo.Name
...
If an exception occurs while processing a controller action, the standard HandleErrorAttribute should display my shared Error.cshtml view, and this worked before I introduced the action filter above and started using the new values from ViewBag in _Layout.cshtml. Now what I get is the standard ASP.Net runtime error page instead of my custom Error.cshtml view.
I have tracked this down to the fact that while rendering the error view, a RuntimeBinderException ("Cannot perform runtime binding on a null reference") is thrown on the use of ViewBag.ProductInfo.Name in _Layout.cshtml.
It appears that even though my action filter has successfully set the value in the ViewBag before the original exception was thrown, a new context with an empty ViewBag is used when rendering my Error.cshtml view.
Is there any way to get data created by an action filter to be available to a custom error view?
cshtml view, you can access ViewBag. TotalStudents property, as shown below. Internally, ViewBag is a wrapper around ViewData. It will throw a runtime exception, if the ViewBag property name matches with the key of ViewData.
To pass the strongly typed data from Controller to View using ViewBag, we have to make a model class then populate its properties with some data and then pass that data to ViewBag with the help of a property. And then in the View, we can access the data of model class by using ViewBag with the pre-defined property.
The ViewBag object value will be set inside Controller and then the value of the ViewBag object will be accessed in the cshtml file (View) using Razor syntax in ASP.Net MVC Razor.
I have come up with my own solution through the addition of another filter.
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);
}
}
}
This filter needs to be hooked in globally in the Global.asax.cs Application_Start()
method by calling 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With