In this post I’ll be showing how you can use the DesignerProperties.GetIsInDesignMode method to populate controls when you’re designing WPF applications in Expression Blend.
This solution does not work in Visual Studio 2010. To find out about what options do work in Visual Studio check out the following posts:
The image below shows a user control in Expression Blend which allows users take part in a poll to find out what their favorite colors of the rainbow are.
As you can see it’s difficult if not impossible to imagine what we are actually designing here.
Creating a ViewModelProvider a.k.a ViewModel ServiceLocator
Essentially what we want is to use a ViewModel populated with sample data during design time and the actual ViewModel when the application is running.
To do this we can use the ViewModel and DesignTimeViewModel classes shown below.
public class ViewModel : INotifyPropertyChanged
{
private string _name;
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public ObservableCollection<RainbowColor> RainbowColors { get; set; }
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
}
public class DesignTimeViewModel : ViewModel
{
public DesignTimeViewModel()
{
Name = "Roy G Biv";
RainbowColors = new ObservableCollection<RainbowColor>()
{
new RainbowColor() {Description = "Red", Color = "#FFFF0000"},
new RainbowColor() {Description = "Orange", Color = "#FFFFA500"},
new RainbowColor() {Description = "Yellow", Color = "#FFFFFF00"},
new RainbowColor() {Description = "Green", Color = "#FF008000"},
new RainbowColor() {Description = "Blue", Color = "#FF0000FF"},
new RainbowColor() {Description = "Indigo", Color = "#FF4B0082"},
new RainbowColor() {Description = "Violet", Color = "#FFEE82EE"}
};
}
}
public class RainbowColor
{
public string Description { get; set; }
public string Color { get; set; }
}
A provider/service locater class allows Expression Blend to determine which ViewModel to use.
public class ViewModelProvider
{
public ViewModel ViewModel
{
get
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
{
return new DesignTimeViewModel();
}
return new ViewModel();
}
}
}
Setting the DataContext to use the ViewModelProvider in XAML
In order to bind the sample data to the user control during design time we need to add a line in the opening tag, create a resource and set the data context to use the ViewModel returned by the provider/service locator.
The lines to do this are highlighted in the XAML for the user control below.
In this post we will be looking at how validation can be done by implementing the IDataErrorInfo interface for a calculator we have been building as part of the Silverlight refactoring series.
The IDataErrorInfo interface gives you the ability to do validation without throwing exceptions.
The full solution for this post can be downloaded here.
Pre IDataErrorInfo
As you can see in the code below we are throwing exceptions in the setters for the two values we want to add together.
public string FirstValue
{
get { return _firstValue; }
set
{
_firstValue = value;
try
{
int.Parse(_firstValue);
}
catch (Exception)
{
throw new Exception("That's not a number");
}
OnPropertyChanged("FirstValue");
}
}
public string SecondValue
{
get { return _secondValue; }
set
{
_secondValue = value;
try
{
int.Parse(_secondValue);
}
catch (Exception)
{
throw new Exception("That's not a number");
}
OnPropertyChanged("SecondValue");
}
}
Implementing the IDataErrorInfo interface
The IDataErrorInfo interface consists of two properties.
For the Error property we can just return null because we don’t want to return a single error message for the entire object.
public string Error
{
get { return null; }
}
For the property which returns an error for a text box we could do the validation like this
public string this[string columnName]
{
get
{
string error = null;
switch (columnName)
{
case "FirstValue":
try
{
int.Parse(_firstValue);
}
catch (Exception)
{
error = "That is not a number";
}
break;
case "SecondValue":
try
{
int.Parse(_firstValue);
}
catch (Exception)
{
error = "That is not a number";
}
break;
}
return error;
}
}
But as we saw in a previous post about using ValidatesOnExceptions to do validation it’s much easier to write unit tests when there are separation of concerns and the ViewModel is not responsible for validation.
So this means we need to create a class for storing the validation error for each textbox
public class ValidationBase
{
public readonly Dictionary<string, string> Errors;
public ValidationBase()
{
Errors = new Dictionary<string, string>();
}
public void AddError(string propertyName, string message)
{
if (!Errors.ContainsKey(propertyName))
{
Errors[propertyName] = message;
}
}
public void RemoveErrors(string propertyName)
{
Errors.Remove(propertyName);
}
public string GetErrorMessageForProperty(string propertyName)
{
string message;
Errors.TryGetValue(propertyName, out message);
return message;
}
public bool HasErrors()
{
return Errors.Count != 0;
}
}
which is inherited by a CalculatorValidator class that returns a boolean value if the property value is not valid
[Export(typeof(ICalculatorValidator))]
public class CalculatorValidator : ValidationBase, ICalculatorValidator
{
[ImportMany]
public IEnumerable<ICalculatorValidationRule> CalculatorValidationRules { get; set; }
public bool IsPropertyValid(string propertyName, string value)
{
RemoveErrors(propertyName);
foreach (var calculatorValidationRule in CalculatorValidationRules)
{
if (!calculatorValidationRule.IsValid(value))
{
AddError(propertyName, calculatorValidationRule.ErrorMessage);
return false;
}
}
return true;
}
}
Now we can inject/import the CalculatorValidator into the ViewModel and use it to validate the users input.
As we only want the user to be able to click the calculate button when the form is valid we call we the CheckIfCalculteButtonShouldBeEnabled method when a text box value has changed.
[Export]
public class CalculatorViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string _firstValue;
private string _secondValue;
private string _result;
private readonly ICalculator _calculator;
private readonly RelayCommand _calculateCommand;
public event PropertyChangedEventHandler PropertyChanged;
private readonly ICalculatorValidator _calculatorValidator;
[ImportingConstructor]
public CalculatorViewModel(ICalculator calculator, ICalculatorValidator calculatorValidator)
{
_calculator = calculator;
_calculatorValidator = calculatorValidator;
_calculateCommand = new RelayCommand(Calculate) { IsEnabled = true };
_firstValue = "0";
_secondValue = "0";
}
public void Calculate()
{
Result = _calculator.Add(Convert.ToInt32(FirstValue), Convert.ToInt32(SecondValue)).ToString();
}
public string FirstValue
{
get { return _firstValue; }
set
{
_firstValue = value;
OnPropertyChanged("FirstValue");
}
}
public string SecondValue
{
get { return _secondValue; }
set
{
_secondValue = value;
OnPropertyChanged("SecondValue");
}
}
public void CheckIfCalculteButtonShouldBeEnabled()
{
_calculateCommand.IsEnabled = _calculatorValidator.HasErrors() == false;
}
public string Result
{
get { return _result; }
private set
{
_result = value;
OnPropertyChanged("Result");
}
}
public RelayCommand CalculateCommand
{
get { return _calculateCommand; }
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public string this[string columnName]
{
get
{
string error = null;
switch (columnName)
{
case "FirstValue":
error = ValidateNumber("FirstValue", _firstValue);
break;
case "SecondValue":
error = ValidateNumber("SecondValue", _secondValue);
break;
}
CheckIfCalculteButtonShouldBeEnabled();
return error;
}
}
public string ValidateNumber(string propertyName, string value)
{
if (!_calculatorValidator.IsPropertyValid(propertyName, value))
{
return _calculatorValidator.GetErrorMessageForProperty(propertyName);
}
return null;
}
public string Error
{
get { return null; }
}
}
The code below shows how we can unit test the ViewModel which implements the IDataErrorInfo interface.
[TestFixture]
public class When_using_the_CalculatorViewModel
{
private Mock<ICalculator> _calculator;
private Mock<ICalculatorValidator> _calculatorValidator;
private CalculatorViewModel _calculatorViewModel;
[SetUp]
public void SetUp()
{
_calculator = new Mock<ICalculator>();
_calculatorValidator = new Mock<ICalculatorValidator>();
_calculatorViewModel = new CalculatorViewModel(_calculator.Object, _calculatorValidator.Object);
}
[Test]
public void Initial_value_of_first_number_is_0()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.FirstValue;
// Assert
result.ShouldEqual("0");
}
[Test]
public void Initial_value_of_second_number_is_0()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.SecondValue;
// Assert
result.ShouldEqual("0");
}
[Test]
public void Initial_value_of_calculate_button_is_enabled()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.CalculateCommand.IsEnabled;
// Assert
result.ShouldBeTrue();
}
[Test]
public void ValidateNumber_returns_null_if_value_is_valid()
{
// Arrange
_calculatorValidator.Setup(c => c.IsPropertyValid("X","X")).Returns(true);
// Act
var result = _calculatorViewModel.ValidateNumber("X","X");
// Assert
result.ShouldBeNull();
}
[Test]
public void ValidateNumber_returns_error_message_if_value_is_not_valid()
{
// Arrange
const string errorMessage = "ErrorMessageText";
_calculatorValidator.Setup(c => c.IsPropertyValid("X", "X")).Returns(false);
_calculatorValidator.Setup(c => c.GetErrorMessageForProperty("X")).Returns(errorMessage);
// Act
var result = _calculatorViewModel.ValidateNumber("X", "X");
// Assert
result.ShouldEqual(errorMessage);
}
[Test]
public void Calculate_command_should_not_be_enabled_if_ViewModel_is_not_valid()
{
// Arrange
_calculatorValidator.Setup(c => c.HasErrors()).Returns(true);
// Act
_calculatorViewModel.CheckIfCalculteButtonShouldBeEnabled();
// Assert
_calculatorViewModel.CalculateCommand.IsEnabled.ShouldBeFalse();
}
[Test]
public void Calculate_command_should_be_enabled_if_ViewModel_is_valid()
{
// Arrange
_calculatorValidator.Setup(c => c.HasErrors()).Returns(false);
// Act
_calculatorViewModel.CheckIfCalculteButtonShouldBeEnabled();
// Assert
_calculatorViewModel.CalculateCommand.IsEnabled.ShouldBeTrue();
}
}
The final change we need to make is to change the text box binding to ValidatesOnDataErrors=True in the XAML file.
While could validate the users input using exceptions, it wasn’t possible to disable the calculate button if the input values were invalid (because they were either blank or non-numeric).
The code used in this post can be downloaded here.
So how are we going to solve the problem?
Throughout the Silverlight refactoring series I’ve tried to illustrate how important SOLID design principles are for having testable applications.
So if we think about the ViewModel, we need to ask ourselves if it’s the right place or should be responsible for validation?
In this case I would say no.
When should the button be enabled?
For a calculate button to be enabled, both text boxes must contain numeric values
This means we have to know if both text boxes are valid at the same time. Taking a step back here, let’s think about the bigger picture.
What if there are three or four text boxes?
What we really after is a class that will be responsible for knowing if any text boxes are invalid.
This can be done by using a validation base class.
In the code below notice how the ValidationBase class doesn’t know anything about enabling or disabling the calculate button.
public class ValidationBase
{
public readonly Dictionary<string, string> Errors;
public ValidationBase()
{
Errors = new Dictionary<string, string>();
}
public void AddError(string propertyName, string message)
{
if (!Errors.ContainsKey(propertyName))
{
Errors[propertyName] = message;
}
}
public void RemoveErrors(string propertyName)
{
Errors.Remove(propertyName);
}
public bool IsPropertyValid(string propertyName)
{
return !Errors.ContainsKey(propertyName);
}
public string GetErrorMessageForProperty(string propertyName)
{
string message;
Errors.TryGetValue(propertyName, out message);
return message;
}
public bool IsValid()
{
return Errors.Count == 0;
}
}
The code below shows the unit tests for the ValidationBase class.
[TestFixture]
public class When_using_the_ValidatiorBase
{
private ValidationBase _validationBase;
[SetUp]
public void SetUp()
{
_validationBase = new ValidationBase();
}
[Test]
public void IsValid_should_return_false_when_errors_exist()
{
// Arrange
_validationBase.AddError("propertyName", "message");
// Act
var result = _validationBase.IsValid();
// Assert
result.ShouldBeFalse();
}
[Test]
public void IsValid_should_return_true_when_no_errors_exist()
{
// Arrange
// collection will be empty at this point
// Act
var result = _validationBase.IsValid();
// Assert
result.ShouldBeTrue();
}
[Test]
public void IsPropertyValid_should_return_false_if_error_exists()
{
// Arrange
_validationBase.AddError("propertyName", "message");
// Act
var result = _validationBase.IsPropertyValid("propertyName");
// Assert
result.ShouldBeFalse();
}
[Test]
public void IsPropertyValid_should_return_true_if_error_does_not_exist()
{
// Arrange
// collection will be empty at this point
// Act
var result = _validationBase.IsPropertyValid("X");
// Assert
result.ShouldBeTrue();
}
[Test]
public void Should_be_able_to_return_message_for_error()
{
// Arrange
_validationBase.AddError("propertyName", "message");
// Act
var result = _validationBase.GetErrorMessageForProperty("propertyName");
// Assert
result.ShouldEqual("message");
}
[Test]
public void Should_return_null_if_message_does_not_exist_for_error()
{
// Arrange
// collection will be empty at this point
// Act
var result = _validationBase.GetErrorMessageForProperty("propertyName");
// Assert
result.ShouldBeNull();
}
}
Validating the users input
There are three outcomes when validating what the user has entered:
The value is blank
The value is not a number
The value is a number
All the ViewModel wants to know is if the users input is valid, choosing the appropriate error message isn’t its concern. This means we need a class that will be responsible for validation and returning the relevant message.
[Export(typeof(ICalculatorValidator))]
public class CalculatorValidator : ValidationBase, ICalculatorValidator
{
[ImportMany]
public IEnumerable<ICalculatorValidationRule> CalculatorValidationRules { get; set; }
public void ValidateNumber(string propertyName, string value)
{
RemoveErrors(propertyName);
foreach (var calculatorValidationRule in CalculatorValidationRules)
{
if (!calculatorValidationRule.IsValid(value))
{
AddError(propertyName, calculatorValidationRule.ErrorMessage);
return;
}
}
}
}
This is useful because if we decide to change how to validate the user’s input neither the CalculatorValidator or the ViewModel classes need to be modified.
Hooking it all up
To use the CalculatorValidator in the ViewModel it has to be injected/imported.
Each time a user enters a value in the text boxes the CheckIfNumberIsValid method checks if the calculate button should be enabled and throws an exception if the users value is not valid.
public string FirstValue
{
get { return _firstValue; }
set
{
CheckIfNumberIsValid("FirstValue", out _firstValue, value);
}
}
public string SecondValue
{
get { return _secondValue; }
set
{
CheckIfNumberIsValid("SecondValue", out _secondValue, value);
}
}
public void CheckIfNumberIsValid(string propertyName, out string propertyValue, string value)
{
_calculatorValidator.ValidateNumber(propertyName, value);
CheckIfCalculteButtonShouldBeEnabled();
if (_calculatorValidator.IsPropertyValid(propertyName))
{
propertyValue = value;
OnPropertyChanged(propertyName);
}
else
{
throw new Exception(_calculatorValidator.GetErrorMessageForProperty(propertyName));
}
}
public void CheckIfCalculteButtonShouldBeEnabled()
{
_calculateCommand.IsEnabled = _calculatorValidator.IsValid();
}
Unit testing the ViewModel in MVVM
By using Moq we can unit test the ViewModel to ensure the button is enabled when there are no validation errors and not enabled when there are validation errors.
[TestFixture]
public class When_using_the_CalculatorViewModel
{
private Mock<ICalculator> _calculator;
private Mock<ICalculatorValidator> _calculatorValidator;
private CalculatorViewModel _calculatorViewModel;
[SetUp]
public void SetUp()
{
_calculator = new Mock<ICalculator>();
_calculatorValidator = new Mock<ICalculatorValidator>();
_calculatorViewModel = new CalculatorViewModel(_calculator.Object, _calculatorValidator.Object);
}
[Test]
public void Initial_value_of_first_number_is_0()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.FirstValue;
// Assert
result.ShouldEqual("0");
}
[Test]
public void Initial_value_of_second_number_is_0()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.SecondValue;
// Assert
result.ShouldEqual("0");
}
[Test]
public void Initial_value_of_calculate_button_is_enabled()
{
// Arrange
// checking initial value
// Act
var result = _calculatorViewModel.CalculateCommand.IsEnabled;
// Assert
result.ShouldBeTrue();
}
[Test]
[ExpectedException(typeof(Exception))]
public void Will_throw_exception_if_input_is_invalid()
{
// Arrange
string propertyValue;
_calculatorValidator.Setup(c => c.IsPropertyValid("X")).Throws(new Exception());
// Act
_calculatorViewModel.CheckIfNumberIsValid("X", out propertyValue, "X");
// Assert
// should throw exception
}
[Test]
public void Will_set_property_value_if_input_is_valid()
{
// Arrange
string propertyValue;
_calculatorValidator.Setup(c => c.IsPropertyValid("X")).Returns(true);
// Act
_calculatorViewModel.CheckIfNumberIsValid("X", out propertyValue, "11");
// Assert
propertyValue.ShouldEqual("11");
}
[Test]
public void Calculate_command_should_not_be_enabled_if_ViewModel_is_not_valid()
{
// Arrange
_calculatorValidator.Setup(c => c.IsValid()).Returns(false);
// Act
_calculatorViewModel.CheckIfCalculteButtonShouldBeEnabled();
// Assert
_calculatorViewModel.CalculateCommand.IsEnabled.ShouldBeFalse();
}
[Test]
public void Calculate_command_should_be_enabled_if_ViewModel_is_valid()
{
// Arrange
_calculatorValidator.Setup(c => c.IsValid()).Returns(true);
// Act
_calculatorViewModel.CheckIfCalculteButtonShouldBeEnabled();
// Assert
_calculatorViewModel.CalculateCommand.IsEnabled.ShouldBeTrue();
}
}
So what have we achieved?
By using a validation base class we are able to store validation errors and can determine if the controls on the page are all valid.
The ViewModel can take advantage of this functionality and use it to enable and disable the calculate button.
Using exceptions for validation isn’t every developers cup of tea, so be sure to keep an eye on the Silverlight refactoring series to see other approaches we can take to do validation in Silverlight 4.