Multi-property Custom Validation Attributes

I am going to expand on last week’s Simple Custom Validation Attributes with custom validation attributes that need to look at more than a single property of a model. Again using the contact application example, lets say that if a contact has a country of USA then state and zip code are required.

As before we have class which inherits from ValidationAttribute. IsValid is still overridden, but it is a different overload than the previous example that provides access to the ValidationContext, which in our case is the contact model that is being validated.

using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;

public class ValidCountryRequired : ValidationAttribute
{
    private readonly string _countryProperty;
    private readonly string[] _requriedByCountries;

    public ValidCountryRequired(string countryProperty,
                                string[] requriedByCountries)
    {
        _countryProperty = countryProperty;
        _requriedByCountries = requriedByCountries;
    }

    protected override ValidationResult IsValid(object value,
                                                ValidationContext context)
    {
        PropertyInfo propertyInfo;
        propertyInfo = context.ObjectType.GetProperty(_countryProperty);
        if (propertyInfo == null)
        {
            return new ValidationResult(_countryProperty +
                                        " does not exist");
        }

        var country = propertyInfo.GetValue(context.ObjectInstance, null);

        if (_requriedByCountries.Contains(country.ToString()) &&
            (value == null ||
             String.IsNullOrWhiteSpace(value.ToString())))
        {
            return new ValidationResult(context.DisplayName +
                                        " is requried for contacts from " +
                                        country);
        }

        return null;
    }
}

For this example I have added a constructor that takes the name of the country property in the model being validated and a string array of the countries that need to make sure the state and zip code are filled in. Both parameters are stored in class level variables for use in the IsValid function.

The IsValid function is still where all the work takes place. The overload of IsValid we are working with provides the value of the property being validated and the validation context that validation is running for. The validation context will allow us to access other properties on the model being validated.

The first step to checking a different property than the one being validated is to get the property info from the validation context by using context.ObjectType.GetProperty and passing the property name that was passed in via the constructor. If GetProperty returns null then the context did not contain the property that was expected. In this case a new ValidationResult is returned with a message that the expected country property was not found.

Using the property info for the country property we can call GetValue with context.ObjectInstance to get the contact’s country. If that country is found in the string array of countries that require the property being validated then a check is run to make sure the property has a value and is not blank.

If the property does not have a value then a new ValidationResult is returned with an error message. If the property does have a value then null is returned indicating that the property is valid.

Example usage from my contact model:

[ValidCountryRequired("Country", new[] { "USA" })]
public string State { get; set; }
[Display(Name = "Zip Code")]
[ValidCountryRequired("Country", new[] { "USA" })]
public string ZipCode { get; set; }

One other thing to note is the usage of context.DisplayName when creating the ValidationResult. By using display name in the message the ZipCode property will show as “Zip Code” in the message returned.

Simple Custom Validation Attributes

Last week I did an overview of  Data Annotations. This week I am going to expand on data annotations by giving an example of a very simple custom validation attribute.

Sticking with my contact application theme lets say that I only wanted to allow contacts from a very small list of countries. The .Net framework does not have a built in validation attribute to cover this scenario which means I would need to create my own custom validation attribute to fill this need.

To create a custom validation attribute all you need is a class that inherits from ValidationAttribute and overrides the IsValid function.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

public class ValidCountry : ValidationAttribute
{
    private readonly List<string> validCountries = new List<string>
    {
        "USA",
        "Canada",
        "Mexico"
    }; 

    public override bool IsValid(object value)
    {
        return value != null &&
               !validCountries.Contains((string)value);
    }
}

When validation for a property is performed the IsValid function of all the property’s validation attributes are run. The IsValid function is called with the value of the property being validated which will have to be cast to the type needed for validation. In my country example value is being cast to string and used see if the value is found in a list of valid countries.

Any property with the ValidCountry attribute would not validate unless the property was set to USA, Canada or Mexico. This is a contrived example, but it does give you an idea of how easy it is to add your own custom validation attributes.

Data Annotations

Validation via data annotations in .NET is a great feature that is simple to implement. Here is an example model class that is using data annotations.

using System.ComponentModel.DataAnnotations;

namespace Contacts.Models
{
    public class Contact
    {
        [Required]
        [StringLength(50, MinimumLength = 4)]
        public string Name { get; set; }
        [StringLength(2)]
        public string State { get; set; }
        [Display(Name = "Zip Code")]
        [StringLength(10)]
        public string ZipCode { get; set; }
    }
}

First step in using data annotations is to add a using statement for the System.ComponentModel.DataAnnotations namespace. This namespace allows you to add validation attributes to the properties that require validation and contains helper classes to run validate on attributed models.

In the example above Required, StringLength and Display are all validation attributes. For the most part the attributes are self-explanatory, but be aware most attributes also have options to give you more control of the validation. For example required attribute has an AllowEmptyStrings property that if set to true will allow an empty string to pass validation. String length is another example which has options for minimum and maximum lengths for the property being validated.

