Question

Il semble que j'ai un problème avec ASP.NET MVC dans la mesure où, si j'ai plus d'un formulaire sur une page qui utilise le même nom dans chacun, mais sous des types différents (radio/caché/etc), alors, lorsque le premiers messages du formulaire (je choisis le bouton radio "Date" par exemple), si le formulaire est restitué (par exemple dans le cadre de la page de résultats), il me semble avoir le problème de la valeur cachée du SearchType sur les autres formulaires est remplacé par la dernière valeur du bouton radio (dans ce cas, SearchType.Name).

Vous trouverez ci-dessous un exemple de formulaire à des fins de réduction.

<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
  <%= Html.RadioButton("SearchType", SearchType.Date, true) %>
  <%= Html.RadioButton("SearchType", SearchType.Name) %>
  <input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>

<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
  <%= Html.Hidden("SearchType", SearchType.Colour) %>
  <input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>

<% Html.BeginForm("Search", "Search", FormMethod.Post); %>
  <%= Html.Hidden("SearchType", SearchType.Reference) %>
  <input type="submit" name="submitForm" value="Submit" />
<% Html.EndForm(); %>

Source de la page résultante (cela ferait partie de la page de résultats)

<form action="/Search/Search" method="post">
  <input type="radio" name="SearchType" value="Date" />
  <input type="radio" name="SearchType" value="Name" />
  <input type="submit" name="submitForm" value="Submit" />
</form>

<form action="/Search/Search" method="post">
  <input type="hidden" name="SearchType" value="Name" /> <!-- Should be Colour -->
  <input type="submit" name="submitForm" value="Submit" />
</form>

<form action="/Search/Search" method="post">
  <input type="hidden" name="SearchType" value="Name" /> <!-- Should be Reference -->
  <input type="submit" name="submitForm" value="Submit" />
</form>

S'il vous plaît, quelqu'un d'autre avec RC1 peut-il confirmer cela ?

C'est peut-être parce que j'utilise une énumération.Je ne sais pas.Je dois ajouter que je peux contourner ce problème en utilisant des balises input () 'manuelles' pour les champs cachés, mais si j'utilise des balises MVC (<%= Html.Hidden(...) %>), .NET MVC les remplace à chaque fois.

Merci beaucoup.

Mise à jour:

J'ai encore vu ce bug aujourd'hui.Il semble que cela coupe la tête lorsque vous renvoyez une page publiée et utilisez MVC pour définir des balises de formulaire cachées avec l'assistant HTML.j'ai contacté Phil Haack à ce sujet, parce que je ne sais pas vers qui me tourner, et je ne crois pas que cela devrait être un comportement attendu comme spécifié par David.

Était-ce utile?

La solution

Oui, ce comportement est en cours de conception. Même si vous définissez explicitement les valeurs, si vous postez à la même URL, nous regardons dans l'état de modèle et d'utiliser la valeur là. En général, cela nous permet d'afficher la valeur que vous avez soumis sur postback, plutôt que la valeur d'origine.

Il existe deux solutions possibles:

Solution 1

Utilisez des noms uniques pour chacun des champs. Notez que par défaut, nous utilisons le nom que vous spécifiez l'ID de l'élément HTML. Il est HTML invalide d'avoir plusieurs éléments ont le même identifiant. Donc, en utilisant des noms uniques est une bonne pratique.

Solution 2

Ne pas utiliser l'assistant caché. Il semble que vous ne avez pas vraiment besoin. Au lieu de cela, vous pouvez faire ceci:

<input type="hidden" name="the-name" 
  value="<%= Html.AttributeEncode(Model.Value) %>" />

Bien sûr, comme je pense à ce plus, en changeant la valeur basée sur un postback est logique pour Encadrés, mais moins de sens pour les entrées cachées. Nous ne pouvons pas changer cela pour v1.0, mais je vais le considérer pour v2. Mais nous devons réfléchir soigneusement les conséquences d'un tel changement.

Autres conseils

Comme d'autres, je me attendais le ModelState à utiliser pour remplir le modèle et que nous utilisons explicitement le modèle dans les expressions dans la vue, il doit utiliser le modèle et non ModelState.

Il est un choix de conception et je ne comprends pourquoi: si les validations échouent, la valeur d'entrée pourrait ne pas être analysable au type de données dans le modèle et que vous voulez rendre tout mauvais valeur que l'utilisateur a tapé, il est donc facile de le corriger.

La seule chose que je ne comprends pas: pourquoi pas par la conception que le modèle est utilisé, ce qui est explicitement définie par le développeur et si une erreur de validation a eu lieu, le ModelState est utilisé

