Проблема: Как обновить ModelState в сценарии публикации + проверки.
У меня есть простая форма:
<%= Html.ValidationSummary() %>
<% using(Html.BeginForm())%>
<%{ %>
<%=Html.TextBox("m.Value") %>
<input type="submit" />
<%} %>
Когда пользователь отправляет сообщение, я хочу проверить ввод и в некоторых случаях я хочу исправить ошибку для пользователя, сообщив ему, что он сделал ошибку, которая уже исправлена:
[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");
}
Что ж, проблема в том, что MVC просто проигнорирует модель, переданную представлению, и выполнит повторную визуализацию независимо от того, что набрал пользователь, а не мое значение ("a").
Это происходит потому, что средство визуализации TextBox проверяет, существует ли ModelState, и если оно не равно нулю, используется значение ModelState. Это значение, конечно, один пользователь набрал перед публикацией.
Поскольку я не могу изменить поведение рендерера TextBox, единственное решение, которое я нашел, - это обновить ModelState самостоятельно. Быстрый и грязный способ состоит в том, чтобы (ab) использовать DefaultModelBinder и переопределить метод, который присваивает значения из форм модели, просто изменяя направление назначения;). Используя DefaultModelBinder мне не нужно анализировать идентификаторы.
Следующий код (основанный на оригинальной реализации DefaultModelBinder) является моим решением для этого:
/// <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);
}
}
}
}
Таким образом, остается вопрос: я делаю что-то необычное или я что-то упускаю? Если первое, то как я мог бы реализовать такую функциональность лучше (используя существующую инфраструктуру MVC)?