Подтвердить что ты не робот

Обновление 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)?

4b9b3361

Ответ 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 (возможно, не удастся)
  • сохранить список автоматических исправлений, если объект действителен после того, как все они передадут его в представление "О программе" и отобразится в виде сообщения типа "М сохранено со следующими исправлениями:...".