Peregrine's View

Yet another C# / WPF / MVVM blog …

MVVM

MVVM – ViewModel (part 2)

I Want to Live, on S.O.L.I.D. Rock

This series of posts continues with the refactor of the StaffManager demo application – this time looking at the main ViewModel.

I’ve mentioned clean code a few times already in previous posts. One of the best ways to help achieve that is by following the S.O.L.I.D. principles. The ‘O’ and ‘L’ parts are outside the scope of this blog series, but the ‘S’, ‘I’ & ‘D’ elements can help with creating a well structured MVVM application.

The first point of attack in refactoring the MainViewModel is to consider how it gets the person / department data. Instead of the ViewModel being directly responsible for data access, this functionality can be extracted out into a separate service. A service is simply a stand-alone module that performs a single task, coded as an interface, that can be injected into a ViewModel (via its constructor or a property).

The interface contract for the minimalist data service in this version of the demo application is …

public interface IStaffManagerDataService
{
    Task<IReadOnlyCollection<Person>> GetPeopleAsync();
    Task<IReadOnlyCollection<Department>> GetDepartmentsAsync();
}

Note that the service returns Model class instances, not WVMs or other ViewModel items. This is the only part of the application that the data repository service needs to know about – whether that’s by direct access to a local database or via a web service in a multi-tiered application structure. The return types from the data service are IReadOnlyCollection<T> rather than just IEnumerable<T>, in line with the Microsoft guidelines. This makes it clear to the caller how the returned set of data items can be used – it is a finite size, includes a count property, it can be iterated over multiple times without any performance hit, but it can’t be updated.

The Async / Await pattern allows the application to remain responsive whilst a long running I.O. operation is in progress. I’m not going to go into much detail here about Async / Await for now – if you want to know more, check out the work of Stephen Cleary and his Nito.AsyncEx library. Just remember that in library code, each time you write “await”, you should add ConfigureAwait(false) to the Task. This prevents the task from potentially capturing the current synchronization context (effectively blocking the U.I. thread), which would cause the application to hang. In the part of the application where you’re actually going to use the result of a task to update the U.I., you should use ConfigureAwait(true) instead. For those of you using Resharper (and if not, why not?), there’s a handy plugin that will check that each await statement is matched with a ConfigureAwait() call. Search for ConfigureAwait under extensions.

To simulate the time it takes to call a web service or access a database, this mock implementation just pauses for a couple of seconds before returning the hard-coded list of model items.

