Implementing Validation in WPF on Entity Framework Entities

Published 07 July 09 05:14 PM

I’ve blogged before about implementing validation on LINQ to SQL classes as well as how to customize the display of error messages in WPF. In this post I want to show how you can use these same techniques to validate entities coming from the Entity Framework (EF). Like LINQ to SQL classes, Entity Framework entities are implemented as partial classes so that you can extend them with your own code on top of the code that the designers generate for you. You can extend EF entities in a similar way as LINQ to SQL classes. 

Creating the Partial Class

Let’s take the example that I started here in this How Do I video on building a simple data entry form to edit customers. You can download the code for that video here. In this sample I have two projects, one for the WPF client (WpfEfDataEntry) and one for the Data Access Layer (WpfEfDAL) that contains a simple Entity Data Model (edmx) of a little database I created that tracks customers and their orders.

image

image

To extend the Customer class that is generated from the EF designer, right-click on the DAL project and select Add –> Class then name it Customer. This places the class in the same Namespace as the entities that are generated by the designer. This is necessary for partial classes to work. (Partial classes are just a way that you can define one class in multiple physical files and Visual Studio will handle compiling them into one class for you.)

image Here’s a trick in VB. You know you got your partial class in the right namespace when you drop down the Declarations dropdown and you see the list of partial methods and properties that the class defines.

Also in VB the Partial keyword is only required on one of the class declarations in one of the files. (In C# it’s required on all of them.) The EF designer generates the Customer class with the partial keyword. If you click the “Show all Files” button on the Solution Explorer toolbar and then expand the .edmx you can open the .Designer file and see the entity Partial Class definitions:

Partial Public Class Customer    

You can of course be explicit in VB and add the Partial keyword to all your partial class files as well.

Adding Validation to the Partial Class

To add validation we can implement the IDataErrorInfo interface in our customer partial class. Using this interface will make validation errors display in Winforms as well as WPF so I tend to prefer this implementation over others like ValidationRules collection in WPF. For this example let’s make sure that the LastName field isn’t empty but we’ll also provide a default value by specifying it in the constructor. This code is the same code we would use if we were working with LINQ to SQL classes.

Imports System.ComponentModel

Partial Public Class Customer
    Implements IDataErrorInfo

#Region "IDataErrorInfo Members"
    Private m_validationErrors As New Dictionary(Of String, String)

    Private Sub AddError(ByVal columnName As String, ByVal msg As String)
        If Not m_validationErrors.ContainsKey(columnName) Then
            m_validationErrors.Add(columnName, msg)
        End If
    End Sub

    Private Sub RemoveError(ByVal columnName As String)
        If m_validationErrors.ContainsKey(columnName) Then
            m_validationErrors.Remove(columnName)
        End If
    End Sub

    Public ReadOnly Property HasErrors() As Boolean
        Get
            Return m_validationErrors.Count > 0
        End Get
    End Property

    Public ReadOnly Property [Error]() As String _
Implements
System.ComponentModel.IDataErrorInfo.Error Get If m_validationErrors.Count > 0 Then Return "Customer data is invalid" Else Return Nothing End If End Get End Property Default Public ReadOnly Property Item(ByVal columnName As String) As String _
Implements
System.ComponentModel.IDataErrorInfo.Item Get If m_validationErrors.ContainsKey(columnName) Then Return m_validationErrors(columnName).ToString Else Return Nothing End If End Get End Property #End Region Public Sub New() 'Set defaults Me.LastName = "[new]" End Sub

 

Now we can write our validation code to check the LastName field. If you look back at the generated Customer class in the .Designer file, notice that there are OnFieldNameChanging and OnFieldNameChanged methods that are also declared as Partial. These are partial methods, a new feature introduced with Visual Studio 2008, that allow you to supply additional code that is called from the generated class. The Changing/Changed methods are called in the property setters. We’ll define the OnLastNameChanged to make sure the user enters a LastName:

    ''' <summary>
    ''' This method is called in the LastName property setter of the customer
    '''  partial class generated by the Entity Data Model designer.
    ''' </summary>
    Private Sub OnLastNameChanged()
        'Perform validation. 
        If _LastName Is Nothing OrElse _LastName.Trim() = "" OrElse _LastName.Trim() = "[new]" Then
            Me.AddError("LastName", "Please enter a last name.")
        Else
            Me.RemoveError("LastName")
        End If
    End Sub

End Class

Now all we need to do is specify on the binding in the XAML of the WPF form to display the validation errors.

<TextBox Name="txtLastName" Width="Auto" Height="28" Margin="3" 
         Text="{Binding Path=LastName, ValidatesOnDataErrors=True}"/>

This is exactly the same as we did in this post when working with LINQ to SQL. Read that post to also see how to change the default error template which controls how the errors are displayed. 

Adding Validation to Entity References

As you can see validating scalar properties on EF entities works the same as with LINQ to SQL classes. However what if we wanted to make sure that an entity reference was also specified on an EF entity? I’ve posted before about how to get notified when entity references change. But what if we also want to make sure an entity reference is not empty? For instance, in the case of the Order entity above, how would we write a validation to make sure that the Customer entity reference was specified on the Order before we tried to save?

Looking back up at the Customer (1)—(*) Order in the diagram above, the Order entity has a reference to its Customer parent as specified by the navigation property. In the database there is a foreign key relationship on CustomerID and that is inferred here by EF. This is a difference from LINQ to SQL classes where the classes contain the foreign keys as scalar properties as well. We can’t validate EF entities the same way because there are no scalar properties for the foreign keys. Instead we need to add an event handler to the AssociationChanged event on the entity reference (like I showed before) and then add in our validation. Remember that the AssociationChanged event will fire twice when we are selecting a new reference, once when the old entity reference is removed and then once when the new one is added.

Imports System.ComponentModel

Public Class Order
    Implements IDataErrorInfo

    Public Sub New()
        'Handle this event so that UI can be notified if the customer is changed
        AddHandler Me.CustomerReference.AssociationChanged, AddressOf Customer_AssociationChanged

        'Set defaults
        Me.OrderDate = Date.Today()
        'Customer is required 
Me.AddError("Customer", "Please select a customer.") End Sub Private Sub Customer_AssociationChanged(ByVal sender As Object, _ ByVal e As CollectionChangeEventArgs) If e.Action = CollectionChangeAction.Remove Then OnPropertyChanging("Customer") Else If e.Action = CollectionChangeAction.Add Then Me.RemoveError("Customer") End If OnPropertyChanged("Customer") End If End Sub

imageWhat I’m doing is putting the Order in an immediate error state so that the user can see that a Customer must be selected on the Order before it is valid. The error will only go away once they select a Customer.

I’ve created a sample application that you can download from Code Gallery that demonstrates using EF with WPF in a variety of ways including this example so have a look. Also make sure you check out these How Do I videos on EF and WPF.

Enjoy!

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Alessandro said on July 8, 2009 6:27 AM:

Thanks Beth, I really needed this :-)

