When you use the Data Annotations Model Binder, you use validator
attributes to perform validation. The System.ComponentModel.DataAnnotations
namespace includes the following validator attributes:
The Product class in Listing 1 illustrates how to use these validator attributes. The Name, Description, and UnitPrice properties are marked as required. The Name property must have a string length that is less than 10 characters. Finally, the UnitPrice property must match a regular expression pattern that represents a currency amount.
public class Customer { public virtual string CustomerID { get; set; } public virtual string CompanyName { get; set; } public virtual string Address { get; set; } public virtual string City { get; set; } public virtual string PostalCode { get; set; } public virtual string Country { get; set; } public virtual string Phone { get; set; } } This domain entity is used to represent
public class Customer { [Required] public virtual string CustomerID { get; set; } [Required] [StringLength(15)] public virtual string CompanyName { get; set; } public virtual string Address { get; set; } public virtual string City { get; set; } public virtual string PostalCode { get; set; } public virtual string Country { get; set; } public virtual string Phone { get; set; } } Let’s take a look at
With the use of
At the end,
public class Customer { [Required] public virtual string CustomerID { get; set; } [Required] [StringLength(15)] public virtual string CompanyName { get; set; } public virtual string Address { get; set; } public virtual string City { get; set; } public virtual string PostalCode { get; set; } [Country(AllowCountry="USA")] public virtual string Country { get; set; } [Phone] public virtual string Phone { get; set; } }
[TestMethod] public void ValidateCustomer_Customer_NoError() { // Arrange Customer customer = new Customer(); customer.CustomerID = "aaa"; customer.CompanyName = "AAA company"; // Act var validationResult = ValidationHelper.ValidateEntity<customer>(customer); // Assert Assert.IsFalse(validationResult.HasError); }
public class EntityValidationResult { public IList<validationresult> Errors { get; private set; } public bool HasError { get { return Errors.Count > 0; } } public EntityValidationResult(IList<validationresult> errors = null) { Errors = errors ?? new List<validationresult>(); } } public class EntityValidator<t> where T : class { public EntityValidationResult Validate(T entity) { var validationResults = new List<validationresult>(); var vc = new ValidationContext(entity, null, null); var isValid = Validator.TryValidateObject (entity, vc, validationResults, true); return new EntityValidationResult(validationResults); } } public class ValidationHelper { public static EntityValidationResult ValidateEntity<t>(T entity) where T : class { return new EntityValidator<t>().Validate(entity); } } The line of code that does actual validation is:
var isValid = Validator.TryValidateObject(entity, vc, validationResults, true); Note: The last parameter,
The validation process is like this:
- Range – Enables you to validate whether the value of a property falls between a specified range of values.
- ReqularExpression – Enables you to validate whether the value of a property matches a specified regular expression pattern.
- Required – Enables you to mark a property as required.
- StringLength – Enables you to specify a maximum length for a string property.
- Validation – The base class for all validator attributes.
The Product class in Listing 1 illustrates how to use these validator attributes. The Name, Description, and UnitPrice properties are marked as required. The Name property must have a string length that is less than 10 characters. Finally, the UnitPrice property must match a regular expression pattern that represents a currency amount.
using System.ComponentModel; using System.ComponentModel.DataAnnotations; namespace MvcApplication1.Models { public class Product { public int Id { get; set; } [Required] [StringLength(10)] public string Name { get; set; } [Required] public string Description { get; set; } [DisplayName("Price")] [Required] [RegularExpression(@"^\$?\d+(\.(\d{2}))?$")] public decimal UnitPrice { get; set; } } }
Introduction
When you have domain entities in domain layer, usually you also have validation rules as a part of entities’ business rules. You will face the question where to specify the validation rules and how to verify the validation rules. This article will show you a way by usingDataAnnotations
library to specify and verify validation rules for domain entities.Domain Entity
A domain entity is a class used to represent something in your problem domain. A typical domain entity looks like this:public class Customer { public virtual string CustomerID { get; set; } public virtual string CompanyName { get; set; } public virtual string Address { get; set; } public virtual string City { get; set; } public virtual string PostalCode { get; set; } public virtual string Country { get; set; } public virtual string Phone { get; set; } } This domain entity is used to represent
Customer
in system by holding Customer
related information. I assume when adding a new customer
into the system, you must provide CustomerID
and CompanyName
, and the CompanyName
must have maximum 10 characters. If you provided value for Country
, then the value must be USA
. If you provided value for Phone
, then the phone number must have correct format. In summary, the Customer
entity has following validation rules:CustomerID
is requiredCompanyName
is requiredCompanyName
can have maximum 10 charactersCountry
can only beUSA
when there is a valuePhone
must have format ###-###-#### (# means digit) when there is a value
DataAnnotations
DataAnnotations
is a library in .NET Framework. It resides in assembly System.ComponentModel.DataAnnotations
. The purpose of DataAnnotations
is to custom domain entity class with attributes. Therefore, DataAnnotations
contains
validation attributes to enforce validation rules, display attributes
to specify how data from the class or member is displayed, and data
modeling attributes to specify the intended use of data members and the
relationships between data classes. One example of validation attribute
is RequiredAttribute
that is used to specify that a value must be provided. One example of display attribute is DisplayAttribute
that is used to specify localizable string
s for data types and members that are used in the user interface. And, one example of data modeling attribute is KeyAttribute
that
is used to specify the property as unique identity for the entity. We
will only show how to use validation attributes in here to verify
validation rules for domain entity. Specify Validation Rule for Domain Entity
Before we can verify validation rules ofCustomer
entity, we need to put two built-in validation attributes of DataAnnotations
, RequiredAttribute
and StringLengthAttribute
, on Customer
class. The Customer
class will be something like this:public class Customer { [Required] public virtual string CustomerID { get; set; } [Required] [StringLength(15)] public virtual string CompanyName { get; set; } public virtual string Address { get; set; } public virtual string City { get; set; } public virtual string PostalCode { get; set; } public virtual string Country { get; set; } public virtual string Phone { get; set; } } Let’s take a look at
RequiredAttribute
and StringLengthAttribute
in detail:RequiredAttribute
and StringLengthAttribute
all inherit from ValidationAttribute
that is the base class of all validation attributes.With the use of
RequiredAttribute
and StringLengthAttribute
, we are only able to specify validation rules for the first three requirements, how about the last two requirements: Country
can only be USA
when there is a value and Phone
must
have format ###-###-#### (# means digit) when there is a value. The
solution is to create custom validation attributes for them. We need to
create two custom validation attributes here, one is for Country
and another one is for Phone
. CountryAttribute for Country Validation
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class CountryAttribute: ValidationAttribute { public string AllowCountry { get; set; } public override bool IsValid(object value) { if (value == null) { return true; } if (value.ToString() != AllowCountry) { return false; } return true; } }PhoneAttribute for Phone Validation
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class PhoneAttribute : RegexAttribute { public PhoneAttribute() : base(@"^[2-9]\d{2}-\d{3}-\d{4}$", RegexOptions.IgnoreCase) { } } The class diagram is like this:At the end,
Customer
class with all validation attributes on it will be like this:public class Customer { [Required] public virtual string CustomerID { get; set; } [Required] [StringLength(15)] public virtual string CompanyName { get; set; } public virtual string Address { get; set; } public virtual string City { get; set; } public virtual string PostalCode { get; set; } [Country(AllowCountry="USA")] public virtual string Country { get; set; } [Phone] public virtual string Phone { get; set; } }
Verify Validation Rules for Domain Entity
After validation rules are specified onCustomer
entity, we can verify Customer
against the rules in our application with DataAnnotations.Validator
class.[TestMethod] public void ValidateCustomer_Customer_NoError() { // Arrange Customer customer = new Customer(); customer.CustomerID = "aaa"; customer.CompanyName = "AAA company"; // Act var validationResult = ValidationHelper.ValidateEntity<customer>(customer); // Assert Assert.IsFalse(validationResult.HasError); }
ValidationHelper
is a helper class that wraps up the call to DataAnnotations.Validator
.public class EntityValidationResult { public IList<validationresult> Errors { get; private set; } public bool HasError { get { return Errors.Count > 0; } } public EntityValidationResult(IList<validationresult> errors = null) { Errors = errors ?? new List<validationresult>(); } } public class EntityValidator<t> where T : class { public EntityValidationResult Validate(T entity) { var validationResults = new List<validationresult>(); var vc = new ValidationContext(entity, null, null); var isValid = Validator.TryValidateObject (entity, vc, validationResults, true); return new EntityValidationResult(validationResults); } } public class ValidationHelper { public static EntityValidationResult ValidateEntity<t>(T entity) where T : class { return new EntityValidator<t>().Validate(entity); } } The line of code that does actual validation is:
var isValid = Validator.TryValidateObject(entity, vc, validationResults, true); Note: The last parameter,
validateAllProperties
, of TryValidateObject
method is a Boolean type variable. You must pass in true
to
enable verification on all types of validation attributes, include the
custom validation attributes we used above. If you pass in false
, only RequiredAttribute
used on entity will be verified. The name of this parameter is very misleading. The validation process is like this:
Summary
DataAnnotations
library provides an easy way to specify
and verify validation rules on domain entity. Also, it is opening for
change by design, so developers can add their own validation attributes
for their own situation. By specifying validation rules on domain entity
directly allow upper layers have no worry of validation anymore.