Argh! Why does System.Web.Mvc.HandleErrorInfo get passed to my views?

29,426

Solution 1

Here is an issue on codeplex explaining why that error occurs.

Quote from http://web.archive.org/web/20131004122626/http://aspnet.codeplex.com/workitem/1795 since original link is dead:

HandleError Attribute should not store exception information in ViewData

When the HandleError attribute handles an exception, it stores the exception information in the ViewData. This is a problem when the Error.aspx inherits from the site.master and the site.master class is declared as follows.

public partial class Site : System.Web.Mvc.ViewMasterPage<SiteViewData>
{
}

SiteViewData contains:

public class SiteViewData 
{
  public String Title { get; set; } 
}

Each page ViewData class inherits from the SiteViewData class and looks something like this

public class IndexViewData : SiteViewData
{
  public String Message { get; set; }
  public String SupportedLanguages {get; set;}
}

This approach allows one to write code in the Site.Master page as follows

<title><%= Html.Encode(ViewData.Model.Title) %></title>

Unfortunately, when an exception is thrown, the model has been replaced with an instance of the HandleErrorInfo class. This causes an InvalidOperationException to be thrown with the information

The model item passed into the dictionary is of type System.Web.Mvc.HandleErrorInfo but this dictionary requires a model item of type Igwt.Boh.Website.Web.Controllers.SiteViewData.

Is it possible for a new ErrorData property to be added to the ViewResult class to store the instance of the HandleErrorInfo class instead? This way the ViewData does not get changed.

Chances are pretty good that any exception thrown in the action will occur after the IndexViewData (and SiteViewData) properties have already been initialized.

Closed Jan 27, 2010 at 12:24 AM by

Won't fix - see comments.


The comments mentioned with "wontfix" are from a former member of the Microsoft team, along with their suggestion for working around it (bolded):

By the time the [HandleError] attribute executes, we've lost the reference to the original ActionResult object. We don't even know if you intended to show a view anyway - maybe you intended to redirect. The part of the pipeline (the ViewResult) that would have been responsible for passing the model from the controller to the view is gone.

If an exception occurs, any model the application was working on should probably be treated as corrupt or unavailable anyway. The best practice would be to write your Error view such that neither it nor its dependencies (such as its master page) requires the original model.

Solution 2

My solution for dealing with the problem is to remove the @model directive at the top of the layout page and then do some checks where I'd normally expect to see my model to switch between the different models that might get passed in e.g.

@if (Model is System.Web.Mvc.HandleErrorInfo)
{
    <title>Error</title>
}
else if (Model.GetType() == typeof(MyApp.Models.BaseViewModel))
{
    <meta name="description" content="@Model.PageMetaDescription">
    <title>@Model.PageTitleComplete</title>
}

Solution 3

I've just tracked down a similar issue in my app and wanted to describe the fix for me. In my case, I was getting the following exception:

System.InvalidOperationException: The model item passed into the dictionary is of 
type 'System.Web.Mvc.HandleErrorInfo', but this dictionary requires a model item of
type 'Web.Models.Admin.Login'.

And I was using [HandleError] to route errors to ~/Shared/Error.cshtml

What happened [at least in my case] was: ~/Shared/Error.cshtml had Layout = "~/Views/SiteLayout.cshtml"; to ensure that the Error page was styled correctly (like the rest of the site) without duplicating the layout/css includes.

~/Views/SiteLayout.cshtml had a partial included: ~/Shared/LightboxLogin.cshtml which provides an inline lightbox for the login. ~/Shared/LightboxLogin.cshtml had a further partial to embed the actual login form: @Html.Partial("Login") which includes ~/Shared/Login.cshtml This is used for the login functionality on the front-end of the site.

Because the error was caused in the Admin area of the site, the controller was "Admin" and when an error occurred, Error.cshtml was invoked, which included SiteLayout.cshtml with a HandleErrorInfo model. This in turn included LightboxLogin, which then included the Partial, Login... but there was another view in ~/Admin/Login.cshtml which was included by the @Html.Partial("Login") instead.

This view at ~/Admin/Login.cshtml had this: @model Web.Models.Admin.Login

So, lesson learned here is be careful of your naming of your partials that you want to include. If ~/Shared/Login.cshtml was ~/Shared/PublicLoginForm.cshtml and @Html.Partial("PublicLoginForm") was used then this issue would have been avoided.

