Reputation: 1966
I've read many articles on how to validate data in WPF - MVVM, but have gotten even more confused than I started.
What I'm trying to do is simply add a new row to my db. The validation checks if the device name (the object being inserted) is over 2 characters long AND I want to check for uniqueness, and this is where it becomes problematic.
The Model
, from my understanding should take care of its own validation: e.g. character length, email address validity (if it has an email property), etc. However, what about getting access to the data layer in order to check for uniqueness? The Model should not know anything about that layer (from my understanding). This validation rule, has now become a "business" rule.
Here's my RegisterViewModel
:
class RegisterViewModel : ViewModelBase, IDataErrorInfo
{
private string _DeviceName = "";
public string DeviceName
{
get { return _DeviceName; }
set
{
_DeviceName = value;
OnPropertyChanged("DeviceName");
}
}
public string Error
{
get
{
throw new NotImplementedException();
}
}
// We'd also check for uniqueness here?
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "DeviceName" && String.IsNullOrEmpty(DeviceName))
{
return "Device name is required";
}
return result;
}
}
public RegisterViewModel()
{
DeviceName = System.Environment.MachineName;
}
public ICommand Register { get; set; }
}
Of course, some would argue that IDataErrorInfo
should be implemented in the Model
class, and I would somewhat agree, but again, the whole uniqueness problem is still there.
Here's my device
Model:
public partial class device
{
public int did { get; set; }
public int uid { get; set; }
public string Name { get; set; }
}
And finally, the View
<Window x:Class="IPdevices.Register"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:IPdevices"
mc:Ignorable="d"
Title="Register" Height="202.884" Width="417.007" Icon="logo31_nowriting.ico" ResizeMode="NoResize" WindowStartupLocation="CenterScreen">
<Grid>
<GroupBox x:Name="groupBox" Header="Device" VerticalAlignment="Top" RenderTransformOrigin="0.877,2.187" Margin="10,10,10,0" Height="96">
<Grid Margin="0,0,3,3">
<Grid.RowDefinitions>
<RowDefinition Height="47*"/>
<RowDefinition Height="49*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="146*"/>
<ColumnDefinition Width="197*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="textBox" Height="24" TextWrapping="Wrap" VerticalAlignment="Top" Grid.Column="1" Margin="0,6,0,0" Text="{Binding Path=DeviceName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true, NotifyOnValidationError=true}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Label x:Name="label" Content="Device Name:" RenderTransformOrigin="1.372,1.475" Margin="10,2,10,0" Height="28" VerticalAlignment="Top"/>
<TextBox x:Name="textBox1" Height="23" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Margin="0,7,0,0" Grid.Row="1" Grid.Column="1"/>
<Label x:Name="label1" Content="Location:" Margin="10,3,10,0" Grid.Row="1" Height="27" VerticalAlignment="Top"/>
</Grid>
</GroupBox>
<Button x:Name="button" Content="Register" Height="25" VerticalAlignment="Top" Margin="10,129,10,0" IsDefault="True" ToolTip="Register" Command="{Binding Register}"/>
</Grid>
</Window>
At this point, the validation works, and a red outline pops up. Though I'm not sure about how to deal with uniqueness property for the device name. On top of that, how do I allow the Register button become active only when everything passes?
Any input would be appreciated.
Upvotes: 2
Views: 2611
Reputation: 3043
Tough question to be fair. When it comes to data validation, you can have different approaches and they are generally all working.
Thus I will give my personal view. Reading this post I wrote about MVVM may help you understand my position. So let's try to answer each of your question:
The Model, from my understanding should take care of its own validation
Yes, as long as it is possible, but as you see you may need to do more than that for some validation
The Model should not know anything about that layer.
It's a design decision, but I would generally agree.
This validation rule, has now become a "business" rule.
I really don't understand why? Be really careful with the word "business". Is this uniqueness a true requirement from your business experts? Or is it just a technical rule because you can't store it in a relational database otherwise?
Anyway, In my opinion a less ambiguous term when you need something to validate several models, or something to validate one model but using external data (like in your example) is called a service, and it should be build around use cases (in my post I take a buying service as example)
Of course, some would argue that IDataErrorInfo should be implemented in the Model class
And I would argue that IDataErrorInfo was designed with a really simplistic view of the world. It works in little CRUD application, but is not really convenient to manage realistic business application. I would argue that you should not use it. Just add properties in your view model, and manage the red popup and stuff by yourself. It is really not that hard, put a red border, change your property after validation, and it's done.
Though I'm not sure about how to deal with uniqueness property for the device name.
What is your use case? Create a service around this use case, and validate the uniqueness of your device in this service.
On top of that, how do I allow the Register button become active only when everything passes?
Forget about IDataErrorInfo, just implement it yourself. Anytime a property is updated in the UI, call your validation service again. If everytihing passes, active your button, otherwise specify to the user why it's not correct.
Does it make sense?
Upvotes: 1