Property validation with attributes

NHibernate Validator provides data validation for classes. In this recipe, we will show you how to use NHibernate Validator attributes to validate your entities.

Getting ready

Complete the Eg.Core model and mappings from Chapter 2, Models and Mappings.

How to do it…

  1. Create a new class library project named Eg.AttributeValidation.
  2. Copy the Eg.Core model and mappings from Chapter 2, Models and Mappings to this new project.
  3. Change the namespace and assembly references in the mappings to Eg.AttributeValidation.
  4. Change the namespaces for the entity classes to Eg.AttributeValidation.
  5. Install the NHibernate Validator package using NuGet Package Manager Console by running the following command:
    Install-Package NHibernate.Validator -Project Eg.AttributeValidation
    
  6. Create a new attribute class named NotNegativeDecimalAttribute with the following code:
    [Serializable]
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
        public class NotNegativeDecimalAttribute 
        : DecimalMinAttribute
    {
        public NotNegativeDecimalAttribute()
        : base(0M)
         {
         }
    }
  7. Open Product.cs and add the following attributes:
    public class Product : Entity
    {
       [NotNull, Length(Min=1, Max=255)]
       public virtual string Name { get; set; }
    
       [NotNullNotEmpty]
       public virtual string Description { get; set; }
    
       [NotNull, NotNegativeDecimal]
       public virtual Decimal UnitPrice { get; set; }
    }
  8. Create a new console project named Eg.AttributeValidation.Runner.
  9. Install NHibernate, NHibernate Validator, and log4net packages to the Eg.AttributeValidation.Runner project using NuGet Package Manager Console by running the following commands:
    Install-Package NHibernate
    Install-Package NHibernate.Validator
    Install-Package log4net
    
  10. Set up an App.config with the standard log4net and hibernate-configuration sections, just as we did in the Configuring NHibernate with App.config and Configuring NHibernate logging recipes in Chapter 2, Models and Mappings.
  11. In the <configSections> element, add an additional section declaration named nhv-configuration with the following xml:
    <section name="nhv-configuration"
    type="NHibernate.Validator.Cfg.ConfigurationSectionHandler, NHibernate.Validator" />
  12. Add the <nhv-configuration> section with the following xml:
    <nhv-configuration xmlns="urn:nhv-configuration-1.0">
       <property name='apply_to_ddl'>true</property>
       <property name='autoregister_listeners'>true</property>
       <property name='default_validator_mode'> 
       OverrideExternalWithAttribute</property>
       <mapping assembly='Eg.AttributeValidation'/>
    </nhv-configuration>
  13. Add a new class named BasicSharedEngineProvider using the following code:
    public class BasicSharedEngineProvider : 
    ISharedEngineProvider
      {
        private readonly ValidatorEngine ve;
        public BasicSharedEngineProvider(ValidatorEngine ve)
         {
          this.ve = ve;
         }
        public ValidatorEngine GetEngine()
         {
            return ve;
         }
        public void UseMe()
         {
           Environment.SharedEngineProvider = this;
         }
      }
  14. In Program.cs, use the following code:
    class Program
    {
      static void Main(string[] args)
      {
        XmlConfigurator.Configure();
        var log = LogManager.GetLogger(typeof(Program));
    
        SetupNHibernateValidator();
    
        var cfg = new Configuration().Configure();
        cfg.Initialize();
    
        var sessionFactory = cfg.BuildSessionFactory();
    
        var schemaExport = new SchemaExport(cfg);
        schemaExport.Execute(true, true, false);
    
        var junk = new Product
                     {
                       Name = "Spiffy Junk",
                       Description = "Stuff we can't sell.",
                       UnitPrice = -1M
                     };
    
        using (var session = sessionFactory.OpenSession())
        using (var tx = session.BeginTransaction())
         {
       try
         {
          session.Save(junk);
          tx.Commit();
         }
       catch (InvalidStateException validationException)
        {
          var errors =validationException.GetInvalidValues();
          foreach (var error in errors)
            {
           log.ErrorFormat("Error with property {0}: {1}",
              error.PropertyName,
              error.Message);
            }
          tx.Rollback();
        }
      }
    }
    
      private static ValidatorEngine GetValidatorEngine()
      {
        var validatorEngine = new ValidatorEngine();
        validatorEngine.Configure();
        return validatorEngine;
      }
    
      private static void SetupNHibernateValidator()
      {
        var validatorEngine = GetValidatorEngine();
        new BasicSharedEngineProvider(validatorEngine).UseMe();
      }
    }
  15. Build and run your program.

