ASP.Net MVC Html.HiddenFor con un valor incorrecto

132

Estoy usando MVC 3 en mi proyecto, y veo un comportamiento muy extraño.

Estoy tratando de crear un campo oculto para un valor particular en mi Modelo, el problema es que, por alguna razón, el valor establecido en el campo no corresponde al valor en el Modelo.

p.ej

Tengo este código, solo como prueba:

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

Creo que ambos campos ocultos tendrían el mismo valor. Lo que hago es establecer el valor en 1 la primera vez que visualizo la Vista, y luego, después del envío, aumento el valor del campo Modelo en 1.

Entonces, la primera vez que renderizo la página, ambos controles tienen el valor 1, pero la segunda vez que los valores renderizados son estos:

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

Como puede ver, el primer valor es correcto, pero el segundo valor parece ser el mismo que la primera vez que visualizo la Vista.

¿Qué me estoy perdiendo? ¿Los * For Html helpers almacenan en caché los valores de alguna manera? Si es así, ¿cómo puedo desactivar este almacenamiento en caché?

Gracias por tu ayuda.

willvv
fuente
Acabo de probar algo más. Si elimino la llamada HiddenFor y dejo solo la llamada Hidden, pero usando el nombre "Paso", también muestra solo el primer valor (1).
willvv
1
sucede en get también
Oren A

Respuestas:

191

Eso es normal y así es como funcionan los asistentes HTML Primero usan el valor de la solicitud POST y luego el valor en el modelo. Esto significa que incluso si modifica el valor del modelo en la acción de su controlador si hay la misma variable en la solicitud POST, su modificación será ignorada y se usará el valor POSTed.

Una posible solución es eliminar este valor del estado del modelo en la acción del controlador que intenta modificar el 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;

Otra posibilidad es escribir un ayudante HTML personalizado que siempre usará el valor del modelo e ignorará los valores POST.

Y otra posibilidad más:

<input type="hidden" name="Step" value="<%: Model.Step %>" />
Darin Dimitrov
fuente
55
Realmente aprecié la publicación de blog de Simon Ince sobre esto. La conclusión que saco de esto es asegurar que su flujo de trabajo sea correcto. Entonces, si ha aceptado un modelo de vista válido y ha hecho algo con él, redirija a una acción de confirmación, incluso si esto también simplemente se retira y muestra un modelo equivalente. Esto significa que tiene un ModelState nuevo. blogs.msdn.com/b/simonince/archive/2010/05/05/… (vinculado desde una publicación que escribí sobre esto hoy: oceanbites.blogspot.com/2011/02/mvc-renders-wrong-value.html )
Lisa
2
Realmente me gusta MVC3, pero este bit es realmente torpe. Espero que lo arreglen en MVC4.
KennyZ
55
Wow, este me hizo ir por bastante tiempo. Básicamente utilicé la primera sugerencia, pero simplemente llamé ModelState.Clear () antes de regresar. Esto parece funcionar muy bien, ¿hay alguna razón para no usar Clear?
Jason
1
El ".Remove" no funcionó para mí. Pero ModelState.Clear () lo hizo justo antes de la devolución en Controller. La escritura personalizada de su Oculto también funcionaría bien. Todo esto sucede porque los desarrolladores no quieren perder sus "valores de formulario" si presionan "enviar" y la base de datos no se guarda correctamente. La mejor solución: no nombrar diferentes campos en la misma página con el mismo nombre / id.
Dexter
1
Para su información, este comportamiento molesto se transmitió gentilmente a ASP.NET Core en caso de que a alguien le preocupara que las cosas mejorarían
John Hargrove
19

Encontré el mismo problema al escribir un asistente que muestra diferentes partes de un modelo más grande en cada paso.
Los datos y / o errores del "Paso 1" se mezclarían con el "Paso 2", etc., hasta que finalmente me di cuenta de que ModelState tenía la "culpa".

Esta fue mi solución simple:

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

return View(model[newPageIndex]);
Peter B
fuente
10
ModelState.Clear()resolvió mi problema con solicitudes POST secuenciales en una situación similar.
Evan Mulawski,
Gracias por la sugerencia ModelState.Clear () Evan. Esta fue una anomalía que nunca he encontrado antes. Tenía varias publicaciones secuenciales ajax.beginform y una de ellas conservaba los valores de una publicación anterior. Depuración de agujero negro. Alguien sabe por qué esto se almacena en caché?
Rob
1

Este código no 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 always (!) lee desde ModelState no el modelo en sí. Y si no encuentra la tecla "Paso", producirá el valor predeterminado para ese tipo de variable, que será 0 en este caso

Aquí está la solución. Lo escribí para mí, pero no me importa compartirlo porque veo que muchas personas están luchando con este travieso ayudante de 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)
            };
        }
    }
}

Luego solo lo usa como de costumbre desde su vista:

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

Vale la pena mencionar que también funciona con colecciones.

Ruslan Georgievskiy
fuente
Esta solución no funcionó completamente. Después de la siguiente publicación, la propiedad está nula en acción
user576510
Bueno, este es el código de producción donde funciona bien. No puedo decir por qué no funciona para usted, pero si ve el campo oculto con el valor correcto representado en la página, no veo una razón obvia por la que no se restauraría en la propiedad del modelo. Sin embargo, si ve un valor de campo oculto incorrecto en la página, esa es otra historia, me gustaría saber en qué circunstancias sucede esto antes de que ocurra lo mismo en mi producción :-) Gracias.
Ruslan Georgievskiy
0

Creo que estoy luchando demasiado con la misma situación, donde uso el mismo estado modelo entre llamadas y cuando modifico una propiedad modelo en el back-end. Sin embargo, no me importa si uso textboxfor u hiddenfor.

Simplemente evito la situación usando scripts de página para almacenar el valor del modelo como una variable js, porque necesito el campo oculto para ese propósito al principio.

No estoy seguro si esto ayuda, pero solo considere ...

abdulkadir pekeroglu
fuente