将参数传递给提供给属性的Type
本文关键字:Type 属性 参数传递 | 更新日期: 2023-09-27 18:15:01
我有一个用来装饰对象属性的属性。该属性标识需要对其执行验证的属性。我实际上是在实现策略模式,并将所有验证(实际上只有大约6种类型)构建到可以跨多个类使用的单个对象中。我想做的是为验证类提供参数,而不必为每个验证对象变体创建属性。
我的属性是这样的:
[AttributeUsage(AttributeTargets.Property)]
public class ValidationRuleAttribute : Attribute
{
public ValidationRuleAttribute(Type validationRule, string customFailureMessage = "")
{
if (typeof(IValidationRule).IsAssignableFrom(validationRule))
{
this.ValidationRule = string.IsNullOrEmpty(customFailureMessage)
? Activator.CreateInstance(validationRule, customFailureMessage) as IValidationRule
: Activator.CreateInstance(validationRule) as IValidationRule;
}
else
{
throw new ArgumentException(
string.Format(
"ValidationRule attributes can only be used with IValidationRule implementations. The '{0}' Tyoe is not supported.",
validationRule.Name));
}
}
public IValidationRule ValidationRule { get; private set; }
}
作为一个例子,我有一个简单的StringIsNotNull验证对象。我想对它进行扩展,允许我指定最小字符串长度要求。所以StringIsNotEmptyValidation
会变成StringHasMinimumLengthValidation
public class StringIsNotEmptyValidation : IValidationRule
{
private readonly string customErrorMessage;
public StringIsNotEmptyValidation()
{
}
public StringIsNotEmptyValidation(string customErrorMessage)
{
this.customErrorMessage = customErrorMessage;
}
public string ResultMessage { get; private set; }
public IValidationMessage Validate(System.Reflection.PropertyInfo property, IValidatable sender)
{
string value = property.GetValue(sender).ToString();
// Validate
bool isFailed = string.IsNullOrWhiteSpace(value);
if (isFailed)
{
if (string.IsNullOrEmpty(this.customErrorMessage))
{
DisplayNameAttribute displayName = property.GetCustomAttribute<DisplayNameAttribute>(true);
string errorMessage = displayName == null
? string.Format("You can not leave {0} empty.", property.Name)
: string.Format("You can not leave {0} empty.", displayName.DisplayName);
this.ResultMessage = errorMessage;
return new ValidationErrorMessage(errorMessage);
}
else
{
this.ResultMessage = this.customErrorMessage;
return new ValidationErrorMessage(customErrorMessage);
}
}
this.ResultMessage = string.Empty;
return null;
}
}
在我的模型中,我用属性和验证对象来修饰我的属性。
[RepositoryParameter(DbType.String)]
[ValidationRule(typeof(StringIsNotEmptyValidation))]
public string WorkDescription
{
get
{
return this.workDescription ?? string.Empty;
}
set
{
this.SetPropertyByReference(ref this.workDescription, value);
if (this.HasValidationMessageType<ValidationErrorMessage>(this.GetPropertyName(p => p.WorkDescription)))
{
this.Validate();
}
}
}
我想要做的是这样写我的属性用法:
[ValidationRule(new StringIsNotEmptyValidation(minimumLength: 4))]
由于不能在属性构造函数中实例化对象,因此我被迫在属性构造函数中提供如下属性:
[ValidationRule(typeof(StringIsNotEmptyValidation), minLength: 4)]
我不喜欢这样,因为如果我有一个ObjectIsNotNull
或StringIsInRange
,我将需要做两件事:
- 为每个参数变化(或批过载)创建一个新属性
- 在构造函数中设置验证规则实例,这些实例将具有不同的属性名称。
验证对象实现以下接口
public interface IValidationRule
{
string ResultMessage { get; }
IValidationMessage Validate(PropertyInfo property, IValidatable sender);
}
我不想用大量的属性来膨胀我的接口,这些属性可能会被使用,也可能不会被使用,这取决于实现它的规则。这也使得为规则对象分配属性参数变得困难。
所以我的问题是我如何为IValidationRule
具体类提供参数,而不创建多个属性类型来促进这一点?这样我就可以进行跨对象验证。传递给验证规则的PropertyInfo来自PropertyInfo的缓存。我需要保持反射的数量使用下来,否则我只是使用属性为每个规则参数和使用sender
反射找出使用的范围。
在与Corey讨论这个问题后,它确实出现在通用应用程序中支持属性,它只是缺少DataAnnotations
命名空间。为了访问属性,我必须向System添加一个using语句。反射,以便访问一系列暴露GetCustomAttribute
方法的扩展方法。它们现在是扩展方法,而不是内置于Type类中。
所以我想最后,我可以在属性中创建我的验证逻辑,而不是单独的对象。我想不出走这条路有什么缺点。
为了访问通用应用程序中的属性,您必须将System.Reflection
作为using语句包含,然后通过GetRuntimeProperties()
扩展方法进行访问。
var validationRule = this
.GetType()
.GetRuntimeProperties() // Can be GetRuntimeFields or GetRuntimeMethods as well.
.FirstOrDefault(p => p.GetCustomAttribute<IntegerInRangeAttribute>() != null);
所以这里有几个选项。
首先,也是经常使用的,是为您想要处理的每种类型的规则具有不同的属性。您已经为每个规则构建了类,所以不要使用一些封装属性来实例化它们,只需将每个规则设置为一个属性:
[StringMinLengthRule(5)]
public string SomeString { get; set; }
将验证逻辑构建到属性中——比如使用一个完成大部分工作的基本属性,调用一个虚拟方法来执行实际的验证。然后,您可以枚举规则属性并从您的验证方法调用它们。
接下来,您可以在您的属性上拥有许多不同的属性,这些属性可以在声明期间设置,以为您的各种规则提供属性:
[Validation(RuleType.StringMinLength, MinLength = 5)]
public string SomeString { get; set; }
您仍然可以在ValidationAttribute
本身中处理规则,或者在运行时创建IValidationRule
实例来处理实际的验证。不幸的是,没有什么可以阻止您添加Validation
属性,该属性为规则类型设置了错误的属性,从而导致在运行时尝试验证实例时出现错误。
终于,一些工作,但可能不应该…它有点丑:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ValidationRuleAttribute : Attribute
{
public IValidationRule ValidationRule { get; private set; }
public ValidationRuleAttribute(RuleType type, params object[] parms)
{
if (type == RuleType.NotNull)
{
if (parms.Length != 0)
throw new ArgumentException("RuleType.NotNull requires 0 parameters", "parms");
ValidationRule = new NotNullValidation();
}
if (type == RuleType.StringMinLength)
{
if (parms.Length != 1)
throw new ArgumentException("RuleType.StringMinLength requires 1 parameter", "parms");
if (!(parms[0] is int))
throw new ArgumentException("RuleType.StringMinLength requires an integer", "parms");
ValidationRule = new StringLengthValidation((int)parms[0]);
}
}
}
最大的问题是,它不会抱怨,直到你试图在运行时实例化一个类,有一个坏的Validation
属性。您的代码可以非常愉快地运行,直到它尝试创建那个坏类的实例,此时所有属性都将被实际构造,并且那些ArgumentException
开始飞行。