Sidenote: I fixed this like so [as I didn't want to restructure my views]:

@if (!(Model is HandleErrorInfo))
{
   @Html.Partial("LightboxLogin")
}

Which means the Partial is not included when the layout is included in an Error condition.

Solution 4

I had this error with strongly typed views and fixed it by also setting the original request context's RouteData.Values["controller"] and "action" to match the error page controller and action names.

If you look here you will see an enhanced HandleErrorAttribute implementation which besides the JSON support also shows you what is going on in the base class with the result view.

https://www.dotnettricks.com/learn/mvc/exception-or-error-handling-and-logging-in-mvc4

If the ViewResult construction here is anything like the logic used by Microsoft, then the issue could be that it is only able to specify a new (error condition) view, not the controller or action (as it has changed from the original request). Perhaps that's why the MVC framework/handlers are getting confused with typed views. It seems like a bug to me.

The example above does NOT include this fix, so you'll have to edit it as follows (last two lines and comment are new):

var model = new HandleErrorInfo(httpError, controllerName, actionName);
filterContext.Result = new ViewResult
{
    ViewName = View,
    MasterName = Master,
    ViewData = new ViewDataDictionary(model),
    TempData = filterContext.Controller.TempData
};

// Correct routing data when required, e.g. to prevent error with typed views
filterContext.RouteData.Values["controller"] = "MyError";  // MyErrorController.Index(HandleErrorInfo)
filterContext.RouteData.Values["action"] = "Index";

If you're not handling it in a filter/attribute then you only need do something like the last two lines where you are dealing with the routing data, e.g. many "OnError" examples construct an error controller then call IContoller.Execute. But that's another story.

Anyway, if you get this error, wherever you are handling the error, just reset the original "controller" and "action" name to whatever you are using and it may fix it for you too.

Share:
29,426
spmorgan
Author by

spmorgan

I'm an ASP.NET MVC/Web API (C#)/AngularJS developer founding AtMyBase.com, a website to help U.S. military members stationed overseas enjoy their tour more.

Updated on May 13, 2020

Comments

  • spmorgan
    spmorgan almost 4 years

    I'm experiencing a rather frustrating problem. My MVC site runs fine for the most part, but randomly throws an error (which shows a friendly error to the user). When I check the logs, this is what I get:

    System.InvalidOperationException: The model item passed into the dictionary is of type 'System.Web.Mvc.HandleErrorInfo' but this dictionary requires a model item of type 'BaseViewData'.
    

    Moments later, the same user could hit refresh and the page loads fine. I'm stuck. ;(

    Update: added stack trace

    System.Web.HttpUnhandledException: Exception of type 'System.Web.HttpUnhandledException' was thrown. ---> System.InvalidOperationException: The model item passed into the dictionary is of type 'System.Web.Mvc.HandleErrorInfo' but this dictionary requires a model item of type 'BaseViewData'.
       at System.Web.Mvc.ViewDataDictionary`1.SetModel(Object value)
       at System.Web.Mvc.ViewDataDictionary..ctor(ViewDataDictionary dictionary)
       at System.Web.Mvc.HtmlHelper`1..ctor(ViewContext viewContext, IViewDataContainer viewDataContainer, RouteCollection routeCollection)
       at System.Web.Mvc.ViewMasterPage`1.get_Html()
       at ASP.views_shared_site_master.__Render__control1(HtmlTextWriter __w, Control parameterContainer)
       at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
       at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
       at System.Web.UI.Control.Render(HtmlTextWriter writer)
       at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
       at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children)
       at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer)
       at System.Web.UI.Page.Render(HtmlTextWriter writer)
       at System.Web.Mvc.ViewPage.Render(HtmlTextWriter writer)
       at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter)
       at System.Web.UI.Control.RenderControl(HtmlTextWriter writer)
       at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
       --- End of inner exception stack trace ---
       at System.Web.UI.Page.HandleError(Exception e)
       at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
       at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
       at System.Web.UI.Page.ProcessRequest()
       at System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context)
       at System.Web.UI.Page.ProcessRequest(HttpContext context)
       at ASP.views_shared_error_aspx.ProcessRequest(HttpContext context)
       at System.Web.Mvc.ViewPage.RenderView(ViewContext viewContext)
       at System.Web.Mvc.WebFormView.RenderViewPage(ViewContext context, ViewPage page)
       at System.Web.Mvc.WebFormView.Render(ViewContext viewContext, TextWriter writer)
       at System.Web.Mvc.ViewResultBase.ExecuteResult(ControllerContext context)
       at System.Web.Mvc.ControllerActionInvoker.InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
       at System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName)
       at System.Web.Mvc.Controller.ExecuteCore()
       at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
       at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext)
       at System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext)
       at System.Web.Mvc.MvcHandler.ProcessRequest(HttpContext httpContext)
       at System.Web.Mvc.MvcHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext httpContext)
       at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
       at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)