Business objects can be directly validated in WPF using the IDataErrorInfo interface provided in WPF 3.5. With the help of the VAB and IDataErrorInfo we can implement a very easy and convenient Attribute based validation of the business objects in WPF. In this post, I’ll show an example how to achieve VAB + WPF integration to validate the business objects.
I am going to use the IDataErrorInfo implementation as in my previous article.
Using IDataErrorInfo requires you to implement the methods
public string Error{ get; }
public string this[string columnName]{ get; }
My base entity implements the IDataErrorInfo and INotifyPropertyChanged interfaces
public abstract class BaseEntity
{
[NotNullValidator(MessageTemplate="Id cannot be null", Tag="Id")]
public T Id { get; set; }
public abstract ValidationResults SelfValidate();
public abstract string GetError(ValidationResults results);
public abstract bool IsValid { get; }
#region IDataErrorInfo Members
public string Error
{
get
{
var __ValidationResults = SelfValidate();
return GetError(__ValidationResults);
}
}
public string this[string columnName]
{
get
{
var __ValidationResults = SelfValidate();
if (__ValidationResults != null)
{
var __ColumnResults = __ValidationResults.FirstOrDefault<ValidationResult>(x => string.Compare(x.Tag, columnName, true) == 0);
return __ColumnResults != null ? __ColumnResults.Message : string.Empty;
}
return string.Empty;
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Every business class inherits the base entity class and implements the abstract methods
[HasSelfValidation]
public class User : BaseEntity<Guid>
{
private string ___name;
private decimal ___cost;
private DateTime ___dateOfJoining;
[ValidatorComposition(Microsoft.Practices.EnterpriseLibrary.Validation.CompositionType.And)]
[StringLengthValidator(
0, RangeBoundaryType.Exclusive,
45, RangeBoundaryType.Inclusive,
MessageTemplate="Name should be in the range 0 - 45", Tag="Name")]
[NotNullValidator(MessageTemplate="Name cannot be null")]
public string Name
{
get { return ___name; }
set
{
___name = value;
OnPropertyChanged("Name");
}
}
[RangeValidator(
typeof(decimal),
"0.00", RangeBoundaryType.Inclusive,
"999.99", RangeBoundaryType.Inclusive,
MessageTemplate="Cost should be in the range 0 - 999.99", Tag="Cost")]
public decimal Cost
{
get { return ___cost; }
set
{
___cost = value;
OnPropertyChanged("Cost");
}
}
public DateTime DateOfJoining
{
get { return ___dateOfJoining; }
set
{
___dateOfJoining = value;
OnPropertyChanged("DateOfJoining");
}
}
[SelfValidation]
public void Validate(ValidationResults results)
{
if (DateOfJoining < new DateTime(2003, 10, 1) || DateOfJoining > DateTime.Now)
results.AddResult(new ValidationResult("Date of joining should be between 01-Oct-2003 and today", this, string.Empty, "DateOfJoining", null));
}
public override ValidationResults SelfValidate()
{
return EntityValidator<User>.ValidateEntity(this);
}
public override string GetError(ValidationResults results)
{
return EntityValidator<User>.GetValidationErrors(results);
}
public override bool IsValid
{
get{ return SelfValidate().IsValid; }
}
}
I have created an EntityValidator class that encapsulates my VAB method calls to validate the entity.
Finally the WPF Code
<Style x:Key="__TextBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="Width" Value="160" />
<Setter Property="Height" Value="30" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel DockPanel.Dock="Right" >
<AdornedElementPlaceholder />
<Image Source="{StaticResource Error}"
Width="15" Height="15"
ToolTip="{Binding Path=AdornedElement.ToolTip,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Adorner}}}" />
DockPanel>
ControlTemplate>
Setter.Value>
Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
Trigger>
Style.Triggers>
Style>
<TextBox Style="{StaticResource __TextBoxStyle}"
Text="{Binding Path=User.Cost,
Mode=TwoWay,
ValidatesOnDataErrors=True, Converter={StaticResource __DecimalConverter},
UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1" Grid.Row="1" />
I have my SaveCommand implemented in the ViewModel as
public ICommand SaveCommand
{
get
{
if (___SaveCommand == null) ___SaveCommand = new DelegateCommand
(
() => Save(User),
() => User.IsValid
);
return ___SaveCommand;
}
}
<Button Grid.Row="3" Grid.Column="1" Width="120"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="Save" Margin="20 0 0 0"
Command="{Binding SaveCommand}" />
The IsValid method checks whether the entity is valid for save.
Output