Validating complex properties

In this recipe, we will be creating complex business rule validators that allow us to evaluate more than one property at a time, and the contents of a property.

Getting ready

We will be using the NuGet Package Manager to install the entity framework 4.1 assemblies.

The package installer can be found at http://nuget.org.

We will also be using a database for connecting to the data and updating it.

Open the Improving Complex Property Validation solution in the included source code examples.

How to do it...

Let's get connected to the database using the following steps:

  1. We start by adding a new unit test named ValidationTests to the test project. We make a test that connects to the database and adds an object. This will test whether the configuration and our validation code are properly separated:
    using System;
    using System.Collections.Generic;
    using System.Data.Entity.Validation;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using BusinessLogic;
    using DataAccess;
    using DataAccess.Database;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Test.Properties;
    using System.Data.Entity;
    
    namespace Test
    {
      [TestClass]
      public class ValidationTest
      {
    
        [TestMethod]
        [ExpectedException(typeof (DbEntityValidationException))]
        public void ShouldErrorOnTitleLongerThanDescription()
        {
          //Arrange
          var init = new Initializer();
          var context = new BlogContext(Settings.Default.BlogConnection);
          init.InitializeDatabase(context);
    
          var blog = new Blog()
          {
            Creationdate = DateTime.Now,
            ShortDescription = "Test",
            Title = "This is a lot longer"
          };
    
          //Act
          context.Set<Blog>().Add(blog);
          context.SaveChanges();
    
          //Assert
          Assert.Fail("Didn't Error");
        }
    
        [TestMethod]
        [ExpectedException(typeof(DbEntityValidationException))]
        public void ShouldErrorOnTitleContainsAt()
        {
          //Arrange
          var init = new Initializer();
          var context = new BlogContext(Settings.Default.BlogConnection);
          init.InitializeDatabase(context);
    
          var blog = new Blog()
          {
            Creationdate = DateTime.Now,
            ShortDescription = "Test",
            Title = "That"
          };
    
          //Act
          context.Set<Blog>().Add(blog);
          context.SaveChanges();
          //Assert
          Assert.Fail("Didn't Error");
        }
    
        [TestMethod]
        [ExpectedException(typeof(DbEntityValidationException))]
        public void ShouldErrorOnTitleAndDescriptionLengthIs15()
        {
          //Arrange
          var init = new Initializer();
          var context = new BlogContext(Settings.Default.BlogConnection);
          init.InitializeDatabase(context);
    
          var blog = new Blog()
          {
            Creationdate = DateTime.Now,
            ShortDescription = "Testing",
            Title = "Somethin"
          };
    
          //Act
          context.Set<Blog>().Add(blog);
          context.SaveChanges();
    
          //Assert
          Assert.Fail("Didn't Error");
        }
      }
    }
  2. Add an initializer to the DataAccess project in the Database folder with the following code to set up the data:
    using System;
    using System.Data.Entity;
    using BusinessLogic;
    
    namespace DataAccess.Database
    {
    
      public class Initializer : DropCreateDatabaseAlways<BlogContext>
      {
        public Initializer()
        {
        }
        protected override void Seed(BlogContext context)
        {
          context.Set<Blog>().Add(new Blog()
          {
            Creationdate = DateTime.Now,
            ShortDescription = "Testing",
            Title = "Test Blog"
          });
          context.SaveChanges();
        }
      }
    }
  3. In the BusinessLogic project, add a new C# class named Blog with the following code:
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Text.RegularExpressions;
    
    namespace BusinessLogic
    {
      public class Blog : IValidatableObject
      {
        public int Id { get; set; }
    
        public DateTime Creationdate { get; set; }
    
        public string ShortDescription { get; set; }
    
        public string Title { get; set; }
    
        public double Rating { get; set; }
    
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
          List<ValidationResult> results = new List<ValidationResult>();
          var target = ((Blog)validationContext.ObjectInstance);
    
          if (target.Title.Length > target.ShortDescription.Length)
            results.Add(new ValidationResult("Description cannot be shorter than the title"));
          if(target.Title.Contains("at"))results.Add(new ValidationResult("WE hate the word at!"));
    
          if(target.Title.Length + target.ShortDescription.Length == 15)
            results.Add(new ValidationResult("No adding to 15!!!"));
    
          return results;
        }
      }
    }
  4. Add a Mapping folder to the DataAccess project, and add a BlogMapping class to the folder with the following code:
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.ModelConfiguration;
    using BusinessLogic;
    
    namespace DataAccess.Mappings
    {
      public class BlogMapping : EntityTypeConfiguration<Blog>
      {
        public BlogMapping()
        {
          this.ToTable("Blogs");
          this.HasKey(x => x.Id);
    
          this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("BlogId");
    
          this.Property(x => x.Title).IsRequired().HasMaxLength(250);
    
          this.Property(x => x.Creationdate).HasColumnName("CreationDate").IsRequired();
    
          this.Property(x => x.ShortDescription).HasColumnType("Text").IsMaxLength().IsOptional().HasColumnName("Description");
    
       }
    
      }
    }
  5. Modify the BlogContext class to contain the new mappings with the following code:
    using System;
    using System.Data.Entity;
    using System.Linq;
    using BusinessLogic;
    using DataAccess.Mappings;
    
    namespace DataAccess
    {
      public class BlogContext : DbContext, IUnitOfWork
      {
        public BlogContext(string connectionString) : base(connectionString)
        {
    
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
          modelBuilder.Configurations.Add(new BlogMapping());
          base.OnModelCreating(modelBuilder);
        }
    
        public IQueryable<T> Find<T>() where T : class
        {
          return this.Set<T>();
        }
    
        public void Refresh()
        {
          this.ChangeTracker.Entries().ToList().ForEach(x=>x.Reload());
        }
    
        public void Commit()
        {
          this.SaveChanges();
        }
      }
    }
  6. Run our test, and see how it works.

How it works...

We start off by creating some tests that explain our intent, and will serve to validate that our solutions work. These tests specify that the title and the description, when combined, cannot be exactly 15 characters. The title cannot be longer than the short description, and the title must not contain the letters at in order. These are a couple scenarios that will be evaluated at the same time.

The Blog object is a normal object, but it implements the IValidatableObject interface which will allow us to evaluate the entire object. This interface forces us to implement the Validate method. The validation context allows us to look at the properties by casting the object instance to the type for the validator.

The mappings that we have set up have no knowledge of the previously enforced validation rules, but will still be enforced of their own right, once wired into the Blog context. These are a standard that we will see often, when dealing with validation.

There's more...

As we proceed, we will observe that the more complex the business rules get, the more we will have to tend toward using this solution. However,, there are several options that will allow us to build reusable validation.

Reusing the base class logic

When dealing with the shared validation logic across many related types, you can put the validation interface implementation on the base class. The validation context allows us to access the values by using the key value pairs by the property name. The preferred method is to deal with the object directly so as to avoid magic strings. The only issue is, if we want to define a generic and non-type specific validation, then we would have to use the key value pairs.

Performing all at once validation

Validation done in an all at once fashion is a validation on the object, and each property is not evaluated individually. This loses some of the granularity of the results, but gives us the flexibility to evaluate more complex scenarios that could not be handled in a simple validation solution.

See also

In this chapter:

  • Validating simple properties
  • Creating custom property validation
..................Content has been hidden....................

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