I love attribute validation! They can be used for a myriad of things. In .NET Core MVC we use them to validate models that come into our controllers. In one of our projects we kept running into the same thing: we need to validate a value against an array of pre-defined values. So we wrote some base validation attributes.

Validation for the win!

Here we see a model with multiple validation attributes. Required, EmailAddress and StringLengthare well known. But now I want to validate that the Label is one a list of preset values from my config. I also would like to validate that the Name is a new repository name.

using System;
using System.ComponentModel.DataAnnotations;

public class GitHubProvisioningRequest
{
    [Required, Label]
    public string Label { get; set; }

    [Required]
    public string Description { get; set; }

    [Required, TeamIdShouldExist]
    public int TeamId { get; set; }

    [Required]
    public string Tag { get; set; }

    [Required, StringLength(100, MinimumLength = 5), NewRepositoryName]
    public string Name { get; set; }

    [Required, EmailAddress]
    public string EmailAddress { get; set; }
}

A base class for “Is One Of”

We have an array of values and the validation value must be one of those values. If the value is not present in the array, we must return a validation result. Let’s capture this behaviour in a base validation attribute:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;

public abstract class IsOneOfValidationAttribute : ValidationAttribute
{
    protected abstract object[] GetValues(ValidationContext validationContext);

    protected abstract string GetInvalidValueMessage(
        object invalidValue, 
        object[] validValues);

    protected TOption GetOption<TOption>(ValidationContext validationContext)
        where TOption: class, new()
    {
        return validationContext.GetRequiredService<IOptions<TOption>>().Value;
    }

    protected override ValidationResult IsValid(
        object value, 
        ValidationContext validationContext)
    {
        var values = GetValues(validationContext);
        var exists = values.Contains(value);

        if (exists)
        {
            return ValidationResult.Success;
        }

        var msg = GetInvalidValueMessage(value, values);
        return new ValidationResult(msg);
    }
}

Implementations of this class need to provide an array of valid values (GetValues) and a validation error message (GetInvalidValueMessage).

Note: I wanted to make this a generic class to support strong typing, but C# seems to forbid it.

Validating against IOptions

I need to validate a lot agains my application configuration, that’s why I added the GetOption in the base class. It uses dependency injection to get the option. Check this example in which the label attribute:

public class LabelAttribute : IsOneOfValidationAttribute
{
    protected override object[] GetValues(ValidationContext validationContext)
    {
        var option = GetOption<ProvisioningConfiguration>(validationContext);
        return option.GitHub.Labels;
    }

    protected override string GetInvalidValueMessage(
        object invalidValue,
        object[] validValues)
    {
        var valid = String.Join(", ", validValues);
        return $"{invalidValue} is not a valid or allowed. Options are: [{valid}]";
    }
}

How about “Not One Of”?

This case is slightly different. I decided not to list all the invalid options, as in most use cases we only need to show that the value itself is invalid.

public abstract class IsNotOneOfValidationAttribute : ValidationAttribute
{
    protected abstract object[] GetValues(ValidationContext validationContext);

    protected abstract string GetInvalidValueMessage(object invalidValue);

    protected TOption GetOption<TOption>(ValidationContext validationContext)
        where TOption: class, new()
    {
        return validationContext.GetRequiredService<IOptions<TOption>>().Value;
    }

    protected override ValidationResult IsValid(
        object value,
        ValidationContext validationContext)
    {
        var values = GetValues(validationContext);
        var exists = values.Contains(value);

        if (!exists)
        {
            return ValidationResult.Success;
        }

        var msg = GetInvalidValueMessage(value);
        return new ValidationResult(msg);
    }
}

Validation against a service

Now, let’s implement the NewRepositoryNameAttribute, by inheriting from the IsNotOneOfValidationAttribute base. We’ll use dependency injection to get the IGitHubProvisioningService which we need to validate the repository name.

using Blaze.PlatformProvisioning.Resources.Base.Validators;
using Microsoft.Extensions.DependencyInjection;
using System.ComponentModel.DataAnnotations;

public class NewRepositoryNameAttribute : IsNotOneOfValidationAttribute
{
    protected override object[] GetValues(ValidationContext validationContext)
    {
        var git = validationContext.GetRequiredService<IGitHubProvisioningService>();
        return git.GetAllRepositoryNames().Result;
    }

    protected override string GetInvalidValueMessage(object invalidValue)
    {
        return $"'{invalidValue}' is already in use.";
    }
}

Note: I would not recommend the “not one of” validator for all your “value exists in database” calls.

Conclusion

With validation attributes .NET provides a powerful and extendable mechanism to influence the way objects are validated. Having some base classes will cut your development time in halve.