【问题标题】:MVC3 Input Dependent ValidationMVC3 输入相关验证
【发布时间】:2012-03-05 20:20:52
【问题描述】:

注意:我对 MVC3 比较陌生。
使用这个框架,输入验证似乎非常好,您可以只说 [Required] 并且客户端和服务器端验证都可以从那里工作。但是如果我想实现条件验证呢?

场景:我将有一个保管箱,要求您选择 2 个选项之一。如果选择选项 1,将出现 2 个文本输入字段,并且两者都是必需的。如果选择选项 2,将出现 2 个单选按钮,您需要选择其中之一。 MVC3 验证如何实现这一点?

显然,在模型中,我们不能只进行标准要求的验证,因为根据我们选择的下拉选项,某些字段不会被提交。

【问题讨论】:

    标签: c# jquery asp.net-mvc-3 validation


    【解决方案1】:

    这个框架的输入验证似乎很不错

    真的吗?您描述的场景是使用数据注释进行验证的限制的完美示例。

    我将尝试探索 3 种可能的技术。转到此答案的末尾以及我使用和推荐的第三种技术。

    让我在开始探索它们之前展示将用于这 3 个场景的控制器和视图,因为它们是相同的。

    控制器:

    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View(new MyViewModel());
        }
    
        [HttpPost]
        public ActionResult Index(MyViewModel model)
        {
            return View(model);
        }
    }
    

    查看:

    @model MyViewModel
    
    @using (Html.BeginForm())
    {
        <div>
            @Html.LabelFor(x => x.SelectedOption)
            @Html.DropDownListFor(
                x => x.SelectedOption, 
                Model.Options, 
                "-- select an option --", 
                new { id = "optionSelector" }
            )
            @Html.ValidationMessageFor(x => x.SelectedOption)
        </div>
        <div id="inputs"@Html.Raw(Model.SelectedOption != "1" ? " style=\"display:none;\"" : "")>
            @Html.LabelFor(x => x.Input1)   
            @Html.EditorFor(x => x.Input1)
            @Html.ValidationMessageFor(x => x.Input1)
    
            @Html.LabelFor(x => x.Input2)
            @Html.EditorFor(x => x.Input2)
            @Html.ValidationMessageFor(x => x.Input2)
        </div>
        <div id="radios"@Html.Raw(Model.SelectedOption != "2" ? " style=\"display:none;\"" : "")>
            @Html.Label("rad1", "Value 1")
            @Html.RadioButtonFor(x => x.RadioButtonValue, "value1", new { id = "rad1" })
    
            @Html.Label("rad2", "Value 2")
            @Html.RadioButtonFor(x => x.RadioButtonValue, "value2", new { id = "rad2" })
    
            @Html.ValidationMessageFor(x => x.RadioButtonValue)
        </div>
        <button type="submit">OK</button>
    }
    

    脚本:

    $(function () {
        $('#optionSelector').change(function () {
            var value = $(this).val();
            $('#inputs').toggle(value === '1');
            $('#radios').toggle(value === '2');
        });
    });
    

    这里没什么特别的。实例化传递给视图的视图模型的控制器。在视图中,我们有一个表单和一个下拉列表。我们使用 javascript 订阅此下拉列表的更改事件,并根据所选值切换此表单的不同区域。


    可能性1

    第一种可能性是让您的视图模型实现IValidatableObject。请记住,如果您决定在视图模型上实现此接口,则不应在视图模型属性上使用任何验证属性,否则将永远不会调用 Validate 方法:

    public class MyViewModel: IValidatableObject
    {
        public string SelectedOption { get; set; }
        public IEnumerable<SelectListItem> Options
        {
            get
            {
                return new[]
                {
                    new SelectListItem { Value = "1", Text = "item 1" },
                    new SelectListItem { Value = "2", Text = "item 2" },
                };
            }
        }
    
        public string RadioButtonValue { get; set; }
    
        public string Input1 { get; set; }
        public string Input2 { get; set; }
    
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (SelectedOption == "1")
            {
                if (string.IsNullOrEmpty(Input1))
                {
                    yield return new ValidationResult(
                        "Input1 is required", 
                        new[] { "Input1" }
                    );
                }
                if (string.IsNullOrEmpty(Input2))
                {
                    yield return new ValidationResult(
                        "Input2 is required",
                        new[] { "Input2" }
                    );
                }
            }
            else if (SelectedOption == "2")
            {
                if (string.IsNullOrEmpty(RadioButtonValue))
                {
                    yield return new ValidationResult(
                        "RadioButtonValue is required",
                        new[] { "RadioButtonValue" }
                    );
                }
            }
            else
            {
                yield return new ValidationResult(
                    "You must select at least one option", 
                    new[] { "SelectedOption" }
                );
            }
        }
    }
    

    这种方法的好处在于您可以处理任何复杂的验证场景。这种方法的缺点是它不太可读,因为我们将验证与消息和错误输入字段名称选择混合在一起。


    可能性2

    另一种可能性是编写自定义验证属性,如[RequiredIf]

    [AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = true)]
    public class RequiredIfAttribute : RequiredAttribute
    {
        private string OtherProperty { get; set; }
        private object Condition { get; set; }
    
        public RequiredIfAttribute(string otherProperty, object condition)
        {
            OtherProperty = otherProperty;
            Condition = condition;
        }
    
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var property = validationContext.ObjectType.GetProperty(OtherProperty);
            if (property == null)
                return new ValidationResult(String.Format("Property {0} not found.", OtherProperty));
    
            var propertyValue = property.GetValue(validationContext.ObjectInstance, null);
            var conditionIsMet = Equals(propertyValue, Condition);
            return conditionIsMet ? base.IsValid(value, validationContext) : null;
        }
    }
    

    然后:

    public class MyViewModel
    {
        [Required]
        public string SelectedOption { get; set; }
        public IEnumerable<SelectListItem> Options
        {
            get
            {
                return new[]
                {
                    new SelectListItem { Value = "1", Text = "item 1" },
                    new SelectListItem { Value = "2", Text = "item 2" },
                };
            }
        }
    
        [RequiredIf("SelectedOption", "2")]
        public string RadioButtonValue { get; set; }
    
        [RequiredIf("SelectedOption", "1")]
        public string Input1 { get; set; }
        [RequiredIf("SelectedOption", "1")]
        public string Input2 { get; set; }
    }
    

    这种方法的好处是我们的视图模型是干净的。这样做的坏处是,使用自定义验证属性可能会很快达到极限。例如,考虑更复杂的场景,您需要递归到子模型和集合等。这很快就会变得一团糟。


    可能性 3

    第三种可能性是使用FluentValidation.NET。这是我个人使用和推荐的。

    所以:

    1. Install-Package FluentValidation.MVC3 在 NuGet 控制台中
    2. Application_Start 中的Global.asax 中添加以下行:

      FluentValidationModelValidatorProvider.Configure();
      
    3. 为视图模型编写一个验证器:

      public class MyViewModelValidator : AbstractValidator<MyViewModel>
      {
          public MyViewModelValidator()
          {
              RuleFor(x => x.SelectedOption).NotEmpty();
              RuleFor(x => x.Input1).NotEmpty().When(x => x.SelectedOption == "1");
              RuleFor(x => x.Input2).NotEmpty().When(x => x.SelectedOption == "1");
              RuleFor(x => x.RadioButtonValue).NotEmpty().When(x => x.SelectedOption == "2");
          }
      }
      
    4. 而视图模型本身就是一个 POCO:

      [Validator(typeof(MyViewModelValidator))]
      public class MyViewModel
      {
          public string SelectedOption { get; set; }
          public IEnumerable<SelectListItem> Options
          {
              get
              {
                  return new[]
                  {
                      new SelectListItem { Value = "1", Text = "item 1" },
                      new SelectListItem { Value = "2", Text = "item 2" },
                  };
              }
          }
      
          public string RadioButtonValue { get; set; }
          public string Input1 { get; set; }
          public string Input2 { get; set; }
      }
      

    这样做的好处是我们在验证和视图模型之间有一个完美的分离。它integrates nicely with ASP.NET MVC。我们可以以非常简单流畅的方式隔离unit test我们的验证器。

    这有什么不好的是,当微软在设计 ASP.NET MVC 时,他们选择了声明式验证逻辑(使用数据注释)而不是命令式验证逻辑,后者更适合验证场景并且可以处理任何事情。 FluentValidation.NET 实际上并不是在 ASP.NET MVC 中执行验证的标准方式,这很糟糕。

    【讨论】:

    • 感谢您的回复。这些解决方案中的任何一个都可以处理客户端验证吗?
    • @Peanut,这种场景不能通过客户端验证开箱即用,因为它是有条件的。通过与 jquery.validate 插件集成来实现自定义客户端验证当然是微不足道的。
    • 对,我是这么认为的。感谢所有选项。
    • @DarinDimitrov 可能性 2,这适用于客户端和服务器还是仅适用于服务器?
    • @Vyache,如我的回答所示,它只是服务器端。如果你想让它在客户端工作,你需要实现必要的自定义逻辑。
    猜你喜欢
    • 1970-01-01
    • 2011-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-22
    • 2012-02-02
    • 1970-01-01
    相关资源
    最近更新 更多