Implementing Validation in WPF on Entity Framework Entities
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.
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.)
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
What 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!
Comments
Anonymous
July 07, 2009
Thanks Beth, I really needed this :-)Anonymous
July 07, 2009
Can the same be done with asp.net and how can I get list of errors on the UIAnonymous
July 08, 2009
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, -BAnonymous
July 08, 2009
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 modelAnonymous
July 09, 2009
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. -BAnonymous
July 09, 2009
Thanks Beth. You have your own charm to impress your attendees :-) CheersAnonymous
August 08, 2009
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.Anonymous
August 10, 2009
Hi Marco -- Thanks for sharing!Anonymous
August 14, 2009
The comment has been removedAnonymous
August 14, 2009
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, -BAnonymous
August 15, 2009
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. -TonyAnonymous
August 16, 2009
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. MikeAnonymous
August 16, 2009
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! MikeAnonymous
February 05, 2010
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. }Anonymous
February 05, 2010
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, -BAnonymous
August 30, 2010
Hi Beth, I implemented your validation. It seems to be working fine with border highlighted when the value is missing. My question is saving. Does this validation technique suppose to stop the commands from saving back to the database? Mine save to database even though there are fields highlighted with errors.Anonymous
August 30, 2010
Hi Scott, No you would need to check the Error collection to determine if the data was valid before executing your save routine.Anonymous
September 02, 2010
Thanks!Anonymous
September 21, 2010
Beth, When adding validation for an Entity Reference things work nicely when creating the new Order. I however have problems when instanciating an existing Order, the business object thinks there is no Customer reference. Seems that the Customer_AssociationChanged method does not get executed to clear the error. Any suggestions?Anonymous
February 19, 2013
Hi Beth. your application is usefully and i implemented some of your codes in my Silverlight project. But i have a problem yet. I cannot to bind two data source in ComboBox and selected item dont display. pleasssssssse HELP me to solve this my strong problem. mmbarzanooni@gmail.comAnonymous
July 25, 2014
code sample is not in C#, in VB.NET,really ?Anonymous
August 07, 2014
Impossible to get the video or code. I have this message : The Archive Gallery has been retired. Could you please give me a link where i can download this sample ? Thanks