Entity validaton using Custom Data Annotation attributes

Ok. Today I have extended the validation part a bit. The code is about 90% the same from the post I wrote yesterday (Entity framework 4 – Part 5 – Validation using Data Annotations). But I have now added some custom data annotation attributes and made it possible for the services to add custom validation of the entities.

I will not show all the code since much of it is covered in Entity framework 4 – Part 5 – Validation using Data Annotations and as always, you can download a complete code example.

The EntityValidator
It uses the builtin Validator and ValidationResult found in System.ComponentModel.DataAnnotations. It validates sent entities by looking at your entity for validation attributes. I have now also made it possible to inject a Func, which lets you perform custom validation. What I medan by this is that you can provide a func that generates validation results that gets merged into the validation results generated by the validation attributes. The Func takes the Entity and a bool as in-params. The bool contains true if the validation attributes resulted in a valid entity. The result of the Func should be an IEnumerable, which will be merged by the validationresults generated by the validation attributes.

public class EntityValidator<T> where T : IEntity
{
    public EntityValidationResult Validate(T entity, Func<T, bool, IEnumerable<ValidationResult>> customValidation = null)
    {
        var validationResults = new List<ValidationResult>();
        var vc = new ValidationContext(entity, null, null);
        var isValid = Validator.TryValidateObject(entity, vc, validationResults, true);

        if (customValidation != null)
            validationResults.AddRange(customValidation(entity, isValid));

        return new EntityValidationResult(validationResults);
    }
}

The Service
To ease things in my services I have implemented a helper method in a base-class which my services extends. This method will only invoke the customvalidation Func if the validation attributes haven’t generated an invalid entity.

public abstract class Service
{
    protected IEntityStore EntityStore { get; private set; }

    protected Service(IEntityStore entityStore)
    {
        EntityStore = entityStore;
    }

    protected EntityValidationResult ValidateEntity<T>(T entity, Func<T, IEnumerable<ValidationResult>> customValidation = null)
        where T : IEntity
    {
        Func<T, bool, IEnumerable<ValidationResult>> customValidationProxy = null;
        
        if (customValidation != null)
            customValidationProxy = (e, isValid) => isValid ? customValidation(e) : null;

        return new EntityValidator<T>().Validate(entity, customValidationProxy);
    }
}

In my Security-service I provide some custom validation to check if the Email isn’t allready taken, by providing a customvalidation Func. I also make use of some extension methods to get access to named queries for my UserAccount entities.

public class SecurityService : Service, ISecurityService
{
    public SecurityService(IEntityStore entityStore)
        : base(entityStore)
    {
    }

    public ServiceResponse<UserAccount> SetupNewUserAccount(UserAccount userAccount)
    {
        var validationResult = ValidateEntity<UserAccount>(userAccount, CustomValidationForSettingUpNewAccount);

        if (!validationResult.HasViolations)
        {
            EntityStore.AddEntity(userAccount);
            EntityStore.SaveChanges();
        }

        return new ServiceResponse<UserAccount>(userAccount, validationResult);
    }

    private IEnumerable<ValidationResult> CustomValidationForSettingUpNewAccount(UserAccount userAccount)
    {
        var violations = new List<ValidationResult>();

        var emailIsTaken = EntityStore.EmailIsTakenByOther(userAccount.Username, userAccount.Email);
        if (emailIsTaken)
            violations.Add(new ValidationResult("Email is allready taken."));

        return violations;
    }
}

Named queries – Extension methods to my Entitystore
Since I don’t use a custom implementaion of a Entitystore for the application (although there is one provided in the example code), I have implemented my specific entity queries as extension methods. So if I want access to e.g. specific queries for my useraccounts, I just import the namespace where they are located (Sds.Christmas.Storage.Queries.UserAccounts).

using Sds.Christmas.Storage.Queries.UserAccounts;

public static class UserAccountQueries
{
    public static bool EmailIsTakenByOther(this IEntityStore entityStore, string username, string email)
    {
        return
            entityStore.Query<UserAccount>().Where(
                u =>
                    u.Username.Equals(username, StringComparison.InvariantCultureIgnoreCase) &&
                    u.Email.Equals(email, StringComparison.InvariantCultureIgnoreCase)).Count() > 0;
    }
}

Custom data annotaions
Since my UserAccount contains an Email I have created a custom Email-validationattribute and applied it to the Email-property. The errormessages are retrieved via resx-files.

[Serializable]
public class UserAccount
    : Entity
{
    [Required(AllowEmptyStrings = false, ErrorMessageResourceName = "UserAccountRequiredUsername", ErrorMessageResourceType = typeof(ModelValidationMessages))]
    [StringRange(MinLength=5, MaxLength=20, ErrorMessageResourceName = "UserAccountInvalidLength", ErrorMessageResourceType = typeof(ModelValidationMessages))]
    public virtual string Username { get; set; }

    [Required(AllowEmptyStrings = false, ErrorMessageResourceName = "UserAccountRequiredPassword", ErrorMessageResourceType = typeof(ModelValidationMessages))]
    public virtual string Password { get; set; }

    [Required(AllowEmptyStrings = false, ErrorMessageResourceName = "UserAccountRequiredEmail", ErrorMessageResourceType = typeof(ModelValidationMessages))]
    [Email(ErrorMessageResourceName = "UserAccountEmailHasInvalidFormat", ErrorMessageResourceType = typeof(ModelValidationMessages))]
    public virtual string Email { get; set; }
}

[Serializable]
public class EmailAttribute : RegexAttribute
{
    public EmailAttribute() : base(@"^[\w-\.]{1,}\@([\w]{1,}\.){1,}[a-z]{2,4}$", RegexOptions.IgnoreCase)
    {}
}

[Serializable]
public class RegexAttribute : ValidationAttribute
{
    public string Pattern { get; set; }
    public RegexOptions Options { get; set; }

    public RegexAttribute(string pattern, RegexOptions options = RegexOptions.None)
    {
        Pattern = pattern;
        Options = options;
    }

    public override bool IsValid(object value)
    {
        return IsValid(value as string);
    }

    public bool IsValid(string value)
    {
        return string.IsNullOrEmpty(value) ? true : new Regex(Pattern, Options).IsMatch(value);
    }
}

If you now try to add two different useraccounts with the same Email, you will not succed.

//Setup new useraccount using Service
var userAccount = SetupNewUserAccount();
var userAccount2 = SetupNewUserAccount(); //=> Gives Email is allready taken error.

That’s it. Don’t forget to download and explore the code.

//Daniel

About these ads

One thought on “Entity validaton using Custom Data Annotation attributes

  1. Pingback: Extend IQueryable instead of a certain dataprovider – more decoupled code « Daniel Wertheim

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s