(Print this page)

WPF - Using IDataErrorInfo on a class exposing validation attributes from a MetaData class
Published date: Monday, March 16, 2015
On: Moer and Éric Moreau's web site

Believe me. I searched the web not so long ago to find this information and couldn’t find anything complete. This article will try to document it.

I was using DataAnnotations to validate my classes before saving to Entity Framework 6 and tried to use IDataErrorInfo using the same validation attributes already in place instead of recreating them.

This seem simple but partial classes and the MetadataType attributes add some complexity to the task.

I made the demo so that even if you are not using the MetadataType and partial classes, you can still use this code as shown here below.

The context

I have a project that has a set of classes created using Entity Framework 6 database first model. Since I don’t want (and nobody should ever) manually modify the classes generated by EF, I created partial classes to complete the object as I wanted.

Because EF is correctly using DataAnnotations to validate before saving, I have created class only containing the DataAnnotations validation attributes to each signature.

Finally, I have attached the class containing the validation attributes to my partial class using the MetadataType attribute on the class.

At this point EF6 is correctly doing the validation preventing me to save if any property does not follow the validation rules.

Instead of waiting for the Save operation to fail, I wanted to use the IDataErrorInfo to proactively show the errors (if any) on the input screen so that the user can find quickly and easily what the errors are. The user experience is way better.

The demo application

Once again this month, the demo application is provided in both VB.Net and C# (C# code available in the downloadable demo attached to this article). It was built using Visual Studio 2013.

As you can see in figure 1, it is very easy to notice that the range field does not match the validation rules. In this specific demo, there is an area at the bottom of the dialog displaying all the errors. This is something we don’t often see. A preferred way of showing errors is often to use tooltips and this demo also shows how to do it. Last but not least, the Ok button is only available when there are no error at all.

Figure 1: The demo application in action



File MyEntityGenerated: The generated class

I will start with this file. It is a simple class containing 3 properties. The class name is MyEntity and in my scenario, it is the kind of classes that has been generated by Entity Framework. Because we want to always be able to regenerate it, we should never modify it. Because it is marked as Partial, we can have another file elsewhere in the project to complete the behavior of the object.

This is the code contained in the MyEntityGenerated file:

Partial Public Class MyEntity

    Private _range As Integer
    Private _required As String
    Private _noValidation As String

    Public Property Range() As Integer
        Get
            Return _range
        End Get
        Set(value As Integer)
            If _range <> value Then
                _range = value
                OnPropertyChanged("Range")
            End If
        End Set
    End Property

    Public Property Required() As String
        Get
            Return _required
        End Get
        Set(value As String)
            If _required <> value Then
                _required = value
                OnPropertyChanged("Required")
            End If
        End Set
    End Property

    Public Property NoValidation() As String
        Get
            Return _noValidation
        End Get
        Set(value As String)
            If _noValidation <> value Then
                _noValidation = value
                OnPropertyChanged("NoValidation")
            End If
        End Set
    End Property

End Class

This is the class on which we want to add validation attributes but since we cannot add them there, it has to be elsewhere.

File BaseClass: Stuff we don’t want to repeat

We all have methods that we don’t want to repeat over and over through the code. I have the code required for this demo in this BaseClass. Two behaviors will be covered by this base class.

The class is defined with this header:

Public MustInherit Class BaseClass
    Implements INotifyPropertyChanged
    Implements IDataErrorInfo

The first behavior is to implement the INotifyPropertyChanged interface. This behavior is required so that the UI refreshes automatically when the properties are modified. The code related to the INotifyPropertyChanged is really simple to implement:

Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Sub OnPropertyChanged(propertyName As String)
    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub

