ASP.NET MVC Beta 1: DefaultModelBinder persiste à tort dans le paramètre et l'état de validation entre des demandes non liées

StackOverflow https://stackoverflow.com/questions/238460

Question

Lorsque j'utilise la liaison de modèle par défaut pour lier les paramètres de formulaire à un objet complexe, paramètre d'une action, le framework mémorise les valeurs transmises à la première requête, ce qui signifie que toute requête ultérieure relative à cette action obtient les mêmes données que la première. Les valeurs de paramètre et l'état de validation sont persistants entre les demandes Web non liées.

Voici le code de mon contrôleur (service représente l'accès au back-end de l'application):

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Create()
    {
        return View(RunTime.Default);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(RunTime newRunTime)
    {
        if (ModelState.IsValid)
        {
            service.CreateNewRun(newRunTime);
            TempData["Message"] = "New run created";
            return RedirectToAction("index");
        }
        return View(newRunTime);
    }

Ma vue .aspx (fortement typée comme ViewPage<RunTime >) contient des directives telles que:

<%= Html.TextBox("newRunTime.Time", ViewData.Model.Time) %>

Ceci utilise la DefaultModelBinder classe, qui est étaient destinés à permettre la recherche automatique des propriétés de mon modèle .

Je frappe la page, entrez des données valides (par exemple, heure = 1). L’application enregistre correctement le nouvel objet avec time = 1. Je le frappe ensuite à nouveau et saisis différentes données valides (par exemple, time = 2). Toutefois, les données sauvegardées sont les données d'origine (par exemple, heure = 1). Cela affecte également la validation. Par conséquent, si mes données d'origine étaient invalides, toutes les données que je saisirai ultérieurement seront considérées comme non valides. Le redémarrage d'IIS ou la reconstruction de mon code supprime l'état persistant.

Je peux résoudre le problème en écrivant mon propre classeur de modèles codé en dur, dont un exemple naïf de base est présenté ci-dessous.

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create([ModelBinder(typeof (RunTimeBinder))] RunTime newRunTime)
    {
        if (ModelState.IsValid)
        {
            service.CreateNewRun(newRunTime);
            TempData["Message"] = "New run created";
            return RedirectToAction("index");
        }
        return View(newRunTime);
    }


internal class RunTimeBinder : DefaultModelBinder
{
    public override ModelBinderResult BindModel(ModelBindingContext bindingContext)
    {
        // Without this line, failed validation state persists between requests
        bindingContext.ModelState.Clear();


        double time = 0;
        try
        {
            time = Convert.ToDouble(bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"]);
        }
        catch (FormatException)
        {
            bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".Time", bindingContext.HttpContext.Request[bindingContext.ModelName + ".Time"] + "is not a valid number");
        }

        var model = new RunTime(time);
        return new ModelBinderResult(model);
    }
}

Est-ce que je manque quelque chose? Je ne pense pas que ce soit un problème de session de navigateur, car je peux reproduire le problème si les premières données sont entrées dans un navigateur et les secondes dans un autre.

Était-ce utile?

La solution

Il s’est avéré que le problème était que mes contrôleurs étaient réutilisés entre les appels. L'un des détails que j'ai choisi d'omettre de mon message d'origine est que j'utilise le conteneur Castle.Windsor pour créer mes contrôleurs. Je n'avais pas réussi à marquer mon contrôleur avec le style de vie Transient, donc je recevais la même instance à chaque demande. Ainsi, le contexte utilisé par le classeur était en cours de réutilisation et, bien sûr, il contenait des données obsolètes.

J'ai découvert le problème en analysant avec soin la différence entre le code d'Eilon et le mien, en éliminant toutes les autres possibilités. Selon la documentation sur Castle , il s'agit d'un & Quot ; terrible erreur " ;! Que ceci soit un avertissement pour les autres!

Merci de votre réponse, Eilon. Désolé de prendre votre temps.

Autres conseils

J'ai essayé de reproduire ce problème mais je ne vois pas le même comportement. J'ai créé presque exactement le même contrôleur et les mêmes vues que vous avez (avec quelques hypothèses) et chaque fois que j'ai créé un nouveau & "RunTime &"; J'ai mis sa valeur dans TempData et je l'ai envoyé via la redirection. Ensuite, sur la page cible, j'ai saisi la valeur, qui correspond toujours à la valeur saisie dans cette requête - jamais une valeur périmée.

Voici mon contrôleur:

Classe publique HomeController: Controller {     public ActionResult Index () {         ViewData [& Quot; Title & Quot;] = & Quot Home Page & Quot ;;         string message = " Bienvenue: " + TempData [& Quot; Message & Quot;]];         if (TempData.ContainsKey (" valeur "))) {             int theValue = (int) TempData [" valeur "];             message + = " " + theValue.ToString ();         }         ViewData [& Quot; Message & Quot;]] = message;         retourner Voir ();     }

[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Create() {
    return View(RunTime.Default);
}

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(RunTime newRunTime) {
    if (ModelState.IsValid) {
        //service.CreateNewRun(newRunTime);
        TempData["Message"] = "New run created";
        TempData["value"] = newRunTime.TheValue;
        return RedirectToAction("index");
    }
    return View(newRunTime);
}

}

