Introduction
Input validation with WPF and the MVVM design pattern is something of a tricky beast to handle. The default validation syntax available in XAML is not conducive to the separation
of concerns that MVVM design patterns encourage. Validation logic doesn’t belong in the View. You should be able to unit test your Model's error detection and input validation.
If validation is in the View, you cannot do that.
At the time I initially created this solution, a fairly extensive Google search didn’t find any solutions that I was happy with. My understanding is that there are some
pretty good options out there now that even use some classes available to you in .NET. So this article may be nothing more than an intellectual exercise, but I have
still found the solution I am sharing to be extremely fast to work with, and to include additional capability that I haven’t seen in other solutions.
The project provided is very simple. I leave the ability to implement roll back behavior on individual fields to you, but I didn’t include the SimpleCommand
class that
I typically use. That is part of my Timeline project.
The template in the project has a stubbed out button that can be hooked up to roll back functionality. You can see it in action with my
BookWeaver application.
Background
I first developed this solution several years ago while doing some WPF work as a consultant for a currency trading firm. They retained all ownership and rights to that code,
so I left that gig with all the concepts, but none of the implementation. I later recreated the concepts with small simplifications. Their implementation included localization,
and a few other advanced needs that I haven’t since recreated.
Goals
I set about this project with a few goals:
- Have each field in the Model know its state including:
- Changed/Unchanged
- Valid/Invalid
- Error Message
- Provide a simple and reusable means in the View to bind to and respond to the field’s state information.
- Provide automatic input sanitization such as string trimming.
- Eliminate tedious and repetitive code in the Model to check for errors. For example, I didn’t want to have
if(field <0) fieldError=true…
repeated in the Model
every time I needed a greater than zero validation rule.
- Eliminate tedious and repetitive code in the Model to sanitize input.
- Allow commit and roll back on the Model.
- Provide the View with information to display a validation summary
As you can probably tell, a lot of these goals have been achieved in different frameworks etc. I still feel that the project I am sharing can be very useful.
This simple implementation doesn’t address the need to localize error messages, but it can be extended to separate the error message from the Model layer of code.
Using the code
ModelBase
My implementation uses a ModelBase
class that uses a generic so you can set a Model to back any Entity. The ModelBase
tracks an observable collection of errors
and changed fields. It also has event handlers that a specific implementation can tie into to tie into error and change state management in the base. You can bind your
View to the ObservableCollection
s in the Model to display validation summaries etc.
ExtendedBoundField
The ExtendedBoundField
class is also a generic class. The class allows you to add to a collection of validators and sanitizers to a given value. It knows the value’s original
and current value, and consequently knows the field's state.
Validators and sanitizers
This project has a simple collection of Validators and Sanitizers you can use. You can also implement field comparison validators and many other kinds with
a little bit of creativity. Every Validator or Sanitizer you create can be snapped in to any ExtendedBoundField
, eliminating repetitive hand coded validation and sanitization.
Comparers and cloners
Should the need arise, you can control how comparison and assignment of your fields are managed as well. I prefer to keep things simple enough not to need these
because they can become serious brain teasers if you are not careful. Disclaimer: The implementation with Comparers and Cloners hasn’t been thoroughly tested.
I believe it should work well, but none of my personal projects have needed this. If you do use it, make sure to test it thoroughly.
Model implementations
This project has a very simple Model backing a dummy Person Entity. The potential downside to the framework at this point is the need for a lot of code in the specific Model.
Fortunately, the use of a Code Snippet allows you to very rapidly generate the code you need for each ExtendedBoundField
. If you install the Code Snippet included in this
demo project, then you can declare and hook up an ExtendedBoundField
in a matter of seconds. Simply type “exf” and tab through the snippet's different steps.
#region FirstName Extended Bound Field
private ExtendedBoundField<String> _firstName;
public ExtendedBoundField<String> FirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
_firstName.ErrorChangedHandler += Model_ErrorChangedHandler;
_firstName.ValueChangedHandler += Model_ValueChangedHandler;
_firstName.PropertyChanged += FirstName_PropertyChanged;
_firstName.OnCommitted += FirstName_OnCommitted;
}
}
void FirstName_OnCommitted(object sender, EventArgs e)
{
MyEntity.FirstName = FirstName.Value;
}
void FirstName_PropertyChanged(object sender,
System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Value")
{
MyEntity.FirstName = FirstName.Value;
}
}
#endregion
Notice that the Class
property for each ExtendedBoundField
wires up the events from the field to the appropriate information
in the ModelBase
in the setter. That allows us to encapsulate most of the work with an ExtendedBoundField
so that we can do this in a snippet.
The region is particularly nice.
_firstName.ErrorChangedHandler += Model_ErrorChangedHandler;
_firstName.ValueChangedHandler += Model_ValueChangedHandler;
_firstName.PropertyChanged += FirstName_PropertyChanged;
_firstName.OnCommitted += FirstName_OnCommitted;
In your Model’s constructor, you can then initialize each field and assign it validators and sanitizers. The last thing you need to do in the Model is to override
Commit
and RollBack
. You simply call each ExtendeBoundField
’s Commit
or RollBack
functions.
FirstName = new ExtendedBoundField<string>(p.FirstName, "FirstName",
"First Name is required and must be no longer than 100 letters");
var trimmer = new TrimStringSanitizer();
FirstName.AddSanitizer(trimmer);
var val = new RequiredStringValidator(true);
FirstName.AddValidator(val);
var max = new MaxLengthValidator(100);
FirstName.AddValidator(max);
Note: Commit
is very important if you want input sanitization to work. The Sanitizers run when a given ExtendedBoundField
is committed.
So if you never call that, then your Sanitizers do nothing.
If you can’t figure out how to handle some complicated validation for your Model via Validators, you can override a Model function AdditionalValidationPasses
.
This is useful if you need to do more complicated logic across multiple fields. It is to your advantage to do things through the Validators though because that allows
you to simply and automatically indicate what fields have errors in the UI, and every Validator you create becomes a reusable tool. Every bit of logic you put
in the override does not give you that benefit.
Similarly, you can override AdditionalHasChangesCheck
if you can’t figure out Cloners for your complex types. If you get stuck with a difficult input sanitization,
you can do work in the Commit
function. As mentioned, it will best suite your needs to leverage the framework, and I have found that you can for the vast majority
of the scenarios you will encounter.
ViewModels
This project doesn’t demonstrate how the ViewModels can work with this. ViewModels should expose commands that allow Views to run commands to commit or roll back
the Model’s values, among other things. The ViewModelBase
class also has several other properties that I usually find helpful in my MVVM development.
Views
The last trick to making this framework useful comes in how you bind to the field to respond to the change and error state in your fields. The key to making this work
is understanding how DataContext
as a property behaves. By binding a given UI Element’s DataContext
to the ExtendedBoundField
, you can then create styles
and templates that have simple paths to every property on the ExtendedBoundField
. All of this would have been for naught without that because you would have to duplicate triggers
and styles for the specific path to every ExtendedBoundField
.
<TextBox Style="{StaticResource EBTextBoxStyle}"
DockPanel.Dock="Left"
DataContext="{Binding Path=MyModel.FirstName}"
Width="150"
HorizontalAlignment="Left"/>
The following style works for all textboxes with an ExtendedBoundField
as the data context.
<Style x:Key="EBTextBoxStyle"
BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="{x:Type TextBox}">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsChanged}"
Value="true" />
<Condition Binding="{Binding Path=IsValid}"
Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush"
Value="{StaticResource ErrorBrush}" />
<Setter Property="BorderThickness"
Value="2" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsChanged}"
Value="true" />
<Condition Binding="{Binding Path=IsValid}"
Value="true" />
</MultiDataTrigger.Conditions>
<Setter Property="BorderBrush"
Value="{StaticResource ChangeBrush}" />
<Setter Property="BorderThickness"
Value="2" />
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=IsChanged}"
Value="false" />
<Condition Binding="{Binding Path=IsValid}"
Value="true" />
</MultiDataTrigger.Conditions>
</MultiDataTrigger>
</Style.Triggers>
<Setter Property="Text"
Value="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
<Setter Property="VerticalAlignment"
Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<Grid HorizontalAlignment="Stretch"
Width="{TemplateBinding Width}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Border BorderBrush="{TemplateBinding Property=BorderBrush}"
BorderThickness="{TemplateBinding Property=BorderThickness}"
Grid.Column="0"
HorizontalAlignment="Stretch"
Background="{TemplateBinding Property=Background}">
<ScrollViewer HorizontalAlignment="Stretch"
x:Name="PART_ContentHost" />
</Border>
<DockPanel Grid.Column="1"
LastChildFill="True">
-->
-->
<Button ToolTip="Roll back - not hooked up"
Name="RollBackButton"
IsTabStop="False"
DockPanel.Dock="Left" />
-->
<ContentPresenter Content="{StaticResource WarningImg}"
ToolTip="{Binding Path=Message}"
Name="ErrorIcon" />
</DockPanel>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsValid}"
Value="true">
<Setter TargetName="ErrorIcon"
Property="Visibility"
Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsValid}"
Value="false">
<Setter TargetName="ErrorIcon"
Property="Visibility"
Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsChanged}"
Value="true">
<Setter TargetName="RollBackButton"
Property="Visibility"
Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsChanged}"
Value="false">
<Setter TargetName="RollBackButton"
Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Conclusion
I have found this implementation to be extremely flexible, extensible, and fast to work with. Granted, if you take away the snippet, it becomes a lot more work to use.
I recognize that in some ways this reinvents wheels that exist, though to my knowledge those wheels didn’t exist at the time I created this. This does add a few bits
of nice functionality that I am not aware of in any other solution. I hope this is useful for other WPF developers out there. Even if it just primes you with an idea
or two for using other Validation methods, then that is cool.