2009-04-05 17 views
12

El problema: cómo actualizar ModelState en el escenario de publicación + validación.Actualización de ModelState con el objeto modelo

Tengo una forma sencilla:

<%= Html.ValidationSummary() %> 
<% using(Html.BeginForm())%> 
<%{ %> 
    <%=Html.TextBox("m.Value") %> 
    <input type="submit" /> 
<%} %> 

Cuando el usuario envía Quiero validar la entrada y en algunas circunstancias Quiero corregir el error para el usuario, haciéndole saber que él hizo un error que es ya fijada:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Index(M m) 
{ 
    if (m.Value != "a") 
    { 
     ModelState.AddModelError("m.Value", "should be \"a\""); 
     m.Value = "a"; 
     return View(m); 
    } 
    return View("About");    
} 

Bueno, el problema es, MVC simplemente ignorar el modelo aprobado a la vista y volver a hacer lo que el usuario ha escrito - y no mi valor ("a"). Esto sucede, porque el representador TextBox comprueba si hay un ModelState y si no es nulo, se usa el valor de ModelState. Ese valor es, por supuesto, el usuario escrito antes de publicar.

Como no puedo cambiar el comportamiento del renderizador de TextBox, la única solución que encontré sería actualizar el ModelState yo mismo. El método quick'n'dirty es (ab) usar DefaultModelBinder y anular el método que asigna los valores de los formularios al modelo simplemente cambiando la dirección de asignación;). Usando DefaultModelBinder no tengo que analizar los identificadores. El siguiente código (basado en la implementación original de DefaultModelBinder) es mi solución a este:

/// <summary> 
    /// Updates ModelState using values from <paramref name="order"/> 
    /// </summary> 
    /// <param name="order">Source</param> 
    /// <param name="prefix">Prefix used by Binder. Argument name in Action (if not explicitly specified).</param> 
    protected void UpdateModelState(object model, string prefix) 
    { 
     new ReversedBinder().BindModel(this.ControllerContext, 
      new ModelBindingContext() 
      { 
       Model = model, 
       ModelName = prefix, 
       ModelState = ModelState, 
       ModelType = model.GetType(), 
       ValueProvider = ValueProvider 
      }); 
    } 

    private class ReversedBinder : DefaultModelBinder 
    { 
     protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor) 
     { 
      string prefix = CreateSubPropertyName(bindingContext.ModelName, propertyDescriptor.Name); 
      object val = typeof(Controller) 
       .Assembly.GetType("System.Web.Mvc.DictionaryHelpers") 
       .GetMethod("DoesAnyKeyHavePrefix") 
       .MakeGenericMethod(typeof(ValueProviderResult)) 
       .Invoke(null, new object[] { bindingContext.ValueProvider, prefix }); 
      bool res = (bool)val; 
      if (res) 
      { 

       IModelBinder binder = new ReversedBinder();//this.Binders.GetBinder(propertyDescriptor.PropertyType); 
       object obj2 = propertyDescriptor.GetValue(bindingContext.Model); 

       ModelBindingContext context2 = new ModelBindingContext(); 
       context2.Model = obj2; 
       context2.ModelName = prefix; 
       context2.ModelState = bindingContext.ModelState; 
       context2.ModelType = propertyDescriptor.PropertyType; 
       context2.ValueProvider = bindingContext.ValueProvider; 
       ModelBindingContext context = context2; 
       object obj3 = binder.BindModel(controllerContext, context); 

       if (bindingContext.ModelState.Keys.Contains<string>(prefix)) 
       { 
        var prefixKey = bindingContext.ModelState.Keys.First<string>(x => x == prefix); 
        bindingContext.ModelState[prefixKey].Value 
            = new ValueProviderResult(obj2, obj2.ToString(), 
                   bindingContext.ModelState[prefixKey].Value.Culture); 
       } 
      } 
     } 
    } 

Así que la pregunta sigue siendo: ¿estoy haciendo algo muy poco común o me estoy perdiendo algo? Si el primero, ¿cómo podría implementar tal funcionalidad de una mejor manera (utilizando la infraestructura MVC existente)?

Respuesta

4

Puede aceptar una colección de formulario como parámetro en lugar de su objeto modelo en su controlador, como este: public ActionResult Index(FormCollection Form).

Por lo tanto, la carpeta de modelo predeterminada no actualizará el estado del modelo y obtendrá el comportamiento que desea.

Editar: O simplemente puede actualizar el ModelStateDictionary para reflejar sus cambios en el modelo.


[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Index(M m) 
{ 
    if (m.Value != "a") 
    { 
     ModelState["m.Value"].Value = new ValueProviderResult("a", m.Name, 
        CultureInfo.CurrentCulture); 
     ModelState.AddModelError("m.Value", "should be \"a\""); 
     m.Value = "a"; 
     return View(m); 
    } 
    return View("About");    
} 

Nota: No estoy seguro de si esta es la mejor manera. Pero parece funcionar y debería ser el comportamiento que desea.

+0

Pero quiero obtener el enlace predeterminado. Lo quiero porque quiero hacer uso de ModelState. Solo quiero actualizar el ModelState para reflejar los cambios en mi objeto modelo. – user87338

+0

Por favor, mira mi edición. –

+0

Su comentario es exactamente lo que estoy haciendo, pero lo hace usted mismo y estoy usando la carpeta de desaprobación, así que tengo algo más "genérico". El cambio de valor ("a") ocurre en un nivel inferior, por lo que no sé realmente qué accesorios han cambiado. Y también usted no querría ModelState ["m.Value"]. Value = new ValueProviderResult ("a", m.Name, CultureInfo.CurrentCulture); para la propiedad de cada objeto, ¿verdad? :). – user87338

22

Sé que esta publicación es bastante antigua, pero es un problema que he tenido antes y simplemente pensé en una solución simple que me gusta, simplemente borre el ModelState después de que tenga los valores publicados.

UpdateModel(viewModel); 
ModelState.Clear(); 

viewModel.SomeProperty = "a new value"; 
return View(viewModel); 

y la vista tiene que utilizar el objeto de modelo de vista (posiblemente modificado) en lugar de ModelState.

Quizás esto sea realmente obvio. ¡Parece así en retrospectiva!

0

¿Estoy haciendo algo extremadamente raro o me falta algo?

Creo que esto es bastante raro. Creo que MVC está asumiendo que los errores de validación son un asunto de sí/no y, en este caso, está utilizando un error de validación como medio para brindar comentarios generales de los usuarios.

Creo que MVC también parece más feliz cuando los POST fallan debido a errores de validación, o realizan una acción y redirigen o representan algo completamente diferente. Fuera de los errores de validación del modelo, es bastante raro volver a hacer la misma entrada.

He estado utilizando MVC desde hace aproximadamente un año y acabo de encontrarme con esto en otro contexto, donde después de un POST quería presentar un formulario nuevo como respuesta.

[HttpPost] 
public ActionResult Upload(DocumentView data) { 
    if(!ModelState.IsValid) return View(data); 
    ProcessUpload(data); 
    return View(new DocumentView()); 
} 

MVC está prestando la ModelState de data, no es mi nuevo objeto. Muy sorprendente.

En el primer caso, entonces ¿cómo podría implementar dicha funcionalidad en una mejor manera

  1. implemento a correcciones automáticas en JavaScript (tal vez no sea posible)
  2. mantener una lista de correcciones automáticas hecho, si el objeto es válido después de todos esos, a continuación, pasarlo a la vista "Acerca de" y mostrar como un mensaje como "M guardado, con las siguientes correcciones: ...".
Cuestiones relacionadas