[英]Data Annotations [Required] attribute on property of split Entity Data Model
[英]Conditionally required property using data annotations
我有一个像这样的 class :
public class Document
{
public int DocumentType{get;set;}
[Required]
public string Name{get;set;}
[Required]
public string Name2{get;set;}
}
现在,如果我在Name
和Name2
属性上放置[Required]
数据注释,那么一切正常,如果Name
或Name2
为空,验证将引发错误。
但是我希望仅在DocumentType
等于 1 时才需要Name
字段,并且仅在DocumentType
等于 2 时才需要Name2
。
public class Document
{
public int DocumentType{get;set;}
[Required(Expression<Func<object, bool>>)]
public string Name{get;set;}
[Required(Expression<Func<object, bool>>)]
public string Name2{get;set;}
}
但我知道我不能,它会导致错误。 我应该怎么做这个要求?
我编写了一个RequiredIfAttribute
,当不同的属性具有特定值(您需要的值)或不同的属性具有特定值以外的任何值时,它需要特定的属性值。
这是可能有帮助的代码:
/// <summary>
/// Provides conditional validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute
{
#region Properties
/// <summary>
/// Gets or sets the other property name that will be used during validation.
/// </summary>
/// <value>
/// The other property name.
/// </value>
public string OtherProperty { get; private set; }
/// <summary>
/// Gets or sets the display name of the other property.
/// </summary>
/// <value>
/// The display name of the other property.
/// </value>
public string OtherPropertyDisplayName { get; set; }
/// <summary>
/// Gets or sets the other property value that will be relevant for validation.
/// </summary>
/// <value>
/// The other property value.
/// </value>
public object OtherPropertyValue { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
/// </summary>
/// <value>
/// <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
/// </value>
/// <remarks>
/// How this works
/// - true: validated property is required when other property doesn't equal provided value
/// - false: validated property is required when other property matches provided value
/// </remarks>
public bool IsInverted { get; set; }
/// <summary>
/// Gets a value that indicates whether the attribute requires validation context.
/// </summary>
/// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
public override bool RequiresValidationContext
{
get { return true; }
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
/// </summary>
/// <param name="otherProperty">The other property.</param>
/// <param name="otherPropertyValue">The other property value.</param>
public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
: base("'{0}' is required because '{1}' has a value {3}'{2}'.")
{
this.OtherProperty = otherProperty;
this.OtherPropertyValue = otherPropertyValue;
this.IsInverted = false;
}
#endregion
/// <summary>
/// Applies formatting to an error message, based on the data field where the error occurred.
/// </summary>
/// <param name="name">The name to include in the formatted message.</param>
/// <returns>
/// An instance of the formatted error message.
/// </returns>
public override string FormatErrorMessage(string name)
{
return string.Format(
CultureInfo.CurrentCulture,
base.ErrorMessageString,
name,
this.OtherPropertyDisplayName ?? this.OtherProperty,
this.OtherPropertyValue,
this.IsInverted ? "other than " : "of ");
}
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException("validationContext");
}
PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (otherProperty == null)
{
return new ValidationResult(
string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
}
object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);
// check if this value is actually required and validate it
if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
{
if (value == null)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
// additional check for strings so they're not empty
string val = value as string;
if (val != null && val.Trim().Length == 0)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
使用数据注释的有条件要求的属性
[RequiredIf(dependent Property name, dependent Property value)]
e.g.
[RequiredIf("Country", "Ethiopia")]
public string POBox{get;set;}
// POBox is required in Ethiopia
public string Country{get;set;}
[RequiredIf("destination", "US")]
public string State{get;set;}
// State is required in US
public string destination{get;set;}
public class RequiredIfAttribute : ValidationAttribute
{
RequiredAttribute _innerAttribute = new RequiredAttribute();
public string _dependentProperty { get; set; }
public object _targetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this._dependentProperty = dependentProperty;
this._targetValue = targetValue;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var field = validationContext.ObjectType.GetProperty(_dependentProperty);
if (field != null)
{
var dependentValue = field.GetValue(validationContext.ObjectInstance, null);
if ((dependentValue == null && _targetValue == null) || (dependentValue.Equals(_targetValue)))
{
if (!_innerAttribute.IsValid(value))
{
string name = validationContext.DisplayName;
return new ValidationResult(ErrorMessage=name + " Is required.");
}
}
return ValidationResult.Success;
}
else
{
return new ValidationResult(FormatErrorMessage(_dependentProperty));
}
}
}
开箱即用,我认为这仍然是不可能的。
但是我发现了这篇关于 Mvc.ValidationToolkit 的有前途的文章(也在这里,不幸的是这只是 alpha,但你可能也可以从这段代码中提取你需要的方法并自己集成它) ,它包含了很好的声音属性RequiredIf
似乎与您的原因完全匹配:
install-package Microsoft.AspNet.Mvc
)using Mvc.ValidationToolkit;
添加using Mvc.ValidationToolkit;
[RequiredIf("DocumentType", 2)]
或[RequiredIf("DocumentType", 1)]
,因此只要DocumentType
不等于 1,如果name
或name2
均未提供,则对象是有效的或 2查看 Fluent 验证
https://www.nuget.org/packages/FluentValidation/
项目描述 .NET 的小型验证库,它使用流畅的接口和 lambda 表达式为您的业务对象构建验证规则。
我一直使用 System.ComponentModel.DataAnnotations 中实现的 IValidatableObject;
下面的例子
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (this.SendInAppNotification)
{
if (string.IsNullOrEmpty(this.NotificationTitle) || string.IsNullOrWhiteSpace(this.NotificationTitle))
{
yield return new ValidationResult(
$"Notification Title is required",
new[] { nameof(this.NotificationTitle) });
}
}
查看MVC万无一失的验证。 如果我没记错的话,它在模型中有数据注释,比如RequiredIf(依赖属性,依赖值) 。 您可以从以下位置下载万无一失:
Visual Studio(2017) -> 工具 -> Nuget 包管理器 -> 管理解决方案的 Nuget 包。 除了 jquery 文件之外,还参考 mvcfoolproof.unobtrusive.min.js。
查看 ExpressiveAnnotations .net 库Git 参考
它具有“RequiredIf”和“AssertThat”验证属性
我通过扩展RequiredAttribute
类解决了这个问题,从CompareAttribute
和Robert 的优秀解决方案中借用了一些逻辑:
/// <summary>
/// Provides conditional <see cref="RequiredAttribute"/>
/// validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : RequiredAttribute
{
/// <summary>
/// Gets or sets a value indicating whether other property's value should
/// match or differ from provided other property's value (default is <c>false</c>).
/// </summary>
public bool IsInverted { get; set; } = false;
/// <summary>
/// Gets or sets the other property name that will be used during validation.
/// </summary>
/// <value>
/// The other property name.
/// </value>
public string OtherProperty { get; private set; }
/// <summary>
/// Gets or sets the other property value that will be relevant for validation.
/// </summary>
/// <value>
/// The other property value.
/// </value>
public object OtherPropertyValue { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
/// </summary>
/// <param name="otherProperty">The other property.</param>
/// <param name="otherPropertyValue">The other property value.</param>
public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
: base()
{
OtherProperty = otherProperty;
OtherPropertyValue = otherPropertyValue;
}
protected override ValidationResult IsValid(
object value,
ValidationContext validationContext)
{
PropertyInfo otherPropertyInfo = validationContext
.ObjectType.GetProperty(OtherProperty);
if (otherPropertyInfo == null)
{
return new ValidationResult(
string.Format(
CultureInfo.CurrentCulture,
"Could not find a property named {0}.",
validationContext.ObjectType, OtherProperty));
}
// Determine whether to run [Required] validation
object actualOtherPropertyValue = otherPropertyInfo
.GetValue(validationContext.ObjectInstance, null);
if (!IsInverted && Equals(actualOtherPropertyValue, OtherPropertyValue) ||
IsInverted && !Equals(actualOtherPropertyValue, OtherPropertyValue))
{
return base.IsValid(value, validationContext);
}
return default;
}
}
用法示例:
public class Model {
public bool Subscribe { get; set; }
[RequiredIf(nameof(Subscribe), true)]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
通过这种方式,您可以获得所有标准的Required
验证功能。
注意:我使用的是 .NET 5,但我试图删除在 c# 9.0 中添加的语言功能以获得更广泛的兼容性。
我知道这个问题来自很久以前,但有人在罗伯特回答的评论部分询问如何使用不引人注目作为解决方案的一部分。
我也想要客户端验证,所以我将修改后的代码分享给 Robert 的原始代码。 除了实现IClientModelValidator
并具有附加的AddValidation
方法之外,它基本上是相同的代码。 客户端验证仍然尊重 IsInverted 属性。
实现 IClientModelValidator
public sealed class RequiredIfAttribute : ValidationAttribute, IClientModelValidator
新的 AddValidation 方法
public void AddValidation(ClientModelValidationContext context)
{
var viewContext = context.ActionContext as ViewContext;
var modelType = context.ModelMetadata.ContainerType;
var instance = viewContext?.ViewData.Model;
var model = instance?.GetType().Name == modelType.Name
? instance
: instance?.GetType()?.GetProperties().First(x => x.PropertyType.Name == modelType.Name)
.GetValue(instance, null);
object otherValue = modelType.GetProperty(this.OtherProperty)?.GetValue(model, null);
object value = modelType.GetProperty(context.ModelMetadata.Name)?.GetValue(model, null);
string displayName = context.ModelMetadata.DisplayName ?? context.ModelMetadata.Name;
string errorMessage = null;
// check if this value is actually required and validate it
if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
{
if (value == null)
{
errorMessage = this.FormatErrorMessage(displayName);
}
// additional check for strings so they're not empty
string val = value as string;
if (val != null && val.Trim().Length == 0)
{
errorMessage = this.FormatErrorMessage(displayName);
}
}
if (!string.IsNullOrWhiteSpace(errorMessage))
{
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-required", errorMessage);
}
}
完整代码
/// <summary>
/// Provides conditional validation based on related property value.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class RequiredIfAttribute : ValidationAttribute, IClientModelValidator
{
#region Properties
/// <summary>
/// Gets or sets the other property name that will be used during validation.
/// </summary>
/// <value>
/// The other property name.
/// </value>
public string OtherProperty { get; private set; }
/// <summary>
/// Gets or sets the display name of the other property.
/// </summary>
/// <value>
/// The display name of the other property.
/// </value>
public string OtherPropertyDisplayName { get; set; }
/// <summary>
/// Gets or sets the other property value that will be relevant for validation.
/// </summary>
/// <value>
/// The other property value.
/// </value>
public object OtherPropertyValue { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether other property's value should match or differ from provided other property's value (default is <c>false</c>).
/// </summary>
/// <value>
/// <c>true</c> if other property's value validation should be inverted; otherwise, <c>false</c>.
/// </value>
/// <remarks>
/// How this works
/// - true: validated property is required when other property doesn't equal provided value
/// - false: validated property is required when other property matches provided value
/// </remarks>
public bool IsInverted { get; set; }
/// <summary>
/// Gets a value that indicates whether the attribute requires validation context.
/// </summary>
/// <returns><c>true</c> if the attribute requires validation context; otherwise, <c>false</c>.</returns>
public override bool RequiresValidationContext
{
get { return true; }
}
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class.
/// </summary>
/// <param name="otherProperty">The other property.</param>
/// <param name="otherPropertyValue">The other property value.</param>
public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
: base("'{0}' is required because '{1}' has a value {3}'{2}'.")
{
this.OtherProperty = otherProperty;
this.OtherPropertyValue = otherPropertyValue;
this.IsInverted = false;
}
#endregion
public void AddValidation(ClientModelValidationContext context)
{
var viewContext = context.ActionContext as ViewContext;
var modelType = context.ModelMetadata.ContainerType;
var instance = viewContext?.ViewData.Model;
var model = instance?.GetType().Name == modelType.Name
? instance
: instance?.GetType()?.GetProperties().First(x => x.PropertyType.Name == modelType.Name)
.GetValue(instance, null);
object otherValue = modelType.GetProperty(this.OtherProperty)?.GetValue(model, null);
object value = modelType.GetProperty(context.ModelMetadata.Name)?.GetValue(model, null);
string displayName = context.ModelMetadata.DisplayName ?? context.ModelMetadata.Name;
string errorMessage = null;
// check if this value is actually required and validate it
if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
{
if (value == null)
{
errorMessage = this.FormatErrorMessage(displayName);
}
// additional check for strings so they're not empty
string val = value as string;
if (val != null && val.Trim().Length == 0)
{
errorMessage = this.FormatErrorMessage(displayName);
}
}
if (!string.IsNullOrWhiteSpace(errorMessage))
{
context.Attributes.Add("data-val", "true");
context.Attributes.Add("data-val-required", errorMessage);
}
}
/// <summary>
/// Applies formatting to an error message, based on the data field where the error occurred.
/// </summary>
/// <param name="name">The name to include in the formatted message.</param>
/// <returns>
/// An instance of the formatted error message.
/// </returns>
public override string FormatErrorMessage(string name)
{
return string.Format(
CultureInfo.CurrentCulture,
base.ErrorMessageString,
name,
this.OtherPropertyDisplayName ?? this.OtherProperty,
this.OtherPropertyValue,
this.IsInverted ? "other than " : "of ");
}
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (validationContext == null)
{
throw new ArgumentNullException("validationContext");
}
PropertyInfo otherProperty = validationContext.ObjectType.GetProperty(this.OtherProperty);
if (otherProperty == null)
{
return new ValidationResult(
string.Format(CultureInfo.CurrentCulture, "Could not find a property named '{0}'.", this.OtherProperty));
}
object otherValue = otherProperty.GetValue(validationContext.ObjectInstance);
// check if this value is actually required and validate it
if (!this.IsInverted && object.Equals(otherValue, this.OtherPropertyValue) ||
this.IsInverted && !object.Equals(otherValue, this.OtherPropertyValue))
{
if (value == null)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
// additional check for strings so they're not empty
string val = value as string;
if (val != null && val.Trim().Length == 0)
{
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
return ValidationResult.Success;
}
}
如果您已将 jquery.js、jquery.validate.js 和 jquery.validate.unobtrusive.js 脚本文件(按此顺序)包含到您的布局或 razor 视图中,这应该可以正常工作。
我写了一个简单的自定义验证属性,它非常易读。
using System;
using System.ComponentModel.DataAnnotations;
namespace some.namespace
{
public class RequiredIfAttribute : ValidationAttribute
{
public string PropertyName { get; set; }
public object Value { get; set; }
public RequiredIfAttribute(string propertyName, object value = null, string errorMessage = "")
{
PropertyName = propertyName;
Value = value;
ErrorMessage = errorMessage;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (PropertyName == null || PropertyName.ToString() == "")
{
throw new Exception("RequiredIf: you have to indicate the name of the property to use in the validation");
}
var propertyValue = GetPropertyValue(validationContext);
if (HasPropertyValue(propertyValue) && (value == null || value.ToString() == ""))
{
return new ValidationResult(ErrorMessage);
}
else
{
return ValidationResult.Success;
}
}
private object GetPropertyValue(ValidationContext validationContext)
{
var instance = validationContext.ObjectInstance;
var type = instance.GetType();
return type.GetProperty(PropertyName).GetValue(instance);
}
private bool HasPropertyValue(object propertyValue)
{
if (Value != null)
{
return propertyValue != null && propertyValue.ToString() == Value.ToString();
}
else
{
return propertyValue != null && propertyValue.ToString() != "";
}
}
}
}
你可以像这样使用它
public class Document
{
public int DocumentType{get;set;}
[RequiredIf("DocumentType", "1", ErrorMessage = "The field is required.")]
public string Name{get;set;}
[RequiredIf("DocumentType", "2", ErrorMessage = "The field is required.")]
public string Name2{get;set;}
}
我不能完全给你你所要求的,但你有没有考虑过以下内容?
public abstract class Document // or interface, whichever is appropriate for you
{
//some non-validted common properties
}
public class ValidatedDocument : Document
{
[Required]
public string Name {get;set;}
}
public class AnotherValidatedDocument : Document
{
[Required]
public string Name {get;set;}
//I would suggest finding a descriptive name for this instead of Name2,
//Name2 doesn't make it clear what it's for
public string Name2 {get;set;}
}
public class NonValidatedDocument : Document
{
public string Name {get;set;}
}
//Etc...
理由是int DocumentType
变量。 您可以将其替换为为您需要处理的每种“类型”文档使用具体的子类类型。 这样做可以让您更好地控制您的属性注释。
似乎在不同情况下只需要您的某些属性,这可能表明您的文档类试图做太多事情,并支持上述建议。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.