How it works…

NHibernate Validator, or NHV, has a few major components. ValidatorEngine is the main class that is used to interact with NHV. Similar to the session factory, applications typically have only one instance. NHV uses an implementation of ISharedEngineProvider to find the single instance of the ValidatorEngine. NHV can be used independently from NHibernate to validate any class. When integrated with NHibernate, it validates each entity before inserting or updating data. This integration is accomplished through the ValidatePreInsertEventListener and ValidatePreUpdateEventListener event listeners.

To integrate NHV with NHibernate, we begin by creating a ValidatorEngine. The call to ValidatorEngine.Configure() loads our NHV configuration from the App.config. Next, we create an ISharedEngineProvider to return our ValidatorEngine. We configure NHV to use the shared engine provider by setting the static property Environment.SharedEngineProvider. Finally, after configuring NHibernate, but before creating the session factory, we call Initialize(), an NHV extension method, for the NHibernate configuration object.

Our NHV configuration, in App.config, contains the following four configuration settings:

  • apply_to_ddl: When this property is set to true, hbm2ddl will generate database constraints to enforce many of our validation attributes. For example, the script to create our UnitPrice column, shown next, now has a check constraint to enforce our NotNegativeDecimal rule:
    UnitPrice DECIMAL(19,5) not null check(UnitPrice>=0)
  • autoregister_listeners: This property determines if the Initialize extension method will add the pre-insert and pre-update event listeners to the NHibernate configuration or not.
  • default_validator_mode: This property determines the priority of validation rules when using a mix of XML validation definitions, validation classes, or attributes.
  • The NHV mapping element behaves similarly to the NHibernate mapping element. It defines an assembly containing our entities decorated with attributes.

In this recipe, we attempt to save a new product with a negative UnitPrice. This violates our NotNegativeDecimal validation rule. Without NHibernate Validator, our application will silently accept the invalid data, leading to potentially larger problems later. If we had simply added a constraint in the database, our application would attempt to insert the bad data, then throw an unwieldy SQLException that gives us no information about which property is invalid and why. With NHibernate Validator, the event listeners validate each entity before data is written to the database. If they find invalid data, they throw an InvalidStateException that tells us exactly which properties of the entity are invalid and why.

Note

When a validation event listener throws an InvalidStateException, the session is in an undefined state. Once this happens, the only operation that can be safely performed on the session is Dispose.

You may be wondering why we created a separate NotNegativeDecimalAttribute class. Couldn't we have just decorated our UnitPrice property with [DecimalMin(0M)]? As it turns out, we can't do this. In C#, we can't use Decimal parameters in this manner. To work around this limitation, we subclass the DecimalMinAttribute and hardcode the zero inside the NotNegativeDecimalAttribute class.

In our assemblies, attribute decorations are not stored as Intermediate Language (IL) instructions, but as metadata. This limits the types we can use as parameters. The C# specification available on http://msdn.microsoft.com/en-us/library/aa664615 defines the types that can be used as bool, byte, char, double, float, int, long, short, string, object, System.Type, enum, or any one-dimensional array of these types. Decimal is not on the list.

There's more…

If you check your entities for invalid data prior to saving them, you don't run the risk of blowing up the NHibernate session. To validate an object explicitly, your code might look similar to this:

var ve = Environment.SharedEngineProvider.GetEngine();
var invalidValues = ve.Validate(someObject);

invalidValues is an array of InvalidValue objects describing each failed validation rule. If it's empty, the object is valid. If not, you can easily display the validation messages to the user without risking the session.

NHibernateValidator can be used to validate any class, not just NHibernate entities. You can easily adapt this sort of explicit validation to integrate with ASP.NET MVC's model validation.

See also

  • Creating validator classes
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.231.15