Home Validation strategy
Reply: 2

Validation strategy

hina10531
1#
hina10531 Published in 2017-12-07 15:23:38Z

I'm trying to build a series of attribute classes to make it easier for our development team to validate objects. The objects are POCO classes like this.

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

I want to decorate this model with a custom attribute.

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

Then I would create my own class implementing ValidationAttribute, the base class in .NET Framework, which belongs to System.ComponentModel.DataAnnotations.

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

And then I can validate the User model whenever I want by making the set of instances like ValidationContext, List<ValidationResult>.

But in an enterprise environment, problems just can't be solved by a specific class. My validation scenario requires more complex and more flexible ways. Imagine that one of the required validation scenarios would something like this.

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; }
}

Then I would need to make another attribute class

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;
        }
    }
}

But the problem is that there are a lot of other models that have a nested list items. Try to imagine the time when I want to reuse the MustHaveMoreThanOneItem in one of the other models like...

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

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

You already know that it's not going to work because it was strongly typed only for List<Client>. So I decided to use Generic there to solve this problem.

But to my disappointment, the _Attribute interface doesn't support Generic. There's no additional implementation like _Attribute<T> : Attribute and therefore, no ValidationAttribute<T> alas!! I just cannot use Generic here !!

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

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

So I made a conclusion that Attribute must have been designed for a fixed set of validations like email format, card format, null check, and etc IMAO.

But I still want to use an attribute and give a lot of flexibilities in it to prevent the duplicated, verbose validation codes like this.

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 ) ...
.
.
.

I want to pose two questions here.

  1. If Attirbute supports Generic, this problem will be solved?
  2. Is there any way to implement Generic Attribute? in order to use [MustHaveMoreThanOneItem<Employee>] annotation on a class member?
stuartd
2#
stuartd Reply to 2017-12-07 15:41:30Z

You can generically check any object that implements IEnumerable like this:

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;
    }
}
Jason W
3#
Jason W Reply to 2017-12-07 16:25:40Z

C# by definition does not support generic type attributes, although this has been requested actively for a long time:

  • https://github.com/dotnet/roslyn/issues/953
  • https://github.com/dotnet/csharplang/issues/124

However, you can still inject a type into a validation attribute via constructor. You then can use reflection or whatever you need to define your custom validation criteria.

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;
    }
}

Now this allows you to use something similar to your goal:

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

    [MustHaveMoreThanOneItem(typeof(Employee))]
    public IList<Employee> { get; set; }
}
You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.311127 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO