Frage

Ich habe anscheinend ein Problem mit ASP.NET MVC: Wenn ich mehr als ein Formular auf einer Seite habe, das in jedem den gleichen Namen, aber unterschiedliche Typen (Radio/Hidden/usw.) verwendet, dann, wenn das Bei den ersten Formularbeiträgen (ich wähle zum Beispiel das Optionsfeld „Datum“) habe ich beim erneuten Rendern des Formulars (z. B. als Teil der Ergebnisseite) anscheinend das Problem, dass der Wert des Suchtyps auf den anderen Formularen ausgeblendet ist wird auf den letzten Wert des Optionsfelds (in diesem Fall SearchType.Name) geändert.

Nachfolgend finden Sie ein Beispielformular zur Reduzierung.

<% 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(); %>

Quelle der resultierenden Seite (dies wäre Teil der Ergebnisseite)

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

Kann das bitte noch jemand mit RC1 bestätigen?

Vielleicht liegt es daran, dass ich eine Enumeration verwende.Ich weiß nicht.Ich sollte hinzufügen, dass ich dieses Problem umgehen kann, indem ich „manuelle“ input()-Tags für die ausgeblendeten Felder verwende. Wenn ich jedoch MVC-Tags (<%= Html.Hidden(...) %>) verwende, werden diese durch .NET MVC ersetzt jedes Mal.

Vielen Dank.

Aktualisieren:

Ich habe diesen Fehler heute wieder gesehen.Es scheint, als ob dies zu kurz kommt, wenn Sie eine gepostete Seite zurückgeben und MVC verwenden, um versteckte Formular-Tags mit dem HTML-Helfer festzulegen.Ich habe kontaktiert Phil Haack darüber, weil ich nicht weiß, an wen ich mich sonst wenden soll, und ich glaube nicht, dass dieses Verhalten, wie von David beschrieben, zu erwarten ist.

War es hilfreich?

Lösung

Ja, ist dieses Verhalten zur Zeit von Entwurf. Auch wenn Sie sind explizit Werte einstellen, wenn Sie wieder auf die gleiche URL posten, sehen wir in Modellzustand und den Wert dort verwenden. In der Regel ist dies erlaubt es uns, den Wert, den Sie auf Postbacks vorgelegt anzuzeigen, anstatt den ursprünglichen Wert.

Es gibt zwei mögliche Lösungen:

Lösung 1

Verwenden Sie einen eindeutigen Namen für jedes der Felder. Beachten Sie, dass standardmäßig wir den Namen, den Sie als ID des HTML-Elements angeben,. Es ist ungültig HTML haben mehrere Elemente, die die gleiche ID haben. Also mit eindeutigen Namen ist eine gute Praxis.

Lösung 2

Sie die versteckten Helfer nicht verwenden. Es scheint, wie Sie es wirklich nicht brauchen. Stattdessen könnten Sie dies tun:

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

Natürlich, wie ich über diese mehr denken, die Wert auf ein Postback basiert Wechsel macht Sinn für Textfelder, aber macht weniger Sinn für versteckte Eingänge. Wir können das nicht für v1.0 ändern, aber ich werde es für v2 betrachten. Aber wir brauchen die Auswirkungen einer solchen Änderung durch sorgfältig zu denken.

Andere Tipps

Wie bei anderen würde ich das Model erwartet verwendet werden, um das Modell zu füllen und als wir ausdrücklich das Modell in Ausdrücken in der Ansicht verwenden, sollte es das Modell und nicht das Model verwenden.

Es ist eine Design-Wahl und ich bekomme, warum: Wenn Validierungen fehlschlagen, kann der Eingangswert nicht mit dem Datentyp im Modell seines parseable und Sie wollen immer noch machen, was falsch Wert der Benutzer eingegeben hat, so ist es einfach, sie zu korrigieren.

Das einzige, was ich nicht verstehe, ist: , warum es nicht durch Design, dass das Modell verwendet wird, die durch den Entwickler explizit festgelegt wird und wenn ein Validierungsfehler aufgetreten ist, wird das Model verwendet

