如何使用数据注释对模型进行条件验证?
例如,假设我们有以下模型(Person 和 Senior):
public class Person
{
[Required(ErrorMessage = "*")]
public string Name
{
get;
set;
}
public bool IsSenior
{
get;
set;
}
public Senior Senior
{
get;
set;
}
}
public class Senior
{
[Required(ErrorMessage = "*")]//this should be conditional validation, based on the "IsSenior" value
public string Description
{
get;
set;
}
}
以及以下观点:
<%= Html.EditorFor(m => m.Name)%>
<%= Html.ValidationMessageFor(m => m.Name)%>
<%= Html.CheckBoxFor(m => m.IsSenior)%>
<%= Html.ValidationMessageFor(m => m.IsSenior)%>
<%= Html.CheckBoxFor(m => m.Senior.Description)%>
<%= Html.ValidationMessageFor(m => m.Senior.Description)%>
我想成为基于“IsSenior”属性选择的“Senior.Description”属性条件必填字段(true -> required)。如何使用数据注释在 ASP.NET MVC 2 中实现条件验证?
Senior
对象始终是高级对象,那么在这种情况下,为什么 IsSenior 可以为假。当 Person.IsSenior
为假时,您是否只需要将“Person.Senior”属性设为空。或者为什么不按如下方式实现 IsSenior
属性:bool IsSenior { get { return this.Senior != null; } }
。
在 MVC3 中添加条件验证规则有更好的方法;让您的模型继承 IValidatableObject
并实现 Validate
方法:
public class Person : IValidatableObject
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsSenior && string.IsNullOrEmpty(Senior.Description))
yield return new ValidationResult("Description must be supplied.");
}
}
在 Introducing ASP.NET MVC 3 (Preview 1) 阅读更多信息。
我通过处理控制器包含的 "ModelState" 字典解决了这个问题。 ModelState 字典包括所有必须验证的成员。
这是解决方案:
如果您需要基于某些字段实现条件验证(例如,如果 A=true,则需要 B),同时维护属性级别的错误消息(对于对象级别的自定义验证器不是这样),您可以实现这一点通过处理“ModelState”,只需从中删除不需要的验证。
...在某个班级...
public bool PropertyThatRequiredAnotherFieldToBeFilled
{
get;
set;
}
[Required(ErrorMessage = "*")]
public string DepentedProperty
{
get;
set;
}
……上课继续……
...在某些控制器动作中...
if (!PropertyThatRequiredAnotherFieldToBeFilled)
{
this.ModelState.Remove("DepentedProperty");
}
...
有了这个,我们实现了条件验证,而其他一切都保持不变。
更新:
这是我的最终实现:我在模型上使用了一个接口,并使用了用于验证实现所述接口的模型的 action 属性。接口规定了 Validate(ModelStateDictionary modelState) 方法。 action 上的属性只是调用 IValidatorSomething 上的 Validate(modelState)。
我不想让这个答案复杂化,所以我没有提到最终的实现细节(最后,这在生产代码中很重要)。
我昨天遇到了同样的问题,但我以一种非常干净的方式完成了它,它适用于客户端和服务器端验证。
条件:基于模型中其他属性的值,您要使其他属性成为必需的。这是代码
public class RequiredIfAttribute : RequiredAttribute
{
private String PropertyName { get; set; }
private Object DesiredValue { get; set; }
public RequiredIfAttribute(String propertyName, Object desiredvalue)
{
PropertyName = propertyName;
DesiredValue = desiredvalue;
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
Object instance = context.ObjectInstance;
Type type = instance.GetType();
Object proprtyvalue = type.GetProperty(PropertyName).GetValue(instance, null);
if (proprtyvalue.ToString() == DesiredValue.ToString())
{
ValidationResult result = base.IsValid(value, context);
return result;
}
return ValidationResult.Success;
}
}
此处 PropertyName 是您要设置条件的属性 DesiredValue 是必须验证您的其他属性是否需要的 PropertyName(属性)的特定值
说你有以下
public class User
{
public UserType UserType { get; set; }
[RequiredIf("UserType", UserType.Admin, ErrorMessageResourceName = "PasswordRequired", ErrorMessageResourceType = typeof(ResourceString))]
public string Password
{
get;
set;
}
}
最后但并非最不重要的是,为您的属性注册适配器,以便它可以进行客户端验证(我把它放在 global.asax,Application_Start 中)
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute),typeof(RequiredAttributeAdapter));
我一直在使用这个惊人的 nuget,它可以做动态注释ExpressiveAnnotations
您可以验证您可以梦想的任何逻辑:
public string Email { get; set; }
public string Phone { get; set; }
[RequiredIf("Email != null")]
[RequiredIf("Phone != null")]
[AssertThat("AgreeToContact == true")]
public bool? AgreeToContact { get; set; }
您可以通过从 ModelState 中删除错误来有条件地禁用验证器:
ModelState["DependentProperty"].Errors.Clear();
现在有一个框架可以开箱即用地执行此条件验证(以及其他方便的数据注释验证):http://foolproof.codeplex.com/
具体来说,看看 [RequiredIfTrue("IsSenior")] 验证器。您将其直接放在要验证的属性上,因此您可以获得与“高级”属性相关联的验证错误的所需行为。
它以 NuGet 包的形式提供。
您需要在 Person 级别进行验证,而不是在 Senior 级别进行验证,或者 Senior 必须具有对其父 Person 的引用。在我看来,您需要一种自我验证机制来定义对 Person 的验证,而不是在它的某个属性上。我不确定,但我认为 DataAnnotations 不支持开箱即用。您可以创建自己的 Attribute
,它从 ValidationAttribute
派生,可以在类级别进行修饰,然后创建一个自定义验证器,该验证器还允许这些类级别的验证器运行。
我知道验证应用程序块支持开箱即用的自我验证,但 VAB 的学习曲线相当陡峭。不过,这里有一个使用 VAB 的示例:
[HasSelfValidation]
public class Person
{
public string Name { get; set; }
public bool IsSenior { get; set; }
public Senior Senior { get; set; }
[SelfValidation]
public void ValidateRange(ValidationResults results)
{
if (this.IsSenior && this.Senior != null &&
string.IsNullOrEmpty(this.Senior.Description))
{
results.AddResult(new ValidationResult(
"A senior description is required",
this, "", "", null));
}
}
}
我遇到了同样的问题,需要修改 [Required] 属性 - 使字段依赖于 http 请求。解决方案类似于 Dan Hunex 的答案,但他的解决方案无法正常工作(见评论)。我不使用不显眼的验证,只使用 MicrosoftMvcValidation.js 开箱即用。这里是。实现您的自定义属性:
public class RequiredIfAttribute : RequiredAttribute
{
public RequiredIfAttribute(/*You can put here pararmeters if You need, as seen in other answers of this topic*/)
{
}
protected override ValidationResult IsValid(object value, ValidationContext context)
{
//You can put your logic here
return ValidationResult.Success;//I don't need its server-side so it always valid on server but you can do what you need
}
}
然后您需要实现您的自定义提供程序以将其用作 global.asax 中的适配器
public class RequreIfValidator : DataAnnotationsModelValidator <RequiredIfAttribute>
{
ControllerContext ccontext;
public RequreIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute)
{
ccontext = context;// I need only http request
}
//override it for custom client-side validation
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
//here you can customize it as you want
ModelClientValidationRule rule = new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
//and here is what i need on client side - if you want to make field required on client side just make ValidationType "required"
ValidationType =(ccontext.HttpContext.Request["extOperation"] == "2") ? "required" : "none";
};
return new ModelClientValidationRule[] { rule };
}
}
并用一行修改你的 global.asax
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredIfAttribute), typeof(RequreIfValidator));
就在这里
[RequiredIf]
public string NomenclatureId { get; set; }
对我来说主要的优势是我不必像在不显眼的验证中那样编写自定义客户端验证器。它与 [必需] 一样工作,但仅在您想要的情况下。
DataAnnotationsModelValidator
的部分正是我需要看到的。谢谢你。
从模型状态有条件地删除错误的典型用法:
使控制器操作的第一部分有条件 执行逻辑以从 ModelState 中删除错误 执行现有逻辑的其余部分(通常是模型状态验证,然后是其他所有内容)
例子:
public ActionResult MyAction(MyViewModel vm)
{
// perform conditional test
// if true, then remove from ModelState (e.g. ModelState.Remove("MyKey")
// Do typical model state validation, inside following if:
// if (!ModelState.IsValid)
// Do rest of logic (e.g. fetching, saving
在您的示例中,保持一切原样并将建议的逻辑添加到控制器的操作中。我假设传递给控制器操作的 ViewModel 具有 Person 和 Senior Person 对象,其中包含从 UI 填充的数据。
我正在使用 MVC 5,但你可以尝试这样的事情:
public DateTime JobStart { get; set; }
[AssertThat("StartDate >= JobStart", ErrorMessage = "Time Manager may not begin before job start date")]
[DisplayName("Start Date")]
[Required]
public DateTime? StartDate { get; set; }
在您的情况下,您会说“IsSenior == true”。然后,您只需要检查您的发布操作的验证。
不定期副业成功案例分享
ModelState.IsValid
- 不直接调用 Validate