ASP.Net MVC Html.HiddenFor avec une valeur incorrecte

132

J'utilise MVC 3 dans mon projet et je constate un comportement très étrange.

J'essaie de créer un champ caché pour une valeur particulière sur mon modèle, le problème est que, pour une raison quelconque, la valeur définie sur le champ ne correspond pas à la valeur dans le modèle.

par exemple

J'ai ce code, juste comme test:

<%:Html.Hidden("Step2", Model.Step) %>
<%:Html.HiddenFor(m => m.Step) %>

Je pense que les deux champs cachés auraient la même valeur. Ce que je fais est de définir la valeur sur 1 la première fois que j'affiche la vue, puis après la soumission, j'augmente la valeur du champ Modèle de 1.

Ainsi, la première fois que je rend la page, les deux contrôles ont la valeur 1, mais la deuxième fois, les valeurs rendues sont les suivantes:

<input id="Step2" name="Step2" type="hidden" value="2" />
<input id="Step" name="Step" type="hidden" value="1" />

Comme vous pouvez le voir, la première valeur est correcte, mais la deuxième valeur semble être la même que la première fois que j'affiche la vue.

Qu'est-ce que je rate? Les helpers * For Html mettent-ils en cache les valeurs d'une manière ou d'une autre? Si tel est le cas, comment puis-je désactiver cette mise en cache?.

Merci de votre aide.

willvv
la source
Je viens de tester autre chose. Si je supprime l'appel HiddenFor et laisse uniquement l'appel Hidden, mais en utilisant le nom "Step", il ne rend également que la première valeur (1).
willvv
1
se passe également dans get
Oren A

Réponses:

191

C'est normal et c'est ainsi que fonctionnent les helpers HTML. Ils utilisent d'abord la valeur de la requête POST et ensuite la valeur du modèle. Cela signifie que même si vous modifiez la valeur du modèle dans l'action de votre contrôleur s'il y a la même variable dans la requête POST, votre modification sera ignorée et la valeur POSTed sera utilisée.

Une solution de contournement possible consiste à supprimer cette valeur de l'état du modèle dans l'action du contrôleur qui tente de modifier la valeur:

// remove the Step variable from the model state 
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;

Une autre possibilité est d'écrire un assistant HTML personnalisé qui utilisera toujours la valeur du modèle et ignorera les valeurs POST.

Et encore une autre possibilité:

<input type="hidden" name="Step" value="<%: Model.Step %>" />
Darin Dimitrov
la source
5
J'ai vraiment apprécié le billet de blog de Simon Ince à ce sujet. La conclusion que j'en tire est de m'assurer que votre flux de travail est correct. Donc, si vous avez accepté un modèle de vue valide et fait quelque chose avec lui, redirigez-vous vers une action de confirmation, même si cela récupère et affiche simplement un modèle équivalent. Cela signifie que vous avez un nouveau ModelState. blogs.msdn.com/b/simonince/archive/2010/05/05/… (lié à un article que j'ai écrit à ce sujet aujourd'hui: oceanbites.blogspot.com/2011/02/mvc-renders-wrong-value.html )
Lisa
2
J'aime vraiment MVC3, mais ce morceau est vraiment maladroit. J'espère qu'ils le corrigent dans MVC4.
KennyZ
5
Wow, celui-ci m'a fait marcher pendant un bon moment. J'ai essentiellement utilisé la première suggestion, mais j'ai simplement appelé ModelState.Clear () avant de revenir. Cela semble très bien fonctionner, y a-t-il une raison de ne pas utiliser Clear?
Jason
1
Le ".Remove" ne fonctionnait pas pour moi. Mais ModelState.Clear () a fait juste avant le retour dans Controller. L'écriture personnalisée de votre Hidden fonctionnerait également bien. Tout cela se produit parce que les développeurs ne veulent pas perdre leurs «valeurs de formulaire» s'ils appuient sur «soumettre» et que la base de données ne s'enregistre pas correctement. Meilleure solution: ne nommez pas différents champs sur la même page avec le même nom / identifiant.
Dexter
1
Pour info, ce comportement ennuyeux a été gracieusement reporté sur ASP.NET Core au cas où quelqu'un craindrait que les choses s'améliorent
John Hargrove
19

