web-dev-qa-db-fra.com

ASP.Net MVC Html.HiddenFor avec une valeur incorrecte

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

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

par exemple.

J'ai ce code, juste comme test:

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

Je pensais 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 rends la page, les deux contrôles ont la valeur 1, mais la deuxième fois, les valeurs affichées 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 constater, la première valeur est correcte, mais la seconde semble être identique à la première fois que j'affiche la vue.

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

Merci de votre aide.

123
willvv

C'est normal et c'est ainsi que fonctionnent les helpers HTML. Ils utilisent d'abord la valeur de la requête POST, puis 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 demande 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é consiste à é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 %>" />
180
Darin Dimitrov

J'ai rencontré le même problème lors de l'écriture d'un Wizard montrant différentes parties d'un modèle plus grand à chaque étape.
Les données et/ou les erreurs de "Step 1" seraient mélangées avec "Step 2", etc.

C'était ma solution simple:

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

return View(model[newPageIndex]);
15
Peter B

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 (!) à partir de ModelState et non le modèle lui-même. Et si elle ne trouve pas la touche "Step", elle 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-même mais cela ne me dérange pas de le partager car je vois que beaucoup de gens ont des problèmes 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)

Cela vaut la peine de mentionner que cela fonctionne aussi avec les collections.

1

Je suis trop aux prises avec la même situation, 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 dans le backend. Cependant, peu importe 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 ..

0