Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom advanced entity validation with Dynamic Data

I'm looking for a solution to perform some custom entity validation (which would require database access, cross-member validation...) when the user saves its changes in a Dynamic Data screen, with Entity Framework.
The validation is more complex than what I can do with the attributes (it requires an access to the database, etc.)

Can you intercept the SaveChanges call ?
I tried to override ValidateEntity in the DbContext object, but Dynamic Data doesn't seem to call it (probably because it's using the internal ObjectContext, not sure why), and overriding SaveChanges doesn't help either.
I don't see any event that I could subscribe to...

The documentation should help :

Customize validation for an individual data field by overriding the OnValidate method or handling the Validate event, which are invoked when any data field is changed. This approach lets you add validation and business logic for an individual field. This approach is more general than adding validation for an individual field. It is useful when the same validation logic can be applied to more than one data field. It also lets you perform validation checks that involve multiple fields.

But I'm using POCO Entity Framework 6 classes so there's no OnValidate method to override, and from what I read this is for LinqToSql, and I can't find the Validate event they mention.

I tried to subscribe to the SavingChanges event of the inner ObjectContext in the constructor of my DbContext, to call manually the ValidateEntity, but I don't know what to do with the result. If I throw a DbEntityValidationException (or a ValidationException like suggested in this article), ASPNET handle it like any exception (yellow screen).

Implementing IValidatableObject doesn't work either.

I also tried implementing my own DynamicValidator to see what happens, but with no success, it seems to handle the exception (if I override ValidateException, and put a breakpoint, I see it) but it's still bubbled up to the default error handler and displays a yellow screen. I must be missing something.

So, how should you perform complex validation (cross-field, with queries, etc.) on entities before saving in Dynamic Data / EF ?

like image 617
Julien N Avatar asked Apr 28 '16 08:04

Julien N


2 Answers

I would argue that logic like you are trying to perform doesn't belong at such a level in your architecture. Let the database enforce the constraints it is supposed to, like foreign keys etc., and have your business logic a layer above. For example, on your entity you are wanting to validate you could add a IsValidForAddOrUpdate() method, which contains the logic you'd put in your validators anyway. Then just utilise the new methods:

if (entity.IsValidForAddOrUpdate())
{
    db.Set<Entity>().Add(entity);
    db.SaveChanges()
}
else throw new DbValidationException("Entity failed validation due to rule xyz.");
like image 115
James Avatar answered Nov 14 '22 10:11

James


One way how to achive this could be implementing IDataErrorInfo interface on your entities like this:

public partial class MyEntity : IDataErrorInfo
{
    public MyEntity()
    {
    }

    ...

    #region IDataErrorInfo Members
    public string Error
    {
        get { throw new NotImplementedException(); }
    }
    public string this[string propertyName]
    {
        get
        {
            //Custom Validation logic
            return  MyValidator.ValidateProperty(this, propertyName);
        }
    }
    #endregion  
}

To access current DBContext from IDataErrorInfo methods you can use this answer. Then override your Context's SaveChanges method:

    public override int SaveChanges()
    {
        this.ObjectContext.DetectChanges();

        // Get all the new and updated objects
        var objectsToValidate =
        ChangeTracker.Entries().Where(x => x.State == EntityState.Modified || x.State == EntityState.Added).
        Select(e => e.Entity);

        // Check each object for errors
        foreach (var obj in objectsToValidate)
        {
            if (obj is IDataErrorInfo)
            {
                // Check each property
                foreach (var property in obj.GetType().GetProperties())
                {
                    var columnError = (obj as IDataErrorInfo)[property.Name];
                    if (columnError != null) {
                    //Handle your validation errors
                    throw new DbEntityValidationException(columnError); }
                }
            }
        }

        return base.SaveChanges();
    }

See also this answer to make it work with DataAnnotations.

You wrote:

If I throw a DbEntityValidationException (or a ValidationException like suggested in this article), ASPNET handle it like any exception (yellow screen).

See this answer. When you call SaveChanges you need to catch DbEntityValidationException (or ValidationException), if you do not catch them to process them within your Controller, they are processed by the default error handler.

Or you can use DynamicValidator control to catch ValidationExceptions:

    <!-- Capture validation exceptions -->
    <asp:DynamicValidator ID="ValidatorID" ControlToValidate="GridView1" 
        runat="server" /> 

The problem with DynamicValidator is that it requires ControlToValidate property and it catches only exceptions coming from that control. Also exceptions encapsulated within other exceptions can crate problems. There is a workaround - you can inherit from DynamicValidator and override its ValidateException method see this blog.

See this article.

like image 42
Vojtěch Dohnal Avatar answered Nov 14 '22 10:11

Vojtěch Dohnal