【问题标题】:Updating ModelState with model object使用模型对象更新 ModelState
【发布时间】:2009-04-05 18:03:27
【问题描述】:

问题:在posting+validation场景下如何更新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 我不必解析 id。 以下代码(基于 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 基础架构)?

【问题讨论】:

    标签: asp.net-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");            
      }
      

      注意:我不确定这是否是最好的方法。但它似乎有效,它应该是你想要的行为。

      【讨论】:

      • 但我想获取默认绑定。我想要它是因为我想使用 ModelState。我只想更新 ModelState 以反映模型对象的变化。
      • 您的评论正是我正在做的,但您自己做,我正在使用 te deault binder,所以我有一些更“通用”的东西。值的变化(“a”)发生在较低的层,所以我实际上不知道哪些道具发生了变化。而且你也不想 ModelState["m.Value"].Value = new ValueProviderResult("a", m.Name, CultureInfo.CurrentCulture);对于每一个对象的属性,你:)。
      • :) 你是对的。我只是再次查看了 ModelState 对象,似乎没有简单的方法可以做到这一点。我试过 ModelState["m.Value"].Value.AttemptedValue = "a";但事实证明该属性是只读的。
      【解决方案3】:

      我是在做一些非常不寻常的事情还是我错过了什么?

      我认为这非常罕见。我认为 MVC 假设验证错误是是/否事件,在这种情况下,您使用验证错误作为提供一般用户反馈的手段。

      我认为,当 POST 因验证错误而失败,或者执行操作并重定向或呈现完全不同的内容时,MVC 似乎也是最快乐的。在模型验证错误之外,重新渲染相同的输入是非常罕见的。

      我已经使用 MVC 大约一年了,只是在另一个上下文中遇到了这个问题,在 POST 之后我想呈现一个新的表单作为响应。

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

      MVC 正在渲染来自dataModelState,而不是我的新对象。非常令人惊讶。

      如果是前者,那我怎样才能更好地实现这样的功能

      1. 在 javascript 中实现自动修复(可能不可能)
      2. 保留所做的自动修复列表,如果对象在所有这些之后仍然有效,则将其传递到“关于”视图并显示为“M 已保存,具有以下更正:...”之类的消息。

      【讨论】:

        猜你喜欢
        • 2013-11-20
        • 1970-01-01
        • 1970-01-01
        • 2019-01-23
        • 1970-01-01
        • 1970-01-01
        • 2012-04-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多