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)?
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
Por favor, mira mi edición. –
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