Friday, December 14, 2012 #

Sharing DataAnnotations between the UI and the business logic

Along with with .Net version 4.0 came DataAnnotations which is a library for validating properties in classes. The library consists of validation-attributes, core classes and interfaces for validating properties. Using DataAnnotations to validate the UI is modern, elegant and is understood straight out of the box by Microsoft frameworks such as ASP.Net MVC and WPF, as long as the platform is .Net 4.0 or above. Now, wouldn’t it be nice if we could extend the usage of DataAnnotations and annotate the domain models within the business logic, sharing the same annotations? This would provide a common set of annotated validation rules and simplifying validation. And, when used in conjunction with Entity Framework Code First, we are also effectively validating the data access.

DataAnnotations in the UI

A common scenario is to validate forms in web pages with dataannotated properties and give feedback to the client whenever a validation fails. This approach is very elegant because very little code must be written in order to achieve validation. Also, when using a viewmodel for the view, the annotation is done on the viewmodel, not the view, which loosens the coupling between the view and the viewmodel.

The recipe is very simple:

  • Annotate the property:

    image

  • Bind the view to the property:

    image
    (Using ASP.Net MVC 4 application)

  • The result:

    image

 

How to share dataannotations?

Now, using dataannotations on the client side is fairly straight forward, there are plenty of documentation describing how to do this. There are some differences in the binding mechanism and view validation depending on the UI framework, but the annotation of the properties are the same. Of course, in an application there is also business logic with domain models and most often a repository for storing data. The repositories and domain models need also validate input coming from outside the bounded context (from DDD). Often, but not always the validation rules are the same in UI as in the business logic. One could of course have separate validation definitions, one in the UI and one in the business logic, this is would enforce loose coupling and layered architecture, but would mean duplicating the validation rules. Another approach would be to skip the layering all together and use domain models as viewmodels, but this would lead to high coupling and breaking separated patterns such as MVVM, MVC, MVP and other variants.

The solution is to declare the annotations in a separate solution-project (type class library), we could then use the same DataAnnotations in multiple context boundaries and still enforce the architecture.

image

Now lets say we have a Customer domain model and a viewmodel RegisterCustomerVm for registering customers. Also, they both have a property Name which also have the same validation rule. Further, lets say the rule is that name is required and that the length of the name must not exceed 100 characters.

Since we are sharing DataAnnotations, we must define custom ValidationAttributes in the shared visual studio project. We cannot annotate the built-in attributes directly on the properties of view models or the domain models, the rules must be defined one place, and one place only.

So, declaring the above validation rule can be done like this:

public class CustomerNameAttribute : ValidationAttribute
{
        private string _msg;
        private const int MaximumCharacters = 100;
 
        public override bool IsValid(object value)
        {
            var strValue = value as string;
            if (string.IsNullOrEmpty(strValue))
            {
                _msg = "Name is required.";
                return false;
            }
 
            if (strValue.Length > MaximumCharacters)
            {
                _msg = string.Format("Must contain less than {0} characters.", MaximumCharacters);
                return false;
            }
 
            return true;
        }
 
        public override string FormatErrorMessage(string name)
        {
            return _msg;
        }
}
 
When declaring the validation rule we must inherit the attribute from VaidationAttribute, override IsValid() and FormatErrorMessage(). We have to override the FormatMessage() in order to create custom error messages. IsValid(), will be called by the framework when validation occurs. Unfortunately, there are no generic implementation of ValidationAttribute, so we have to cast the object parameter in IsValid().
 
 
To apply the rule on the view model is the simple:
 
public class RegisterCustomerVm
{
    [CustomerName]
    public string Name { get; set; }
}
 
Failing to oblique the validation rule leads to:
 
image
 
Or:
 
image

 

DataAnnotating the domain models

Validating the user interface is simple, but annotating the domain models is actually even more simple and elegant. In an object-oriented manner we can now validate the domain models in the construction of the class. Of course, there are different approaches and techniques one could use, but in this example demonstrates the basic technique.

With this technique, we shall use constructors to encapsulate our domain models. For the Customer domain model, it has only one parameter, called Name. The class looks like this:

public class Customer
{
    public int Id { get; private set; }
 
    [CustomerName]
    public string Name { get; private set; }
 
    public Customer(string name)
    {
        Name = name;
    }
}

Note that the properties have private setters, so that encapsulation will be enforced. Also, there is a property Id, if you are using EF Code First, the id of the entity will be generated for us, so we don’t have to create an id in the constructor.

As the code stands now, no validation will occur, so we have to validate the validation-attribute when constructing. This can be done by using the ValidateObject() method in the core library of DataAnnotation, located in the DataAnnotations.Validations assembly:
 
public Customer(string name)
{
    Name = name;
 
    Validator.ValidateObject(this, new ValidationContext(this), true);
}
 
The ValidateObject method will look for attributes of type ValidationAttribute in the specified object. In this case, it will find that the Name property of Customer has a matching attribute. Next, the ValidateObject will call IsValid() in the matching ValidationAttributes. Finally, if IsValid() fails, it will throw a ValidationException.
 
 
In order to get a small footprint in the domain models, we can define a simple extension method on object:
 
public static class ObjectExtension
{
    public static void Validate(this object obj)
    {
        Validator.ValidateObject(obj, new ValidationContext(obj), true);
    }
}
 
The Customer domain model is now complete with validation:
 
public class Customer
{
    public int Id { get; private set; }
 
    [CustomerName]
    public string Name { get; private set; }
 
    public Customer(string name)
    {
        Name = name;
        this.Validate();
    }
}

Now this is really a small footprint of the validation, it’s object-oriented and elegant! Open-mouthed smile

 

Unit-Testing that the validation rules are enforced on the domain models

If you want to test the validation rules with unit-test (of course you want), it is very simple. We just have to validate that the ValidationException is thrown whenever we break the validation rule.

[TestMethod]
public void WhenCustomerIsCreatedValidateThatNameIsNotNull()
{
    AssertHelper.Throws<ValidationException>(() =>
    {
        var c = new Customer(null);
    });
}
 
[TestMethod]
public void WhenCustomerIsCreatedValidateThatNameIsNotEmpty()
{
    AssertHelper.Throws<ValidationException>(() =>
    {
        var c = new Customer("");
    });
}
 
[TestMethod]
public void WhenCustomerIsCreatedValidateThatNameDoesNotExceedOneHundredCharacters()
{
    AssertHelper.Throws<ValidationException>(() =>
    {
        var c = new Customer(new string('x', 101));
    });
}

I have created an AssertHelper class with a Throws() method that throws AssertFailedException if the specified generic exception was not thrown.

 

Happy validating! :D

Posted On Friday, December 14, 2012 3:07 PM | Comments (1)