ASP.Net MVC Html.HiddenFor z nieprawidłową wartością

132

Używam MVC 3 w moim projekcie i widzę bardzo dziwne zachowanie.

Próbuję utworzyć ukryte pole dla określonej wartości w moim modelu, problem polega na tym, że z jakiegoś powodu wartość ustawiona w polu nie odpowiada wartości w modelu.

na przykład

Mam taki kod jako test:

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

Myślę, że oba ukryte pola miałyby taką samą wartość. Robię to, ustawiając wartość na 1, gdy po raz pierwszy wyświetlam Widok, a następnie po przesłaniu zwiększam wartość pola Model o 1.

Tak więc przy pierwszym renderowaniu strony obie kontrolki mają wartość 1, ale przy drugim renderowaniu wartości są następujące:

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

Jak widać, pierwsza wartość jest poprawna, ale druga wartość wydaje się być taka sama, jak przy pierwszym wyświetleniu widoku.

czego mi brakuje? Czy pomocnicy * For Html buforują wartości w jakiś sposób? Jeśli tak, jak mogę wyłączyć to buforowanie ?.

Dzięki za pomoc.

willvv
źródło
Właśnie przetestowałem coś innego. Jeśli usunę wywołanie HiddenFor i pozwolę tylko na wywołanie Hidden, ale używając nazwy „Step”, wyrenderuje również tylko pierwszą wartość (1).
willvv
1
zdarza się również w get
Oren A

Odpowiedzi:

191

To normalne i tak działają pomocnicy HTML. Najpierw używają wartości żądania POST, a następnie wartości w modelu. Oznacza to, że nawet jeśli zmodyfikujesz wartość modelu w akcji kontrolera, jeśli w żądaniu POST znajduje się ta sama zmienna, modyfikacja zostanie zignorowana i zostanie użyta wartość POST.

Jednym z możliwych obejść jest usunięcie tej wartości ze stanu modelu w akcji kontrolera, która próbuje zmodyfikować wartość:

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

Inną możliwością jest napisanie niestandardowego pomocnika HTML, który zawsze będzie używał wartości modelu i ignorował wartości POST.

I jeszcze jedna możliwość:

<input type="hidden" name="Step" value="<%: Model.Step %>" />
Darin Dimitrov
źródło
5
Naprawdę doceniam wpis na blogu Simona Ince na ten temat. Wniosek, jaki z tego wyciągam, to upewnienie się, że przepływ pracy jest prawidłowy. Więc jeśli zaakceptowałeś prawidłowy model widoku i zrobiłeś coś z nim, przekieruj do akcji potwierdzającej, nawet jeśli to również po prostu wróci i wyświetli równoważny model. Oznacza to, że masz nowy ModelState. blogs.msdn.com/b/simonince/archive/2010/05/05/… (link z postu, który napisałem dzisiaj: oceanbites.blogspot.com/2011/02/mvc-renders-wrong-value.html )
Lisa
2
Bardzo lubię MVC3, ale ten kawałek jest naprawdę niezgrabny. Mam nadzieję, że naprawią to w MVC4.
KennyZ
6
Wow, ten był dla mnie dość długi. Zasadniczo użyłem pierwszej sugestii, ale przed powrotem wywołałem ModelState.Clear (). Wygląda na to, że działa świetnie. Czy jest jakiś powód, aby nie używać Clear?
Jason
1
„.Remove” nie działa dla mnie. Ale ModelState.Clear () zrobił tuż przed powrotem w kontrolerze. Niestandardowe pisanie ukrytego również działałoby dobrze. Dzieje się tak, ponieważ programiści nie chcą stracić swoich „wartości formularza”, jeśli naciśną „wyślij”, a baza danych nie zapisze poprawnie. Najlepsze rozwiązanie: nie nazywaj różnych pól na tej samej stronie tą samą nazwą / identyfikatorem.
Dexter
2
FYI to irytujące zachowanie zostało łaskawie przeniesione do ASP.NET Core na wypadek, gdyby ktoś martwił się, że sytuacja się poprawi
John Hargrove
19

Napotkałem ten sam problem podczas pisania kreatora, który na każdym kroku pokazuje różne części większego modelu.
Dane i / lub błędy z „kroku 1” pomieszałyby się z „krokiem 2”, itd., Aż w końcu zdałem sobie sprawę, że „winę” ponosi ModelState.

To było moje proste rozwiązanie:

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

return View(model[newPageIndex]);
Peter B.
źródło
10
ModelState.Clear()rozwiązałem mój problem z kolejnymi żądaniami POST w podobnej sytuacji.
Evan Mulawski
Dzięki za wskazówkę ModelState.Clear () Evan. To była anomalia, której nigdy wcześniej nie spotkałem. Miałem kilka kolejnych postów ajax.beginform, a jeden z nich zachowywał wartości z poprzedniego postu. Debugowanie czarnej dziury. Czy ktoś wie, dlaczego to jest buforowane?
Rob
1

Ten kod nie będzie działał

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

... ponieważ HiddenFor zawsze (!) czyta z ModelState, a nie sam model. A jeśli nie znajdzie klucza „Step”, wygeneruje wartość domyślną dla tego typu zmiennej, która w tym przypadku będzie wynosić 0

Oto rozwiązanie. Napisałem to dla siebie, ale nie mam nic przeciwko udostępnieniu, ponieważ widzę, że wiele osób zmaga się z tym niegrzecznym pomocnikiem 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)
            };
        }
    }
}

Następnie po prostu używaj go jak zwykle z poziomu widoku:

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

Warto wspomnieć, że działa również z kolekcjami.

Ruslan Georgievskiy
źródło
to rozwiązanie nie w pełni działało. Po następnym wpisie właściwość w akcji ma wartość null
user576510
Cóż, to jest kod z produkcji, gdzie działa dobrze. Nie potrafię powiedzieć, dlaczego to nie działa, ale jeśli widzisz ukryte pole z poprawną wartością wyrenderowaną na stronie, nie widzę oczywistego powodu, dla którego nie zostałoby przywrócone do właściwości modelu. Jeśli jednak zauważysz niewłaściwą wartość pola ukrytego na stronie - to inna historia, bardzo chciałbym wiedzieć, w jakich okolicznościach to się dzieje, zanim to samo stanie się na mojej produkcji :-) Dziękuję.
Ruslan Georgievskiy
0

Myślę, że zbyt zmagam się z taką samą sytuacją, w której używam tego samego stanu modelu między wywołaniami i kiedy zmieniam właściwość modelu na zapleczu. Chociaż nie ma dla mnie znaczenia, czy używam textboxfor, czy hiddenfor.

Po prostu omijam tę sytuację, używając skryptów strony do przechowywania wartości modelu jako zmiennej js, ponieważ na początku potrzebuję do tego celu hiddenfield.

Nie jestem pewien, czy to pomoże, ale zastanów się ...

abdulkadir pekeroglu
źródło