Pregunta

Parece que tengo un problema con ASP.NET MVC en el sentido de que, si tengo más de un formulario en una página que usa el mismo nombre en cada uno, pero como tipos diferentes (radio/oculto/etc), entonces, cuando el publicaciones del primer formulario (elijo el botón de opción 'Fecha', por ejemplo), si el formulario se vuelve a representar (por ejemplo, como parte de la página de resultados), parece que tengo el problema de que el valor oculto de SearchType en los otros formularios se cambia al último valor del botón de opción (en este caso, SearchType.Name).

A continuación se muestra un formulario de ejemplo para fines de reducción.

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

Fuente de la página resultante (esto sería parte de la página de resultados)

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

¿Alguien más con RC1 puede confirmar esto?

Tal vez sea porque estoy usando una enumeración.No sé.Debo agregar que puedo evitar este problema usando etiquetas de entrada () 'manuales' para los campos ocultos, pero si uso etiquetas MVC (<%= Html.Hidden(...) %>), .NET MVC las reemplaza cada vez.

Muchas gracias.

Actualizar:

He vuelto a ver este error hoy.Parece que esto aparece cuando devuelves una página publicada y usas etiquetas de formulario ocultas establecidas en MVC con el asistente HTML.he contactado Phil Haack sobre esto, porque no sé a quién más acudir, y no creo que este sea el comportamiento esperado como lo especifica David.

¿Fue útil?

Solución

Sí, este comportamiento es por diseño actualmente. A pesar de que se está configurando valores explícitamente, si usted pone de nuevo a la misma URL, nos fijamos en el modelo de estado y utilizar el valor allí. En general, esto nos permite visualizar el valor que indicó en la devolución de datos, en lugar de su valor original.

Hay dos soluciones posibles:

Solución 1

Utilice nombres únicos para cada uno de los campos. Tenga en cuenta que por defecto se utiliza el nombre que especifique que el ID del elemento HTML. Es HTML no válido tener múltiples elementos tienen el mismo identificador. Así, utilizando nombres únicos es una buena práctica.

Solución 2

No utilice el ayudante Oculto. Parece como si realmente no lo necesita. En su lugar, usted puede hacer esto:

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

Por supuesto, cuando pienso en esto más, cambiando el valor basado en una devolución de datos tiene sentido para los cuadros de texto, pero tiene menos sentido para las entradas ocultas. No podemos cambiar esto para v1.0, pero voy a tener en cuenta para v2. Pero tenemos que pensar detenidamente las implicaciones de tal cambio.

Otros consejos

Lo mismo que los demás me habría esperado que el ModelState que se utilizará para rellenar el modelo y como se utiliza explícitamente el modelo de expresiones en la vista, se debe utilizar el modelo y no ModelState.

Es una opción de diseño y yo no entiendo por qué: si validaciones fallan, el valor de entrada puede que no sea analizable para el tipo de datos en el modelo y que todavía quiere hacer cualquier valor equivocado que el usuario escribió, por lo que es fácil de corregirlo.

Lo único que no entiendo es: Por qué no es por diseño que se utiliza el Modelo, el cual se establece explícitamente por el promotor y si se ha producido un error de validación, se utiliza el ModelState

He visto a muchas personas que utilizan soluciones como

  • ModelState.Clear (): Borra todos los valores ModelState, pero básicamente desactiva el uso de la validación predeterminada en MVC
  • ModelState.Remove ( "SomeKey"): Igual que ModelState.Clear (), pero necesita la microgestión de llaves ModelState, que es demasiado trabajo y que no se siente bien con la función de auto-unión de MVC. Se siente como 20 años atrás, cuando estábamos también gestionar claves Forma y cadena de consulta.
  • Rendering HTMLthemselves: demasiado trabajo, el detalle y tira a la basura los métodos de ayuda HTML con las características adicionales. Un ejemplo:. @ Vuelva Html.HiddenFor por m.Name)" id = "@ Html.IdFor (m => m.Name)" value = "@ Html.AttributeEncode (Model.Name)"> o reemplazar @Html. DropDownListFor por ...
  • Crear ayudantes HTML personalizadas para reemplazar defecto MVC HTML ayudantes para evitar el problema mediante el diseño. Este es un enfoque más genérico a continuación la prestación de su HTML, pero todavía requiere más conocimiento de HTML + MVC o descompilación System.Web.Mvc a seguir manteniendo todas las demás características, pero desactivar ModelState prioridad sobre modelo.
  • Aplicar el patrón de POST-REDIRECT-GET: esto es fácil en algunos entornos, pero más difícil de los que tienen una mayor interacción / complejidad. Este patrón tiene sus pros y contras y no debe ser obligado a aplicar este modelo debido a una elección por diseño de ModelState sobre el modelo.

Problema:

Así que la cuestión es que el modelo se llena desde ModelState y en la vista nos propusimos explícitamente para utilizar el modelo. Todo el mundo espera que el valor de modelo (en caso de que cambió) que se utilizará, a menos que haya un error de validación; entonces el ModelState se puede utilizar.

Actualmente en el Ayudante MVC extensiones del valor ModelState obtiene prioridad sobre el valor de modelo.

Solución

Así que la solución real a este problema debe ser: para cada expresión para tirar el valor Modelo ModelState el valor debe ser eliminado si no hay error de validación para ese valor. Si hay un error de validación para que el control de entrada el valor ModelState no debe ser removido y se va a utilizar como normal. Creo que esto resuelve el problema exactamente, lo que es mejor que la mayoría de las soluciones.

El código está aquí:

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

Y a continuación, creamos nuestras propias extensiones Helper HTML TODO esto antes de llamar a las extensiones 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);
    }

