1/27/2016

Use of FluentValidation for creating a sophisticated data validation framework in ASP.NET MVC

Categories: ASP.NET, nopCommerce 

Other articles by: LavishKumar Lavish Kumar

Data validation is quite critical when it comes to usability and data integrity of any software or application. It helps in improving the quality of the data as well as it ensures the data consistency. Data annotations is still one of the most popular ways to do model validation in ASP.NET MVC. But, a lot of developers and projects are leaning towards Fluent Validation library these days. Why? Well, Fluent Validation is quite versatile and offers advantages like:

- Easy for unit test validation rules

- Option to split the validation rules completely from the model

Again, there is nothing wrong in data annotations as similar results can be accomplished by following that approach. But, too many annotations can make your model look quite ugly. 


What is Fluent Validation?

Fluent Validation is a small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules. Download Here: https://github.com/JeremySkinner/FluentValidation


In this article, we will go over the process of implementing Fluent Validation in ASP.NET web application. For this, we are going to take a sample web application that includes a Product Model and views for adding, editing and deleting the products from the product table. 

First, we need to download the FluentValidation package from NuGet. We can do so by going to Package Manager Console and entering the following command: Install-Package FluentValidation.MVC5





Here is the example of our current Product Model where we have all the properties defined:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using FluentValidation;
using FluentValidation.Attributes;
using FluentValidation.Results;
using System.ComponentModel.DataAnnotations;
using EFCodeFirstMVCApplication.Models;
 
 
namespace EFCodeFirstMVCApplication.Models
{
 
 
    public class Product
    {
        public int ID { get; set; }
 
 
        public string Prod_SKU { get; set; }
        public string Prod_Name { get; set; }
        public DateTime CreateDate { get; set; }
 
     }
 
}



In order to implement FluentValidation, we need to add a line to the Global.asax file (in Application _Start method). This will hook up FluentValidation class library to the ASP.NET MVC 5 validation framework. We will call FluentValidationModelValidatorProvider.Configure() method inside the global.asax file like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using FluentValidation.Mvc;
 
namespace EFCodeFirstMVCApplication
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
 
           // Adding FluentValidation
            FluentValidationModelValidatorProvider.Configure();
        }
    }
}



Now, we can start setting up the actual data validation. There are two ways of creating custom validators. The first is to create a custom property validator, the second is to make use of the Custom method on AbstractValidator. Let’s create a new class called “FluentProductValidator” like this:

public class FluentProductValidator : AbstractValidator<Product>
 
     {
           public FluentProductValidator()
 
        {
 
        }
     }



Next step is to add validation rules in the FluentProductValidator class for “Prod_SKU” & “Prod_Name”.

public class FluentProductValidator : AbstractValidator<Product>
 
     {
           public FluentProductValidator()
 
        {
 
            RuleFor(x => x.Prod_SKU).NotEmpty().WithMessage("SKU is required");
            RuleFor(x => x.Prod_Name).NotEmpty().WithMessage("Name is required");
             
        }
     }



Our complete Product Model files looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using FluentValidation;
using FluentValidation.Attributes;
using FluentValidation.Results;
using System.ComponentModel.DataAnnotations;
using EFCodeFirstMVCApplication.Models;
 
 
namespace EFCodeFirstMVCApplication.Models
{
 
 
[Validator(typeof(FluentProductValidator))]
    public class Product
    {
        public int ID { get; set; }
 
 
        public string Prod_SKU { get; set; }
        public string Prod_Name { get; set; }
        public DateTime CreateDate { get; set; }
 
        public virtual ICollection<ProductManufacturerMapping> ProductCategoryMappings { get; set; }
    }
 
  
 
 
     public class FluentProductValidator : AbstractValidator<Product>
 
     {
           public FluentProductValidator()
 
        {
 
            RuleFor(x => x.Prod_SKU).NotEmpty().WithMessage("SKU is required");
            RuleFor(x => x.Prod_Name).NotEmpty().WithMessage("Name is required");
             
        }
     }
 
}


Now, if we run the application and go to Create OR Edit, we should see the validations like this:



We have successfully implemented the FluentValidation in our ASP.NET MVC web application.



Open-source .NET projects like nopCommerce are using FluentValidation for data validation
FluentValidation is a very powerful validation framework that allows many features that regular ComponentModel framework does not offer which is why project like nopCommerce is using it in the official software. Now the question is, Fluent Validation benefits projects like nopCommerce?

- Fluent Validation gives nopCommerce far more control over the validation rules

- nopCommerce project can make use of conditional validation which is quite easier as compared to data annotations

- It allows nopCommerce an option to separate all the validations from the view models

