PDC10: Kung Fu Silverlight – Architectural Patterns and Practices with MVVM and RIA Services
MVVM (Model/View/ViewModel) is an architectural pattern that is well-suited for Silverlight and WPF development. It is a variation of the MVC pattern that originated from the development of Expression Blend.
At its heart, MVVM imposes three kinds of classes that separate out ideas of presentation (Views), logic (ViewModels) and data (Models). Some of the advantages of structuring a project this way: (i) it facilitates code separation, which helps testability and helps you focus your classes on a specific role; (ii) it supports well the data binding architecture of Silverlight and WPF; (iii) it enables designers and developers to collaborate on a project with minimal overhead. (iv) it is a consistent, maintainable, well-understood pattern; (v) it scales well from small to large applications.
Models, Views, and ViewModels
Let’s take a look at each of the components of MVVM in a little more detail:
- The Model is the domain object or entity: it’s a class of properties that has the responsibility of storing your data (for example, a Person object that stores a name and address).
- The View represents a screen, a page or even a control on the page. It provides a user-friendly presentation of the data, including themes, styles, declarative bindings, events, display of validation errors.
- The ViewModel glues the View to the Model. It can interact through data bindings to talk to the view – presenting data, retrieving or saving data, talking to services, or handling any actions that might happen on the UI.
As shown in the diagram above, the ViewModel is the DataContext for the View, and that is how it gets data and commands. In general you should try and move as much code as possible out of the View and into the ViewModel – this makes it easier to unit test the code through the use of mock objects. The Model is fairly straightforward: the main step you need to take is to implement the INotifyPropertyChanged interface to provide a notification that the data has changed. Lastly, there are various techniques for binding the View to the ViewModel: in this session you’ll see them being bound with a ViewModelLocator.
Commands and Behaviors
What happens after you want to display data? For example, what if you want to perform some action based on a UI interaction? This is where commands and behaviors come in.
A command is something you can fire based on a user action that starts an event in the ViewModel. It uses a binding syntax to do that. Any control that derives from ButtonBase will let you bind a command directly to it. In the ViewModel you can create your own instance of ICommand (a good recipe for this is RelayCommand from the MVVM Light toolkit) to handle the event.
For other actions where a command isn’t possible (for example, a ListBox item selection), you can use a behavior. Some good pre-built options include EventToCommand (again from MVVM Light) or InvokeCommandAction (from Blend 4’s Interactivity DLL).
Services and Separation
As we’ve seen the primary role of the ViewModel is to perform tasks: GetBook, SaveToStorage, NavigateTo etc. However, as this becomes more complex, the ViewModel starts to include code to call web services, access third-party libraries, etc. It starts to make sense to create a separate service class that separates out this helper code and makes it available across multiple ViewModels.
So services provide related tasks to the caller, and are passed through the constructor using dependency injection (passing the class into the constructor as an interface). This abstraction makes the service very testable and easy to write, and keeps a clear delineation between the task and its physical execution on the disk or network.
This is a topic that is sometimes neglected. A lot of the code in your application won’t run at design-time, because the designer can’t be waiting for a web service to return before it shows anything on screen. Design-time data ensures that you can see a fully-populated view of the UI when you’re developing or designing the application. Without some data, it’s almost impossible to style an application, or for that matter, to ensure that the bindings are right.
One way to provide design-time data is to use sample data (Expression Blend makes this relatively easy); another approach is to use design-time services. A proposed approach to this is to use a ServiceProvider, which is one line of code that automatically switches between design-time and run-time service code as appropriate.
Child Windows and Messages
The big question to answer with a child window: should you create a separate ViewModel, or use the parent’s ViewModel? Like most architectural questions, the answer is “it depends”. In some cases, the child window is deeply related to the parent child and is essentially an extension of the parent; in other cases, it’s really an unrelated screen. Depending on how you’re using the child window, either approach may be the appropriate.
One note: a child window doesn’t inherit the DataContext from its parent, so you either need to pass the ViewModel through the constructor or have the child window find the ViewModel it needs.
How should the child window be displayed? You can do this in many ways – often a delegated service is an appropriate choice. As a suitable pattern, we can use messaging to handle this.
Messages are events that provide a loosely-coupled model between the ViewModel and the child window. The process is usually triggered by a user action, which initiates a command to the ViewModel. If there is some business logic that needs to be run to determine whether to display the child window or not, that should be in the ViewModel. Then a message is used to send the request to display the window – either to a service or directly to the view. Some popular choices for the message pattern include the EventAggregator from PRISM and the Messenger class from MVVM Light.
WCF RIA Services and MVVM
RIA Services is very helpful in these kinds of applications: it takes care of managing change tracking for entities, provides methods for saving and reading data, generates entities and models, handles data validation.
With RIA Services, you create your Domain Services as you normally do; you can use the Entities as your models (they already implement INotifyPropertyChanged), and you get a DomainContext object that acts as a gateway to your services and provides change tracking.