The IDataErrorInfo interface requires a bit more code but since it is written only once, it doesn’t really matter. The interface requires us to implement only 2 properties: Error, Item (this in C#). In many examples, you will see code written in the Item property (this in C#) that does the actual validation. I didn’t want to go that way as I want to reuse the validation attributes that we will defined a bit later.

The Error property in this demo is used at the bottom of the dialog to list all the validation errors. It is using a dictionary of Validators and a dictionary of PropertyGetters that will be filled later. Using these 2 dictionary, properties failing there validation rules will have the message associated with the attributes concatenated in a string. You will often see examples in which this Error property returns an empty string just to satisfy the interface.

The Item property gets called when the property needs to be validated. In the Xaml file, the ValidatesOnDataErrors is set to true so this property gets called at least when the dialog is shown. Also according to the UpdateSourceTrigger value in the Xaml file, this property will get called on every keystroke (PropertyChanged) or when the field is losing the focus (LostFocus).

This is the code required for my generic implementation of the IDataErrorInfo interface:

Protected MustOverride ReadOnly Property PropertyGetters() As Dictionary(Of String, Func(Of Object, Object))
Protected MustOverride ReadOnly Property Validators() As Dictionary(Of String, ValidationAttribute())

Public ReadOnly Property [Error]() As String Implements IDataErrorInfo.[Error]
    Get
        If PropertyGetters Is Nothing Then
            Return String.Empty
        End If
        Dim errors = (From i In Validators
                      From v In i.Value
                      Where Not v.IsValid(PropertyGetters(i.Key)(Me))
                      Select v.ErrorMessage).ToList()
        Return String.Join(Environment.NewLine, errors.ToArray())
    End Get
End Property

Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements IDataErrorInfo.Item
    Get
        If PropertyGetters Is Nothing Then
            Return String.Empty
        End If

        If PropertyGetters.ContainsKey(columnName) Then
            Dim value As Object
            Try
                value = PropertyGetters(columnName)(Me)
            Catch ex As Exception
                value = Nothing
            End Try
            Dim errors = Validators(columnName).
                Where(Function(v) Not v.IsValid(value)).
                [Select](Function(v) v.ErrorMessage).ToArray()
            OnPropertyChanged("Error")
            Return String.Join(Environment.NewLine, errors)
        End If

        OnPropertyChanged("Error")
        Return String.Empty
    End Get
End Property

Protected Shared Function GetValidations([property] As PropertyInfo) As ValidationAttribute()
    Return DirectCast([property].GetCustomAttributes(GetType(ValidationAttribute), True), ValidationAttribute())
End Function

Protected Shared Function GetValueGetter(propertyInfo As PropertyInfo) As Func(Of Object, Object)
    If propertyInfo Is Nothing Then
        Return Nothing
    End If

    Dim instance = Expression.Parameter(GetType(Object), "i")
    Dim convertInstance = Expression.TypeAs(instance, propertyInfo.DeclaringType)
    Dim [property] = Expression.[Property](convertInstance, propertyInfo)
    Dim cast = Expression.TypeAs([property], GetType(Object))
    Return DirectCast(Expression.Lambda(cast, instance).Compile(), Func(Of Object, Object))
End Function

File MyEntity: The other partial class and the validation attributes

We now have a base class and the class that we want to validate, we can add the missing pieces.

First, the validation rules. I told you that I was using DataAnnotations. It takes form of a small class listing all the properties and their validation attributes. In my demo, only 2 properties (of the 3) have validations. So my small class reads like this:

Public Class MyEntityMetadata
    <Required(ErrorMessage:="Field 'Range' is required.")>
    <Range(1, 10, ErrorMessage:="Field 'Range' is out of range (1-10).")>
    Public Property Range As Integer

    <Required(ErrorMessage:="Field 'Required' is required.")>
    <StringLength(5, ErrorMessage:="Field 'Requires' must be smaller than 5 characters.")>
    Public Property Required As String
End Class

To attach these attributes to the MyEntity class, we use the MetadataType attribute like this:

<MetadataType(GetType(MyEntityMetadata))>
Partial Public Class MyEntity
    Inherits BaseClass

We now need to fill the 2 dictionaries of the BaseClass. These 2 dictionaries are defined as MustOverride (abstract in C#). The class is using a shared constructor (static in C#) to fill them. Using a bit of reflection and a bit of Linq, the validation attributes are retrieved from the class and the property getters are built.

This is the code that I have in this class:

Private Shared ReadOnly InternalPropertyGetters As Dictionary(Of String, Func(Of Object, Object))
Private Shared ReadOnly InternalValidators As Dictionary(Of String, ValidationAttribute())

Shared Sub New()
    'get the validation attributes - don't forget to replace the entity name (2 places)
    InternalValidators = DirectCast(GetType(MyEntity).GetCustomAttributes(GetType(MetadataTypeAttribute), True)(0), MetadataTypeAttribute).
        MetadataClassType.
        GetProperties().
        Where(Function(p) GetValidations(p).Length <> 0).
        ToDictionary(Function(p) p.Name, Function(p) GetValidations(p))

    InternalPropertyGetters = GetType(MyEntity).GetProperties().
        Where(Function(p) InternalValidators.ContainsKey(p.Name)).
        ToDictionary(Function(p) p.Name, Function(p) GetValueGetter(p))
End Sub

Protected Overrides ReadOnly Property PropertyGetters() As Dictionary(Of String, Func(Of Object, Object))
    Get
        Return InternalPropertyGetters
    End Get
End Property

Protected Overrides ReadOnly Property Validators() As Dictionary(Of String, ValidationAttribute())
    Get
        Return InternalValidators
    End Get
End Property

File ValidationViewModel: Our view model

This is the easy part. In this demo, the only thing the view model only need to exposes an instance of the MyEntity class. This is the code I have:

Public Property MyObj() As MyEntity

Public Sub New()
    MyObj = New MyEntity With {.Range = 12, .Required = "yes"}
End Sub

File ValidationWindow.xaml: Our view

The view has no code behind. Everything is in the Xaml.

The first important thing is a resource to style the textboxes bound to a field that has an error:

<Style TargetType="{x:Type TextBox}" >
    <!--This resource add a red icon and a tooltip to the right of textbox having errors, a red border around the textbox-->
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="Margin" Value="3,3,25,3" />
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <DockPanel LastChildFill="true">
                    <Border Background="Red" DockPanel.Dock="right" Width="20" Height="20" Margin="3" CornerRadius="10"
                            ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}">
                        <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white">
                        </TextBlock>
                    </Border>
                    <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                        <Border BorderBrush="red" BorderThickness="1" />
                    </AdornedElementPlaceholder>
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Then each of the properties are bound much like this:

<TextBox Grid.Row="0" Grid.Column="1"
	     Text="{Binding Required, 
                BindingGroupName=input, 
                ValidatesOnDataErrors=True, 
                ValidatesOnExceptions=True, 
                UpdateSourceTrigger=PropertyChanged}"