- Unit testing is far easier as compared to data annotations

- Fluent Validation offers a better client-side validation support

 

Let’s take a look at how nopCommerce is making use of FluentValidation in the project

One of the most important thing in an e-Commerce business is customer data. In order to maintain quality data (i.e. customer information) in the database, it is critical to have strong data validation rules on the public store. nopCommerce makes the best use of Fluent Validation in this area so let’s look into “AddressValidator.cs” that can be found in this location:

Nop.Web\Validators\Common\AddressValidator.cs


Here is the source code:

using FluentValidation;
using FluentValidation.Results;
using Nop.Core.Domain.Common;
using Nop.Services.Directory;
using Nop.Services.Localization;
using Nop.Web.Framework.Validators;
using Nop.Web.Models.Common;
 
namespace Nop.Web.Validators.Common
{
    public class AddressValidator : BaseNopValidator<AddressModel>
    {
        public AddressValidator(ILocalizationService localizationService,
            IStateProvinceService stateProvinceService,
            AddressSettings addressSettings)
        {
            RuleFor(x => x.FirstName)
                .NotEmpty()
                .WithMessage(localizationService.GetResource("Address.Fields.FirstName.Required"));
            RuleFor(x => x.LastName)
                .NotEmpty()
                .WithMessage(localizationService.GetResource("Address.Fields.LastName.Required"));
            RuleFor(x => x.Email)
                .NotEmpty()
                .WithMessage(localizationService.GetResource("Address.Fields.Email.Required"));
            RuleFor(x => x.Email)
                .EmailAddress()
                .WithMessage(localizationService.GetResource("Common.WrongEmail"));
            if (addressSettings.CountryEnabled)
            {
                RuleFor(x => x.CountryId)
                    .NotNull()
                    .WithMessage(localizationService.GetResource("Address.Fields.Country.Required"));
                RuleFor(x => x.CountryId)
                    .NotEqual(0)
                    .WithMessage(localizationService.GetResource("Address.Fields.Country.Required"));
            }
            if (addressSettings.CountryEnabled && addressSettings.StateProvinceEnabled)
            {
                Custom(x =>
                {
                    //does selected country has states?
                    var countryId = x.CountryId.HasValue ? x.CountryId.Value : 0;
                    var hasStates = stateProvinceService.GetStateProvincesByCountryId(countryId).Count > 0;
 
                    if (hasStates)
                    {
                        //if yes, then ensure that state is selected
                        if (!x.StateProvinceId.HasValue || x.StateProvinceId.Value == 0)
                        {
                            return new ValidationFailure("StateProvinceId", localizationService.GetResource("Address.Fields.StateProvince.Required"));
                        }
                    }
                    return null;
                });
            }
            if (addressSettings.CompanyRequired && addressSettings.CompanyEnabled)
            {
                RuleFor(x => x.Company).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.Company.Required"));
            }
            if (addressSettings.StreetAddressRequired && addressSettings.StreetAddressEnabled)
            {
                RuleFor(x => x.Address1).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.StreetAddress.Required"));
            }
            if (addressSettings.StreetAddress2Required && addressSettings.StreetAddress2Enabled)
            {
                RuleFor(x => x.Address2).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.StreetAddress2.Required"));
            }
            if (addressSettings.ZipPostalCodeRequired && addressSettings.ZipPostalCodeEnabled)
            {
                RuleFor(x => x.ZipPostalCode).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.ZipPostalCode.Required"));
            }
            if (addressSettings.CityRequired && addressSettings.CityEnabled)
            {
                RuleFor(x => x.City).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.City.Required"));
            }
            if (addressSettings.PhoneRequired && addressSettings.PhoneEnabled)
            {
                RuleFor(x => x.PhoneNumber).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.Phone.Required"));
            }
            if (addressSettings.FaxRequired && addressSettings.FaxEnabled)
            {
                RuleFor(x => x.FaxNumber).NotEmpty().WithMessage(localizationService.GetResource("Account.Fields.Fax.Required"));
            }
        }
    }
}



All the validation rules in nopCommerce are making use of localization resource values so that depending on the selected language for public store, the validation messages can be shown. If we look closely to the source code (above), we will find that nopCommerce is using “NotEmpty” validator (in this case) instead of NotNull validator.  Whereas, in QueuedEmailValidator.cs, nopCommerce project is making use of “NotNull() validator”:





using FluentValidation;
using Nop.Admin.Models.Messages;
using Nop.Services.Localization;
using Nop.Web.Framework.Validators;
 
