public string Name
{
get { return name; }
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("Name can not be empty");
name = value;
NotifyPropertyChanged();
}
}
what to do when name is empty the code gets stuck at name= value .....does not shows message.
what should be done?
Try like this
public string Name
{
get
{
if (string.IsNullOrEmpty(name))
{
throw new ArgumentException("Name can not be empty");
}
return name;
}
set { name = value; }
}
If you actually need to stop the application execution in cases where name is null or empty, then by all means, throw an exception in property setter. It's not "forbidden"... but in most cases I would put the validation of properties somewhere else.
This is because if you do throw an exception then things might blow up, or you can "get stuck", as you put it. Also, if you need to reuse your model in different scenario, where properties are the same but name could be empty, what would you do then?
I don't know if your scenario involves UI or not but you can use ValidationRules to check the validity of properties and give cues to user via UI bindings.
Let's say you have a simple User model consisting of only name and age properties.
public class User : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
private int _age;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
public int Age
{
get { return _age; }
set { _age = value; OnPropertyChanged(); }
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Now, you want to chekc that yes, the name is actually not null nor empty string. Then you could create simple validation rule to check for this.
public class NameLengthRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string name = value as string;
return string.IsNullOrWhiteSpace(name) ? new ValidationResult(false, "Name can't be empty") : new ValidationResult(true, null);
}
}
Now when you have these in place, and you have instance of type User in your DataContext, you can use the user input and rule on your UI side (assuming it's WPF) like so.
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<AdornedElementPlaceholder/>
<TextBlock Foreground="Red">Invalid value!</TextBlock>
</DockPanel>
</ControlTemplate>
<Style x:Key="validationError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Validation.ErrorTemplate="{StaticResource validationTemplate}" Style="{StaticResource validationError}" Margin="150">
<TextBox.Text>
<Binding Path="User.Name" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:NameLengthRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</StackPanel>
</Grid>
Which gives you the following on valid input
and the following on invalid input
Related
I have a property, bound to a TextBox, which is only allowed to be a positive number (i.e. x > 0). Inspired by this post I have decided to implement this using IDataErrorInfo interface.
Following the instructions in that post, I can get a tool-tip warning to show, if the input cannot be validated. But I would like to have the validation warning shown in a seperate TextBlock, just below the input TextBox.
XAML:
<!-- ValidatingControl Style -->
<Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding
Path=(Validation.Errors)[0].ErrorContent,
RelativeSource={x:Static RelativeSource.Self}}" />
</Trigger>
</Style.Triggers>
</Style>
(...)
<TextBlock Text="Product price:" />
<TextBox
Name="Price"
DataContext="{Binding SelectedProduct}"
Style="{StaticResource ValidatingControl}"
Text="{Binding Path=Price,
StringFormat=N0,
ConverterCulture=da-DK,
Mode=TwoWay,
ValidatesOnDataErrors=True}"/>
<!-- This should contain validation warning -->
<TextBlock Margin="0 0 0 10" />
Binding property (C#):
public class ProductModel : IDataErrorInfo
{
public decimal Price { get; set; }
(...)
// Implementation of IDataErrorInfo
string IDataErrorInfo.Error
{
get { return null; }
}
string IDataErrorInfo.this[string columnName]
{
get
{
if (columnName == "Price")
{
// Validate property and return a string if there is an error
if (Price < 0)
return "Cannot be negative.";
}
// If there's no error, null gets returned
return null;
}
}
}
You can bind your textblock to IDataError interfaces indexer property.
Here modified code
<StackPanel>
<TextBlock Text="Product price:" />
<TextBox Name="Price" Text="{Binding Path=Price, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}"/>
<!-- This should contain validation warning -->
<TextBlock Margin="0 0 0 10" Text="{Binding [Price]}" />
</StackPanel>
Also you need to do some modifications to your view model.
class ViewModel : IDataErrorInfo, INotifyPropertyChanged
{
decimal _price;
public decimal Price
{
get => _price;
set
{
_price = value;
RaisePropertyChanged(nameof(Price));
RaisePropertyChanged("Item[]");
}
}
// Implementation of IDataErrorInfo
string IDataErrorInfo.Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
if (columnName == "Price")
{
// Validate property and return a string if there is an error
if (Price < 0)
return "Cannot be negative.";
}
// If there's no error, null gets returned
return null;
}
}
void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Note, that indexer property for IDataErrorInfo interface is implemented implicitly, otherwise WPF system for some reason can't bind to it.
Also take a look to the INotifyPropertyChanged interface implementation for Price property, especially on the RaisePropertyChanged("Item[]"); line. Without this line WPF's system binding system will not know, if there is an error.
You need to define the indexer as public property, implement the INotifyPropertyChanged interface and raise a change notification for the indexer when the Price property is set. This should work:
public class ProductModel : IDataErrorInfo, INotifyPropertyChanged
{
private decimal _price;
public decimal Price
{
get { return _price; }
set
{
_price = value;
NotifyPropertyChanged();
NotifyPropertyChanged("Item[]");
}
}
string IDataErrorInfo.Error
{
get { return null; }
}
public string this[string columnName]
{
get
{
if (columnName == "Price")
{
// Validate property and return a string if there is an error
if (Price < 0)
return "Cannot be negative.";
}
// If there's no error, null gets returned
return null;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<!-- This should contain validation warning -->
<TextBlock DataContext="{Binding SelectedProduct}" Margin="0 0 0 10" Text="{Binding [Price]}" />
I have an ObservableCollection which is based on the following class:
public class Data
{
public string Text { get; set; }
public DateTime Date { get; set; }
public bool IsActive { get; set; }
}
This observablecollection is used and binded as ItemSource for ListView. The following is DataTemplate for displaying data -
<DataTemplate>
<ViewCell>
<Frame OutlineColor="White" HasShadow="False">
<!-- Data -->
</Frame>
</ViewCell>
</DataTemplate>
Since the ObservableCollection is a collection of Data class which has a boolean property, I want to use it in order to change the frame's background color:
If property IsActive is true - BackgroundColor is Red
If property IsActive is false - BackgroundColor is Blue
I've looked into implementation of Triggers, however I can't seem to get them working correctly, and I'm not sure what I'm missing.
According to Xamarin Documentation I should be able to do :
<Frame>
<Frame.Trigger>
<!-- -->
</Frame.Trigger>
</Frame>
However that doesn't seem to be possible. Neither is this -
<Frame>
<Frame.Style>
<Style TargetType="Frame">
<Setter Property="BackgroundColor" Value="Blue" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="BackgroundColor" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Frame.Style>
</Frame>
The code above gives the following error message:
Xamarin.Forms.Xaml.XamlParseException: Position 28:26. The Property TargetType is required to create a Xamarin.Forms.DataTrigger object.
Not sure about your trigger problem but I think you should be able to accomplish the color changing by first implementing INotifyPropertyChanged on your Data class like so:
public class Data : INotifyPropertyChanged
{
public string Text { get; set; }
public DateTime Date { get; set; }
private bool _isActive;
public bool IsActive
{
get { return _isActive; }
set
{
if (value == _isActive)
{
return;
}
_isActive = value;
NotifyPropertyChanged("IsActive");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then, in your xaml, you should be able to do something like this:
<DataTemplate>
<ViewCell>
<Frame Background="{Binding IsActive, Converter={StaticResource IsActiveToColorConverter}}" OutlineColor="White" HasShadow="False">
<!-- Data -->
</Frame>
</ViewCell>
</DataTemplate>
Where IsActiveToColorConverter looks something like:
public class IsActiveToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isActive = (bool) value;
return isActive ? "Red" : "Blue";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
View Model
public class MyViewModel
{
public string MyProperty { get; set; }
}
XAML
<CheckBox IsChecked="{Binding !MyProperty.Equals('Steve')}" />
Is this possible? How?
This sort of thing can be done (and many say should be done) in Xaml without involving logic from the View Model. To see it work, create a View Model like this...
public class ViewModel : INotifyPropertyChanged
{
private string _myProperty;
public string MyProperty
{
[DebuggerStepThrough]
get { return _myProperty; }
[DebuggerStepThrough]
set
{
if (value != _myProperty)
{
_myProperty = value;
OnPropertyChanged("MyProperty");
}
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
And then bind it to some Xaml that looks like this...
<Grid>
<CheckBox Content="Some check box">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="IsChecked" Value="False"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyProperty}" Value="Steve">
<Setter Property="IsChecked" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</Grid>
This is a standard WPF Checkbox that has been styled with a data trigger. The trigger will set the IsChecked property to true whenever the 'MyProperty' property contains "Steve". Otherwise the CB will be unchecked (per the overriding Setter in the Style). It works because the trigger listens to changes in the VM's 'MyProperty'. So visualization is entirely relegated to the user surface.
Triggers can be combined (and even used with Template Selectors) to access powerful functions built-in to WPF; and they will bind to any dependency property on the Check box, like Background colour etc.
A lot of people will suggest a converter, which certainly works. But I've found a much quicker way is to create a new bool property to use and bind to that:
public string MyProperty{get;set;}
public bool MyPropertyChecked
{
get { return !MyProperty.Equals('Steve')}
}
I have an application where a number of custom buttons are dynamically generated within a WrapPanel. All works fine and I am able to assign border thickness, ImageSource, Content etc. as I generate the buttons in code. The customer now has a requirement to allow them to choose border colours for individual buttons and try as I might I cannot figure out the correct binding scenario. I'm on a steep WPF learning curve here so it may be that my initial design is somewhat off kilter.
In my Generic.XAML I have the button specified thus:
<Style TargetType="{x:Type local:LauncherButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LauncherButton}">
<Border Name="LauncherButtonBorder" BorderThickness="{TemplateBinding BThickness}"
CornerRadius="10" Background="White" >
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush" Value="SteelBlue" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="PaleGoldenrod" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<DockPanel LastChildFill="True" Background="White" Margin="3">
<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center"
Foreground="{DynamicResource TaskButtonTextBrush}" FontWeight="Bold"
Margin="5,0,0,0" VerticalAlignment="Center" FontSize="10"
Background="Transparent" DockPanel.Dock="Bottom" TextWrapping="Wrap" />
<Image Source="{TemplateBinding ImageSource}" Stretch="Uniform" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I want to dynamically change in c# the border colours that are currently set to static SteelBlue and PaleGoldenrod.
The button class is defined thus:
public class LauncherButton : ButtonBase
{
static LauncherButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LauncherButton), new FrameworkPropertyMetadata(typeof(LauncherButton)));
}
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public Thickness BThickness
{
get { return (Thickness) GetValue(BThicknessProperty); }
set { SetValue(BThicknessProperty,value);}
}
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(LauncherButton), new UIPropertyMetadata(null));
public static readonly DependencyProperty BThicknessProperty =
DependencyProperty.Register("BThickness", typeof(Thickness), typeof(LauncherButton), new UIPropertyMetadata(null));
}
and I'm binding some of the properties to an instance of the following class:
public class CustomButton:INotifyPropertyChanged
{
private string _type;
private string _buttonId;
private string _name;
private string _image;
private string _link;
private string _parent;
private List<CustomButton> _children;
private bool _isExpanded;
private bool _isSelected;
public string ButtonId
{
get { return _buttonId; }
set
{
if (value == _buttonId) return;
_buttonId = value;
OnPropertyChanged("ButtonId");
}
}
public string Type
{
get { return _type; }
set
{
if (value == _type) return;
_type = value;
OnPropertyChanged("Type");
}
}
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
public string Image
{
get { return _image; }
set
{
if (value == _image) return;
_image = value;
OnPropertyChanged("Image");
}
}
public string Link
{
get { return _link; }
set
{
if (value == _link) return;
_link = value;
OnPropertyChanged("Link");
}
}
public string Parent
{
get { return _parent; }
set
{
if (value == _parent) return;
_parent = value;
OnPropertyChanged("Parent");
}
}
public List<CustomButton> Children
{
get { return _children; }
set
{
if (Equals(value, _children)) return;
_children = value;
OnPropertyChanged("Children");
}
}
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value.Equals(_isExpanded)) return;
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value.Equals(_isSelected)) return;
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Are you trying to make the two brushes used for Border.BorderBrush dynamic?
If so you can address it in a few ways.
Add two dependency properties to LauncherButton for say NormalBorderBrush and MouseOverBorderBrush and then set it as you wish when you use the Button. Now to get the Border to use this, within it's Style where you set SteelBlue or PaleGoldenRod, apply a RelativeSource FindAncestor binding with AncestorType as local:LauncherButton and point it to the corresponding Brush(NormalBorderBrush or MouseOverBorderBrush)
Example:
public class LauncherButton : ButtonBase {
...
public static readonly DependencyProperty NormalBorderBrushProperty =
DependencyProperty.Register("NormalBorderBrush", typeof(Brush), typeof(LauncherButton),
new UIPropertyMetadata(Brushes.Blue));
public static readonly DependencyProperty MouseOverBorderBrushProperty =
DependencyProperty.Register("MouseOverBorderBrush", typeof(Brush), typeof(LauncherButton),
new UIPropertyMetadata(Brushes.Red));
public Brush NormalBorderBrush
{
get { return (Brush)GetValue(NormalBorderBrushProperty); }
set { SetValue(NormalBorderBrushProperty, value); }
}
public Brush MouseOverBorderBrush
{
get { return (Brush)GetValue(MouseOverBorderBrushProperty); }
set { SetValue(MouseOverBorderBrushProperty, value); }
}
}
in xaml:
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderBrush"
Value="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:LauncherButton}},
Path=NormalBorderBrush}" />
<Style.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter Property="BorderBrush"
Value="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:LauncherButton}},
Path=MouseOverBorderBrush}" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
and usage:
<local:LauncherButton BThickness="5"
Content="Hellooooo"
MouseOverBorderBrush="Green"
NormalBorderBrush="Aqua" />
Sample Download - This does not contain the converter for Brush usage, that should be easy enough to implement.
OR You could have two brushes defined as dynamic resources and override their color's from your code when you need to.
OR You can use the Button's BorderBrush property which it already has and apply this to the Border with a TemplateBinding BorderBrush. Now this would mean you need to switch the BorderBrush accordingly when your IsMouseOver state change occurs.
OR you could even go to extents of retrieving the button's Style and getting a reference to the Border element by finding it via it's Name and then tweaking it at run-time.
Personally I'd opt for option 1. Finally use a converter or likewise in the Binding to make it MVVM friendly.
I am using MVVMLight. This is my Department model/POCO class. I do not want to pollute it by any means.
public partial class Department
{
public int DepartmentId { get; set; }
public string DepartmentCode { get; set; }
public string DepartmentFullName { get; set; }
}
Here is the CreateDepartmentViewModel :
public class CreateDepartmentViewModel : ViewModelBase
{
private IDepartmentService departmentService;
public RelayCommand CreateDepartmentCommand { get; private set; }
public CreateDepartmentViewModel(IDepartmentService DepartmentService)
{
departmentService = DepartmentService;
this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute);
}
private Department _department = new Department();
public Department Department
{
get
{
return _department;
}
set
{
if (_department == value)
{
return;
}
_department = value;
RaisePropertyChanged("Department");
}
}
private Boolean CanExecute()
{
return true;
}
private void CreateDepartment()
{
bool success = departmentService.SaveDepartment(_department);
}
}
The DepartmentCode and DepartmentFullName is bind to UI as shown below.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Department Code" Grid.Row="0"/>
<TextBox Grid.Row="0" Text="{Binding Department.DepartmentCode, Mode=TwoWay}" Margin="150,0,0,0"/>
<TextBlock Text="Department Name" Grid.Row="1"/>
<TextBox Grid.Row="1" Text="{Binding Department.DepartmentFullName, Mode=TwoWay}" ToolTip="Hi" Margin="150,0,0,0"/>
<Button Grid.Row="2" Content="Save" Width="50" Command="{Binding CreateDepartmentCommand}"/>
</Grid>
Before saving the Department, I need to validate that both DepartmentCode and DepartmentFullName has some text in it.
Where should my validation logic reside ? In ViewModel itself ? If so, how do i decouple my validation logic so that it is also unit testable ?
I've found the easiest way to accomplish this is to use a
System.Windows.Controls.ValidationRule
It only takes 3 straight-forward steps.
First you create a ValidationRule. This is a completely separate class that exists outside both your Model and ViewModel and defines how the Text data should be validated. In this case a simple String.IsNullOrWhiteSpace check.
public class DepartmentValidationRule : System.Windows.Controls.ValidationRule
{
public override System.Windows.Controls.ValidationResult Validate(object value, CultureInfo ultureInfo)
{
if (String.IsNullOrWhiteSpace(value as string))
{
return new System.Windows.Controls.ValidationResult(false, "The value is not a valid");
}
else
{
return new System.Windows.Controls.ValidationResult(true, null);
}
}
}
Next, specify that your TextBoxes should use an instance of your new class to perform validation on the Text entered by specifing the ValidationRules property of the Text binding. You get the added bonus of the TextBox border turning red if the Validation fails.
<TextBlock Text="Department Code" Grid.Row="0"/>
<TextBox Name="DepartmentCodeTextBox" Grid.Row="0" Margin="150,0,0,0">
<TextBox.Text>
<Binding Path="Department.DepartmentCode" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DepartmentValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text="Department Name" Grid.Row="1"/>
<TextBox Name="DepartmentNameTextBox" Grid.Row="1" ToolTip="Hi" Margin="150,0,0,0">
<TextBox.Text>
<Binding Path="Department.DepartmentFullName" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DepartmentValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Finally, create a Style to disable the Save button if either TextBox fails validation. We do this by binding to the Validation.HasError property of the Textbox we bound our Validation rule to. We'll name this style DisableOnValidationError just to make things obvious.
<Grid.Resources>
<Style x:Key="DisableOnValidationError" TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=DepartmentCodeTextBox}" Value="True" >
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=DepartmentNameTextBox}" Value="True" >
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
And finally we set the DisableOnValidationError style on the Save button
<Button Grid.Row="2" Content="Save" Width="50" Command="{Binding CreateDepartmentCommand}"
Style="{StaticResource DisableOnValidationError}"/>
Now, if either of your TextBoxes fails Validation the TextBox gets highlighted and the Save button will be disabled.
The DepartmentValidationRule is completely separate from your business logic and is reusable and testable.
What about using ValidationRules class , this will decouple your model from poppluting it with validation code.
This will work great for individual controls but you can also delegate this logic to some custom validation classes , MvvmValidator framework will help you. This framework lets you write complex validation logic in the form of rules and these rules can be configured at ViewModel level and can be fired on submit button. its a nice decouple way of applying validations without populating your domian objects.
Create a DepartmentValidator class, which will be easily unit tested. Also, this class will allow you to eliminate duplication of validation in the server-side and UI scenarios.
public class DepartmentValidator
{
private class PropertyNames
{
public const string DepartmentFullName = "DepartmentFullName";
public const string DepartmentCode = "DepartmentCode";
}
public IList<ValidationError> Validate(Department department)
{
var errors = new List<ValidationError>();
if(string.IsNullOrWhiteSpace(department.DepartmentCode))
{
errors.Add(new ValidationError { ErrorDescription = "Department code must be specified.", Property = PropertyNames.DepartmentCode});
}
if(string.IsNullOrWhiteSpace(department.DepartmentFullName))
{
errors.Add(new ValidationError { ErrorDescription = "Department name must be specified.", Property = PropertyNames.DepartmentFullName});
}
if (errors.Count > 0)
{
return errors;
}
return null;
}
}
Create a DepartmentViewModel that wraps your Department model and implements IDataErrorInfo, so that you have more granular control and can display validation errors using standard Validation Templates.
public class DepartmentViewModel : IDataErrorInfo, INotifyPropertyChanged
{
private Department _model;
public DepartmentViewModel(Department model)
{
_model = model;
Validator = new DepartmentValidator();
}
public DepartmentValidator Validator { get; set; }
public string DepartmentFullName
{
get
{
return _model.DepartmentFullName;
}
set
{
if(_model.DepartmentFullName != value)
{
_model.DepartmentFullName = value;
this.OnPropertyChanged("DepartmentFullName");
}
}
}
public string DepartmentCode
{
get
{
return _model.DepartmentCode;
}
set
{
if(_model.DepartmentCode != value)
{
_model.DepartmentCode = value;
this.OnPropertyChanged("DepartmentCode");
}
}
}
public int DepartmentId
{
get
{
return _model.DepartmentId;
}
}
public string this[string columnName]
{
get
{
var errors = Validator.Validate(_model) ?? new List<ValidationError>();
if (errors.Any(p => p.Property == columnName))
{
return string.Join(Environment.NewLine, errors.Where(p => p.Property == columnName).Select(p => p.ErrorDescription));
}
return null;
}
}
public string Error
{
get
{
var errors = Validator.Validate(_model) ?? new List<ValidationError>();
return string.Join(Environment.NewLine, errors);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Expose the DepartmentViewModel, rather than the Department Model, and hook up the PropertyChanged event to the CreateDepartmentCommand so that your Save button will be automatically disabled when the department fails validation and so that you can display validation errors. Expose a ValidationErrors property.
public CreateDepartmentViewModel(IDepartmentService DepartmentService)
{
departmentService = DepartmentService;
_department = new DepartmentViewModel(new Department());
this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute);
_department.PropertyChanged += (s,a) =>
{
ValidationErrors = Department.Errors;
RaisePropertyChanged("ValidationErrors");
this.CreateDepartmentCommand.RaiseCanExecuteChanged();
}
}
public DepartmentViewModel Department
{
get
{
return _department;
}
set
{
if (_department == value)
{
return;
}
_department = value;
RaisePropertyChanged("Department");
}
}
public string ValidationErrors {get; set;}
private Boolean CanExecute()
{
return string.IsNullOrEmpty(ValidationErrors);
}
Before saving the Department, you might want to validate again.
private void CreateDepartment()
{
if(Department.Error!=null)
{
ValidationErrors = Department.Error;
RaisePropertyChanged("validationErrors");
return;
}
bool success = departmentService.SaveDepartment(_department);
}
I also find this annoying as it drives you business logic into the ViewModel forcing you to accept that and leave it there or duplicate it in the Service Layer or Data Model. If you don't mind losing some of the advantages of using annotations, etc. This is an approach I have used and seen most recommended - adding errors to a ValidationDictionary from the service layer.
You can also mix these, with business logic handled as above in your service layer, and UI-only relevant validations annotated in your ViewModel.
*Note That I am answering this from a MVC perspective, but I think it is all still relevant.
Add new method in your view model (Is Valid) and Modify the CanExecte method, you can easily test this by testing the CanExecute method:
public class CreateDepartmentViewModel : ViewModelBase
{
private IDepartmentService departmentService;
public RelayCommand CreateDepartmentCommand { get; private set; }
public CreateDepartmentViewModel(IDepartmentService DepartmentService)
{
departmentService = DepartmentService;
this.CreateDepartmentCommand = new RelayCommand(CreateDepartment, CanExecute);
}
private Department _department = new Department();
public Department Department
{
get
{
return _department;
}
set
{
if (_department == value)
{
return;
}
_department = value;
RaisePropertyChanged("Department");
}
}
private bool IsValid()
{
return !string.IsNullOrEmpty(this.Department.DepartmentCode) && !string.IsNullOrEmpty(this.Department.DepartmentFullName);
}
private Boolean CanExecute()
{
return this.IsValid();
}
private void CreateDepartment()
{
bool success = departmentService.SaveDepartment(_department);
}
}
You can make your Model class implement IDataErrorInfo interface.
If you don't want to pollute your Model, you can create a new class the inherits from it, and do the validation there
public class ValidDepartment : Department, IDataErrorInfo
{
#region IDataErrorInfo Members
public string Error
{
get { return null; }
}
public string this[string name]
{
get
{
if (name == "DepartmentCode")
{
if (string.IsNullOrEmpty(DepartmentCode)
return "DepartmentCode can not be empty";
}
if (name == "DepartmentFullName")
{
if (string.IsNullOrEmpty(DepartmentFullName)
return "DepartmentFullName can not be empty";
}
return null;
}
}
#endregion
}
In your ViewModel replace Department with ValidDepartment
private ValidDepartment _department = new ValidDepartment ();
public ValidDepartment Department
{
get
{
return _department;
}
set
{
if (_department == value)
{
return;
}
_department = value;
RaisePropertyChanged("Department");
}
}
In your View set ValidatesOnDataErrors=True to your binding controls
<TextBox Grid.Row="1" ToolTip="Hi" Margin="150,0,0,0">
<TextBox.Text>
<Binding Path="Department.DepartmentFullName"
Mode="TwoWay"
ValidatesOnDataErrors="True">
</Binding>
</TextBox.Text>
</TextBox>
Set TextBox Style and Validation.ErrorTemplate to determine how your validation will appear in the UI, for example, via Tooltip :
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
You can learn more about validation in WPF here, and here
Hope this helps
I use fluent validation in all my projects, not only to decouple, but also easily unit test my validation rules. http://fluentvalidation.codeplex.com/.
It also has a nuget package http://www.nuget.org/packages/FluentValidation/