验证策略

时间:2017-12-07 15:23:38

标签: c# .net generics data-annotations

我正在尝试构建一系列属性类,以便我们的开发团队更轻松地验证对象。对象是像这样的POCO类。

public class User
{
    public string Name { get; set; }
    public string Company { get; set; }
}

我想用自定义属性修饰此模型。

public class User
{ 
    [MustHaveValue]
    public string Name { get; set; }
    public string Company { get; set; }
}

然后我将创建自己的类,实现ValidationAttribute,这是.NET Framework中的基类,属于System.ComponentModel.DataAnnotations

public class MustHaveValueAttribute : ValidationAttribute
{
    .
    .
    public override IsValid(object value) 
    {
        // validation logic.
    }
}

然后我可以通过制作UserValidationContext等实例集来随时验证List<ValidationResult>模型。

但在企业环境中,问题不能通过特定的类来解决。我的验证方案需要更复杂,更灵活的方法。想象一下,所需的验证方案之一就是这样的。

public class User
{
    public string Name { get; set; }
    public string Company { get; set; }

    // Check if an item exists in this list.
    [MustHaveMoreThanOneItem]
    public IList<Client> Clients { get; set; }
}

然后我需要创建另一个属性类

public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
    .
    .
    public override IsValid(object value) 
    {
        // Let's assume this value is List<Client> for now.
        // I know the exact type, so I'm going to cast it to List<Client> without further considerations
        List<Client> clients = value as List<Client>;
        if(clients.Count > 0) {
            return true;
        } else {
            return false;
        }
    }
}

但问题是,还有很多其他模型都有嵌套列表项。试着想象一下我想在其他模型中重用MustHaveMoreThanOneItem的时间,比如......

public class Department
{ 
    public string Name { get; set; }

    [MustHaveMoreThanOneItem]
    public IList<Employee> { get; set; }
}

您已经知道它无法正常工作,因为它仅针对List<Client>进行了强类型输入。所以我决定用Generic来解决这个问题。

但令我失望的是,_Attribute界面并不支持Generic。没有像_Attribute<T> : Attribute那样的额外实施,因此,没有ValidationAttribute<T>唉!!我不能在这里使用Generic !!

public class Department
{ 
    public string Name { get; set; }

    // No way to use this syntax.
    [MustHaveMoreThanOneItem<Employee>]
    public IList<Employee> { get; set; }
}

所以我得出结论,Attribute必须是针对一组固定的验证而设计的,例如电子邮件格式,卡片格式,空格检查等IMAO。

但是我仍然想要使用一个属性并在其中提供很多灵活性,以防止像这样重复,冗长的验证代码。

if(model.Clients.Count > 0) ...
if(model.Name != null) ...
if(model.Clients.GroupBy(x => x.Country == Country.USA).Count >= 1) ...
if(model.Clients.Where(x => x.CompanyName == Company.Google).ToList().Count > 1 ) ...
.
.
.

我想在这里提出两个问题。

  1. 如果Attirbute支持Generic,这个问题会解决吗?
  2. 有没有办法实施Generic Attribute才能使用 关于班级成员的[MustHaveMoreThanOneItem<Employee>]注释?

2 个答案:

答案 0 :(得分:2)

您可以通常检查实现IEnumerable的任何对象:

public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
    public override bool IsValid(object value) 
    {
        // omitted null checking
        var enumerable = value as IEnumerable;
        var enumerator = enumerable.GetEnumerator();
        if (!enumerator.MoveNext())
        {
            return false;
        }

        if (!enumerator.MoveNext())
        {
            return false;
        }

        return true;
    }
}

答案 1 :(得分:2)

根据定义,C#不支持泛型类型属性,尽管这已被长期主动请求:

但是,您仍然可以通过构造函数将类型注入验证属性。然后,您可以使用反射或任何您需要的方法来定义自定义验证标准。

public class MustHaveMoreThanOneItemAttribute : ValidationAttribute
{
    public Type EnumerableType { get; }
    public MustHaveMoreThanOneItemAttribute(Type t)
        => this.EnumerableType = typeof(ICollection<>).MakeGenericType(t);
    public override bool IsValid(object value)
    {
        var count = this.EnumerableType.GetProperty("Count").GetValue(value) as int?;
        return (count ?? 0) > 1;
    }
}

现在,您可以使用类似于目标的内容:

public class Department
{ 
    public string Name { get; set; }

    [MustHaveMoreThanOneItem(typeof(Employee))]
    public IList<Employee> { get; set; }
}