Introduction
If you've never used data annotations to perform user input validation from your application it's something worth considering. You can easily add validation to your web and desktop application by adding Data Annotations to your model classes. I like this approach because it keeps your business rules tied directly to the data in the model object that is being validated.
Data Annotations allow you to describe the business rules you want to apply to your model. Input validation can be done automatically on the client side in ASP.NET MVC or explicitly validating the model against the rules. This tip will describe how it can be done manually on the server-side of an ASP.NET applications or within the repository code of WPF applications.
Using the code
In order to use Data Annotation in your project, you have to make a .NET reference to the
System.ComponentModel.DataAnnotations.dll file and also include the System.ComponentModel.DataAnnotations
namespace in the code that will be using the data annotations.
As an example, consider an application for viewing and updating data in the AdventureWorks database - a SQL Server reference database from Microsoft. The code below is the class for the Product model in the AdventureWorks database. The Product model defines the attributes of a product in the user interface. The user interface might give the user the ability to update certain attributes of a product. In ASP.NET MVC, this would be the "model" class that persists the data between the view and the data store.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace AdventureWorksService.Models
{
public class Product
{
public int ProductId { get; set; }
[Required(ErrorMessage = "The Product Name is required")]
[StringLength(50, ErrorMessage = "The Product Name is too long")]
public string ProductName { get; set; }
[Required(ErrorMessage = "The Product Number is required")]
[RegularExpression(@"^[A-Z]+-[\w]*\d+[-]*[A-Z]*", ErrorMessage = "The Product Number is not in the correct format")]
[StringLength(25, ErrorMessage = "The Product Number is too long")]
public string ProductNumber { get; set; }
[Required(ErrorMessage = "The Standard Cost is required")]
[DataType(DataType.Currency, ErrorMessage = "The Standard Cost is not in the correct format")]
[RegularExpression(@"^\$?\d+(\.(\d{2}))?$", ErrorMessage = "The Standard Cost is not in the correct format")]
[Range(0, 5000, ErrorMessage = "The Standard Cost must be between 0 and 5000")]
public decimal StandardCost { get; set; }
[Required(ErrorMessage = "The List Price is required")]
[DataType(DataType.Currency, ErrorMessage = "The List Price is not in the correct format")]
[RegularExpression(@"^\$?\d+(\.(\d{2}))?$", ErrorMessage = "The List Price is not in the correct format")]
[Range(0, 5000, ErrorMessage = "The List Price must be between 0 and 5000")]
public decimal ListPrice { get; set; }
[Required(ErrorMessage = "The Product Model is required")]
public int ProductModel { get; set; }
[Required(ErrorMessage = "The Product Subcategory is required")]
public int ProductSubcategory { get; set; }
}
}
Using the Data Annotation library you can define validation rules using attributes above the field to which the rule applies. For example the rule
[StringLength(50)]
above the field ProductName
means that the value provided by ProductName
cannot be more than 50 characters.
The following data annotation attributes are used in the example above.
Required
- Specifies that a value must be provided -
Range
- Designates minimum and maximum constraints -
RegularExpression
- Uses a regular expression to determine valid values -
DataType
- Specifies a particular type of data,
such as e-mail address or phone number or in this case currency.
There are a few other available data annotation types but these are the most common. In addition to the attribute an optional error message can be provided. This message will be returned to the client if the appropriate validation fails.
Now that we defined the rules we must manually invoke the validation engine against the user input.
Below is partial source code for the
ProductRepository
class which is responsible for database operations against the Product model. One of its methods is
UpdateProduct
which converts the Product model from the view into a Product Entity type for the Entity Framework DB context (the other methods were removed from this post since they are not relevant to the topic).
Before the update is done I want to make sure that the user entered valid data in the view. I do this by creating a
ValidationContext
object and then pass it to a Validator.TryValidateObject
static method both of which are provided by the Data Annotation library. This method will return a collection of
ValidationResult
objects each of which represent a rule that was broken.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Text;
using AdventureWorksService.Models;
using AdventureWorksService.Entities;
namespace AdventureWorksService.Repository
{
public class ProductRepository : IProductRepository
{
...
public OperationStatus UpdateProduct(Models.Product productViewModel)
{
try
{
var validationContext = new ValidationContext(productViewModel, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(productViewModel, validationContext, validationResults, true);
if (!isValid)
{
OperationStatus opStatus = new OperationStatus();
opStatus.Success = false;
foreach (ValidationResult message in validationResults)
{
opStatus.Messages.Add(message.ErrorMessage);
}
return opStatus;
}
else
{
using (AdventureWorksEntities _db = new AdventureWorksEntities())
{
Entities.Product product = _db.Products.First(p => p.ProductID == productViewModel.ProductId);
product.Name = productViewModel.ProductName;
product.ProductNumber = productViewModel.ProductNumber;
product.StandardCost = productViewModel.StandardCost;
product.ListPrice = productViewModel.ListPrice;
product.ProductModelID = productViewModel.ProductModel;
product.ProductSubcategoryID = productViewModel.ProductSubcategory;
_db.SaveChanges();
}
return new OperationStatus { Success = true };
}
}
catch (Exception e)
{
return OperationStatus.CreateFromException("Error updating product.", e);
}
}
}
}
The code above validates user input after it has been passed from the client's browser to the server (if this were part of a web application). The same data annotation rules in the Product model class can also be applied on the client side using ASP.NET MVC and Unobtrusive
JavaScript. Validating on the client side is a great way to ensure that the business rules are enforced before the input reaches the server. However you should at a minimum validate on the server because you may want to use your Repository code in other applications outside of your web project. Also a crafty user can easily evade JavaScript validation using the browser's developer console and running the application in debug mode.
Regardless of whether you use client-side or service-side validation (or both), the broken rule messages can be pushed to the client-side UI like below. The Validation engine will validate the user input against all rules even if a previous rule was broken. In this example, the value for List Price failed two of its rules.
If all of the rules passed then the
ValidationResult
collection will be empty and the product will be updated in the database with the user input.
Click here to view the source code on CodePlex
with the use of the data annotation.
For more information and a full reference on available data annotations on msdn check out the link: http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations(v=vs.100).aspx
If you have any questions about using Data Annotations please feel free to reply to this post.