public class StaffManagerDataService: IStaffManagerDataService
{
    public async Task<IReadOnlyCollection<Person>> GetPeopleAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);

        var people = new List<Person>
        {
            new Person {Id = 1, DepartmentId = 1, FirstName = "Alan", LastName = "Jones", IsManager = false},
            new Person {Id = 2, DepartmentId = 1, FirstName = "Joseph", LastName = "Preston", IsManager = true},
            new Person {Id = 3, DepartmentId = 1, FirstName = "Stella", LastName = "Mcbride", IsManager = false},
            new Person {Id = 4, DepartmentId = 1, FirstName = "Branden", LastName = "Owens", IsManager = false},
            new Person {Id = 5, DepartmentId = 1, FirstName = "Leonard", LastName = "Marquez", IsManager = false},
            new Person {Id = 6, DepartmentId = 1, FirstName = "Colin", LastName = "Brady", IsManager = false},
            new Person {Id = 7, DepartmentId = 2, FirstName = "Callum", LastName = "Roberts", IsManager = true},
            new Person {Id = 8, DepartmentId = 2, FirstName = "Jillian", LastName = "Scott", IsManager = false},
            new Person {Id = 9, DepartmentId = 2, FirstName = "Calvin", LastName = "Moran", IsManager = false},
            new Person {Id = 10, DepartmentId = 3, FirstName = "Harlan", LastName = "Reid", IsManager = false},
            new Person {Id = 11, DepartmentId = 3, FirstName = "Felix", LastName = "Schroeder", IsManager = false},
            new Person {Id = 12, DepartmentId = 3, FirstName = "Joseph", LastName = "Smith", IsManager = true},
            new Person {Id = 13, DepartmentId = 3, FirstName = "Jasmine", LastName = "Emerson", IsManager = false},
            new Person {Id = 14, DepartmentId = 3, FirstName = "Lucas", LastName = "Edwards", IsManager = false},
            new Person {Id = 15, DepartmentId = 3, FirstName = "David", LastName = "Baxter", IsManager = false},
            new Person {Id = 16, DepartmentId = 4, FirstName = "Kane", LastName = "Foreman", IsManager = false},
            new Person {Id = 17, DepartmentId = 4, FirstName = "Laurel", LastName = "Curtis", IsManager = false},
            new Person {Id = 18, DepartmentId = 4, FirstName = "Lucy", LastName = "Tanner", IsManager = true},
            new Person {Id = 19, DepartmentId = 4, FirstName = "Christian", LastName = "Pittman", IsManager = false},
            new Person {Id = 20, DepartmentId = 4, FirstName = "Patricia", LastName = "Wilkinson", IsManager = false}
        };

        return new ReadOnlyCollection<Person>(people);
    }

    public async Task<IReadOnlyCollection<Department>> GetDepartmentsAsync()
    {
        await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);

        var departments = new List<Department>
        {
            new Department {Id = 1, Description = "I.T."},
            new Department {Id = 2, Description = "Accounts"},
            new Department {Id = 3, Description = "Sales"},
            new Department {Id = 4, Description = "Logistics"}
        };

        return new ReadOnlyCollection<Department>(departments);
    }
}

The main ViewModel can now be coded as

public class MainViewModel : ObservableObject
{
    private readonly IStaffManagerDataService _dataService;

    // parameterless constructor purely for Xaml designer
    public MainViewModel() : this(null)
    {
    }

    [PreferredConstructor]
    public MainViewModel(IStaffManagerDataService dataService)
    {
        _dataService = dataService;

        LoadDataCommand = new Command(async () => await OnLoadData().ConfigureAwait(false));
        AddPersonCommand = new Command(OnAddPerson);
        DeletePersonCommand = new Command(OnDeletePerson);
        ListSelectedPeopleCommand = new Command(OnListSelectedPeople);
    }

    public ICommand LoadDataCommand { get; }

    private async Task OnLoadData()
    {
        // run the two data retrieval tasks in parallel
        var getDepartmentsTask = _dataService.GetDepartmentsAsync();
        var getPeopleTask = _dataService.GetPeopleAsync();

        await Task.WhenAll(getPeopleTask, getDepartmentsTask).ConfigureAwait(true);

        var departments = await getDepartmentsTask.ConfigureAwait(true);
        _departmentVmList.Clear();
        _departmentVmList.AddRange(departments.Select(d => new DepartmentViewModel {Model = d}));

        var people = await getPeopleTask.ConfigureAwait(true);
        _personVmList.Clear();
        _personVmList.AddRange(people.Select(p => new PersonViewModel {Model = p}));

        foreach (var personVm in _personVmList)
        {
            var departmentVm = _departmentVmList.FirstOrDefault(d => d.Model.Id == personVm.Model.DepartmentId);
            departmentVm?.AddPerson(personVm);
        }

        _personVmList.Sort();
        SelectedPersonVm = _personVmList.FirstOrDefault();
    }

    public ICommand AddPersonCommand { get; }

    private void OnAddPerson()
    {
        var newPersonVm = new PersonViewModel {Model = new Person()};
        _personVmList.Add(newPersonVm);
        SelectedPersonVm = newPersonVm;
    }

    public ICommand DeletePersonCommand { get; }

    private void OnDeletePerson()
    {
        var personVmToRemove = SelectedPersonVm;
        SelectedPersonVm = null;
        _personVmList.Remove(personVmToRemove);
    }

    public ICommand ListSelectedPeopleCommand { get; }