# goldy said on July 8, 2009 6:28 AM:

Can the same be done with asp.net

and how can I get list of errors on the UI

# Beth Massi said on July 8, 2009 12:45 PM:

Hi Goldy,

Check out this tutorial on how you can use Entity Framework entities that implement IDataErrorInfo in ASP.NET MVC:

http://www.asp.net/learn/mvc/tutorial-37-vb.aspx

HTH,

-B

# goldy said on July 9, 2009 6:15 AM:

Thanks Beth for your help.

But i was looking something for WebForms. I am aware of ASP.net MVC which allows to integrate business validation in model

# Beth Massi said on July 9, 2009 11:55 AM:

Hi Goldy,

I'm not aware of any best practices on using IDataErrorInfo with webform validation controls. You probably should ask the quesiton on the www.asp.net forums.

-B

# Waleed El-Badry said on July 10, 2009 4:03 AM:

Thanks Beth. You have your own charm to impress your attendees :-)

Cheers

# Marco Rentier said on August 8, 2009 8:48 PM:

One thing I noted while implementing a small validation framework on top of the Entity Framework is that it's really helpful to be able to fire validation depending upon the state of entity (Added, Modified, Deleted) and to be able to specify to validate when the value changes and/or when persisting. I have a short blog entry here http://bitsthatbite.blogspot.com/2009/08/validation-within-entity-framework.html . I usually create the validation directly after I create the entities. Turns out to be a great way of doing contract driven development/testing. As you start writing code that uses these entities, the validations are always performed.

# Beth Massi said on August 10, 2009 12:54 PM:

Hi Marco -- Thanks for sharing!

# Tony said on August 14, 2009 1:49 PM:

Thanks Beth, but after working my way through this and an implementation of my own, I had some difficulties in implementing the behavior I wanted.  When a user creates a new object bound to my WPF form with a partial class implementing IDataErrorInfo, I find that if the user immediately clicks the button to that does the DataContext.SaveChanges, leaving all the fields empty (or just one), I can say "bad user", but the fields that fail to validate do not show up with the red border because there has been no event to fire the Validation check.  How can I get VS to run the validation on all the fields where I have set ValidatesOnDataErrors=true?  I want it to highlight a field as invalid after a user has changed it to be invalid (that works), but also, when the user has forgotten to set the field altogether, without it being all marked up with red boxes before they have even attempted to input something...  Thoughts?  

# Beth Massi said on August 14, 2009 2:35 PM:

Hi Tony,