Esta solución elimina el problema, pero no requiere que usted pueda descomponer, analizar y reconstruir lo MVC está ofreciendo normalmente (no se olvide maneja también los cambios a través del tiempo, las diferencias de navegadores, etc.).

Creo que la lógica de "Valor Modelo, salvo error de validación continuación ModelState" que debería haber sido por diseño. Si lo fuera, no habría mordido a tanta gente, pero todavía cubierto lo que se pretendía MVC TODO.

Me encontré con el mismo problema.Los ayudantes de HTML como la precedencia de TextBox() para los valores pasados ​​parecen comportarse exactamente de manera opuesta a lo que deduje del Documentación En donde dice:

El valor del elemento de entrada de texto.Si este valor es una referencia nula (Nada en Visual Basic), el valor del elemento se recupera de el objeto ViewDataDictionary.Si no existe ningún valor allí, el valor es recuperado del objeto ModelStateDictionary.

Para mí, leí que se usa el valor, si se pasa.Pero leyendo la fuente TextBox():

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

Parece indicar que el orden real es exactamente lo contrario de lo que está documentado.El orden real parece ser:

  1. Estado del modelo
  2. Ver datos
  3. Valor (pasado a TextBox() por la persona que llama)

El cara a cara - este error sigue existiendo en MVC 3. Estoy usando la sintaxis de marcado de afeitar (como lo que realmente importa), pero me encontré con el mismo error con un bucle foreach que produjo el mismo valor para un objeto propiedad cada una sola vez.

Esta sería la esperada behavoir - MVC no utiliza un estado de vista u otro detrás de la espalda trucos para pasar información adicional en la forma, por lo que no tiene idea de qué forma ha enviado (el nombre del formulario no es parte de los datos están, sólo una lista de pares de nombre / valor).

Cuando MVC hace que el formulario de vuelta, es simplemente comprobando si existe un valor presentado con el mismo nombre - de nuevo, no tiene forma de saber que forman un valor denominado vino, o incluso qué tipo de control que era (si se utiliza un radio, texto o escondido, todo es sólo nombre = valor cuando su presentarse a través de 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);

Ejemplo para reproducir el "problema de diseño", y una posible workaroud. No hay ninguna solución para las 3 horas perdidas tratando de encontrar el "error", aunque ... Tenga en cuenta que este "diseño" se encuentra todavía en 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);
    }

Si el formulario contiene un principio esto: <%= Html.HiddenFor( m => m.ProductId ) %>

Si el valor original de "Nombre" (cuando se dictó la forma) es "ficticia", después de que el formulario se envía espera ver "ficticio (configuración del usuario)" prestados. Sin ModelState.Clear() seguirá viendo "ficticia" !!!!!!

solución correcta:

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

Siento que este no es un buen diseño en absoluto, como toda forma desarrollador MVC tiene que tener eso en cuenta.

Este problema todavía existe en MVC 5, y claramente no se considera un error que está muy bien.

Estamos encontrando que, aunque por su diseño, este no es el comportamiento esperado para nosotros. En lugar siempre queremos el valor del campo oculto para operar de manera similar a otros tipos de campos y no ser tratado especial, o tirar de su valor de alguna colección oscura (que nos recuerda ViewState!).

Unos resultados (valor correcto para nosotros es el valor del modelo, es incorrecto el valor ModelState):

  • Html.DisplayFor() muestra el valor correcto (que tira de Modelo)
  • Html.ValueFor no (se saca de ModelState)
  • ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData).Model tira del valor correcto

Nuestra solución es simplemente poner en práctica nuestra propia extensión:

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

Esto puede ser 'por diseño' pero no es lo que está documentado:

Public Shared Function Hidden(  

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

Miembro de System.Web.Mvc.Html.InputExtensions

     

Resumen: Devuelve una etiqueta de entrada oculto

.      

Parámetros:
    HtmlHelper: El asistente de HTML
.     Nombre: El nombre de campo de formulario y la clave System.Web.Mvc.ViewDataDictionary utilizada para buscar el valor
.     Valor: El valor de la entrada oculta. Si es null, se ve en la System.Web.Mvc.ViewDataDictionary y luego System.Web.Mvc.ModelStateDictionary para el valor.

Esto parece sugerir que sólo cuando el parámetro valor es nulo (o no especificada) sería la HtmlHelper buscar otro sitio para un valor.

En mi aplicación, Tengo un formulario en el que: html.Hidden ( "a distancia", True) está rindiendo como se <input id="remote" name="remote" type="hidden" value="False" />

Tenga en cuenta el valor es cada vez anulada por lo que está en el diccionario ViewData.ModelState.

O me estoy perdiendo algo?

Así que en MVC 4 del "problema de diseño" sigue ahí. Aquí está el código que tenía que utilizar con el fin de establecer los valores correctos ocultos en una colección ya que independientemente de lo que hago en el controlador, la vista siempre mostró valores incorrectos.

código antiguo

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
}

código actualizado

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

¿Se fijaron en esta MVC 5?

Hay solución:

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

Como otros han sugerido, fui con el uso de código HTML directamente en lugar de utilizar los HtmlHelper (TextBoxFor, CheckBoxFor, HiddenFor etc.).

El problema, sin embargo, con este enfoque es que hay que poner el nombre y atributos id como cadenas. Quería mantener mis del modelo de tipo firme-por lo que utiliza los HtmlHelper NameFor y IdFor.

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

Actualización: Aquí está una extensión útil HtmlHelper

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

A continuación, puede utilizarlo como

@Html.MyHiddenFor(m => m.Name)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top