Обновление ModelState с помощью объекта модели
Проблема: как обновить 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, и если оно не null - используется значение ModelState. Это значение, конечно, один пользователь напечатал до публикации.
Так как я не могу изменить поведение обработчика TextBox, единственным найденным решением было бы обновить ModelState самостоятельно. Метод quick'n'dirty состоит в том, чтобы (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)?
Ответы
Ответ 1
Я знаю, что это сообщение довольно старое, но это проблема, с которой я столкнулся раньше, и я просто подумал о простом решении, которое мне нравится - просто очистите ModelState после того, как у вас есть опубликованные значения.
UpdateModel(viewModel);
ModelState.Clear();
viewModel.SomeProperty = "a new value";
return View(viewModel);
и представление должно использовать (возможно модифицированный) объект модели представления, а не ModelState.
Возможно, это действительно очевидно. Кажется, это ретроспективно!
Ответ 2
Вы можете принять коллекцию форм как параметр вместо вашего объекта модели в своем контроллере, например: public ActionResult Index(FormCollection Form)
.
В связи с этим связующее устройство по умолчанию не обновляет состояние модели, и вы получите нужное поведение.
Изменить. Или вы можете просто обновить ModelStateDictionary, чтобы отразить ваши изменения в модели.
[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");
}
Примечание. Я не уверен, что это лучший способ. Но, похоже, это работает, и это должно быть поведение, которое вы хотите.
Ответ 3
Я делаю что-то очень необычное или я что-то не хватает?
Я думаю, что это довольно редко. Я думаю, что MVC предполагает, что ошибки проверки - дело "да/нет", и в этом случае вы используете ошибку проверки в качестве средства для предоставления общей обратной связи с пользователями.
Я думаю, что MVC также кажется счастливым, когда POST либо терпят неудачу из-за ошибок проверки, либо выполняют действие, и перенаправляют, либо делают что-то совершенно другое. Снаружи для ошибок проверки модели довольно редко повторять один и тот же ввод.
Я использую MVC около года и просто столкнулся с этим в другом контексте, где после POST я хотел отобразить новую форму в качестве ответа.
[HttpPost]
public ActionResult Upload(DocumentView data) {
if(!ModelState.IsValid) return View(data);
ProcessUpload(data);
return View(new DocumentView());
}
MVC передает ModelState
из data
, а не мой новый объект. Очень удивительно.
Если первый, то как я мог бы реализовать такую функциональность лучшим образом
- реализовать автоматические исправления в javascript (возможно, не удастся)
- сохранить список автоматических исправлений, если объект действителен после того, как все они передадут его в представление "О программе" и отобразится в виде сообщения типа "М сохранено со следующими исправлениями:...".