Click here to Skip to main content
15,886,689 members
Articles / Desktop Programming / WPF

IDataErrorInfo with Fluent Validation

Rate me:
Please Sign up or sign in to vote.
5.00/5 (7 votes)
9 Dec 2014CPOL2 min read 17.1K   3   6  
Article on Validation in XAML applications with Fluent technique

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 DLLs.

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

C#
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 parameter. i.e. in this case
        // model.ID is of type int so it will take parameter 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 the 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 example: 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.

C#
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 the 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 called 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:

C#
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:

C#
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.

XML
<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>

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
India India
Passionate about Microsoft Technologies like WPF, Windows Azure, ASP.NET, Win Phone and also on Cross platform Mobile Apps, Mongo DB's, IOT & WOT. I love learning and working with (or) Creating Design Patterns . Writer | Technology Evangelist | Technology Lover | Microsoft Developer Guidance Advisory Council Member | Cisco Champion | Speaker |

Blog : http://adityaswami89.wordpress.com/

Comments and Discussions

 
-- There are no messages in this forum --