Some of my recent projects have required a lot of user interaction. So I’ve had to prepare my applications for the possibility of someone mistyping or typing random stuff into a text box just to see what would happen. I spent some time trying to find a good way of handling validation in a WPF app. I came across a great tool called Fluent Validation that makes validation almost fun.
FluentValidation
A Fluent Interface aims to make code more readable mainly by allowing the programmer to chain method calls together. Wikipedia says there is more to it than this but at any rate that is as much of it as I understand. To start using FluentValidation head to http://fluentvalidation.codeplex.com/ and download it or, if you like to do things the easy way, use Nuget to add it to your project (PM> Install-Package FluentValidation).
With FluentValidation you create a class which defines rules for every property of a class that is bound to user input. Here is an example:
Public Class PartValidator Inherits AbstractValidator(Of Part) 'http://blogsprajeesh.blogspot.com/2009/11/fluent-validation-wpf-implementation.html Public Sub New() RuleFor(Function(part) (part.LeftPartProgramName)). NotEmpty. Matches("[A-Z](?:[A-Z]|[0-9]){0,15}\.MIN"). WithMessage("Invalid program name") RuleFor(Function(part) (part.FinishedPartNumber)). NotEmpty. Length(10, 11) RuleFor(Function(part) (part.RawPartNumber)). NotEmpty. Length(10, 11) RuleFor(Function(part) (part.QualityCheckCount)). InclusiveBetween(2, 300) RuleFor(Function(part) (part.LoaderYShift)). InclusiveBetween(-0.5#, 0.5#) RuleFor(Function(part) (part.LoaderZShift)). InclusiveBetween(-0.5#, 0.5#) RuleFor(Function(part) (part.Variable1)). InclusiveBetween(1, 200). NotEqual(Function(p) p.Variable2) RuleFor(Function(part) (part.Variable2)). InclusiveBetween(1, 200). NotEqual(Function(p) p.Variable1) End Sub End Class
Here I am using some of the built in validators such as .Match(Regex Expression), .NotEmpty, .NotEqual and .InclusiveBetween. You can find a list of the built in validators and information about how to make your own in the documentation section here.
FluentValidation isn’t WPF specific but, since I’m doing everything in WPF, I’m going to show you how I’m using it in WPF. The method I’m using I converted from this C# example: http://goo.gl/3mQZJ
Here is the class I needed to validate. (To keep the post length down I’ve removed some properties)
</pre></pre> Imports System.ComponentModel Imports FluentValidation Imports FluentValidation.Results Public Class Part : Implements INotifyPropertyChanged, IDataErrorInfo 'Backing Store variables removed for space Public Property FinishedPartNumber As String Get Return _finishedPartNumber End Get Set(ByVal Value As String) If (_finishedPartNumber = Value) Then Return _finishedPartNumber = Value NotifyPropertyChanged("FinishedPartNumber") End Set End Property '... More properties... #Region "Implement INotifyPropertyChanged" Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged Private Sub NotifyPropertyChanged(PropertyName As String) RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName)) End Sub #End Region #Region "Validation" Public Function SelfValidate() As ValidationResult Dim v = ValidationHelper.Validate(Of PartValidator, Part)(Me) IsValid = v.IsValid Return v End Function Public ReadOnly Property [Error] As String Implements IDataErrorInfo.Error Get Return ValidationHelper.GetError(SelfValidate()) End Get End Property Default Public ReadOnly Property Item(columnName As String) As String Implements IDataErrorInfo.Item Get Dim __ValidationResults = SelfValidate() If __ValidationResults Is Nothing Then Return String.Empty End If Dim __ColumnResults = __ValidationResults.Errors.FirstOrDefault(Function(x) String.Compare(x.PropertyName, columnName, True) = 0) Return If(__ColumnResults IsNot Nothing, __ColumnResults.ErrorMessage, String.Empty) End Get End Property #End Region End Class
The validation action is happening in the implemented ‘Item’ property. Here we can check all of our items for
To notify the user of a validation error all we have to do is add ValidatesOnDataErrors=”True” to our binding for the input field like so:
<TextBox Text=”{Binding LeftPartProgramName, ValidatesOnDataErrors=True}”/>
In a standard WPF window this will give us a red outline around the textbox with the message we specified for that property given in the tool tip. This doesn’t work well for touch screen apps so you will want to create an error template to display the message. Google “WPF error template” for examples like this one. Some WPF themes like MahApps Metro theme have a nice template for touch screens already built in.
Here is the ValidationHelper Class
Imports System.ComponentModel Imports FluentValidation Imports FluentValidation.Results Imports System.Text Public Class ValidationHelper Public Shared Function Validate(Of T As {IValidator(Of K), New}, K As Class)(entity As K) As ValidationResult Try Dim __Validator As IValidator(Of K) = New T() Return __Validator.Validate(entity) Catch ex As Exception MessageBox.Show(ex.Message) End Try End Function Public Shared Function GetError(result As ValidationResult) As String Dim __ValidationErrors = New StringBuilder() For Each validationFailure In result.Errors __ValidationErrors.Append(validationFailure.ErrorMessage) __ValidationErrors.Append(Environment.NewLine) Next Return __ValidationErrors.ToString() End Function End Class
Nice!!! Thank you for sharing this, great info!!