namespace Nop.Admin.Validators.Messages
{
    public class QueuedEmailValidator : BaseNopValidator<QueuedEmailModel>
    {
        public QueuedEmailValidator(ILocalizationService localizationService)
        {
            RuleFor(x => x.From).NotEmpty().WithMessage(localizationService.GetResource("Admin.System.QueuedEmails.Fields.From.Required"));
            RuleFor(x => x.To).NotEmpty().WithMessage(localizationService.GetResource("Admin.System.QueuedEmails.Fields.To.Required"));
 
            RuleFor(x => x.SentTries).NotNull().WithMessage(localizationService.GetResource("Admin.System.QueuedEmails.Fields.SentTries.Required"))
                                    .InclusiveBetween(0, 99999).WithMessage(localizationService.GetResource("Admin.System.QueuedEmails.Fields.SentTries.Range"));
 
        }
    }
}



So, what is the difference between these two validators?
FluentValidation ships with several built-in validators. The error message for each validator can contain special placeholders that will be filled in when the error message is constructed.

NotNull Validator
Description: Ensures that the specified property is not null.

Example:
RuleFor(customer => customer.Surname).NotNull();

Example error: 'Surname' must not be empty. String format args:

{PropertyName} = The name of the property being validated
{PropertyValue} = The current value of the property



NotEmpty Validator
Description: Ensures that the specified property is not null, an empty string or whitespace (or the default value for value types, eg 0 for int)

Example:
RuleFor(customer => customer.Surname).NotEmpty();

Example error: 'Surname' should not be empty. String format args:

{PropertyName} = The name of the property being validated
{PropertyValue} = The current value of the property

For more information, please refer: FluentValidation Wiki


How to change/update data validation message in nopCommerce?

Option 1: A good rule of thumb in managing localization resource values is from the administration section. If you are updating any existing value, it should be done from the administrator section (for all languages). And, if you are adding a new message on your site, a proper way to add it is via adding a new localization resource value for each language. This way, you will never have to touch the code while updating the values.

Let’s say we would like to change the message for:

RuleFor(x => x.FirstName)
                .NotEmpty()
                .WithMessage(localizationService.GetResource("Address.Fields.FirstName.Required"));


We should change/update it in the administration section (for each language): Configuration > Languages





Now, if we go to the nopCommerce public store (in account) and try to add the address without first name, we should see the updated message like this:






Option 2: An alternative method to change/update the validation method in nopCommerce is to update directly in the source code. This is not the cleanest approach as in future if you wish you update it again, you will have to change it in the source code. But, at the end of the day this method still works and get the job done.

Go to: Nop.Web\Validators\Common\AddressValidator.cs

Original code:

RuleFor(x => x.FirstName)
                .NotEmpty()
                .WithMessage(localizationService.GetResource("Address.Fields.FirstName.Required"));


New updated validation message in the code:

RuleFor(x => x.FirstName)
                .NotEmpty()
                .WithMessage("New Hardcoded Validation Message Here");


Now, if we go to the nopCommerce public store (in account) and try to add the address without first name, we should see the updated message like this:




Resources:

You can download nopCommerce here: http://www.nopcommerce.com/

nopCommerce Version (used in this article): Version 3.70

Rated 4.00, 5 vote(s). 


About Author

Lavish Kumar
Founder, developer and administrator of StrivingProgrammers.com -By profession, he is a software programmer / web developer, graphic designer & DBA. He is passionate about technology and electronic gadgets. He enjoys writing tech articles and developing web applications.


Comments

sheny
Cheap Authentic Soccer Jerseys
Cheap Basketball Jerseys
cheap nba jerseys
Cheap Nba Jerseys Free Shipping
Cheap NBA Jerseys From China
Cheap NBA Jerseys uk
Cheap NFL Jerseys outlet
Cheap NFL Jerseys supply
cheap NHL jerseys free shipping
Cheap Soccer Jerseys Free Shipping
Fake NBA Jerseys
Replica NBA Jerseys
Wholesale Basketball Jerseys
Wholesale NBA Jerseys
Wholesale nfl jerseys
wholesale nhl jerseys paypal
cheap nhl jerseys
youth nhl jerseys
wholesale mlb jerseys
cheap mlb jerseys
cheap nhl jerseys
wholesale nhl jerseys
cheap replica jerseys
wholesale jerseys online
cheap authentic jerseys
cheap jerseys
wholesale authentic jerseys
Cheap nfl super bowl jerseys
cheap nhl jerseys
wholesale basketball jerseys
cheap basketball jerseys
cheap mlb jerseys free shipping
cheap mlb authentic jerseys
mlb jerseys cheap
cheap authentic jerseys
cheap authentic nfl jerseys
replica nhl jerseys
cheap replica jerseys
cheap jerseys online
cheap jerseys free shipping
cheap mlb jerseys
Cheap nba jerseys
cheap nfl jerseys uk
cheap super bowl jerseys
cheap nfl Jerseys online
cheap nfl jerseys paypal
cheap football jerseys
cheap nfl jerseys
cheap custom nfl jerseys
discount nfl jerseys
cheap nfl football jerseys
wholesale nfl jerseys
nfl ferseys cheap
wholesale nfl jerseys
cheap replica nfl jerseys
wholesale cheap nfl jerseys
Cheap nfl jerseys free shipping
Cheap nhl jerseys
Wholesale nike nfl jerseys
cheap nike jerseys
cheap soccer jerseys online
cheap soccer jerseys
Cheap jerseys from china
wholesale jerseys from china
cheap jerseys china
wholesale cheap jerseys
wholesale jerseys free shipping
wholesale nfl jerseys free shipping
cheap authentic jerseys
Cheap Replica MLB Jerseys
discount nba jerseys

