Quantcast
Channel: Development With A Dot
Viewing all articles
Browse latest Browse all 404

NHibernate Fluent Validation

$
0
0

Some time ago, I wrote a post on fluent validation for Entity Framework Code First. I think it is a cool concept, and I decided to bring it into NHibernate!

In a nutshell, what I want to be able to achieve is something like this:

   1: var validation = sessionFactory
   2:     .FluentlyValidate()
   3:     .Entity<SomeEntity>(x => x.SomeValue != "", "SomeValue is empty")
   4:     .Entity<AnotherEntity>(x => x.AnotherValue != 0, "AnotherValue is 0");

You can see that whenever I am about to save (update or insert) some entity of types SomeEntity or AnotherEntity, the fluent validation will occur, and an exception will be thrown if the entity does not pass. Fluent validation consists of one lambda expression – which can have several conditions – applied to an entity and returning a boolean result.

For this, I am using NHibernate’s listeners, and I am adding them dynamically through the ISessionFactory, this way it will apply to all ISessions spawned from it.

Without further delay, here is the code, first, the session factory extensions:

   1:publicstaticclass SessionFactoryExtensions
   2: {
   3:internalstaticreadonly IDictionary<GCHandle, FluentValidation> validations = new ConcurrentDictionary<GCHandle, FluentValidation>();
   4:  
   5:publicstatic FluentValidation FluentlyValidate(this ISessionFactory sessionFactory)
   6:     {
   7:         var validation = GetValidator(sessionFactory);
   8:  
   9:if (validation == null)
  10:         {
  11:             validation = Register(sessionFactory);
  12:         }
  13:  
  14:return (validation);
  15:     }
  16:  
  17:publicstaticvoid DisableFluentValidation(this ISessionFactory sessionFactory)
  18:     {
  19:         Unregister(sessionFactory);
  20:     }
  21:  
  22:internalstatic FluentValidation GetValidator(this ISessionFactory sessionFactory)
  23:     {
  24:return (validations.Where(x => x.Key.Target == sessionFactory).Select(x => x.Value).SingleOrDefault());
  25:     }
  26:  
  27:privatestaticvoid Unregister(ISessionFactory sessionFactory)
  28:     {
  29:         var validation = validations.Where(x => x.Key.Target == sessionFactory).SingleOrDefault();
  30:  
  31:if (Object.Equals(validation, null) == true)
  32:         {
  33:             validations.Remove(validation);
  34:         }
  35:  
  36:         (sessionFactory as SessionFactoryImpl).EventListeners.FlushEntityEventListeners = (sessionFactory as SessionFactoryImpl).EventListeners.FlushEntityEventListeners.Where(x => !(x is FlushEntityValidatorListener)).ToArray();
  37:     }
  38:  
  39:privatestatic FluentValidation Register(ISessionFactory sessionFactory)
  40:     {
  41:         var validation = (validations[GCHandle.Alloc(sessionFactory)] = new FluentValidation());
  42:         (sessionFactory as SessionFactoryImpl).EventListeners.FlushEntityEventListeners = (sessionFactory as SessionFactoryImpl).EventListeners.FlushEntityEventListeners.Concat(new IFlushEntityEventListener[] { new FlushEntityValidatorListener() }).ToArray();
  43:return (validation);
  44:     }
  45: }

This supports having multiple session factories, and because I am using a GCHandle to wrap them, they are not prevented from being garbage collected.

Next, the class that does the actual validation:

   1:publicsealedclass FluentValidation
   2: {
   3:privatereadonly IDictionary<Type, Dictionary<Delegate, String>> conditions = new ConcurrentDictionary<Type, Dictionary<Delegate, String>>();
   4:
   5:public FluentValidation Clear<T>()
   6:     {
   7:foreach (var type inthis.conditions.Where(x => typeof(T).IsAssignableFrom(x.Key)).Select(x => x.Key))
   8:         {
   9:this.conditions.Remove(type);
  10:         }
  11:
  12:return (this);
  13:     }
  14:  
  15:public FluentValidation Entity<T>(Func<T, Boolean> condition, String message)
  16:     {
  17:if (this.conditions.ContainsKey(typeof(T)) == false)
  18:         {
  19:this.conditions[typeof(T)] = new Dictionary<Delegate, String>();
  20:         }
  21:  
  22:this.conditions[typeof(T)][condition] = message;            
  23:  
  24:return (this);
  25:     }
  26:  
  27:publicvoid Validate(Object entity)
  28:     {
  29:         var applicableConditions = this.conditions.Where(x => entity.GetType().IsAssignableFrom(x.Key)).Select(x => x.Value);
  30:  
  31:foreach (var applicableCondition in applicableConditions)
  32:         {
  33:foreach (var condition in applicableCondition)
  34:             {
  35:                 var del = condition.Key;
  36:  
  37:if (Object.Equals(del.DynamicInvoke(entity), false) == true)
  38:                 {
  39:throw (new ValidationException(entity, condition.Value));
  40:                 }
  41:             }
  42:         }
  43:     }
  44: }

This class holds a collection of validations for an entity type. If the validation fails, it throws an instance of a custom exception class:

   1: [Serializable]
   2:publicsealedclass ValidationException : Exception
   3: {
   4:public ValidationException(Object entity, String message) : base(message)
   5:     {
   6:this.Entity = entity;
   7:     }
   8:  
   9:public Object Entity
  10:     {
  11:         get;
  12:private set;
  13:     }        
  14: }

Finally, the NHibernate listener:

   1:sealedclass FlushEntityValidatorListener : IFlushEntityEventListener
   2: {
   3:#region IFlushEntityEventListener Members
   4:  
   5:publicvoid OnFlushEntity(FlushEntityEvent @event)
   6:     {
   7:         var validator = @event.Session.SessionFactory.GetValidator();
   8:  
   9:if (validator != null)
  10:         {
  11:             validator.Validate(@event.Entity);
  12:         }
  13:     }
  14:  
  15:#endregion
  16: }

When we no longer need fluent validation, we can disable it altogether:

   1: sessionFactory.DisableFluentValidation();

Or just for a particular entity:

   1: validation.Clear<SomeEntity>();

As always, hope you like it! Do send me your comments!


Viewing all articles
Browse latest Browse all 404

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>