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
ValidatorNotEmpty
ValidatorNotEqual
ValidatorEqual
ValidatorLength
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:
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();
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);
}
private bool IsNumber(int ID)
{
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.
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:
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>