Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Server-side Input Validation using Data Annotations

0.00/5 (No votes)
11 Oct 2012 1  
A quick introduction to data annotations in C#

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
{
    /// <summary>
    /// Product
    /// The Product UI Model class
    /// </summary>
    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
{
    /// <summary>
    /// Implementation of the Product Service
    /// </summary>
    public class ProductRepository : IProductRepository
    {                
        ...
 
        /// <summary>
        /// UpdateProduct
        /// Update the product in the database
        /// </summary>
        /// <param name="productViewModel"></param>
        /// <returns></returns>
        public OperationStatus UpdateProduct(Models.Product productViewModel)
        {
            try
            {
                // Use the ValidationContext to validate the Product model against the product data annotations
                // before saving it to the database
                var validationContext = new ValidationContext(productViewModel, serviceProvider: null, items: null);
                var validationResults = new List<ValidationResult>();
 
                var isValid = Validator.TryValidateObject(productViewModel, validationContext, validationResults, true);
 
                // If there any exception return them in the return result
                if (!isValid)
                {
                    OperationStatus opStatus = new OperationStatus();
                    opStatus.Success = false;
 
                    foreach (ValidationResult message in validationResults)
                    {
                        opStatus.Messages.Add(message.ErrorMessage);
                    }
 
                    return opStatus;
                }
                else
                {
                    // Otherwise connect to the data source using the db context and save the 
                    // product model to the database
                    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;
 
                        // Call the update function of the Products entity
                        _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.  

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here