views:

64

answers:

3

How would you implement validation for entity framework entities when different validation logic should be applied in certain situations?

For example, validate the entity in one way if the user is an admin, otherwise validate in a different way.

A: 

Until I hear a brighter idea, I'm doing this:

public partial class MyObjectContext
{
    ValidationContext ValidationContext { get; set; }

    partial void OnContextCreated()
    {
        SavingChanges += new EventHandler(EntitySavingChanges);
    }

    private void EntitySavingChanges(object sender, EventArgs e)
    {
        ObjectStateManager
            .GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Deleted)
            .Where(entry => entry.Entity is IValidatable).ToList().ForEach(entry =>
            {
                var entity = entry.Entity as IValidatable;
                entity.Validate(entry, ValidationContext);
            });
    }
}

interface IValidatable
{
    void Validate(ObjectStateEntry entry, ValidationContext context);
}

public enum ValidationContext
{
    Admin,
    SomeOtherContext
}

public partial class MyEntity : IValidatable
{
    public ValidationContext ValidationContext { get; set; }

    public void Validate(ObjectStateEntry entry, ValidationContext context)
    {
        // this validation doesn't apply to admins
        if (context != ValidationContext.Admin)
        {
            // validation logic here
        }
    }  
}
Ronnie Overby
+2  A: 

I put validation attributes on context-specific, dedicated edit models.

The entity has only validations which apply to all entities.

Craig Stuntz
I like it. I don't like the character minimum on comments.
Ronnie Overby
+1  A: 

Before I start talking about how to do this with VAB, let me say that you will have to really think your validation rules over. While differentiating validations between roles is possible, it does mean that the object that a user in one roles saves, is invalid for another user. This means that a user in a certain role might need to change that object before it can save it. This can also happen for the same user when it is promoted to another role. If you're sure about doing this, please read on.


This seems like a good job for Enterprise Library's Validation Application Block (VAB), since it allows validation of these complex scenario's. When you want to do this, forget attribute based validation; it simply won't work. You need configuration based validation for this to work.

What you can do using VAB is using a configuration file that holds the actual validation. It depends a bit on what the actual validation rules should be, but what you can do is create a base configuration that always holds for every object in your domain. And next create one or more configurations that contain only the extended validations. Say, for instance, that you've got a validation_base.config file, a validation_manager.config and a validation_admin.config file.

What you can do is merge those validations together depending on the role of the user. Look for instance at this example that creates three configuration sources, based on the configuration file:

var base = new FileConfigurationSource("validation_base.config");
var mngr = new FileConfigurationSource("validation_manager.config");
var admn = new FileConfigurationSource("validation_admin.config");

Now you have to merge these files into (at least) two configurations. One containing the base + manager and the other that contains the base + admin rules. While merging is not something that is supported out of the box, this article will show you how to do it. When using the code in that article, you will be able to do this:

var managerValidations = 
    new ValidationConfigurationSourceCombiner(base, mngr);

var adminValidations =
    new ValidationConfigurationSourceCombiner(base, admn);

The last thing you need to do is to wrap these validations in a class that return the proper set based on the role of the user. You can that like this:

public class RoleConfigurationSource : IConfigurationSource
{
    private IConfigurationSource base;
    private IConfigurationSource managerValidations;
    private IConfigurationSource adminValidations;

    public RoleConfigurationSource()
    {
        this.base = new FileConfigurationSource("validation_base.config");
        var mngr = new FileConfigurationSource("validation_manager.config");
        var admn = new FileConfigurationSource("validation_admin.config");

        managerValidations = 
            new ValidationConfigurationSourceCombiner(base, mngr);

        adminValidations =
            new ValidationConfigurationSourceCombiner(base, admn);
    }

    public ConfigurationSection GetSection(string sectionName)
    {
        if (sectionName == ValidationSettings.SectionName)
        {
            if (Roles.UserIsInRole("admin"))
            {
                return this.adminValidations;
            }
            else
            {
                return this.managerValidations;
            }
        }

        return null;
    }

    #region IConfigurationSource Members

    // Rest of the IConfigurationSource members left out.
    // Just implement them by throwing an exception from
    // their bodies; they are not used.

    #endregion
}

Now this RoleConfigurationSource can be created once and you can supply it when you validate your objects, as follows:

static readonly IConfigurationSource validationConfiguration =
    new RoleConfigurationSource();

Validator customerValidator =
    ValidationFactory.CreateValidator<Customer>(validationConfiguration);

ValidationResults results = customerValidator.Validate(customer);

if (!results.IsValid)
{
    throw new InvalidOperationException(results[0].Message);
}

Please note that the Validation Application Block is not an easy framework. It take some time to learn it. When your application is big enough, your specific requirements however, will justify its use. If you choose the VAB, start by reading the "Hands-On Labs" document. If you have problems, come back here at SO ;-)

Good luck.

Steven
You can read more about integrating VAB with Entity Framework here: http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=46
Steven
Thanks for the good information.
Ronnie Overby