Friday, 25 June 2010

WPF MVVM Commands - new reduced-boilerplate recipe

Morgan Skinner, an Application Development Consultant with Microsoft UK, had a post on his blog yesterday complaining at the quantity of boilerplate code needed to create commands in ViewModels for WPF and Silverlight. I share his irk about that.

He shows a clever technique involving ICustomTypeDescriptor that avoids creating properties (and hence backing fields) for each of the commands. But I think he goes too far. Without the properties on the ViewModel, its tests are going to have a hard time knowing what commands it exposes; and accessing them through code isn’t going to be as straightforward as a property access.

So I propose an alternative technique. It boils down to this:

public class MyViewModel : ViewModelBase
{
    public ICommand Demonstrate
    {
        get
        {
            return Commands.GetOrCreateCommand(
                () => this.Demonstrate,
                DoDemonstration,
                IsFeelingDemonstrative);
        }
    }

    private void DoDemonstration()
    {
        MessageBox.Show("Ta da!");
    }

    private bool IsFeelingDemonstrative()
    {
        return DateTime.Today.DayOfWeek == DayOfWeek.Friday;
    }
}

As you can see, ViewModelBase is providing a Commands property to manage all the commands. This neatly avoids having to create a field for each one. Commands is an instance of CommandHolder which is equally simple:

public class ViewModelBase : INotifyPropertyChanged
{
    CommandHolder _commandHolder = new CommandHolder();

    // snipped Property Change stuff

    protected CommandHolder Commands
    {
        get
        {
            return (_commandHolder ?? (_commandHolder = new CommandHolder()));
        }
    }
}
public class CommandHolder
{
    Dictionary<string, ICommand> _commands = new Dictionary<string, ICommand>();

    public ICommand GetOrCreateCommand<T>(
        Expression<Func<T>> commandNameExpression, 
        Action executeCommandAction, 
        Func<bool> canExecutePredicate)
    {
        return GetOrCreateCommand(
            commandNameExpression,
            parameter => executeCommandAction(),
            parameter => canExecutePredicate());    
    }

    public ICommand GetOrCreateCommand<T>(
        Expression<Func<T>> commandNameExpression, 
        Action<object> executeCommandAction, 
        Func<object, bool> canExecutePredicate)
    {
        var commandName = SymbolHelpers.GetPropertyName(commandNameExpression);

        if (!_commands.ContainsKey(commandName))
        {
            var command = new DelegateCommand(executeCommandAction, canExecutePredicate);
            _commands.Add(commandName, command);
        }

        return _commands[commandName];
    }
}

What do you think? Are my concerns about testa-reada-bility warranted? Does my technique eliminate enough of the boilerplate?

I’ve put a full sample on MSDN code gallery.