我有类似以下的类结构
public Class A
{
public B b;
public C c;
public string strA;
}
public Class B
{
public D d;
public string strB;
}
public Class C
{
public string strC1;
public string strC2;
}
public Class D
{
public string strD1;
public string strD2;
}
对于A类的对象,
A objA
我需要验证例如:
他们是非空的字符串。 (当然,应验证对象objA.b,objA.b.d和objA.c不为空)
我希望用类似
的方法来实现它public bool ValidatePropertiesWithList(object obj, List<string> listOfFieldNamesToValidate, out string nameOfThePropertyThatViolatesTheValidation)
因此,我想验证listOfFieldNamesToValidate中名称的属性是否为空,并使用out参数返回违反此属性的属性名称(如果有)。
我应该使用Reflection来实现此验证还是验证属性对我来说是更好的选择?
使用obj.GetType()。GetProperties()似乎是一个很好的起点,但我不知道如何处理类的层次结构。
是否可以使用属性属性标记类的属性,以便我可以优雅地删除listOfFieldNamesToValidate参数?
答案 0 :(得分:2)
使用属性名称列表或Attributes
解决了非常不同的问题:
Attributes
必然意味着有人在编译时知道需要验证的属性(在一般情况下,这个人不是你;在插件场景中思考) 。 Attributes
非常方便管理代码可伸缩性,减少依赖性和耦合;当出现更多类,属性,验证规则等时,更改验证实现可能非常容易出错。在新属性中添加简单的Attribute
相对容易且难以搞定。假设Attribute
路径是您真正想要的路径,我已经实现了一个通用案例验证器,它可以做几件非常好的事情:
Attribute
的所有属性。object
规则将应用于string
属性。请注意,这不适用于用户定义的隐含转换; int
规则不会涵盖long
规则,依此类推。可以禁用此功能。我没有对此进行过广泛的测试,但它应该运行得相当好:
[AttributeUsage(AttributeTargets.Property)]
public class ValidateAttribute: Attribute
{
}
public class Validator<TAttribute> where TAttribute : Attribute
{
private readonly Dictionary<Type, Predicate<object>> rules;
public Validator()
{
rules = new Dictionary<Type, Predicate<object>>();
}
public bool UnregisterRule(Type t) => rules.Remove(t);
public void RegisterRule<TRule>(Predicate<TRule> rule) => rules.Add(typeof(TRule), o => rule((TRule)o));
public bool Validate<TTarget>(TTarget target, IList<string> failedValidationsBag, bool onlyExactTypeMatchRules = false)
{
var valid = true;
var properties = typeof(TTarget).GetProperties().Where(p => p.GetCustomAttribute<TAttribute>() != null);
foreach (var p in properties)
{
var value = p.GetValue(target);
Predicate<object> predicate = null;
//Rule aplicability works as follows:
//
//1. If the type of the property matches exactly the type of a rule, that rule is chosen and we are finished.
// If no exact match is found and onlyExactMatchRules is true no rule is chosen and we are finished.
//
//2. Build a candidate set as follows: If the type of a rule is assignable from the type of the property,
// add the type of the rule to the candidate set.
//
// 2.1.If the set is empty, no rule is chosen and we are finished.
// 2.2 If the set has only one candidate, the rule with that type is chosen and we're finished.
// 2.3 If the set has two or more candidates, keep the most specific types and remove the rest.
// The most specific types are those that are not assignable from any other type in the candidate set.
// Types are removed from the candidate set until the set either contains one single candidate or no more
// progress is made.
//
// 2.3.1 If the set has only one candidate, the rule with that type is chosen and we're finished.
// 2.3.2 If no more progress is made, we have an ambiguous rules scenario; there is no way to know which rule
// is better so an ArgumentException is thrown (this can happen for example when we have rules for two
// interfaces and an object subject to validation implements them both.)
Type ruleType = null;
if (!rules.TryGetValue(p.PropertyType, out predicate) && !onlyExactTypeMatchRules)
{
var candidateTypes = rules.Keys.Where(k => k.IsAssignableFrom(p.PropertyType)).ToList();
var count = candidateTypes.Count;
if (count > 0)
{
while (count > 1)
{
candidateTypes = candidateTypes.Where(type => candidateTypes.Where(otherType => otherType != type)
.All(otherType => !type.IsAssignableFrom(otherType)))
.ToList();
if (candidateTypes.Count == count)
throw new ArgumentException($"Ambiguous rules while processing {target}: {string.Join(", ", candidateTypes.Select(t => t.Name))}");
count = candidateTypes.Count;
}
ruleType = candidateTypes.Single();
predicate = rules[ruleType];
}
}
valid = checkRule(target, ruleType ?? p.PropertyType, value, predicate, p.Name, failedValidationsBag) && valid;
}
return valid;
}
private bool checkRule<T>(T target, Type ruleType, object value, Predicate<object> predicate, string propertyName, IList<string> failedValidationsBag)
{
if (predicate != null && !predicate(value))
{
failedValidationsBag.Add($"{target}: {propertyName} failed validation. [Rule: {ruleType.Name}]");
return false;
}
return true;
}
}
你可以按如下方式使用它:
public class Bar
{
public Bar(int value)
{
Value = value;
}
[Validate]
public int Value { get; }
}
public class Foo
{
public Foo(string someString, ArgumentException someArgumentExcpetion, Exception someException, object someObject, Bar someBar)
{
SomeString = someString;
SomeArgumentException = someArgumentExcpetion;
SomeException = someException;
SomeObject = someObject;
SomeBar = someBar;
}
[Validate]
public string SomeString { get; }
[Validate]
public ArgumentException SomeArgumentException { get; }
[Validate]
public Exception SomeException { get; }
[Validate]
public object SomeObject { get; }
[Validate]
public Bar SomeBar { get; }
}
static class Program
{
static void Main(string[] args)
{
var someObject = new object();
var someArgumentException = new ArgumentException();
var someException = new Exception();
var foo = new Foo("", someArgumentException, someException, someObject, new Bar(-1));
var validator = new Validator<ValidateAttribute>();
var bag = new List<string>();
validator.RegisterRule<string>(s => !string.IsNullOrWhiteSpace(s));
validator.RegisterRule<Exception>(exc => exc == someException);
validator.RegisterRule<object>(obj => obj == someObject);
validator.RegisterRule<int>(i => i > 0);
validator.RegisterRule<Bar>(b => validator.Validate(b, bag));
var valid = validator.Validate(foo, bag);
}
}
当然,bag
的内容是预期的:
Foo: SomeString failed validation. [Rule: String]
Foo: SomeArgumentException failed validation. [Rule: Exception]
Bar: Value failed validation. [Rule: Int32]
Foo: SomeBar failed validation. [Rule: Bar]