Argh! Pourquoi System.Web.Mvc.HandleErrorInfo est-il transmis à mes vues?

Je rencontre un problème plutôt frustrant. Mon site MVC fonctionne correctement dans la plupart des cas, mais génère une erreur au hasard (une erreur évidente pour l’utilisateur). Lorsque je vérifie les journaux, voici ce que je reçois:

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'. 

Quelques instants plus tard, le même utilisateur peut cliquer sur Actualiser et la page se charge correctement. Je suis coincé. ; (

Mise à jour: trace de stack ajoutée

 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, Ssortingng 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) 

Voici un problème sur codeplex expliquant pourquoi cette erreur se produit.

Citation de http://web.archive.org/web/20131004122626/http://aspnet.codeplex.com/workitem/1795 puisque le lien d’origine est mort:

L’atsortingbut HandleError ne doit pas stocker les informations sur les exceptions dans ViewData

Lorsque l’atsortingbut HandleError gère une exception, il stocke les informations sur l’exception dans ViewData . Cela pose un problème lorsque Error.aspx hérite de site.master et que la classe site.master est déclarée comme suit.

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

SiteViewData contient:

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

Chaque classe ViewData page hérite de la classe SiteViewData et ressemble à ceci:

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

Cette approche permet d’écrire du code dans la page Site.Master comme suit

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

Malheureusement, lorsqu’une exception est levée, le modèle a été remplacé par une instance de la classe HandleErrorInfo . Cela provoque une InvalidOperationException avec les informations

L’élément de modèle transmis au dictionnaire est de type System.Web.Mvc.HandleErrorInfo mais ce dictionnaire nécessite un élément de modèle de type Igwt.Boh.Website.Web.Controllers.SiteViewData .

Est-il possible qu’une nouvelle propriété ErrorData soit ajoutée à la classe ViewResult pour stocker l’instance de la classe HandleErrorInfo ? De cette façon, ViewData ne sera pas changé.

Il est fort probable que toute exception levée dans l’action se produise après que les IndexViewData (et SiteViewData ) aient déjà été initialisées.

Fermé le 27 janv. 2010 à 00:24 par

Ne réparera pas – voir les commentaires.


Les commentaires mentionnés avec “wontfix” proviennent d’un ancien membre de l’équipe Microsoft, avec leur suggestion de contourner le problème (en gras):

Au moment où l’atsortingbut [HandleError] est exécuté, nous avons perdu la référence à l’object ActionResult d’origine. Nous ne soaps même pas si vous aviez l’intention de montrer une vue de toute façon – peut-être avez-vous eu l’intention de redirect. La partie du pipeline (ViewResult) qui aurait été chargée de faire passer le modèle du contrôleur à la vue a disparu.

Si une exception se produit, tout modèle sur lequel l’application travaillait doit probablement être traité comme corrompu ou indisponible de toute façon. La meilleure pratique consiste à écrire votre vue Erreur de sorte que ni celle-ci ni ses dépendances (telles que sa page maître) n’exigent le modèle d’origine.

Pour résoudre ce problème, ma solution consiste à supprimer la directive @model en haut de la page de présentation, puis à effectuer des vérifications au cours desquelles je m’attendrais normalement à voir mon modèle basculer entre les différents modèles susceptibles d’être transmis, par exemple.

 @if (Model is System.Web.Mvc.HandleErrorInfo) { Error } else if (Model.GetType() == typeof(MyApp.Models.BaseViewModel)) {  @Model.PageTitleComplete } 

Je viens de localiser un problème similaire dans mon application et je voulais décrire le correctif pour moi. Dans mon cas, je recevais l’exception suivante:

 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'. 

Et j’utilisais [HandleError] pour router les erreurs vers ~/Shared/Error.cshtml

Ce qui s’est passé [du moins dans mon cas] a été: ~/Shared/Error.cshtml avait Layout = "~/Views/SiteLayout.cshtml"; pour vous assurer que la page d’erreur a été correctement stylée (comme le rest du site) sans dupliquer le layout / css includes.

~/Views/SiteLayout.cshtml avait un partiel inclus: ~/Shared/LightboxLogin.cshtml qui fournit une boîte à jour intégrée pour la connexion. ~/Shared/LightboxLogin.cshtml comportait un autre partiel pour incorporer le formulaire de connexion réel: @Html.Partial("Login") qui inclut ~/Shared/Login.cshtml Il est utilisé pour la fonctionnalité de connexion au ~/Shared/Login.cshtml site.

L’erreur étant due à la zone d’administration du site, le contrôleur était “Admin” et lorsqu’une erreur se produisait, Error.cshtml était Error.cshtml , ce qui incluait SiteLayout.cshtml avec un modèle HandleErrorInfo . Cela incluait LightboxLogin , qui incluait ensuite Partial, Loginmais il y avait une autre vue dans ~/Admin/Login.cshtml qui était incluse à la place par @Html.Partial("Login") .

Cette vue sur ~/Admin/Login.cshtml avait ceci: @model Web.Models.Admin.Login

Ainsi, la leçon apprise ici est de faire attention à ne pas nommer les partiels que vous souhaitez inclure. Si ~/Shared/Login.cshtml était ~/Shared/PublicLoginForm.cshtml et que @Html.Partial("PublicLoginForm") était utilisé, ce problème aurait été évité.

Sidenote: J’ai corrigé ceci comme si [parce que je ne voulais pas restructurer mes vues]:

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

Ce qui signifie que le partiel n’est pas inclus lorsque la mise en page est incluse dans une condition d’erreur.

J’ai rencontré cette erreur avec des vues fortement typées et je l’ai corrigée en définissant également RouteData.Values ​​[“controller”] et “action” du contexte de requête d’origine pour correspondre aux noms de contrôleur de page d’erreur et d’action.

Si vous regardez ici, vous verrez une implémentation améliorée de HandleErrorAtsortingbute qui, outre le support JSON, vous indique également ce qui se passe dans la classe de base avec la vue des résultats.

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

Si la construction de ViewResult ressemble à la logique utilisée par Microsoft, le problème pourrait être qu’il est uniquement capable de spécifier une nouvelle vue (condition d’erreur), pas le contrôleur ou l’action (car elle a été modifiée par rapport à la demande d’origine). C’est peut-être pour cette raison que les gestionnaires / framework MVC sont confondus avec les vues typées. Cela ressemble à un bug pour moi.

L’exemple ci-dessus n’inclut PAS ce correctif. Vous devez donc le modifier comme suit (les deux dernières lignes et le commentaire sont nouveaux):

 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, eg to prevent error with typed views filterContext.RouteData.Values["controller"] = "MyError"; // MyErrorController.Index(HandleErrorInfo) filterContext.RouteData.Values["action"] = "Index"; 

Si vous ne le gérez pas dans un filtre / atsortingbut, il vous suffira de faire quelque chose comme les deux dernières lignes où vous traitez avec les données de routage. Par exemple, de nombreux exemples “OnError” construisent un contrôleur d’erreur puis appellent IContoller.Execute. Mais c’est une autre histoire.

Quoi qu’il en soit, si vous obtenez cette erreur, peu importe où vous la manipulez, il vous suffit de réinitialiser les noms “contrôleur” et “action” d’origine en fonction de ce que vous utilisez, ce qui peut le réparer également.