ASP.net MVC Html.HiddenFor com valor errado

132

Estou usando o MVC 3 no meu projeto e estou vendo um comportamento muito estranho.

Estou tentando criar um campo oculto para um valor específico no meu modelo, o problema é que, por algum motivo, o valor definido no campo não corresponde ao valor no modelo.

por exemplo

Eu tenho esse código, apenas como um teste:

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

Eu pensaria que os dois campos ocultos teriam o mesmo valor. O que faço é definir o valor como 1 na primeira vez que exibir a Visualização e, após o envio, aumente o valor do campo Modelo em 1.

Portanto, na primeira vez em que renderizo a página, os dois controles têm o valor 1, mas na segunda vez em que os valores são:

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

Como você pode ver, o primeiro valor está correto, mas o segundo valor parece ser o mesmo da primeira vez que exibi a tela.

o que estou perdendo? Os auxiliares do * For Html estão armazenando em cache os valores de alguma forma? Em caso afirmativo, como posso desativar esse cache?

Obrigado pela ajuda.

willvv
fonte
Acabei de testar outra coisa. Se eu remover a chamada HiddenFor e permitir apenas a chamada Hidden, mas usando o nome "Step", ele também renderiza apenas o primeiro valor (1).
willvv
1
acontece em get as well
Oren A

Respostas:

191

Isso é normal e é assim que os auxiliares de HTML funcionam. Eles primeiro usam o valor da solicitação POST e depois o valor no modelo. Isso significa que, mesmo que você modifique o valor do modelo em sua ação do controlador, se houver a mesma variável na solicitação POST, sua modificação será ignorada e o valor POSTed será usado.

Uma solução possível é remover esse valor do estado do modelo na ação do controlador que está tentando modificar o valor:

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

Outra possibilidade é escrever um auxiliar HTML personalizado que sempre usará o valor do modelo e ignorará os valores POST.

E ainda outra possibilidade:

<input type="hidden" name="Step" value="<%: Model.Step %>" />
Darin Dimitrov
fonte
5
Eu realmente gostei da publicação do blog de Simon Ince sobre isso. A conclusão que tirei é para garantir que seu fluxo de trabalho esteja correto. Portanto, se você aceitou um modelo de exibição válido e fez alguma coisa com ele, redirecione para uma ação de confirmação, mesmo que isso também simplesmente recupere e exiba um modelo equivalente. Isso significa que você tem um ModelState novo. blogs.msdn.com/b/simonince/archive/2010/05/05/… (linkado de um post que escrevi sobre isso hoje: oceanbites.blogspot.com/2011/02/mvc-renders-wrong-value.html )
Lisa
2
Eu realmente gosto do MVC3, mas esse pouco é muito complicado. Espero que eles consertem isso no MVC4.
precisa saber é o seguinte
5
Uau, esse aqui me fez sair por um bom tempo. Basicamente, usei a primeira sugestão, mas chamei ModelState.Clear () antes de retornar. Isso parece funcionar muito bem. Existe algum motivo para não usar o Clear?
Jason
1
O ".Remove" não funcionou para mim. Mas ModelState.Clear () fez logo antes do retorno no Controller. A escrita personalizada do seu Hidden também funcionaria bem. Isso tudo acontece porque os desenvolvedores não querem perder seus "valores de formulário" se pressionar "enviar" e o banco de dados não salvar corretamente. Melhor solução: não nomeie campos diferentes na mesma página com o mesmo nome / ID.
Dexter
1
FYI esse comportamento irritante foi graciosamente transportados para ASP.NET núcleo no caso de alguém estava preocupado as coisas iriam melhorar
John Hargrove
19

Encontrei o mesmo problema ao escrever um Assistente que mostra partes diferentes de um modelo maior a cada etapa.
Os dados e / ou erros do "Passo 1" seriam misturados ao "Passo 2", etc., até que finalmente percebi que o ModelState era o 'culpado'.

Esta foi a minha solução simples:

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

return View(model[newPageIndex]);
Peter B
fonte
10
ModelState.Clear()resolvi meu problema com solicitações sequenciais do POST em uma situação semelhante.
Evan Mulawski
Obrigado pela dica de ModelState.Clear () Evan. Esta foi uma anomalia que eu nunca encontrei antes. Eu tinha várias postagens sequenciais do ajax.beginform e uma delas estava retendo valores de uma postagem anterior. Depurando o buraco negro. Alguém sabe por que isso fica armazenado em cache?
Rob
1

Este código não irá funcionar

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

... porque HiddenFor sempre (!) lê ModelState e não o modelo em si. E se não encontrar a tecla "Step", ela produzirá o padrão para o tipo de variável que será 0 neste caso

Aqui está a solução. Eu escrevi para mim mesmo, mas não me importo de compartilhá-lo, porque eu vejo muitas pessoas lutando com esse ajudante malvado do 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)
            };
        }
    }
}

Depois, basta usá-lo como de costume dentro da visualização:

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

Vale mencionar que também trabalha com coleções.

Ruslan Georgievskiy
fonte
esta solução não funcionou totalmente. Após a próxima postagem, a propriedade back é nula em Ação
user576510 17/11
Bem, este é o código da produção em que funciona bem. Não sei dizer por que ele não funciona para você, mas se você vir o campo oculto com o valor correto renderizado na página, não vejo motivo óbvio para que ele não seja restaurado na propriedade do modelo. Se você vir um valor de campo oculto errado na página - isso é outra história, eu gostaria de saber sob quais circunstâncias isso acontece antes que o mesmo aconteça na minha produção :-) Obrigado.
Ruslan Georgievskiy
0

Estou com muita dificuldade com a mesma situação em que penso, onde uso o mesmo estado de modelo entre as chamadas e quando altero uma propriedade de modelo no back-end. No entanto, não importa para mim, se eu usar textboxfor ou hiddenfor.

Eu apenas ignoro a situação usando scripts de página para armazenar o valor do modelo como uma variável js, porque preciso do campo oculto para esse fim no início.

Não tenho certeza se isso ajuda, mas considere ..

abdulkadir pekeroglu
fonte