J'ai rencontré le même problème lors de l'écriture d'un assistant qui montre différentes parties d'un modèle plus grand à chaque étape.
Les données et / ou les erreurs de «l'étape 1» deviendraient mélangées avec «l'étape 2», etc., jusqu'à ce que je réalise enfin que ModelState était à «blâmer».

C'était ma solution simple:

if (oldPageIndex != newPageIndex)
{
    ModelState.Clear(); // <-- solution
}

return View(model[newPageIndex]);
Peter B
la source
10
ModelState.Clear()a résolu mon problème avec les demandes POST séquentielles dans une situation similaire.
Evan Mulawski
Merci pour le conseil ModelState.Clear () Evan. C'était une anomalie que je n'avais jamais rencontrée auparavant. J'avais plusieurs articles séquentiels sur ajax.beginform et l'un d'eux conservait les valeurs d'un article précédent. Débogage du trou noir. Quelqu'un sait pourquoi cela est mis en cache?
Rob
1

Ce code ne fonctionnera pas

// remove the Step variable from the model state
// if you want the changes in the model to be
// taken into account
ModelState.Remove("Step");
model.Step = 2;

... parce que HiddenFor lit toujours (!) depuis ModelState pas le modèle lui-même. Et s'il ne trouve pas la clé "Step", il produira la valeur par défaut pour ce type de variable qui sera 0 dans ce cas

Voici la solution. Je l'ai écrit pour moi, mais cela ne me dérange pas de le partager car je vois que beaucoup de gens ont du mal avec cette vilaine aide de HiddenFor.

public static class CustomExtensions
{
    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression);
    }

    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression, htmlAttributes);
    }

    public static MvcHtmlString HiddenFor2<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
    {
        ReplacePropertyState(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression, htmlAttributes);
    }

    private static void ReplacePropertyState<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
    {
        string text = ExpressionHelper.GetExpressionText(expression);
        string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(text);
        ModelStateDictionary modelState = htmlHelper.ViewContext.ViewData.ModelState;
        ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);

        if (modelState.ContainsKey(fullName))
        {                
            ValueProviderResult currentValue = modelState[fullName].Value;
            modelState[fullName].Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), currentValue.Culture);
        }
        else
        {
            modelState[fullName] = new ModelState
            {
                Value = new ValueProviderResult(metadata.Model, Convert.ToString(metadata.Model), CultureInfo.CurrentUICulture)
            };
        }
    }
}

Ensuite, vous l'utilisez comme d'habitude depuis votre vue:

@Html.HiddenFor2(m => m.Id)

Il convient de mentionner que cela fonctionne également avec les collections.

Ruslan Georgievskiy
la source
cette solution n'a pas pleinement fonctionné. Après la prochaine publication, la propriété est nulle dans Action
user576510
Eh bien, c'est le code de la production où cela fonctionne bien. Je ne peux pas dire pourquoi cela ne fonctionne pas pour vous, mais si vous voyez le champ caché avec la valeur correcte rendu sur la page, je ne vois aucune raison évidente pour laquelle il ne serait pas restauré dans la propriété du modèle. Si vous voyez une mauvaise valeur de champ caché sur la page - c'est une autre histoire, je serais très désireux de savoir dans quelles circonstances cela se produit avant que la même chose se produise sur ma production :-) Merci.
Ruslan Georgievskiy
0

Je suis trop aux prises avec la même situation que je pense, où j'utilise le même état de modèle entre les appels et lorsque je modifie une propriété de modèle sur le backend. Cependant, cela n'a pas d'importance pour moi, si j'utilise textboxfor ou hiddenfor.

Je contourne simplement la situation en utilisant des scripts de page pour stocker la valeur du modèle en tant que variable js, car j'ai besoin du champ caché à cette fin au début.

Je ne sais pas si cela aide, mais considérez simplement ...

abdulkadir pekeroglu
la source