— csharp — 4 min read
The last WPF application that I worked on and saw through to the bitter end was the control software for the RoboBlot. The last time I worked on it was in 2014, by which point I had migrated to working almost exclusively in Java, for a bunch of reasons.
So, when I was asked at work to design and supervise the development of a basic WPF desktop application, I thought it was a nice opportunity to reflect on what I had done before.
The guy I'm supervising had made a Windows Forms application before, but had no experience with any of the MV-* patterns. MVC, MVP, MVVM, when you float through the sea of acronyms and competing frameworks, it can be extremely unhelpful and confusing.
However, out of these patterns, MVVM has by far the most contradictory and conflicting explanations floating around on the internet. Does the model go in the viewmodel? The viewmodel in the view? Does the viewmodel have a reference to the view? Does the viewmodel implement INotifyPropertyChanged or the model? Or both? Should you duplicate the model properties in the viewmodel or just pass-through?
If you go and search, you'll find a dozen blog posts and accepted stackoverflow answers all advocating different approaches to the above questions. Unfortunately, the "official" documentation is (compared to the documentation of the average Java library) tedious and often preferring snippets over complete examples, struggling to keep people's attention long enough. And even when people get the structure right, they find it less than clear how to integrate that with common patterns of application architecture, like Inversion of Control and Dependency Injection.
Another part of the confusion comes from the way that Windows Forms applications were built - in what is often colloquially called 'The codebehind'. You had your View, Form.Designer.cs, and your codebehind, Form.cs. The functionality associated with a button, for example, could be found in the often monolithic Form.cs. And this paradigm exists in WPF, with our Window.xaml.cs files. And whilst this has it's place in WPF, it allows people to build applications the Windows Forms way, in WPF. Which is just gross.
But anyway, let's cut to the chase.
I don't use a framework like MVVMLight, Caliburn.Micro or Simple MVVM. I'm open to the idea of them and the features they provide but if you're trying to learn how MVVM works, I think it's more beneficial to go through the process yourself, because it's actually not that difficult.
Each MVVM component could be thought of like a layer.
Layer | What you should have there |
---|---|
View | Presentation Logic |
ViewModel | Actions |
Model | Data |
Our view just presents the model data in some meaningful way, and feeds user requests to the ViewModel, where they are processed. The ViewModel is responsible for handling these requests. That means that, if we were using Repository Pattern for our data layer, we might inject repositories into the ViewModel, so that it can process persistence requests for it's attached model class. The model should JUST BE DATA (and INotifyPropertyChanged). Usually, if persistence is involved, the 'Model' is a particular database entity POCO. If this makes no sense to you, I have this pearl of wisdom:
If you're struggling to work out what your 'Model' is, then it's probably whatever you need to complete the 'Action' you want to do.
It's easy if you're working on an application that persists a 'Customer' object for example - that's the model. What if you just want to interface with some underlying APIs, and don't have an actual thing that you want to manipulate? Don't worry about it. Just make the viewmodel, and the actions. Don't get so wound up in the pattern that you feel like you have to make a completely pointless 'Model' class. The View is the only thing using your ViewModel, and as long as it gets it's properties to bind to, it doesn't care. That's the beauty of MVVM - we don't have a circular dependency of responsibility like in MVC. Each layer is observed by the one above.
What about our composition? It's like this:
View has as ViewModel which has a Model.
Simple, right? Not quite. We have a framework whose rules we have to play by, and we also want to use Inversion of Control to componentize our application for when the requirements inevitably change. So, our MainWindow.xaml is a View. That means it needs a reference to a ViewModel. But object creation is delegated to our IoC container - so how does this work in WPF? I'll talk about the scaffolding first.
I've used Unity as the IoC container this time around because it's bloody simple. I make a class to hold my dependency configuration, which I could call DependencyConfiguration.cs
for sake of argument.
1public class DependencyConfiguration2{3 public static void Bootstrap()4 {5 IUnityContainer container = new UnityContainer();6 container.RegisterType<CustomerViewModel, CustomerViewModel>();7 // A bunch of other type registrations for other application components ...8 container.Resolve<MainWindow>().Show();9 }10}
and our MainWindow.xaml.cs
looks like this, with constructor injection for the viewmodel.
1public partial class MainWindow : Window2{3 public MainWindow(CustomerViewModel viewModel)4 {5 this.DataContext = viewModel;6 InitializeComponent();7 }8}
Great - so we create our container, register the ViewModel there, and use the container to create MainWindow
and show it. We also had to setup App.xaml
to tell it not to create MainWindow itself by removing StartupUri=MainWindow.xaml
from the Application
tag, and overriding OnStartup
in App.xaml.cs
like so, to bootstrap our application.
1public partial class App : Application2{3 protected override void OnStartup(StartupEventArgs e)4 {5 base.OnStartup(e);6 DependencyConfiguration.Bootstrap();7 }8}
And thats our WPF application setup with IoC. Not too shabby.
But what does CustomerViewModel.cs
look like, I hear you ask. Well, that's a good question. Let's talk about what we're doing first. Let's say we're making a simple CRUD application against a database, and we have a Customer entity which looks like this:
1public class Customer2{3 public long Id { get; set; }4 public string Name { get; set; }5 public bool IsEndUser { get; set; }6 public string CompanyName { get; set; }7 public string TelNo { get; set; }8}
Our persistence layer (which I'm not going to go into detail about) takes the form of IRepository<T>
, and handles the operations needed for creation, modification, retrieval, deletion, search, etc. So, our customer persistence object is IRepository<Customer>
, and the injection of this is handled by our IoC container. Our basic ViewModel will look something like this:
1public class BaseViewModel<T>2{3 IRepository<T> entityRepository;4 public T Model5 {6 get; set;7 }89 public BaseViewModel(T model, IRepository<T> entityRepository)10 {11 this.Model = model;12 this.entityRepository = entityRepository;13 }1415 public ICommand Add16 {17 get18 {19 return new RelayCommand(x => entityRepository.Add(this.Model));20 }21 }2223 // Update, Delete, Search, etc...24}
With CustomerViewModel
a thin layer on top:
1public class CustomerViewModel : BaseViewModel<Customer>2{3 public CustomerViewModel(Customer model, IRepository<Customer> entityRepository) : base(model, entityRepository) { }4 public CustomerViewModel(IRepository<Customer> entityRepository) : base(new Customer(), entityRepository) { }5}
What's cool about CustomerViewModel
? Notice the two constructors - one that takes a Customer
and one that doesn't. The purpose of this is that IRepository<T>
is always going to have to come from the IoC container, whereas we might want to create a viewmodel by wrapping an existing Customer
that we already have. If a Customer
isn't provided, then the viewmodel is constructed with a fresh instance, ready for somebody to enter details and save, for example.
Now that we have this, we can bind directly to the properties of Model
from the view, for example, {Binding Model.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
. MVVM purists will tell you that this is unacceptable, but I am the laziest person I know, and I will not duplicate all the model properties into the viewmodel if I don't have a bloody compelling reason to.
What's missing? Ah yes, INotifyPropertyChanged
. I want our model class to implement this, since we are binding directly to model properties. And for my money, the best solution to this bar none (remember I am very lazy) is Fody.PropertyChanged. This will do some clever IL weaving to insert the tedious boilerplate for every model property. Add this NuGet package, and then, we can just do:
1[ImplementPropertyChanged]2public class Customer 3{4 public long Id { get; set; }5 public string Name { get; set; }6 public bool IsEndUser { get; set; }7 public string CompanyName { get; set; }8 public string TelNo { get; set; }9}
And it will fire PropertyChanged
events for all the properties! What an incredibly elegant solution.
And that's our basic MVVM implementation.