Surfing in the NET, to find some NHibernate.Validator (NHV) example, I saw that there are various things not so clear about how NHV is working. In this post I’ll try to give you a more deep explication.
Class validation definition
In these examples I will use the follow simple class :
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string CreditCard { get; set; }
}
Using Attributes the definition look like the follow:
public class Customer
{
[Length(Min = 3, Max = 20)]
public string FirstName { get; set; }
[Length(Min=3, Max = 60)]
public string LastName { get; set; }
[CreditCardNumber]
public string CreditCard { get; set; }
}
Using XML mapping the configuration is :
<class name="Customer">
<property name="FirstName">
<length min="3" max="20"/>
</property>
<property name="LastName">
<length min="3" max="60"/>
</property>
<property name="CreditCard">
<creditcardnumber/>
</property>
</class>
NOTE: In this example I will use the NHV convention for XML Validation-Definition that mean the mapping file is an embedded resource, is in the same folder (namespace) of the class and its name is the name of the class followed by “.nhv.xml” (in this case Customer.nhv.xml).
Using fluent-interface configuration :
public class CustomerDef: ValidationDef<Customer>
{
public CustomerDef()
{
Define(x => x.FirstName).LengthBetween(3, 20);
Define(x => x.LastName).LengthBetween(3, 60);
Define(x => x.CreditCard).IsCreditCardNumber();
}
}
As you can see you have 3 ways to define validation constraints for a class. For each class, you must use at least one validation definition and at most two; this mean that you can even mix the “Attribute way” with one of the “External” ways (here “external” mean that the validation is defined out-side the class).
The ValidatorEngine
At first, the ValidatorEngine, is your entry-point. If you are using Attributes, you can do something like this:
public void WithOutConfigureTheEngine()
{
var customer = new Customer { FirstName = "F", LastName = "Fermani" };
var ve = new ValidatorEngine();
Assert.That(ve.IsValid(customer), Is.False);
}
What happen behind the scene is:
JITClassMappingFactory:Reflection applied for Customer
ReflectionClassMapping:For class Customer Adding member FirstName to dictionary with attribute LengthAttribute
ReflectionClassMapping:For class Customer Adding member LastName to dictionary with attribute LengthAttribute
ReflectionClassMapping:For class Customer Adding member CreditCard to dictionary with attribute CreditCardNumberAttribute
As you can see NHV is investigating the class to know all attributes. Now the same example using two instances of ValidatorEngine
var customer = new Customer { FirstName = "F", LastName = "Fermani" };
var ve1 = new ValidatorEngine();
Assert.That(ve1.IsValid(customer), Is.False);
var ve2 = new ValidatorEngine();
Assert.That(ve2.IsValid(customer), Is.False);
What happen behind is:
JITClassMappingFactory:Reflection applied for Customer
ReflectionClassMapping:For class Customer Adding member FirstName to dictionary with attribute LengthAttribute
ReflectionClassMapping:For class Customer Adding member LastName to dictionary with attribute LengthAttribute
ReflectionClassMapping:For class Customer Adding member CreditCard to dictionary with attribute CreditCardNumberAttribute
JITClassMappingFactory:Reflection applied for Customer
ReflectionClassMapping:For class Customer Adding member FirstName to dictionary with attribute LengthAttribute
ReflectionClassMapping:For class Customer Adding member LastName to dictionary with attribute LengthAttribute
ReflectionClassMapping:For class Customer Adding member CreditCard to dictionary with attribute CreditCardNumberAttribute
Ups… NHV is investigating the same class two times.
Now again the same example but using only one ValidatorEngine instance:
var customer1 = new Customer { FirstName = "F", LastName = "Fermani" };
var ve = new ValidatorEngine();
Assert.That(ve.IsValid(customer1), Is.False);
var customer2 = new Customer { FirstName = "Fabio", LastName = "Fermani" };
Assert.That(ve.IsValid(customer2), Is.True);
Here we are validating two instances of Customer class using the same ValidatorEngine and what happen behind is:
JITClassMappingFactory:Reflection applied for Customer
ReflectionClassMapping:For class Customer Adding member FirstName to dictionary with attribute LengthAttribute
ReflectionClassMapping:For class Customer Adding member LastName to dictionary with attribute LengthAttribute
ReflectionClassMapping:For class Customer Adding member CreditCard to dictionary with attribute CreditCardNumberAttribute
As you can see, the class Customer, was investigated only one time, NHV are using reflection only one time.
Conclusion: For performance issue, the ValidatorEngine instance should have the same lifecycle of your application.
The XML convention
As you probably know, I like, very much, all framework complying with rule “DON’T TOUCH MY CODE” (more quite “no invasive framework”). With NHV you can define an “external” XML file as validation definition. The convention, come in place, when you configure the ValidatorEngine to use an “External” source for validation-definitions. The configuration in the application config file is:
<nhv-configuration xmlns='urn:nhv-configuration-1.0'>
<property name='default_validator_mode'>UseExternal</property>
</nhv-configuration>
Given the above first class and embedding the file Customer.nhv.xml in the same namespace, I’m going to run the follow test:
var ve = new ValidatorEngine();
ve.Configure();
var customer1 = new Customer { FirstName = "F", LastName = "Fermani" };
Assert.That(ve.IsValid(customer1), Is.False);
var customer2 = new Customer { FirstName = "Fabio", LastName = "Fermani" };
Assert.That(ve.IsValid(customer2), Is.True);
What happen behind the scene is:
JITClassMappingFactory: - XML convention applied for Customer
XmlClassMapping: - Looking for rules for property : FirstName
XmlClassMapping: - Adding member FirstName to dictionary with attribute LengthAttribute
XmlClassMapping: - Looking for rules for property : LastName
XmlClassMapping: - Adding member LastName to dictionary with attribute LengthAttribute
XmlClassMapping: - Looking for rules for property : CreditCard
XmlClassMapping: - Adding member CreditCard to dictionary with attribute CreditCardNumberAttribute
As you can see, this time, the JITClassMappingFactory (JIT = Just In Time), are using XmlClassMapping instead ReflectionClassMapping. Again the validation definition investigation will be done only one time per ValidatorEngine instance.
If you use XML you can even work without use the convention; in this case you must provide a more complete nhv-configuration section declaring where are mappings files.
The Fluent-Interface
Details about configuration via fluent-interface are available here. To complete the example series, the test is this:
var config = new FluentConfiguration();
config
.SetDefaultValidatorMode(ValidatorMode.UseExternal)
.Register<CustomerDef, Customer>();
var ve = new ValidatorEngine();
ve.Configure(config);
var customer1 = new Customer { FirstName = "F", LastName = "Fermani" };
Assert.That(ve.IsValid(customer1), Is.False);
var customer2 = new Customer { FirstName = "Fabio", LastName = "Fermani" };
Assert.That(ve.IsValid(customer2), Is.True);
What happen behind is:
OpenClassMapping:- For class Customer Adding member FirstName to dictionary with attribute LengthAttribute
OpenClassMapping:- For class Customer Adding member LastName to dictionary with attribute LengthAttribute
OpenClassMapping:- For class Customer Adding member CreditCard to dictionary with attribute CreditCardNumberAttribute
StateFullClassMappingFactory:- Adding external definition for Customer
Here the JITClassMappingFactory don’t is working; NHV is using the StateFullClassMappingFactory during configuration, the JITClassMappingFactory will come in play only after configuration-time. Again the validation definition investigation will be done only one time per ValidatorEngine instance.
The SharedEngineProvider
Perhaps this is the real motivation of this post. As I said above, the ValidatorEngine, should have the same lifecycle of your application, the validation is a cross-cutting-concern and you need to use it from different tiers. In my opinion the better definition of SharedEngineProvider is :
The SharedEngineProvider is the service locator for the ValidatorEngine.
If you are using NHibernate.Validator, especially with its integration with NHibernate, you should define an implementation of ISharedEngineProvider to ensure that, in all your tiers, you are using exactly the same constraints and to avoid more than one ValidatorEngine instances.
The interface is really trivial:
/// <summary>
/// Contract for Shared Engine Provider
/// </summary>
public interface ISharedEngineProvider
{
/// <summary>
/// Provide the shared engine instance.
/// </summary>
/// <returns>The validator engine.</returns>
ValidatorEngine GetEngine();
}
To configure the SharedEngineProvider you can use the application config or the NHibernate.Validator.Cfg.Environment class before any other task (regarding NHV). Any other configuration will be ignored (in fact you don’t have anything to configure the SharedEngineProvider trough FluentConfiguration).
A good way to implements a SharedEngineProvider is using your preferred IoC container, or using CommonServiceLocator. The SharedEngineProvider is used, where available, by the two listeners for NHibernate integration. A generic configuration look like:
<nhv-configuration xmlns='urn:nhv-configuration-1.0'>
<shared_engine_provider class='NHibernate.Validator.Event.NHibernateSharedEngineProvider, NHibernate.Validator'/>
</nhv-configuration>
You should change the class if you want use your own implementation of ISharedEngineProvider. The usage of your own implementation is strongly recommended especially in WEB and even more if you are using an IoC container.
Lovely post Fabio.
ReplyDeleteHowever two parts are still not clear for me:
1. An example implementation of ISharedEngineProvider
2. How is the integration with NHibernate working?
1. Available inside NHV source NHibernate.Validator.Event.NHibernateSharedEngineProvider as showed above.
ReplyDelete2. You can export constraint to DLL to allow NH the creation of RDBMS constraints during SchemaExport. The second integration is provided by the two listeners: and invalid object can't be persisted.
There is one more integration... NHV know how work NH with proxies and collection ;)
Awesome post Fabio, thank you very much. I'm just struggling with validation tasks and this help me a lot.
ReplyDeleteWhy not a post about validation rules applied to whole classes?. I'm not sure I'm following the best path...
Thank you for all your work. It's fantastic.
@Kash
ReplyDeleteWhat do you mean with "rules applied to whole classes" ?
Things like:
ReplyDelete[BusinessRule()]
EntityClass
{
...
}
¿Is this a good way to implement business rules?
Ok, I will.
ReplyDeleteThanks.
ReplyDeleteHi, Fabio. Can you tell me is the NHV validation rules more powerful than XML Schema validation rules? I want to know if it is possible to obtain HNV rules from the rules of XML Schema, and what of fasets will be ignored or another restrictions.
ReplyDeleteThe set of rules is the same.
ReplyDeleteNHV has a set of rules, you can define the validation of your entities in 3 ways: Attributes, XML, Fluent-Interface.
In fluent-interface I tried to provide "more clear names" of the same set of rules, nothing more.
Hi,
ReplyDeleteFabio you should think about writing a book about NHIbernate and get paid for it. I am not using NHibernate because i cannot find information about how to do things with it. I did not find a good article of how Nhibernate fits in a multi tier scenario. The ISession hard coupling is too much for me...
The book "NHibernate in Action" is not enough for you ?
ReplyDeleteAbout "ISession": I really don't know what you are talking about, are you sure you know NH ?
I think that NHibernateSharedEngineProvider is not thread safe but I'm not really-really sure.
ReplyDelete@Jo
ReplyDeleteIn general it not need to be thread safe... static is enough (thread safe is the ValidatorEngine).
BTW re-read the last 2 lines of the post.
Yes I've implemented a SharedEngine to work with my LinFu ServiceContainer container.
ReplyDeletePreviously I've added in this way:
....AddService(typeof(ValidatorEngine), LifecycleType.Singleton)
BTW; I've noticed that S#arp Architecture is using CommonServiceLocator for SharedEngine.
Philip sent me the implementation of CommonServiceLocator for LinFu, but should be available in LinFu site too.
ReplyDeleteThanks guys for this great framework. I wanted to go ahead and share some of my findings since, when I was researching how this worked, I couldn't find any resources (besides diving into the source code)
ReplyDelete1. If you want the pre update/delete NHV listeners to ge attached to NHibernate, you need to do something like this:
NHibernate.Cfg.Configuration cfg = new NHibernate.Cfg.Configuration();
ValidatorInitializer.Initialize(cfg)
Plus, the autoregister_listeners property in the nhv config needs to be true.
2. If you want these listeners to use the same instance of the validator engine, you need to specify the shared_engine_provider property in nhv config file.
My comments assume you are using the provided example implementation NHibernate.Validator.Event.NHibernateSharedEngineProvider.
Note that the nhv config section needs to exist in the App/Web.config (no external file), otherwise this property is ignored... (not 100% sure why this was coded this way)
I'm still trying to understand how/why you need the IoC setup with it. Do you inject the ISharedEngineProvider? if so, would you have to do it in code this way?:
NHibernate.Validator.Enviroment.SharedEngineProvider = myContainer.Create<ISharedEngineProvider>();
Or do you inject the ValidationEngine in the ISharedEngineProvider?
...
ValidationEngine GetEngine()
{
return myContainer.CreateValidationEngine>(LifeCycle.Singleton);
}
Hopefully this clears up some things for people trying to get NHValidator and NHibernate to work together
Is it possible to configure NHibernate.Validator to use the constraints (nullability, length, etc) defined in my hbm.xml files ? If not, doesn't this lead to some unnecessary and duplicated configuration?
ReplyDeleteI believe some duplication is unavoidable
ReplyDeleteIf you are using NHV you should think in the inverse. Is the Validator that inject rules to the persistent layer and not viceversa.
ReplyDeleteI'm not using NHV (yet). I'm asking because since I've got dozens of classes/tables properly mapped, I really would like that NHV read those basic constraints defined in my hbms. Of course other rules would need to be specified, and I would complement them using attributes. When I needed to change a column length there would be only one place to change. So... would be possible to do the inverse? Use the NHV constraints as a source for the mappings?
ReplyDeleteThanks in advance!
ValidatorInitializer.Initialize(nhCfg)
ReplyDeleteif the conf. prop. apply_to_ddl is true, NHV will apply all constraints to nh ddl information.
Thank you Fabio! I'm gonna try that.
ReplyDeleteHi Fabio, Since you seem quite knowlegable on NHV :) - do you know how to pass a resource manager to NHV, so that I can pass keys into the attributes, and it will use my defined validation error messages? If you have time, please take a look at the full details of my issue here on stack overflow (question #1560625). Thanks! Sosh
ReplyDeleteThe link ?
ReplyDeleteSorry, for some reason this website wont let me paste the url into this comment box :/ But go to www.stackoverflow.com and enter 1560625 into the search box. Thanks!
ReplyDelete