Data validation with MVVM-Light WPF and Linq to Entity Framework
I think I have read every google article as I go through wpf mvvm-light and I have no idea where to go. I know that Josh smith, Karl Shifflett's and MVVM LIGHT use their own demo methods to validate the data. I see that more validation requires me to completely "rethink" my model in my view model. This means I need to create a property in my view model for every property on my model that I want to check (and in some cases convert the whole thing to string values ββfor binding / validation). This seems like a lot or redundancy where all I want to do is mark most of the fields as needed.
I am using LINQ for entity structure (self-checking) for my model classes which come from a SQL server database. As a result, I would rather keep the validation / rules of my business data in my view models. I am writing a simple service interface to get data from a model and pass it to my view model.
Most of the examples I can find date back to 2008 (like josh smith). Are these methods valid or are there more modern guidelines for validating mvvm data with .NET 4.5, etc.
So I ask:
1) What methods do you suggest using 2) What methods work best in LINQ to EF with MVVM-Light framework. 3) EDIT: I want to provide feedback to the user when they enter data, not just when the form is submitted
thank
source to share
I ended up using the following. I changed my model to use LINQ for self-tracking entities (see this article for information on STE http://msdn.microsoft.com/en-us/library/vstudio/ff407090%28v=vs.100%29. aspx ).
LINQ to STE raises the OnPropertyChanged event, which implements the iNotifyPropertyChanged interface.
I just created a public partial class for the corresponding model object (entity generated linq code) that I wanted and added an event handler for the event OnPropertyChanged
. Then I used the interface IDataErrorInfo
to check and IDataErrorInfo
error as needed. This allows me to validate fields as they change, which is reflected in the user. It also allows you to perform more advanced validation logic that might be required to query the database (i.e. to find if the username is already in use, etc.) or to create a dialog box.
In addition, having data validation in the model allows me to still have validation if I do direct "batch" operations that bypass the UI.
I then used HasErrors
and HasChanges
and used them to create a boolean that is attached to the relay commands, disabling the crud command buttons when there are errors.
I will post some simple code to describe what I just described, please comment if you want more details.
Here is an extension of the Entity Framework model class:
Imports System.ComponentModel
Partial Public Class client
Implements IDataErrorInfo
#Region "Properties / Declarations"
'Collection / error description
Private m_validationErrors As New Dictionary(Of String, String)
Private _HasChanges As Boolean = False
''Marks object as dirty, requires saving
Public Property HasChanges() As Boolean
Get
Return _HasChanges
End Get
Set(value As Boolean)
If Not Equals(_HasChanges, value) Then
_HasChanges = value
OnPropertyChanged("HasChanges")
End If
End Set
End Property
'Extends the class with a property that determines
'if the instance has validation errors
Public ReadOnly Property HasErrors() As Boolean
Get
Return m_validationErrors.Count > 0
End Get
End Property
#End Region
#Region "Base Error Objects"
'Returns an error message
'In this case it is a general message, which is
'returned if the list contains elements of errors
Public ReadOnly Property [Error] As String Implements System.ComponentModel.IDataErrorInfo.Error
Get
If m_validationErrors.Count > 0 Then
Return "Client 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
#Region "Base Error Methods"
'Adds an error to the collection, if not already present
'with the same key
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
'Removes an error from the collection, if present
Private Sub RemoveError(ByVal columnName As String)
If m_validationErrors.ContainsKey(columnName) Then
m_validationErrors.Remove(columnName)
End If
End Sub
#End Region
Public Sub New()
Me.HasChanges = False
End Sub
#Region "Data Validation Methods"
''handles event and calls function that does the actual validation so that it can be called explicitly for batch processes
Private Sub ValidateProperty(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Handles Me.PropertyChanged
If e.PropertyName = "HasChanges" Then
Exit Sub
End If
IsPropertyValid(e.PropertyName)
HasChanges = True
End Sub
Public Function IsPropertyValid(sProperty As String) As Boolean
Select Case sProperty
''add validation by column name here
Case "chrLast"
If Me.chrLast.Length < 4 Then
Me.AddError("chrLast", "The last name is too short")
Return True
Else
Me.RemoveError("chrLast")
Return False
End If
Case Else
Return False
End Select
End Function
#End Region
End Class
I then included the following code in the view model to bind the command and evaluate if it could be executed.
Public ReadOnly Property SaveCommand() As RelayCommand
Get
If _SaveCommand Is Nothing Then
_SaveCommand = New RelayCommand(AddressOf SaveExecute, AddressOf CanSaveExecute)
End If
Return _SaveCommand
End Get
End Property
Private Function CanSaveExecute() As Boolean
Try
If Selection.HasErrors = False And Selection.HasChanges = True Then
Return True
Else
Return False
End If
Catch ex As Exception
Return False
End Try
End Function
Private Sub SaveExecute()
''this is my LINQ to Self Tracking Entities DataContext
FTC_Context.SaveChanges()
End Sub
below shows how i bound my button (has its own style in WPF)
<Button Content="" Height="40" Style="{DynamicResource ButtonAdd}" Command="{Binding SaveCommand}" Width="40" Cursor="Hand" ToolTip="Save Changes" Margin="0,0,10,10"/>
thus, when there are no validation errors and the current client record is "isDirty", the save button is automatically enabled and disabled if either of these two conditions are not met. So I now have an easy way to validate whatever column / data type I want for an entity, and I can provide feedback to the user when they enter data into the form, and only enable CRUD command buttons after all my "conditions" were met met.
It was a real battle to understand.
source to share
The way I do this (not necessarily correct) is to do my validation in the ViewModel (where CRUD operations usually do), and then if there are validation errors, abort saving / appending any data and use Messenger.Default.Send
to send a custom post type to mine representation. Then I alert the user via DialogBox or otherwise.
I've experimented with Binding ValidationRules in the past, but by far the simplest statement has become the most reliable and consistent method if
.
source to share