Friday, July 31, 2009

Integrating Validation Application Block with WPF

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 : INotifyPropertyChanged, IDataErrorInfo

{

[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

3 comments:

Ale Afonso said...

Before I read you post, I have a question: How did you paste the text with the colors used in Visual Studio? I have tried to do it in my blog but I have not been able

Prajeesh said...

Try using Mozilla when you create a blog post and then paste the code from your VS. IE has some problem with the formattings :)

Deepti said...

Hi Prajeesh,

I have a query.
Actually I am using MVVM model & all the validations should be done using Enterprise Library through configuration file.
Now the thing is,in one of my screens I have a datagrid & i want to validate cell value for range validation using Enterprise library Config file.
My viewModel has a collection rather than the specific property for the cell so I cannot directly create the range validation rule in config file.
I am able to do this with the Custom Validation class but i want to achieve this with the Configuration file.

Is there any best way to do the same.I will really appreciate your help..Please let me know if you dont understand my query.

Thanks in advance.