【问题标题】:how to populate data in controller and avoid ModelState errors如何在控制器中填充数据并避免 ModelState 错误
【发布时间】:2011-03-13 02:14:08
【问题描述】:

也许这不是我需要的解决方案,但这是我想做的:

我有一个公司注册表,每个公司都需要一个管理用户。管理用户可以管理多个公司,因此在公司注册表单中,您可以从下拉列表中选择现有用户。

公司视图模型如下所示:

public class CompanyViewModel {
    [Required]
    public string Name { get; set; }
    // other properties...
    public UserViewModel Administrator { get; set; }
    public IEnumerable<UserViewModel> AvailableUsers { get; set; }
}

用户视图模型如下所示:

public class UserViewModel {
    [Required]
    public string UserName { get; set; }
    [Required]
    public string Password { get; set; }
    // other properties...
}

在公司注册视图中:

<div><input type="radiobutton" name="chooseuser" id="existing"/>Choose an Existing User:</div.
<div>@Html.DropDownListFor(m => m.Administrator.Id, Model.AvailableUsers.Select(u => new SelectListItem { Text = string.Format("{0} - {1} {2}", u.UserName, u.FirstName, u.LastName), Value = u.Id.ToString() }), "<Choose existing user>", new { id = "existingusers" })
        </div>

<div><input type="radiobutton" name="chooseuser" id="createnew"/>Create a new User:</div>
<div><label>Username:</label> @Html.EditorFor(m => m.Administrator.UserName)</div>

通过javascript,基于单选按钮选择,禁用下拉列表并显示新用户表单,或者隐藏新用户表单并启用下拉列表。

问题出在控制器按保存后的保存操作中,如果选择了现有用户并且没有在表单上填写数据,则 ModelState.IsValid 为 false。如果用户选择输入新用户,则验证成功。

处理此问题的最佳方法是什么?

一种选择是将所有用户的所有数据加载到 javascript 中的数据结构中,当现有用户下拉列表中的值发生变化时,可以填充隐藏的“新建”表单字段。但这似乎很蹩脚,因为密码将以纯文本形式放置在 html 中。一旦保存了新用户,我可以变得更高级并将 ajax 用于“创建新”表单并在原始表单上填充用户 ID,但如果可能的话,我希望将其全部保存在一个表单中。

似乎我希望能够从数据库加载现有用户数据并在控制器保存操作中填充模型状态,但是手动编写此代码(即使使用反射)似乎很草率。如果有内置的方法可以做到这一点,那就太好了。

有什么想法吗?

谢谢。

【问题讨论】:

    标签: asp.net-mvc model-view-controller


    【解决方案1】:

    这是一个典型场景,完美地说明了声明式验证(也称为数据注释)的局限性。为了处理它,您可以编写一个自定义验证属性,该属性将应用于 CompanyViewModel 而不是单个属性,并允许您根据用户选择的单选按钮执行验证逻辑(顺便说一句,您将需要一个属性您的视图模型将代表单选按钮选择)。模型验证器的问题是您可能很难处理突出显示的错误。

    这就是我使用FluentValidation.NET 而不是数据注释的原因之一。这使我可以让验证逻辑远离模型并以命令式的方式完成。它还允许我拥有基于视图模型上某些属性的值应用的条件验证器(在这种情况下,这将是单选按钮选择)。

    【讨论】:

      【解决方案2】:

      您可能需要考虑自定义 Modelbinder。

      这是我网站的一些示例代码 - 这是购物车结帐页面的一部分 - 用户可以输入地址,但发送美国的StateCd 和非美国的StateOrProvince。因此,我们查看国家/地区并删除不适用的其他属性的任何模型错误。

      我认为这与您所描述的非常相似(您有两个场景需要不同的规则,但您想使用相同的模型)。

      这里重要的代码是bindingContext.ModelState.Remove(...),它移除了模型状态并允许IsValid返回true。

      public class AddressModelBinder : DefaultModelBinder
      {
          protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
          {
              base.OnModelUpdated(controllerContext, bindingContext);
      
              // get the address to validate
              var address = (Address)bindingContext.Model;
      
              // remove statecd for non-us
              if (address.IsUSA)
              {
                  address.StateOrProvince = string.IsNullOrEmpty(address.StateCd) ? null : CountryCache.GetStateName(address.StateCd);
                  bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateOrProvince");
              }
              else
              {
                  address.StateCd = null;
                  bindingContext.ModelState.Remove(bindingContext.ModelName + ".StateCd");
              }
      
              // update country
              address.Country = CountryCache.GetCountry(address.CountryCode, true).Name;
      
              // validate US zipcode
              if (address.CountryCode == "US")
              {
                  if (new Regex(@"^\d{5}([\-]\d{4})?$", RegexOptions.Compiled).Match(address.ZipOrPostal ?? "").Success == false)
                  {
                      bindingContext.ModelState.AddModelError(bindingContext.ModelName + ".ZipOrPostal", "The value " + address.ZipOrPostal + " is not a valid zipcode");
                  }
              }
      
              // all other modelbinding attributes such as [Required] will be processed as normal
          }
      }
      

      注意:您需要在 global.asax 中注册此模型绑定器。模型绑定组件非常智能,可以让您为模型的任何部分创建不同的模型绑定器(如果它包含不同的对象)。

        ModelBinders.Binders[typeof(UI.Address)] = new AddressModelBinder();
      

      希望这会有所帮助。我认为这适用于您的情况。

      【讨论】:

        猜你喜欢
        • 2018-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-24
        • 2015-04-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多