ASP.Net MVC Html.HiddenFor con valore errato


132

Sto usando MVC 3 nel mio progetto e sto riscontrando un comportamento molto strano.

Sto cercando di creare un campo nascosto per un determinato valore sul mio modello, il problema è che per qualche motivo il valore impostato sul campo non corrisponde al valore nel modello.

per esempio

Ho questo codice, proprio come un test:

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

Penserei che entrambi i campi nascosti avrebbero lo stesso valore. Quello che faccio è impostare il valore su 1 la prima volta che visualizzo la vista, quindi dopo l'invio aumento il valore del campo Modello di 1.

Quindi, la prima volta che eseguo il rendering della pagina entrambi i controlli hanno il valore 1, ma la seconda volta i valori renderizzati sono questi:

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

Come puoi vedere, il primo valore è corretto, ma il secondo valore sembra essere lo stesso della prima volta che visualizzo la vista.

Cosa mi sto perdendo? Gli helper * For Html memorizzano nella cache i valori in qualche modo? In tal caso, come posso disabilitare questa memorizzazione nella cache ?.

Grazie per l'aiuto.


Ho appena provato qualcos'altro. Se rimuovo la chiamata HiddenFor e lascio solo la chiamata Hidden, ma usando il nome "Step", viene visualizzato anche solo il primo valore (1).
willvv

1
succede anche in get
Oren Un

Risposte:


191

È normale ed è così che funzionano gli helper HTML. Utilizzano innanzitutto il valore della richiesta POST e successivamente il valore nel modello. Ciò significa che anche se si modifica il valore del modello nell'azione del controller se è presente la stessa variabile nella richiesta POST, la modifica verrà ignorata e verrà utilizzato il valore POST.

Una possibile soluzione alternativa è rimuovere questo valore dallo stato del modello nell'azione del controller che sta tentando di modificare il valore:

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

Un'altra possibilità è quella di scrivere un helper HTML personalizzato che utilizzerà sempre il valore del modello e ignorerà i valori POST.

E ancora un'altra possibilità:

<input type="hidden" name="Step" value="<%: Model.Step %>" />

5
Ho davvero apprezzato il post sul blog di Simon Ince a riguardo. La conclusione che ne traggo è di garantire che il flusso di lavoro sia corretto. Quindi, se hai accettato un modello di visualizzazione valido e hai fatto qualcosa con esso, reindirizza a un'azione di conferma, anche se questo semplicemente riprende e visualizza un modello equivalente. Questo significa che hai un nuovo ModelState. blogs.msdn.com/b/simonince/archive/2010/05/05/… (collegato da un post che ho scritto oggi: oceanbites.blogspot.com/2011/02/mvc-renders-wrong-value.html )
Lisa

2
Mi piace molto MVC3, ma questo pezzo è davvero goffo. Spero che lo risolvano in MVC4.
KennyZ,

5
Wow, questo mi ha fatto andare per un bel po '. In pratica ho usato il primo suggerimento, ma ho appena chiamato ModelState.Clear () prima di tornare. Questo sembra funzionare alla grande, c'è qualche motivo per non usare Clear?
Jason,

1
".Remove" non ha funzionato per me. Ma ModelState.Clear () ha fatto proprio prima del ritorno in Controller. Anche la scrittura personalizzata di Hidden funzionerebbe bene. Tutto ciò accade perché gli sviluppatori non vogliono perdere i loro "valori di modulo" se premono "Invia" e il DB non viene salvato correttamente. Soluzione migliore: non nominare campi diversi nella stessa pagina con lo stesso nome / ID.
Dexter,

1
Cordiali saluti, questo fastidioso comportamento è stato gentilmente trasferito su ASP.NET Core nel caso in cui qualcuno fosse preoccupato che le cose andrebbero meglio
John Hargrove,

19

Ho riscontrato lo stesso problema durante la scrittura di una procedura guidata che mostra parti diverse di un modello più grande ad ogni passaggio.
I dati e / o gli errori del "Passaggio 1" sarebbero confusi con il "Passaggio 2", ecc., Fino a quando non mi sarei finalmente reso conto che ModelState era da biasimare.

Questa era la mia soluzione semplice:

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

return View(model[newPageIndex]);

10
ModelState.Clear()risolto il mio problema con richieste POST sequenziali in una situazione simile.
Evan Mulawski,

Grazie per il suggerimento ModelState.Clear () Evan. Questa era un'anomalia che non avevo mai visto prima. Avevo diversi post sequenziali ajax.beginform e uno di questi stava mantenendo i valori di un post precedente. Debug buco nero. Qualcuno sa perché questo viene memorizzato nella cache?
Rob

1

Questo codice non funzionerà

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

... perché HiddenFor legge sempre (!) da ModelState non il modello stesso. E se non trova il tasto "Step" produrrà il valore predefinito per quel tipo di variabile che sarà 0 in questo caso

Ecco la soluzione L'ho scritto per me stesso ma non dispiace condividerlo perché vedo che molte persone stanno lottando con questo giocherellona aiutante nascosto.

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

Quindi lo usi come al solito dalla tua vista:

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

Vale la pena ricordare che funziona anche con le collezioni.


questa soluzione non ha funzionato completamente. Dopo il prossimo post indietro la proprietà è nulla in Azione
user576510

Bene, questo è il codice dalla produzione in cui funziona bene. Non so perché non funzioni per te, ma se vedi il campo nascosto con il valore corretto reso nella pagina non vedo alcun motivo ovvio per cui non venga ripristinato nella proprietà del modello. Se vedi un valore di campo nascosto sbagliato nella pagina - questa è un'altra storia, sarei molto desideroso di sapere in quali circostanze ciò accade prima che lo stesso accada sulla mia produzione :-) Grazie.
Ruslan Georgievskiy,

0

Sono troppo alle prese con la stessa situazione che penso, in cui utilizzo lo stesso stato del modello tra le chiamate e quando modifico una proprietà del modello nel backend. Tuttavia, per me non importa se uso textboxfor o hiddenfor.

Ho semplicemente aggirato la situazione usando gli script di pagina per memorizzare il valore del modello come variabile js, perché all'inizio ho bisogno del campo nascosto per quello scopo.

Non sono sicuro se questo aiuta, ma considera solo ..

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.