IDataErrorInfo with Fluent Validation

Fluent validation: A small validation library for .NET that uses a fluent interface and lambda expressions for building validation rules for your business objects.

Codeplex link: https://fluentvalidation.codeplex.com/ 

With Fluent validation we can avoid repetitive code by using the power of lambda expressions. Fluent validation has built in validators like:

NotNull Validator
NotEmpty Validator
NotEqual Validator
Equal Validator
Length Validator
Less Than Validator
Less Than Or Equal Validator
Greater Than Validator
GreaterThan Or Equal Validator
Predicate Validator (aka Must)
RegEx Validator
Email Validator 

Also apart from this you can use your own custom logic with supported error messages. For XAML application we can combine this power with our own IDataErrorInfo.

We will see this with a simple example. Before we go into the example, you can use the NUGET to download Fluent Validation dll’s.

We will first look at the Validation class with Fluent validation: 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentValidation;

namespace FluentApplication
{
    public class ModelValidator : AbstractValidator<Model>
    {

        public  ModelValidator()
        {
            RuleFor(model => model.ID).NotEmpty();
            //Custom rule example
            RuleFor(model => model.ID).Must(IsNumber).WithMessage("This is not a number");
            RuleFor(model => model.ID).LessThan(10).WithMessage("ID should be less than 10");
            RuleFor(model => model.Name).NotEmpty();
            RuleFor(model => model.Name).Length(1, 5);

        }

        

        //Take the datatype of the passing paramter. i.e in this case
        // model.ID is of type int so it will take paramter of int and the value 
        // of model.ID is transferred to this method here automatically
        private bool IsNumber(int ID)
        {
            // You can write your logic here 
            return true;
        }


    }
}

From above we are using AbstractValidator for firing the validation for the model. We give it some rules based on the properties for firing. I have used NotEmpty, LessThan, Length which are in-built properties of Fluent Validation. As you can also see I have used a custom validation through ‘must’ keyword. The Input parameter of the custom method will always reflect the datatype of the calling property. Thus in this case as I have written a custom property for ‘ID’ which is of type ‘Int’ the input parameter of my Method ‘IsNumber’ will carry always the value of passing property, in this case ‘ID’.

You can also use this to trigger children validation , for eg: If you are validating Address you can further trigger internal properties of Address i.e pincode, street names etc.

We will now see the Model which is a generic implementation of MVVM.


using FluentValidation.Results;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FluentValidation;

namespace FluentApplication
{
   public class Model : BaseClass,IDataErrorInfo
    {
       
        private int _id;
        public int ID 
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;               
            }
        }

        private string _name;
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
               
            }
        }

        private string _error;
        public string Error
        {
            get
            {
                return _error;
            }
            set
            {
                _error = value;
            }
        }

        public string this[string columnName]
        {
            get 
            {
                Validator = new ModelValidator();
                if (columnName == "Name")
                {
                    if (Validator.Validate(this, "Name").Errors.Any())
                        return Validator.Validate(this, "Name").Errors.FirstOrDefault().ErrorMessage;
                    else
                    {
                        Validator = null;
                        return string.Empty;
                    }
                }
                if (columnName == "ID")
                {
                    if (Validator.Validate(this, "ID").Errors.Any())
                        return Validator.Validate(this, "ID").Errors.FirstOrDefault().ErrorMessage;
                    else
                    {  
                        Validator = null;
                        return string.Empty;
                    }
                }

                return string.Empty;
            }
        }
    }
}



From above I have implemented the IDataErrorInfo which will internally fire up the FluentValidation. You can fire up for a single property or fire validation for the entire Model. I would prefer each property gets call to its own validation.

Based on the requirement you can use any of the approaches above.

BaseClass implementation is just for INotifyPropertyChanged which is shown below,


using FluentValidation.Results;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FluentApplication
{

   public class BaseClass : INotifyPropertyChanged
    {
        public List<ValidationResult> ValidationResults { get; set; }
        public ModelValidator Validator { get; set; }
        #region NotifyEvents

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {

            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion

    }
}



 

Now we will look at the view model which is a normal MVVM based VM, 


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace FluentApplication
{

    class ViewModel
    {
        public ViewModel()
        {
            ModelLists = new ObservableCollection<Model>();
            ModelLists.Add(new Model() {ID = 1,Name="Adi" });
            ModelLists.Add(new Model() { ID = 2, Name = "Abi" });
        }

        public ObservableCollection<Model> ModelLists { get; set; }
    }
}

 

And now we will have a look at the View. 


<Window x:Class="FluentApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid x:Name="dgMain" ItemsSource="{Binding ModelLists,Mode=TwoWay,ValidatesOnDataErrors=True,ValidatesOnNotifyDataErrors=True,UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False" Margin="22,1,31,161" RenderTransformOrigin="0.5,0.5">
            <DataGrid.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform AngleY="0.669"/>
                    <RotateTransform/>
                    <TranslateTransform Y="1.962"/>
                </TransformGroup>
            </DataGrid.RenderTransform>
            <DataGrid.Columns>
                <DataGridTextColumn Header="ID" Binding="{Binding ID,UpdateSourceTrigger=LostFocus,ValidatesOnDataErrors=True}"/>
                    <DataGridTextColumn Header="Name" Binding="{Binding Name,UpdateSourceTrigger=LostFocus,ValidatesOnDataErrors=True}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

 

 

Leave a comment