Implementing Validation in WPF on Entity Framework Entities

Implementing Validation in WPF on Entity Framework Entities

  • Comments 22

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!

Leave a Comment
  • Please add 2 and 7 and type the answer here:
  • Post
  • Thanks Beth, I really needed this :-)

  • Can the same be done with asp.net

    and how can I get list of errors on the UI

  • 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

  • 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

  • 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

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

    Cheers

  • 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.

  • Hi Marco -- Thanks for sharing!

  • 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?  

  • 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

  • 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

  • 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

  • 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

  • Thanks for a great article! I'm new to WPF and EF and was just looking for approaches to perform validation. I really like what you have described but have a question...

    You place the validation on the onChanged event. It looks like this doesn't stop the value from being accepted. Does this rely on the UI stopping the focus from being lost on the bound control? If so, then isn’t this reliant on there being a UI. What if I don’t have a UI but set via some other process?

    To address required field validation and cross field type validation I was planning on using the following approach…

    Define the following interface to be applied to any EF Entities requiring this functionality.

    namespace DataAccess

    {

       interface ICrossValidate

       {

           void CrossValidate();

       }

    }

    Extend the EF Entities class to invoke the ICrossValidate method on affected entities when performing a Save operation.

    namespace DataAccess

    {

       public partial class MyEntities

       {

           partial void OnContextCreated()

           {

               // Register the handler for the SavingChanges event.

               this.SavingChanges

                   += new EventHandler(context_SavingChanges);

           }

           // SavingChanges event handler.

           private static void context_SavingChanges(object sender, EventArgs e)

           {

               // Find any entities that have been added or modified.

               foreach (ObjectStateEntry entry in

                   ((ObjectContext)sender).ObjectStateManager.GetObjectStateEntries(

                   EntityState.Added | EntityState.Modified))

               {

                   //Ignore relationships and entities that don't implement ICrossValidate.

                   if (!entry.IsRelationship && (entry.Entity is ICrossValidate))

                       {

                           //Perform the cross validation.

                           ((ICrossValidate)entry.Entity).CrossValidate();

                   }

               }

           }

       }

    }

    Extend each of the desired entity classes to implement the ICrossValidate interface.

    namespace DataAccess

    {

       public partial class Employee : EntityObject, ICrossValidate, IDataErrorInfo

       {

           public void CrossValidate()

           {

               // Set all properties to themselves to ensure that required fields are entered.

    // Perform any cross field validation.

    // When an error is encountered, throw an exception.

           }

  • Hi BillC,

    You would use the onChanging events to prevent a value from being set. The CrossValidate looks reasonable as long as you don't care which order the entities are validated.

    Cheers,

    -B

Page 1 of 2 (22 items) 12