Et voici ma vue (Create.aspx):

<% using (Html.BeginForm()) { %>
<%= Html.TextBox("newRunTime.TheValue", ViewData.Model.TheValue) %>
<input type="submit" value="Save" />
<% } %>

De plus, je n'étais pas sûr de ce que le & "RunTime &"; type ressemblait, alors j'ai fait celui-ci:

   public class RunTime {
        public static readonly RunTime Default = new RunTime(-1);

        public RunTime() {
        }

        public RunTime(int theValue) {
            TheValue = theValue;
        }

        public int TheValue {
            get;
            set;
        }
    }

Est-il possible que votre implémentation de RunTime inclue des valeurs statiques ou quelque chose de ce genre?

Merci,

Eilon

Je ne sais pas si c'est lié ou non, mais votre appel à <% = Html.TextBox (" newRunTime.Time " ;, ViewData.Model.Time)% > peut effectivement choisir la mauvaise surcharge (étant donné que Time est un entier, il choisira la object htmlAttributes surcharge plutôt que string value.

En vérifiant le rendu HTML, vous saurez si cela se produit. changer int en ViewData.Model.Time.ToString() forcera la surcharge correcte.

On dirait que votre problème est différent, mais je l’ai remarqué et je l’ai déjà été.

Seb, je ne suis pas sûr de ce que tu veux dire par exemple. Je ne sais rien de la configuration de Unity. Je vais expliquer la situation avec Castle.Windsor et peut-être que cela vous aidera à configurer correctement Unity.

Par défaut, Castle.Windsor renvoie le même objet chaque fois que vous demandez un type donné. C'est le style de vie singleton. La documentation Castle.Windsor .

Dans ASP.NET MVC, chaque instance d'une classe de contrôleur est liée au contexte de la demande Web pour laquelle elle a été créée. Ainsi, si votre conteneur IoC renvoie la même instance de votre classe de contrôleur à chaque fois, vous aurez toujours un contrôleur lié au contexte de la première demande Web qui utilisait cette classe de contrôleur. En particulier, les ModelState objets et les autres objets utilisés par DefaultModelBinder seront réutilisés. Votre objet de modèle lié ainsi que les messages de validation contenus dans <=> seront périmés.

Par conséquent, votre IoC doit renvoyer une nouvelle instance chaque fois que MVC demande une instance de votre classe de contrôleur.

Dans Castle.Windsor, cela s’appelle le style de vie transitoire. Pour le configurer, vous avez deux options:

  1. Configuration XML: vous ajoutez lifestlye = " transitoire " à chaque élément de votre fichier de configuration qui représente un contrôleur.
  2. Configuration en code: vous pouvez indiquer au conteneur d’utiliser le mode de vie transitoire au moment de l’inscription du contrôleur. C’est ce que l’assistant MvcContrib mentionné par Ben fait automatiquement pour vous - jetez un coup d’œil à la méthode RegisterControllers dans Code source de MvcContrib .

J'imagine que Unity offre un concept similaire au style de vie de Castle.Windsor. Vous devrez donc configurer Unity pour utiliser son équivalent du mode de vie transitoire de vos contrôleurs. Il semble que MvcContrib possède des Support Unity - peut-être pourriez-vous y jeter un coup d'œil.

J'espère que cela vous aidera.

Après avoir rencontré des problèmes similaires lors de la tentative d'utilisation du conteneur Windsor IoC dans une application ASP.NET MVC, je devais effectuer le même voyage de découverte pour le faire fonctionner. Voici quelques détails qui pourraient aider quelqu'un d'autre.

Utiliser ceci est la configuration initiale dans le fichier Global.asax:

  if (_container == null) 
  {
    _container = new WindsorContainer("config/castle.config");
    ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(Container)); 
  }

Et en utilisant un WindsorControllerFactory qui, lorsqu'on lui demande une instance de contrôleur, fait:

  return (IController)_container.Resolve(controllerType);

Alors que Windsor reliait correctement tous les contrôleurs, pour une raison quelconque, les paramètres n’étaient pas transférés du formulaire à l’action correspondante du contrôleur. Au lieu de cela, ils étaient tous nuls, même s’il appelait l’action correcte.

Par défaut, le conteneur renvoie des singletons, ce qui est évidemment une mauvaise chose pour les contrôleurs et la cause du problème:

http://www.castleproject.org/monorail/documentation /trunk/integration/windsor.html

Toutefois, la documentation indique que le mode de vie des contrôleurs peut être modifié en mode transitoire, bien qu’il ne vous explique pas réellement comment procéder si vous utilisez un fichier de configuration. Il s'avère que c'est assez facile:

<component 
  id="home.controller" 
  type="DoYourStuff.Controllers.HomeController, DoYourStuff" 
  lifestyle="transient" />

Et sans aucune modification de code, il devrait maintenant fonctionner comme prévu (c'est-à-dire des contrôleurs uniques fournis à chaque fois par une instance du conteneur). Vous pouvez ensuite faire toute votre configuration IoC dans le fichier de configuration plutôt que le code comme le bon garçon / fille que je sais que vous êtes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top