9/17/2016 2:14:30 AM

sheny
cheap jerseys free shipping
cheap mlb jerseys
replica mlb jerseys
discount mlb jerseys
wholesale mlb jerseys
cheap nba houston rockets jerseys
wholesale nba jerseys
cheap nba jerseys wholesale
cheap nba jerseys
cheap nba jerseys free shipping
wholesale nba jerseys
discount nba jerseys
wholesale nba jerseys
nba jerseys wholesale
cheap nba jerseys
cheap nba jerseys from china
cheap nba jerseys online
cheap nba free shipping
wholesale nba jerseys
cheap nba jerseys
cheap nfl jerseys for sale
cheap nfl jerseys free shipping
wholesale nfl jerseys
cheap nfl authentic jerseys
cheap nfl jerseys
nhl jerseys cheap
cheap nhl jerseys
cheap nhl jerseys online
custom nhl Jerseys
cheap nhl jerseys canada
cheap custom nhl jerseys
wholesale nhl jerseys
cheap nhl jerseys from china
cheap nike jerseys from china
wholesale nike jerseys from china
wholesale nike jerseys
cheap nike nfl jerseys
cheap nike sports jerseys
cheap nike jerseys
cheap nike nfl jerseys
cheap soccer jerseys
wholesale soccer jerseys
wholesale soccer jerseys
cheap soccer jerseys
colts super bowl jerseys
patriots super bowl jerseys
super bowl jerseys
cheap super bowl jerseys
patriots super bowl jersey
super bowl jerseys
super bowl jerseys
new england patriots super bowl jerseys
wholesale super bowl jerseys
cheap authentic super bowl jerseys
cheap jerseys from china
discount jerseys online
wholesale authentic elite jerseys
wholesale authentic jerseys
wholesale authentic nfl jerseys
cheap vancouver canucks jerseys
cheap youth jerseys
cheap real madrid jerseys
wholesale youth jerseys
cheap jerseys
cheap stitched jerseys
cheap kids jerseys
cheap jerseys wholesale
cheap jerseys
wholesale jerseys
wholesale nba jerseys free shipping
wholesale nba jerseys
wholesale jerseys free shipping
cheap nfl jerseys
replica nhl jerseys
cheap NHL Jerseys online
NHL Womens Jerseys
Discount NHL Jerseys
Custom Hockey Jerseys
cheap hockey jerseys
Replica NFL Jerseys
Anaheim Ducks jerseys
Cheap NBA jerseys
Wholesale Hockey jerseys
Wholesale NFL Jerseys
Cheap Montreal Canadiens Jerseys
cheap custom nhl jerseys
WHOLESALE NFL JERSEYS
CHEAP NFL JERSEYS
wholesale Soccer jerseys
Wholesale Cheap Sports Jerseys
discount jerseys
Authentic NFL Jerseys
Cheap NHL Jerseys
About Jerseys News
cheap jerseys wholesale
Best Shoes news Website
Sitemap-Shoes City SiteMap
Fashion Sunglasses News Website
Sitemap CheapSunglassesFreeShipping
Sitemap
Buy Wholesale Cheap Free Shipping China
ShopCartBoss Home SiteMap
Cheap Pandora Store
Sitemap
Cheap Pandora
Wholesale Pandora charms
cheap Pandora Charms
Pandora Charms cheap
Pandora Jewelry
Wholesale Pandora
mlb jerseys
nba jerseys
ncaa jerseys
nfl jerseys
nhl jerseys
nike nfl jerseys
soccer jerseys

9/17/2016 2:15:26 AM
Notify me when new comments are added to this post
Save comment