An actual in-production MVVM architecture
So the last project where I implemented MVVM that is in production for an enterprise product, the design pattern actually grew to included more than just the three items: Model, View, ViewModel.
Instead it looked like the image you see to the right.
The arrows indicate access.
- Controller – Has access to all other objects. Basically the controller is created and it loads the first Views and ViewModels and assigns the DataContext of the Views.
- View – The view is only consumed by the controller (and dynamically so the controller doesn’t actually reference the View project or dll). Since the view is completely independent it can be replaced any time. The View is only connected to the ViewModel through binding when the controller assigns a ViewModel to the DataContext of a View.
- ViewModel – An adapter layer on top of the model and business that also implements the necessary objects that are expected in the View’s bindings. The ViewModel communicates with the controller in order to tell it to change screens/switch to a new view. It is important in this design to note that the ViewModel never references the View and is completely independent of the View, which allows it to be applied to any View.
- Business – The code that actual does the work. It only needs access to the Model but both the Controller and ViewModel have access to use it. However, the Controller seldom uses it but the ViewModel heavily uses it.
- Model – Data objects. It doesn’t know about anything else, but the Controller, Business, and ViewModel all know about the Model.
How this design formed
Originally the MVVM pattern was used and the project started as a single project, with a separate folder and namespace for Model-View-ViewModel times.
As the application progressed, this design was created as separation of concerns were applied to each layer and as items were broken out into separate projects/dlls.
Why the Controller became a separate layer
Initially it seems that the View or the ViewModel (either) could handle being the Controller. However, we found that neither the View or the ViewModel acting as the Controller was a good idea for a couple of reasons. First, if the View or the ViewModel were also the Controller then they became dependent on too many items. It became clear quickly that the ViewModel was a better choice of the two and so it was chosen at first. However, with references to the View available to the ViewModel, developers began to cheat and couple the View to the ViewModel, until the ViewModel could not build without that exact View and the View was supposed to be dynamic and interchangeable.
So to keep the View decoupled from the ViewModel and visa-versa, a separate Controller project was created and no reference between the View or ViewModel was allowed.
Note: Actually the Business and Controller layers are in the same dll.
Why the Business layer was separated from the ViewModel
Initially the Business layer and the ViewModel layer were the same layer. Some of the business existed directly in the ViewModel and some were in separate classes. It quickly became apparent that the ViewModels were 1) getting too large, and 2) breaking the single responsibility principle. It became apparent that in order to maintain small class files and the single responsibility principle that the Business needed to exist outside of the ViewModel and the ViewModel would simply call the a single Business object or method when needed.
Other mistakes
Initially the Controller was a single class, then we realized that it was doing about ten things, and was over 1000 lines. Oops. It was refactored into separate objects, some of which actually turned out to belong in the business layer. So it appeared that we allowed the business layer to bleed into the controller. As the file was analyzed it was clear that a certain amount of methods and properties were only for a particular use and so these were extracted to a separate class. This was actually repeated multiple times for different uses, after which, the controller returned to just being a controller and it was a smaller easy to maintain class.
Future architecture changes
As the product continued to grow, other changes are seen as necessary even though they haven’t yet been implemented.
Separation of the Controller from the Business layer
A few of the business objects were for the Controller only and the rest of the business objects are for the ViewModel only. Since the Controller and Business layers are still in the same dll, this doesn’t yet matter. However, it was becoming clear as the project grew that the Controller has its business and the ViewModel/functionality had it Business and there is little overlap if any. It is likely that the Controller and Business layer will see a separation and decoupling where the Controller no longer references the Business layer and the business classes used by the Controller is just part of the Controller.
View communication with the DataContext without binding
There arose instances where binding was not supported or did not exist. In many cases we used attached properties or an inherited object and added the missing bindings, however, in some instances this didn’t make sense. What if the View needed to send a message to the DataContext or pass and event to the code behind and neither inheriting the object to add the binding or using an attached property made sense. What made sense was an interface.
I recently posted an article on Creating an Interface for data binding in Views with WPF. Imagine a new layer that exists between the View and the ViewModel called IViewModel. Both the View and the ViewModel reference it and they still don’t reference each other. An interface in the IViewModel project/dll would mainly include properties that help the ViewModel know what is needed for binding. However, in such rare circumstances that it makes sense to communicate to the DataContext, the View can check if the DataContext implements an interfaces and calls a method the interface defines.