Domanda

Mi sembrano avere un problema con ASP.NET MVC, nel senso che, se ho più di un modulo su una pagina che utilizza lo stesso nome in ogni uno, ma diversi tipi (radio/nascosto/ecc), poi, quando la prima forma di post (ho scelto la 'Data' radio button, per esempio), se il modulo è ri-renderizzati (diciamo come parte della pagina dei risultati), Mi sembra di avere il problema che il valore nascosto di SearchType sulle altre forme è cambiato l'ultimo pulsante di opzione valore (in questo caso, SearchType.Nome).

Qui di seguito è un esempio di modulo per la riduzione scopi.

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

Risultante sorgente della pagina (questo sarebbe parte della pagina dei risultati)

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

Per favore, qualcuno può con RC1 confermare questo?

Forse è perché sto usando un enum.Non so.Devo aggiungere che posso aggirare questo problema utilizzando un 'manuale' (ingresso) tag per i campi nascosti, ma se io uso MVC tag<%= Html.Nascosto(...) %>), .NET MVC li sostituisce ogni tempo.

Molte grazie.

Aggiornamento:

Ho visto questo bug ancora oggi.Sembra che questa colture sua testa quando si restituisce una pagina registrata e MVC set di maschera nascosta tag Html helper.Ho contattato Phil Haack su questo, perché non so dove altro andare, e non credo che questo dovrebbe essere il comportamento previsto, come specificato da David.

È stato utile?

Soluzione

Sì, questo comportamento è attualmente alla progettazione. Anche se si sta impostando in modo esplicito i valori, se si inviano indietro allo stesso URL, guardiamo in stato di modello e utilizzare il valore lì. In generale, questo ci permette di visualizzare il valore hai inviato il postback, piuttosto che il valore originale.

Ci sono due possibili soluzioni:

Soluzione 1

Utilizza nomi univoci per ciascuno dei campi. Si noti che di default si usa il nome specificato come l'id dell'elemento HTML. E 'valida HTML per avere più elementi hanno lo stesso ID. Quindi, utilizzando nomi univoci è buona pratica.

Soluzione 2

Non utilizzare l'helper Nascosto. Sembra che davvero non hanno bisogno. Invece, si potrebbe fare questo:

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

Naturalmente, come penso a questo di più, cambiando il valore sulla base di un postback ha senso per Caselle di testo, ma rende meno senso per gli ingressi nascosti. Non possiamo cambiare questo per v1.0, ma io considero per v2. Ma abbiamo bisogno di pensare attentamente le implicazioni di un tale cambiamento.

Altri suggerimenti

Come per gli altri mi sarei aspettato il ModelState da utilizzare per riempire il modello e come si usa esplicitamente il modello nelle espressioni nella vista, si dovrebbe utilizzare il modello e non ModelState.

E 'una scelta di progettazione e io capisco il perché: se convalide esito negativo, il valore di ingresso potrebbe non essere analizzabile per il tipo di dati nel modello e si vuole ancora rendere qualsiasi valore sbagliato l'utente ha digitato, quindi è facile per correggerlo.

L'unica cosa che non capisco è: perché non è vero dal design che il modello è usato, che è impostata in modo esplicito dal committente, se si è verificato un errore di convalida, il ModelState viene utilizzato

Ho visto molte persone che utilizzano soluzioni come

  • ModelState.Clear (): cancella tutti i valori ModelState, ma disabilita fondamentalmente utilizzo di convalida default in MVC
  • ModelState.Remove ( "SomeKey"): come ModelState.Clear (), ma ha bisogno di microgestione di chiavi ModelState, che è troppo lavoro e non si sente a destra con l'auto vincolante funzione da MVC. Si sente come 20 anni fa, quando stavamo anche gestendo Forma e QueryString chiavi.
  • Rendering HTMLthemselves: troppo lavoro, dettagli e getta via i metodi di supporto HTML con le funzioni aggiuntive. Un esempio:. Sostituire @ Html.HiddenFor da m.Name)" id = "@ Html.IdFor (m => m.Name)" value = "@ Html.AttributeEncode (Model.Name)"> o sostituire @Html. DropDownListFor da ...
  • Crea Helpers HTML personalizzato per sostituire di default MVC aiutanti HTML per evitare il problema-design. Questo è un approccio più generico quindi il rendering HTML, ma richiede ancora di più HTML + conoscenza MVC o decompilazione System.Web.Mvc per tenere ancora tutte le altre caratteristiche, ma disabilitare ModelState precedenza sul modello.
  • Applicare il modello POST-REDIRECT-GET: questo è facile in alcuni ambienti, ma più difficile in quelli con una maggiore interazione / complessità. Questo modello ha i suoi pro e contro e che non dovrebbe essere costretto ad applicare questo schema a causa di una scelta di by-design della ModelState su Modello.

Problema

Quindi, il problema è che il modello è riempito da ModelState e nella vista abbiamo impostato in modo esplicito di utilizzare il modello. Ognuno si aspetta che il valore del modello (nel caso in cui è cambiato) da utilizzare, a meno che non ci sia un errore di convalida; poi il ModelState può essere utilizzato.

Attualmente nel Helper MVC estensioni del valore ModelState ottiene la precedenza sul valore del modello.

Soluzione

Quindi, la correzione effettivo per questo problema dovrebbe essere: per ogni espressione di tirare il valore del modello il valore ModelState deve essere rimosso se non v'è alcun errore di convalida per quel valore. Se c'è un errore di convalida per questo controllo di input il valore ModelState non deve essere rimosso e verrà utilizzato come normale. Penso che questo risolve il problema esattamente, che è meglio allora la maggior parte soluzioni alternative.

Il codice è qui:

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

E poi creiamo le nostre estensioni di supporto HTML todo questo prima di chiamare le estensioni 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);
    }