Je l'ai vu beaucoup de gens en utilisant des solutions de contournement comme

  • ModelState.Clear (): efface toutes les valeurs de ModelState, mais désactive essentiellement l'utilisation de la validation par défaut dans MVC
  • ModelState.Remove ( « SomeKey »): Même chose que ModelState.Clear () mais il a besoin microgestion des clés ModelState, ce qui est trop de travail et il ne se sent pas bien avec la fonction de liaison automatique de MVC. Se sent comme 20 ans en arrière quand nous gérions aussi des clés de formulaire et QueryString.
  • Rendu HTMLthemselves: trop de travail, détail et jette les méthodes Helper HTML avec les fonctionnalités supplémentaires. Un exemple:. Remplacer @ Html.HiddenFor par m.Name) » id = "@ Html.IdFor (m => m.Name)" value = "@ Html.AttributeEncode (Model.Name)"> Ou remplacer @Html. DropDownListFor par ...
  • Créer Helpers HTML personnalisés pour remplacer MVC par défaut HTML Helpers pour éviter le problème par la conception. Ceci est une approche plus générique rendant alors votre HTML, mais nécessite encore plus HTML + connaissances MVC ou décompilation System.Web.Mvc garder encore toutes les autres fonctionnalités, mais désactiver la priorité ModelState sur Modèle.
  • Appliquer le POST-REDIRECT-GET Motif: cela est facile dans certains environnements, mais plus difficile dans ceux avec plus d'interaction / complexité. Ce modèle a ses avantages et ses inconvénients et vous ne devriez pas être obligé d'appliquer ce modèle en raison d'un choix par la conception de ModelState sur modèle.

Numéro

Donc, la question est que le modèle est rempli de ModelState et la vue que nous fixons explicitement d'utiliser le modèle. Tout le monde attend la valeur du modèle (dans le cas où il a changé) à utiliser, à moins qu'il ya une erreur de validation; alors le ModelState peut être utilisé.

À l'heure actuelle dans le MVC Helper les extensions de la valeur ModelState obtient la priorité sur la valeur du modèle.

Solution

Ainsi, la solution réelle de cette question devrait être: pour chaque expression pour tirer la valeur du modèle de la valeur ModelState doit être supprimée s'il n'y a pas d'erreur de validation pour cette valeur. S'il y a une erreur de validation pour que le contrôle d'entrée la valeur ModelState ne doit pas être retiré et il sera utilisé comme normal. Je pense que cela résout le problème exactement, ce qui est mieux que la plupart des solutions de contournement.

Le code est ici:

    /// <summary>
    /// Removes the ModelState entry corresponding to the specified property on the model if no validation errors exist. 
    /// Call this when changing Model values on the server after a postback, 
    /// to prevent ModelState entries from taking precedence.
    /// </summary>
    public static void RemoveStateFor<TModel, TProperty>(this HtmlHelper helper,  
        Expression<Func<TModel, TProperty>> expression)
    {
        //First get the expected name value. This is equivalent to helper.NameFor(expression)
        string name = ExpressionHelper.GetExpressionText(expression);
        string fullHtmlFieldName = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);

        //Now check whether modelstate errors exist for this input control
        ModelState modelState;
        if (!helper.ViewData.ModelState.TryGetValue(fullHtmlFieldName, out modelState) ||
            modelState.Errors.Count == 0)
        {
            //Only remove ModelState value if no modelstate error exists,
            //so the ModelState will not be used over the Model
            helper.ViewData.ModelState.Remove(name);
        }
    }

Et puis nous créons nos propres extensions Helper HTML Todo cette avant d'appeler les extensions MVC:

    public static MvcHtmlString TextBoxForModel<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression,
        string format = "",
        Dictionary<string, object> htmlAttributes = null)
    {
        RemoveStateFor(htmlHelper, expression);
        return htmlHelper.TextBoxFor(expression, format, htmlAttributes);
    }

    public static IHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression)
    {
        RemoveStateFor(htmlHelper, expression);
        return htmlHelper.HiddenFor(expression);
    }

Cette solution supprime le problème, mais ne vous oblige pas à décompiler, analyser et reconstruire ce que MVC vous offre normalement (ne pas oublier aussi la gestion des changements dans le temps, les différences de navigateur, etc.).

Je pense que la logique de « valeur du modèle, sauf erreur de validation puis ModelState » aurait dû être par la conception. S'il était, il ne serait pas mordu tant de gens, mais encore couvert ce MVC était destiné todo.

Je viens de rencontrer le même problème.Les assistants HTML comme la priorité TextBox() pour les valeurs transmises semblent se comporter exactement à l'opposé de ce que j'ai déduit de la Documentation Où il est dit:

La valeur de l'élément de saisie de texte.Si cette valeur est une référence NULL (rien dans Visual Basic), la valeur de l'élément est récupérée à partir de l'objet ViewDatadictionnaire.Si aucune valeur n'existe là-bas, la valeur est récupérée à partir de l'objet Modestatedictionary.

Pour moi, j'ai lu que la valeur, si elle est transmise, est utilisée.Mais en lisant la source TextBox() :

string attemptedValue = (string)htmlHelper.GetModelStateValue(name, typeof(string));
tagBuilder.MergeAttribute("value", attemptedValue ?? ((useViewData) ? htmlHelper.EvalString(name) : valueParameter), isExplicitValue);

semble indiquer que l'ordre réel est exactement le contraire de ce qui est documenté.L'ordre réel semble être :

  1. État du modèle
  2. Afficher les données
  3. Valeur (transmise dans TextBox() par l'appelant)

Heads-up - ce bug existe encore dans MVC 3. Je suis en utilisant la syntaxe de balisage Razor (comme ça importe vraiment), mais je rencontré le même bug avec une boucle foreach qui a produit la même valeur pour une propriété d'objet tous les fois.

Ce serait le behavoir attendu - MVC ne pas utiliser un viewstate ou autre derrière vos astuces de retour pour transmettre des informations supplémentaires sous la forme, il n'a aucune idée de quelle forme vous avez soumis (le nom du formulaire ne fait pas partie des données soumis, seule une liste de paires nom / valeur).

Lorsque MVC rend le formulaire de retour, il vérifie simplement si une valeur soumise avec le même nom existe - encore une fois, il n'a aucun moyen de savoir quelle forme une valeur nommée est venu, ou même ce type de contrôle, il a été (si vous utilisez une radio, un texte ou caché, il est tout juste le nom = valeur au moment de son soumis par HTTP).

foreach (var s in ModelState.Keys.ToList())
                if (s.StartsWith("detalleProductos"))
                    ModelState.Remove(s);

ModelState.Remove("TimeStamp");
ModelState.Remove("OtherOfendingHiddenFieldNamePostedToSamePage1");
ModelState.Remove("OtherOfendingHiddenFieldNamePostedToSamePage2");

return View(model);

Exemple de reproduire le « problème de conception », et un possible workaroud. Il n'y a pas de solution pour les 3 heures perdues en essayant de trouver le « bug » bien ... Notez que cette "conception" est encore dans ASP.NET MVC 2.0 RTM.

    [HttpPost]
    public ActionResult ProductEditSave(ProductModel product)
    {
        //Change product name from what was submitted by the form
        product.Name += " (user set)";

        //MVC Helpers are using, to find the value to render, these dictionnaries in this order: 
        //1) ModelState 2) ViewData 3) Value
        //This means MVC won't render values modified by this code, but the original values posted to this controller.
        //Here we simply don't want to render ModelState values.
        ModelState.Clear(); //Possible workaround which works. You loose binding errors information though...  => Instead you could replace HtmlHelpers by HTML input for the specific inputs you are modifying in this method.
        return View("ProductEditForm", product);
    }

Si votre formulaire contient à l'origine ceci: <%= Html.HiddenFor( m => m.ProductId ) %>

Si la valeur d'origine de « Nom » (lorsque la forme a été rendue) est « factice », après l'envoi du formulaire que vous attendez de voir « factice (défini par l'utilisateur) » rendu. Sans ModelState.Clear() vous verrez toujours « factice » !!!!!!

correcte solution de contournement:

<input type="hidden" name="Name" value="<%= Html.AttributeEncode(Model.Name) %>" />

Je crois que ce n'est pas une bonne conception du tout, comme tous les développeurs de forme doit mvc garder cela à l'esprit.

Ce problème existe toujours dans MVC 5, et clairement ce n'est pas considéré comme un bug qui est bien.

Nous constatons que, bien que par sa conception, ce n'est pas le comportement attendu pour nous. Au contraire, nous voulons toujours la valeur du champ caché pour fonctionner de façon similaire à d'autres types de champs et ne pas être traité spécial ou tirer sa valeur de quelque obscure collection (qui nous rappelle ViewState!).

Quelques résultats (valeur correcte pour nous est la valeur du modèle, incorrect est la valeur ModelState):

  • Html.DisplayFor() affiche la valeur correcte (il tire de modèle)
  • Html.ValueFor ne (il tire de ModelState)
  • ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model tire la valeur correcte

Notre solution consiste à mettre en œuvre simplement notre propre extension:

        /// <summary>
        /// Custom HiddenFor that addresses the issues noted here:
        /// http://stackoverflow.com/questions/594600/possible-bug-in-asp-net-mvc-with-form-values-being-replaced
        /// We will only ever want values pulled from the model passed to the page instead of 
        /// pulling from modelstate.  
        /// Note, do not use 'ValueFor' in this method for these reasons.
        /// </summary>
        public static IHtmlString HiddenTheWayWeWantItFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                    Expression<Func<TModel, TProperty>> expression,
                                                    object value = null,
                                                    bool withValidation = false)
        {
            if (value == null)
            {
                value = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model;
            }

            return new HtmlString(String.Format("<input type='hidden' id='{0}' name='{1}' value='{2}' />",
                                    htmlHelper.IdFor(expression),
                                    htmlHelper.NameFor(expression),
                                    value));
        }

Cela peut être « la conception », mais ce n'est pas ce qui est documenté:

Public Shared Function Hidden(  

  ByVal htmlHelper As System.Web.Mvc.HtmlHelper,  
  ByVal name As String, ByVal value As Object)  
As String  
     

Membre de System.Web.Mvc.Html.InputExtensions

     

Résumé: Retourne une balise d'entrée cachée

.      

Paramètres:
    HtmlHelper: L'aide HTML
.     Nom: Le nom du champ de formulaire et la clé System.Web.Mvc.ViewDataDictionary utilisée pour rechercher la valeur
.     valeur: La valeur de l'entrée est masquée. Si nul, regarde la System.Web.Mvc.ViewDataDictionary puis System.Web.Mvc.ModelStateDictionary pour la valeur.

Cela semble suggérer que seulement lorsque le paramètre de valeur est la HtmlHelper serait nulle (ou non spécifié) chercher ailleurs pour une valeur.

Dans mon application, j'ai une forme où: html.Hidden ( « à distance », True) est rendu comme <input id="remote" name="remote" type="hidden" value="False" />

Notez que la valeur devient supplantée par ce qui est dans le dictionnaire ViewData.ModelState.

Ou suis-je manque quelque chose?

Donc, dans MVC 4 le « problème de conception » toujours là. Voici le code que je devais utiliser pour définir les valeurs cachées correctes dans une collection car peu importe ce que je fais dans le contrôleur, la vue a montré toujours des valeurs incorrectes.

ANCIEN code

for (int i = 0; i < Model.MyCollection.Count; i++)
{
    @Html.HiddenFor(m => Model.MyCollection[i].Name) //It doesn't work. Ignores what I changed in the controller
}

code mis à jour

for (int i = 0; i < Model.MyCollection.Count; i++)
{
    <input type="hidden" name="MyCollection[@(i)].Name" value="@Html.AttributeEncode(Model.MyCollection[i].Name)" /> // Takes the recent value changed in the controller!
}

sont-ils fixés dans ce MVC 5?

Il est solution de contournement:

    public static class HtmlExtensions
    {
        private static readonly String hiddenFomat = @"<input id=""{0}"" type=""hidden"" value=""{1}"" name=""{2}"">";
        public static MvcHtmlString HiddenEx<T>(this HtmlHelper htmlHelper, string name, T[] values)
        {
            var builder = new StringBuilder(values.Length * 100);
            for (Int32 i = 0; i < values.Length; 
                builder.AppendFormat(hiddenFomat,
                                        htmlHelper.Id(name), 
                                        values[i++].ToString(), 
                                        htmlHelper.Name(name)));
            return MvcHtmlString.Create(builder.ToString());
        }
    }

Comme d'autres l'ont suggéré, je suis allé avec l'aide du code html directement au lieu d'utiliser les HtmlHelpers (TextBoxFor, CheckBoxFor, HiddenFor etc.).

Le problème que cette approche est que vous devez mettre le nom et les attributs id sous forme de chaînes. Je voulais garder mes propriétés du modèle fortement typé donc je les HtmlHelpers NameFor et IdFor.

<input type="hidden" name="@Html.NameFor(m => m.Name)" id="@Html.IdFor(m=>m.Name)" value="@Html.AttributeEncode(Model.Name)">

Mise à jour: Voici une extension HtmlHelper pratique

    public static MvcHtmlString MyHiddenFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, object htmlAttributes = null)
    {
        return new MvcHtmlString(
            string.Format(
                @"<input id=""{0}"" type=""hidden"" value=""{1}"" name=""{2}"">",
                helper.IdFor(expression),
                helper.NameFor(expression),
                GetValueFor(helper, expression)
            ));
    }

    /// <summary>
    /// Retrieves value from expression
    /// </summary>
    private static string GetValueFor<TModel, TValue>(HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
    {
        object obj = expression.Compile().Invoke(helper.ViewData.Model);
        string val = string.Empty;
        if (obj != null)
            val = obj.ToString();
        return val;
    }

Vous pouvez alors l'utiliser comme

@Html.MyHiddenFor(m => m.Name)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top