    private void OnListSelectedPeople()
    {
        var selectedpeople = PeopleVms.Where(p => p.IsSelected).ToList();

        var message = selectedpeople.Any()
            ? "The following people are selected\r\n    " + string.Join("\r\n    ", selectedpeople.Select(p => p.DisplayName))
            : "No people are selected";

        MessageBox.Show(message);
    }

    private readonly perObservableCollection<PersonViewModel> _personVmList = new perObservableCollection<PersonViewModel>();

    public IEnumerable<PersonViewModel> PeopleVms => _personVmList;

    private readonly perObservableCollection<DepartmentViewModel> _departmentVmList = new perObservableCollection<DepartmentViewModel>();

    public IEnumerable<DepartmentViewModel> DepartmentVms => _departmentVmList;

    private PersonViewModel _selectedPersonVm;

    public PersonViewModel SelectedPersonVm
    {
        get { return _selectedPersonVm; }
        set { Set(nameof(SelectedPersonVm), ref _selectedPersonVm, value); }
    }
}

Note how an instance of the data service is injected into the constructor, and the updated definition of the LoadDataCommand in an async format. I’ve also changed the way that the people and department collections are handled. The lists are out, along with the ugly way of handling them – replaced by ObservableCollections. These trigger any bound controls to refresh, whenever items are added to / removed from the collection. perObservableCollection is a library class that extends the standard C# version by adding Sort() and AddRange() functionality.

Note that when using observable collections, you only need to create the collection once – I see many examples in live code where a brand new list is created each time it is filled, which makes me wonder why they bothered to use observable collections at all.

Just a little pin prick

So how do we go about injecting the data service into the main ViewModel? We can no longer just define the main view’s datacontext in Xaml – all classes used in Xaml must have a paramaterless constructor.

Hands up who though of something like …

public partial class MainView
{
    public MainView()
    {
        InitializeComponent();
        DataContext= new MainViewModel(new StaffManagerDataService());
    }
}

That’s perfectly valid and will run just fine, but it makes defining the service as an interface a moot decision. However, what happens when the application grows in complexity and the ViewModel has multiple services injected, and they have dependencies too? When happens if there are multiple implementations of a service defined, and it isn’t known which to use until run-time? How can unit tests work, when concrete versions of the service classes are replaced with mocked ones? Do you really want to write code like this?

var svc = new ShippingService(
                  new ProductLocator(), 
                  new PricingService(), 
                  new InventoryService(), 
                  new TrackingRepository(
                      new ConfigProvider()), 
                  new Logger(
                      new EmailLogger(
                          new ConfigProvider()))
              );

[Taken from this answer on stackoverflow.com]

The answer is to use dependency injection (DI) / inversion of control (IoC). There are many fine libraries for handling this – Unity, Ninject, Autofac, Castle Windsor etc, but in my experience these are overkill for all but the largest projects. Most applications don’t require such a level of configuration options. My personal choice is another element of the MVVMLight library – SimpleIoC. Despite the name, it’s a full implementation of the IServiceLocator interface in around 1000 lines of code (around 50% of that is comments).

The Bootstrapper class registers items with the container – both interface implementations and stand-alone classes. Call its Run() method once, at application startup.

public static class Bootstrapper
{
    public static void Run()
    {
        SimpleIoc.Default.Register<IStaffManagerDataService, StaffManagerDataService>();
        SimpleIoc.Default.Register<MainViewModel>();
    }
}

Now, to get an instance of a class including all of its dependencies, just ask the IoC container. The main view’s data context can be set as …

public MainView()
{
    InitializeComponent();
    DataContext = SimpleIoc.Default.GetInstance<MainViewModel>();
}

When I come to look at refactoring the View layer in a future post, I’ll introduce a more elegant way of doing this, including having live data displayed at design time. In my next post, I’ll be looking at WPF Commands.

Don’t forget that all of the code samples for this blog are available on Github, along with my own personal C# / WPF library.

If you find this article useful, or you have any other feedback, please leave a comment below.

Leave a Reply

Your email address will not be published. Required fields are marked *