C# 如何使用属性名列表验证类层次结构的属性

C# 如何使用属性名列表验证类层次结构的属性,c#,validation,reflection,C#,Validation,Reflection,我有如下的类结构 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 str

我有如下的类结构

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.strB
  • objA.b.D.strD2
  • objA.c.strC1
它们是非空字符串。(当然,应该验证对象objA.b、objA.b.d和objA.c是否为空)

我想用这样的方法来实现它

public bool ValidatePropertiesWithList(object obj, List<string> listOfFieldNamesToValidate, out string nameOfThePropertyThatViolatesTheValidation)
public bool validateProperties with List(对象对象对象,要验证的字段名称列表,违反验证的属性的字符串名称)
因此,我想验证listOfFieldNamesToValidate中名称为的属性是否不为空,并返回与此冲突的属性(如果有)的名称(out参数)

我应该使用反射来实现这种验证,还是验证属性对我来说是更好的选择

使用obj.GetType().GetProperties()似乎是一个很好的开始,但我不知道如何处理类的层次结构


是否可以使用属性属性标记类的属性,以便以优雅的方式摆脱ListOffieldNames以验证参数?

使用属性名称列表或
属性解决非常不同的问题:

  • 如果在编译时不知道应该验证哪些属性,则应使用名称列表;打电话的人是知道的人,因此他必须提供所需的信息
  • 使用
    属性
    必然意味着有人在编译时知道需要验证的属性(在一般情况下,这个人不是您;请考虑插件场景)<代码>属性
对于管理代码可伸缩性非常方便,减少了依赖性和耦合;当出现更多的类、属性、验证规则等时更改验证实现可能非常容易出现错误。向新属性添加一个简单的
属性
,相对来说比较容易,也很难搞糟 假设
属性
路径是您真正想要的路径,我已经实现了一个通用的案例验证器,它可以做一些漂亮的事情:

  • 它会自动验证用指定的
    属性标记的所有属性
  • 它允许您为不同的属性类型定义验证规则
  • 它不仅匹配精确的类型,在搜索适用的规则时还将使用任何有效的引用转换;例如,如果未找到具有更具体匹配项的其他规则,则
    对象
    规则将应用于
    字符串
    属性。注意,这不适用于用户定义的隐式转换;
    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;
        }
    }
    

    前一篇文章似乎是一个开始,你能让每个类处理它们自己的验证吗?这样,类A就可以调用B.Validate(),而不必知道类B的内部结构。
    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);
        }
    }
    
    Foo: SomeString failed validation. [Rule: String]
    Foo: SomeArgumentException failed validation. [Rule: Exception]
    Bar: Value failed validation. [Rule: Int32]
    Foo: SomeBar failed validation. [Rule: Bar]