5

我有一个像这样的 ViewModel 类:

class CaseModel {
    public Boolean     ClientPresent { get; set; }
    public ClientModel Client        { get; set; }
}

class ClientModel {
    [Required]
    public String      FirstName     { get; set; }
    [Required]
    public String      LastName      { get; set; }
}

视图页面由一个<input type="checkbox" name="ClientPresent" />和一个Html.EditorFor( m => m.Client )局部视图组成。

这个想法是,当用户提供有关案例(业务域对象)的信息时,他们可以通过取消选中 ClientPresent 框来选择不指定有关客户端(另一个 biz 对象)的任何信息。

我希望 ASP.NET MVC 不对子 ClientModel 对象执行任何验证-但是,当将表单 POST 回服务器时,会自动填充 CaseModel.Client 属性,但因为用户FirstNameLastName没有(必然)提供它意味着它未能通过[Required]验证属性,因此ViewData.ModelState.IsValid返回 false 并且用户收到验证错误消息。

我怎样才能得到它,所以如果是假的CaseModel.Client就不会被验证?CaseModel.ClientPresent

请注意,这ClientModel是一个完全独立的 ViewModel 类,并在应用程序的其他地方使用(例如在允许用户编辑客户端的单个实例的 ClientController 类中)。

4

2 回答 2

3

我认识到我的问题与绑定无关,而实际上与验证有关:通过保留值意味着当用户重新加载页面时将填充相同的表单字段,我只需要丢弃验证消息,因为它们没有适用的。

为此,我意识到我可以执行模型属性验证,然后使用一些自定义逻辑来删除验证消息。这与我所做的类似:

public class CaseModel {
    public void CleanValidation(ModelStateDictionary dict) {
        if( this.ClientPresent ) {
            dict.Keys.All( k => if( k.StartsWith("Client") dict[k].Errors.Clear() );
        }
    }
}

(显然我的实际代码更健壮,但你明白了)

CleanValidation 方法由控制器的 action 方法直接调用:

public void Edit(Int64 id, CaseModel model) {
    model.CleanValidation( this.ModelState );
}

我可以通过将CleanValidation方法添加到新接口IComplexModel并让新的模型绑定器自动调用此方法来整理它,这样控制器就不需要自己调用它了。

更新:

我有这个接口适用于任何需要复杂验证的 ViewModel:

public interface ICustomValidation {

    void Validate(ModelStateDictionary dict);
}

在我原来的例子中,CaseModel现在看起来像这样:

 public class CaseClientModel : ICustomValidation {

      public Boolean ClientIsNew { get; set; } // bound to a radio-button
      public ClientModel ExistingClient { get; set; } // a complex viewmodel used by a partial view
      public ClientModel NewClient { get; set; } // ditto

      public void Validate(ModelStateDictionary dict) {

          // RemoveElementsWithPrefix is an extension method that removes all key/value pairs from a dictionary if the key has the specified prefix.
          if( this.ClientIsNew ) dict.RemoveElementsWithPrefix("ExistingClient");
          else                   dict.RemoveElementsWithPrefix("NewClient");
      }
 }

OnActionExecuting在我的公共BaseController类中调用验证逻辑:

protected override void OnActionExecuting(ActionExecutingContext filterContext) {
    base.OnActionExecuting(filterContext);
    if( filterContext.ActionParameters.ContainsKey("model") ) {

        Object                    model = filterContext.ActionParameters["model"];
        ModelStateDictionary modelState = filterContext.Controller.ViewData.ModelState; // ViewData.Model always returns null at this point, so do this to get the ModelState.

        ICustomValidation modelValidation = model as ICustomValidation;
        if( modelValidation != null ) {
            modelValidation.Validate( modelState );
        }
    }
}
于 2012-06-14T14:24:14.860 回答
2

您必须通过从默认模型绑定器继承来创建自定义模型绑定器。

  public class CustomModelBinder: DefaultModelBinder
  {
    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor)
    {
      if (propertyDescriptor.Name == "Client")
      {
          var clientPresent = bindingContext.ValueProvider.GetValue("ClientPresent");

          if (clientPresent == null || 
                string.IsNullOrEmpty(clientPresent.AttemptedValue))
              return;
      }

      base.BindProperty(controllerContext, bindingContext, propertyDescriptor);
    }
  }

全球.asax.cs

ModelBinders.Binders.Add(typeof(CaseModel), new CustomModelBinder());
于 2012-06-14T02:56:16.787 回答