Ich habe viele Leute gesehen mit Abhilfen wie

  • ModelState.Clear (): Löscht alle Model Werte, aber grundsätzlich deaktiviert Verwendung von Standard-Validierung in MVC
  • ModelState.Remove ( „somekey“): Wie ModelState.Clear (), aber braucht Mikromanagement von Model Schlüssel, die zu viel Arbeit ist und es fühlt sich nicht richtig mit der Auto-Funktion von MVC zu binden. Man fühlt sich wie 20 Jahre zurück, als wir auch Form und Abfrage-Zeichenfolge-Schlüssel zu verwalten.
  • Rendering HTMLthemselves: zu viel Arbeit, Detail und die HTML-Helper-Methoden mit den zusätzlichen Merkmalen wegwirft. Ein Beispiel:. Ersetzen @ Html.HiddenFor durch m.Name)“id = "@ Html.IdFor (m => m.Name)" value = "@ Html.AttributeEncode (Model.Name)"> Oder ersetzen @Html. DropDownListFor durch ...
  • benutzerdefinierte HTML-Helpers Erstellen eines Standard-MVC HTML-Helfer zu ersetzen, das durch-Design-Problem zu vermeiden. Dies ist ein generischer Ansatz dann Ihre HTML-Rendering, erfordert aber immer noch mehr HTML + MVC Wissen oder decompiling System.Web.Mvc noch alle andere Funktionen bleiben aber Model Vorrang vor Modell.
  • deaktivieren
  • Tragen Sie das POST-REDIRECT-GET-Muster: Das ist in einigen Umgebungen einfach, aber härter in denen, die mit mehr Interaktion / Komplexität. Dieses Muster hat seine Vor- und Nachteile, und Sie sollen wegen einer Auswahl von Model über Modell by-Design nicht gezwungen werden, dieses Muster anzuwenden.

Problem

Also das Problem ist, dass das Modell von Model und in der Ansicht gefüllt wir explizit das Modell zu verwenden. Jeder erwartet, dass der Modellwert (falls es geändert) verwendet werden, es sei denn es einen Fehler bei der Überprüfung ist; dann kann das Model verwendet werden.

Zur Zeit in dem MVC-Helper-Erweiterungen das Model Wert Vorrang vor dem Modellwert bekommt.

Lösung

So ist die eigentliche Lösung für dieses Problem sein soll: für jeden Ausdruck des Modellwert ziehen sollte der Wert Model entfernt werden, wenn kein Fehler für diesen Wert Validierung ist. Wenn es ein Fehler bei der Überprüfung für die Eingangskontrolle ist sollte der Wert Model nicht entfernt werden, und es wird wie gewohnt verwendet werden. Ich denke, das das Problem löst genau, was dann die meisten Abhilfen besser ist.

Der Code ist hier:

    /// <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);
        }
    }

Und dann haben wir unsere eigenen HTML-Helper Erweiterungen todo diese erstellen, bevor die MVC-Erweiterungen Aufruf:

    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);
    }

entfernt Diese Lösung das Problem, aber es nicht erforderlich, dekompilieren, zu analysieren und wieder aufzubauen, was MVC Sie normalerweise anbietet (nicht vergessen auch die Verwaltung von Änderungen über Zeit, Browser-Unterschiede usw.).

Ich denke, die Logik von „Modellwert, es sei denn, Validierungsfehler dann Model“ sollte durch-Design hat. Wenn ja, wäre es nicht so viele Menschen gebissen haben, aber immer noch bedeckt, was MVC todo gedacht war.

Ich bin gerade auf dasselbe Problem gestoßen.HTML-Helfer wie die TextBox()-Priorität für übergebene Werte scheinen sich genau entgegengesetzt zu dem zu verhalten, was ich daraus abgeleitet habe Dokumentation wo steht:

Der Wert des Texteingabeelements.Wenn dieser Wert Nullreferenz ist (nichts in Visual Basic), wird der Wert des Elements aus dem ViewDatadictionary -Objekt abgerufen.Wenn dort kein Wert vorhanden ist, wird der Wert aus dem ModelStatedictionary -Objekt abgerufen.

Ich habe gelesen, dass der Wert, falls übergeben, verwendet wird.Aber beim Lesen der TextBox()-Quelle:

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

scheint darauf hinzudeuten, dass die tatsächliche Reihenfolge genau das Gegenteil von dem ist, was dokumentiert ist.Die tatsächliche Reihenfolge scheint zu sein:

  1. ModelState
  2. Daten anzeigen
  3. Wert (vom Aufrufer an TextBox() übergeben)

Heads-up - dieser Fehler existiert noch in MVC 3. Ich bin mit der Razor-Markup-Syntax (wie das wirklich wichtig ist), aber ich begegnete den gleichen Fehler mit einer foreach-Schleife, die denselben Wert für eine Objekteigenschaft erzeugte jeder Mal.

Dies würde die erwartete behavoir sein - MVC keinen Ansichtszustand oder andere hinter Ihrem Rücken Trick verwenden, um zusätzliche Informationen in Form übergeben, so hat er keine Ahnung, die Sie eingereicht (der Formularname bildet, ist nicht Teil der Daten vorgelegt, nur eine Liste von Namen / Wert-Paaren).