This is just a small sample of the built-in attributes that exist. Other built-in attributes range  from credit card and email address validation to file extension and range validation. If a built-in validation does not meet your needs then a custom attribute a can be created by deriving from ValidationAttribue.

MVC has a lot of great functionality built around data annotations. For instance as long as a client has JavaScript turned a form will not allow submission until the model passes all the validation conditions. On the server-side inside of a controller to run validation all it takes is to run ModelState.IsValid.

With winforms the story is not quite as simple, but still pretty straight forward. The following is a simple example of calling validation on a model.

private void ValidationExample()
{
    var contact = new Contact { Name = "Bob", State = "TN" };

    var context = new ValidationContext(contact);
    var errors = new List<ValidationResult>();

    if (!Validator.TryValidateObject(contact, context, errors))
    {
        foreach (ValidationResult result in errors)
        {
            //act on error
        }
    }
}

Validator.TryValidateObject takes the model to be validated, a validation context (created with the model to validate) and an empty list of type ValidationResult. The function call returns false if any properties fail validation. In the example above the validation would fail since name only contains three characters the minimum length is set to four characters.

Duplicate Records from Migration Seed

I am writing a contact management application as a vehicle for my ASP.NET MVC learning using Entity Framework 6. To pre-load some test data I utilize the Seed method found in Migrations\Configuration.cs which gets added when migrations are enabled for a project.

When testing the contact creation process I noticed that I forgot to add a property for contact’s state. Easy enough to fix. I opened up the Contact class, added the missing state property and updated all the related views. Then I ran the app to test my change and was greeted with this error:ContextChanged

Of course the model backing my DbContext has changed since the database was created, I just added a new property to the model. Being new to entity framework workflow I often see this error when I forget to update the database before running the app to try out a change. It is easy to fix by using the package manager console to Add-Migration and then Update-Database.

After updating the database I ran the app to verify my changes. What I saw when my contact list load was that all my seed data had been duplicated. The jest of my seed function was something like this:

context.Contacts.AddOrUpdate(c => c.Id,
    new Contact
    {
        Id = 0,
        Name = "Eric",
        State = "TN"
    },
    new Contact
    {
        Id = 1,
        Name = "Tommy",
        State = "ND"
    });

To track down the duplication issue the first thing I did was to view the data in the database to make sure the table a primary key set. The Id field was an integer identity column and was the primary key. Next I viewed all the records in the table which looked like this:

Id Name State
0 Eric TN
1 Tommy ND
2 Eric TN
3 Tommy ND

The Id lookup I defined as the first parameter to AddOrUpdate seemed to be my issue. From my tests it seems that AddOrUpdate will always perform an add when trying to match on an identity type column. By changing from c => c.Id to c => c.Name no data is duplicated.

LocalDB v11.0 or MSSQLLocalDB

I have just started learning ASP.NET MVC 5. As a jumping off point I am working though Rick Anderson’s Getting Started with ASP.NET MVC 5. I am completely new to ASP.NET and Entity Framework and Rick’s tutorial has been a great introduction. Everything was going great until I got to the Add a New Field section.

The tutorial is using entity framework 6 to interact with an instance of SQL Express LocalDB. As part of adding a new field to an existing model Rick walks the reader through enabling code first migrations, creating an initial migration and deleting the existing database file. I completed all the steps without any issues.

The next step was to run Update-Database from the package manager console. This command failed with the message “Cannot attach the file ‘path to .mdf’ as database ‘name of database'”. After some searching I found out that using the solution explorer to manually deleting a mdf file from the App_Data directory has been known to cause problems. I restored my mdf from the recycle bin and fired up Visual Studio’s SQL Server Object Explorer and connected using a connection string of “(LocalDB)\v11.0” which I had been using in my application.

As you can see from this screenshot the only databases in this instance of LocalDB are system localdbv110databases. I ran the application and to my surprise it was working again after the restore the mdf file. I tried disconnecting LocalDB and reconnecting with the same results. I tried using the package manager to stop and delete LocalDB. None of this cleared up the problems I was having with the Update-Database command. Some of the post I can across suggested using a different database name in the application’s connection string so that a completely new database would be created, but I was only going to use that tactic as a last resort.

After more searching I came across a post that was using a different connection string localdballfor LocalDB. Instead of “(LocalDB)\v11.0” that I had seen referenced in all of the examples I had seen so far this person was using “(LocalDB)\MSSQLLocalDB”. As it turns out “(LocalDB)\MSSQLLocalDB” is the connection string used for SQL Express 2014 and “(LocalDB)\v11.0” is used for SQL Express 2012. I have both the 2012 and 2014 version installed on my machine. I am not clear on why my database was being created in the 2014 version of SQL Express when my application was using the 2012 connection string. As a fix I have updated all the connection strings in my application to the 2014 version and all is now working without any more problems.