One thing you can do is set the default values of all the fields that need to be filled out in the constructor. This will trigger the validation right away on all the properties and the user will see right away what is required. Up above I'm doing this on the LastName property setting it to "[new]" and then checking that in the validaiton rule.

Another way to do it is to create a Validate() method on the partial class that sets each of the properties to the same value (watch out for nulls), expose that in your collection, and then call that before your save. Something like:

Public Class Customer

   Implements IDataErrorInfo

   Public Sub Validate()

       'Fires property changed which triggers the validation

       Me.LastName = If(_LastName, "") 'cannot be null/nothing

       Me.FirstName = _FirstName

       Me.City = _City

       Me.Address = _Address

       Me.ZIP = _ZIP

   End Sub

...

Public Class CustomerCollection

   Inherits ObservableCollection(Of Customer)

   Sub Validate()

       For Each c In Me

           c.Validate()

       Next

   End Sub

...

'Then back on the form....

Private Sub btnSave_Click() Handles btnSave.Click

       Try

           Me.CustomerData.Validate()

           'Check to see if there are any errors and if so,

           ' move the position to the first error

           Dim errors = From c In Me.CustomerData _

                        Where c.Error IsNot Nothing

           If errors.Count > 0 Then

               Me.View.MoveCurrentTo(errors.First())

           Else

               db.SaveChanges()

               MessageBox.Show("Customer data was saved.")

           End If

       Catch ex As Exception

           MsgBox(ex.ToString())

       End Try

   End Sub

HTH,

-B

# Tony said on August 15, 2009 2:35 PM:

Wow, thanks for the quick response Beth.  I ended up taking a slightly different approach.  Your first suggestion was probably the simplest, but I am using some nice watermarked textboxes and I didn't want to lose the watermark by prepopulating with "[new]" or something.  Also, I would be afraid of ending up with "[new]" in the database.  The second option was a bit less elegant than I had hoped, but I would have used it had I not discovered a code fragment written by Sacha Barber (that guy is a genius).  Here is the code I used in my Save method (sorry, it's in C#):

public void Save()

{

    if (CurrentCustomer.HasErrors)

    {

         // This loop triggers the validation check if some fields are completely untouched

               foreach (var aField in this.FindChildren<TextBox>())

               {

                   var f = aField.GetBindingExpression(TextBox.TextProperty);

                   if (f != null) f.UpdateSource();

               }

                // show a message to user here

           }

           else

           {

               try

               {

                   LocalContext.SaveChanges();

               }

               catch (Exception ex)

               {

                   // show user exception message here.                                      

               }

           }

       }

Now, I know it only works for Textboxes or controls that inherit from it, but that's all I had to deal with.  A more generic version might be possible too, but this call to UpdateSource did the trick by causing the Validation to be triggered.  

-Tony

# Mike G said on August 17, 2009 12:48 AM:

Tony,

I am coming from C++ (and am actually quite new to this stuff and also using C# for my project), however, I may have another solution for you.  I am using a slightly different markup in my XAML for validation.

Whereas Beth is doing this:

<TextBox Name="txtLastName" Width="Auto" Height="28" Margin="3"

        Text="{Binding Path=LastName, ValidatesOnDataErrors=True}"/>

I am doing this:

<TextBox x:FieldModifier="private" Name="txtFirstName"

Text="{Binding Path=FirstName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />

I *believe*, that using UpdateSourceTrigger=PropertyChanged causes the validation to fire from the get-go (or when you do anything to your property for that matter.  It will validate character, for character in fact.

When I create a new record with blank data, for example, all of the fields I am validating are correctly marked.

Mike

# Mike G said on August 17, 2009 1:44 AM:

I just realized that my answer to Tony doesn't help, given that I am doing the guts of my validation differently than in the example.

My validation doesn't require OnPropertyChanging/OnPropertyChanged to be called.

Sorry about that!

Mike

Leave a Comment

(required) 
(optional)
(required) 

  
Enter Code Here: Required

About Beth Massi

Beth is a Program Manager on the Visual Studio Community Team at Microsoft and is responsible for producing and managing content for business application developers, driving community features and team participation onto MSDN Developer Centers (http://msdn.com), and helping make Visual Studio one of the best developer tools in the world. She also produces regular content on her blog (http://blogs.msdn.com/bethmassi), Channel 9, and a variety of other developer sites and magazines. As a community champion and a long-time member of the Microsoft developer community she also helps with the San Francisco East Bay .NET user group and is a frequent speaker at various software development events. Before Microsoft, she was a Senior Architect at a health care software product company and a Microsoft Solutions Architect MVP. Over the last decade she has worked on distributed applications and frameworks, web and Windows-based applications using Microsoft development tools in a variety of businesses. She loves teaching, hiking, mountain biking, and driving really fast.

This Blog

Syndication

Page view tracker