Wenn MVC das Formular zurück macht, es ist einfach zu überprüfen, ob ein eingereichtes Wert mit dem gleichen Namen existiert - wieder, es hat keine Möglichkeit, die zu wissen, ein benannten Wert kam bildet aus, oder auch, welche Art von Kontrolle war es (ob Sie ein Radio, ein Text oder versteckt verwenden, es ist alles nur name = Wert, wenn seine durch HTTP eingereicht).

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);

Beispiel des „Design-Problem“ und eine mögliche workaroud zu reproduzieren. Es gibt keine Abhilfe für die 3 Stunden verloren versuchen, den „Fehler“ zu finden, obwohl ... Beachten Sie, dass dieses "Design" ist nach wie vor in 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);
    }

Wenn Ihr Formular ursprünglich enthält diese: <%= Html.HiddenFor( m => m.ProductId ) %>

Wenn der ursprüngliche Wert von „Namen“ (wenn die Form erbracht wurde) ist „Dummy“, nachdem das Formular abgeschickt wird Sie erwarten, „Dummy (Benutzereinstellung)“ wiedergegeben, um zu sehen. Ohne ModelState.Clear() sehen Sie noch "Dummy" !!!!!!

Die richtige Lösung:

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

Ich fühle mich ist dies nicht ein gutes Design überhaupt, wie jeder mvc Form Entwickler, dass im Auge behalten muss.

Dieses Problem existiert noch in MVC 5, und klar ist es nicht einen Fehler betrachtet, die in Ordnung ist.

Wir finden, dass, obwohl durch Design, das für uns nicht das erwartete Verhalten ist. Vielmehr wollen wir immer der Wert des verborgenen Feldes ähnlich wie andere Arten von Feldern zu arbeiten und nicht besondere behandelt werden, oder seinen Wert aus unerfindlicher Sammlung ziehen (was uns von Viewstate erinnert!).

Einige Befunde (korrekter Wert für uns sind der Modellwert, falsch ist der Modelwert):

  • Html.DisplayFor() zeigt den richtigen Wert (es zieht von Model)
  • Html.ValueFor nicht (es zieht von Model)
  • ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model zieht der richtige Wert

Unsere Lösung ist einfach unsere eigene Erweiterung zu implementieren:

        /// <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));
        }

Dies kann ‚by design‘, aber es ist nicht das, was dokumentiert ist:

Public Shared Function Hidden(  

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

Mitglied von System.Web.Mvc.Html.InputExtensions

     

Zusammenfassung: Gibt einen versteckten Input-Tag

.      

Parameter:
    Htmlhelper: Die HTML-Helfer
.     Name: Das Formular Feldname und System.Web.Mvc.ViewDataDictionary Schlüssel verwendet, um den Wert nachschlagen
.     Wert: Der Wert des versteckten Eingang. Wenn null, schaut auf der System.Web.Mvc.ViewDataDictionary und dann System.Web.Mvc.ModelStateDictionary für den Wert.

Dies scheint, dass nur vorschlagen, wenn der Wert Parameter null ist (oder nicht angegeben) würde an anderer Stelle für einen Wert des Htmlhelper suchen.

In meiner app habe ich eine Form bekommt, wo: html.Hidden ( "remote", True) ist Rendering als <input id="remote" name="remote" type="hidden" value="False" />

Hinweis: Der Wert wird immer übergangen, was im ViewData.ModelState Wörterbuch ist.

Oder bin ich etwas fehlt?

So in MVC 4 das "Design-Problem" immer noch da. Hier ist der Code, den ich zu verwenden, um hatte die richtigen versteckten Werte in einer Sammlung zu setzen, da unabhängig davon, was ich in der Steuerung zu tun, wobei die Ansicht zeigte immer falsche Werte.

OLD 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
}

AKTUALISIERT Code

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!
}

Haben sie dies in MVC 5 fixiert?

Es gibt Abhilfe:

    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());
        }
    }

Wie bereits erwähnt wurde, ging ich mit direkten HTML-Code anstelle der Verwendung der HtmlHelpers (TextBoxFor, CheckBoxFor, HiddenFor etc.).

Das Problem allerdings bei diesem Ansatz ist, dass Sie den Namen und die ID-Attribut als Strings setzen müssen. Ich wollte meine Modell-Eigenschaften halten stark typisierte so habe ich die NameFor und IdFor HtmlHelpers.

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

Update: Hier ist eine handliche Htmlhelper Erweiterung

    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;
    }

Sie können dann verwenden, wie

@Html.MyHiddenFor(m => m.Name)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top