Questa soluzione elimina il problema, ma non richiede di decompilare, analizzare e ricostruire ciò che MVC sta offrendo normalmente (non dimenticare anche la gestione cambiamenti nel tempo, le differenze del browser, ecc.).

Credo che la logica del "valore modello, a meno che l'errore di convalida poi ModelState" che avrebbe dovuto essere by-design. Se fosse, non sarebbe morso così tante persone, ma ancora coperte quello che MVC è stato inteso todo.

Ho appena imbattuto nello stesso problema.Helper Html come TextBox() precedenza per i valori passati sembrano comportarsi esattamente di fronte a quello che ho dedotto dal Documentazione dove si dice:

Il valore di testo di input.Se questo valore è null riferimento (Nothing in Visual Basic), il valore dell'elemento viene recuperato da il ViewDataDictionary oggetto.Se il valore non esiste, il valore è recuperato dal ModelStateDictionary oggetto.

Per me, ho letto che il valore, se approvata, viene utilizzato.Ma la lettura di TextBox() fonte:

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

sembra indicare che l'ordine attuale è l'esatto opposto di ciò che è documentato.Reale ordine sembra essere:

  1. ModelState
  2. ViewData
  3. Valore (passato nella TextBox() dal chiamante)

Heads-up - questo bug esiste ancora in MVC 3. sto usando la sintassi di markup Razor (come che conta davvero), ma ho incontrato lo stesso problema con un ciclo foreach che ha prodotto lo stesso valore per ogni proprietà di un oggetto tempo unico.

Questa sarebbe la behavoir previsto - MVC non utilizza un viewstate o altro dietro i vostri trucchi schiena per passare informazioni supplementari sotto forma, in modo che non ha idea di quale forma hai inviato (il nome del modulo non è parte dei dati presentato, solo un elenco di coppie nome / valore).

Quando MVC rende il modulo posteriore, è semplicemente controllando per vedere se un valore inviato con lo stesso nome esiste - ancora una volta, non ha modo di sapere quale forma un valore di nome è venuto da, o anche quello che tipo di controllo è stato (se si utilizza una radio, testo o nascosto, è tutto solo nome = valore quando il suo inviato tramite 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);

Esempio per riprodurre il "problema di progettazione", e una possibile workaroud. Non v'è alcuna soluzione per le 3 ore perse cercando di trovare il "bug", anche se ... Si noti che questo "disegno" è ancora 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);
    }

Se il modulo contiene originariamente questo: <%= Html.HiddenFor( m => m.ProductId ) %>

Se il valore originale di "Nome" (quando il modulo è stato reso) è "fittizio", dopo che il modulo è presentato che ci si aspetta di vedere "fittizio (set utente)" resa. Senza ModelState.Clear() ci si può comunque vedere "fittizio" !!!!!!

soluzione corretta:

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

Mi sento questo non è un buon design a tutti, come ogni forma di sviluppo MVC ha bisogno di tenere a mente.

Questo problema esiste ancora in MVC 5, e chiaramente non è considerato un bug che va bene.

Stiamo constatando che, anche se in base alla progettazione, questo non è il comportamento previsto per noi. Piuttosto vogliamo sempre il valore del campo nascosto di operare in modo simile ad altri tipi di campi e non essere trattati speciale, o tirare il suo valore da qualche raccolta oscura (che ci ricorda ViewState!).

Alcuni reperti (valore corretto per noi è il valore del modello, è errato il valore ModelState):

  • Html.DisplayFor() visualizza il valore corretto (tira da Model)
  • Html.ValueFor non (tira da ModelState) fa
  • ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model tira il valore corretto

La nostra soluzione è quella di implementare semplicemente il nostro proprio interno:

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

Questo può essere 'by design', ma non è ciò che è documentato:

Public Shared Function Hidden(  

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

Membro del Sistema.Web.Mvc.Html.InputExtensions

Sommario:Restituisce un input nascosto tag.

Parametri:
htmlHelper:HTML helper.
nome:Il nome del campo di modulo e di Sistema.Web.Mvc.ViewDataDictionary chiave utilizzate per la ricerca del valore.
valore:Il valore dell'input nascosto.Se il valore è null, guarda il Sistema.Web.Mvc.ViewDataDictionary e quindi del Sistema.Web.Mvc.ModelStateDictionary per il valore.

Questo sembra suggerire che SOLO quando il valore del parametro è null o non specificato) il HtmlHelper guardare altrove per un valore di.

Nella mia applicazione, ho un form in cui:html.Nascosto("remote", True) è il rendering come <input id="remote" name="remote" type="hidden" value="False" />

Nota il valore è sempre più colpita da quello che è il Simbolo.ModelState dizionario.

O mi manca qualcosa?

Quindi, in MVC 4 del "problema di progettazione" ancora lì. Ecco il codice che ho dovuto utilizzare al fine di impostare i valori nascosti corretti in una collezione dal indipendentemente da quello che faccio nel controller, la vista sempre mostrato valori non corretti.

vecchio codice

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
}

codice aggiornato

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

Si sono risolti in questo MVC 5?

Non c'è soluzione:

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

Come altri hanno suggerito, sono andato con l'utilizzo di codice html diretta invece di utilizzare i HtmlHelpers (TextBoxFor, CheckBoxFor, HiddenFor ecc).

Il problema anche se con questo approccio è che è necessario mettere il nome e l'id attributi come stringhe. Ho voluto mantenere le mie proprietà modello fortemente tipizzato quindi ho usato le HtmlHelpers NameFor e IdFor.

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

Aggiornamento: Ecco un'estensione HtmlHelper a portata di mano

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

È quindi possibile utilizzarlo come

@Html.MyHiddenFor(m => m.Name)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top