/>

The Ok button is also bound to the special Validation.HasError like this:

<Button
	Content="OK"
	Margin="5"
	Height="25"
	Width="80"
	IsDefault="True"
	IsEnabled="{Binding (Validation.HasError), ElementName=Panel, Converter={StaticResource NotConverter}}" />

Run the sample

The most important snippets of code have been shown here but some more code is only visible in the downloaded demo (like the full Xaml, the converter, …).

What has been shown so far are the most important parts, what was not shown elsewhere!

What if you are not using the MetadataType attribute?

Everything is not lost! In fact there is a single line of code to change to get the proper Validators.

My downloadable demo has a class named MyEntityFull. This one is not using partial classes (all the parts into a single class) and the validation attributes are also directly on the properties definition.

The properties are declared like this:

<Required(ErrorMessage:="Field 'Range' is required.")>
<Range(1, 10, ErrorMessage:="Field 'Range' is out of range (1-10).")>
Public Property Range() As Integer
    Get
        Return _range
    End Get
    Set(value As Integer)
        If _range <> value Then
            _range = value
            OnPropertyChanged("Range")
        End If
    End Set
End Property

<Required(ErrorMessage:="Field 'Required' is required.")>
<StringLength(5, ErrorMessage:="Field 'Requires' must be smaller than 5 characters.")>
Public Property Required() As String
    Get
        Return _required
    End Get
    Set(value As String)
        If _required <> value Then
            _required = value
            OnPropertyChanged("Required")
        End If
    End Set
End Property

Public Property NoValidation() As String
    Get
        Return _noValidation
    End Get
    Set(value As String)
        If _noValidation <> value Then
            _noValidation = value
            OnPropertyChanged("NoValidation")
        End If
    End Set
End Property

I told you that a single line of code needed to be changed to support the validation of this class. Do you remember the code presented before in the shared constructor to fill the Validators and the PropertyGetters? The PropertyGetters remains exactly as shown before. The one that change is the Validators, it is in fact a bit simpler because we can use the GetProperties directly on the GetType of the class name. It is now filled like this:

InternalValidators = GetType(MyEntityFull).
    GetProperties().
    Where(Function(p) GetValidations(p).Length <> 0).
    ToDictionary(Function(p) p.Name, Function(p) GetValidations(p))

Conclusion

It is true that we find (almost) everything on the Internet. I spent an afternoon with my friend Laurent digging for that information and we didn’t find it.

Also, even if the project I needed it in is in C#, I haven’t seen much samples in VB. So now at least there is one!

I really hope that this article will be helpful to somebody else.


(Print this page)