MVVM – or the Model-View-ViewModel pattern – is still a hot topic. It is the architectural pattern that many XAML developers have been using to build their applications in WPF, Windows Phone and Silverlight. It’s also great to build Windows 8 Store applications. Here at SilverlightShow, we’ve recently done a 3-part article series that received a lot of interesting feedback. I recommend to read these articles before starting with these, since we are not talking about the base concepts explained in those articles.
But some people indicated they wanted some more information, which is why we did the advanced MVVM topics webinar, where we’ve explored some more advanced concepts around MVVM.
The goal of these articles is on one hand being a companion to the webinar. We can spend some more time explaining the more advanced concepts and patterns used in the demo in the webinar. Hopefully, this will prove to be a good learning experience and give you enough information to apply in your next projects. On the other hand, I do think that there’s a real demand for a sample that talks deep about a real-life MVVM architecture. I hope to give that here.
The complete solution can be downloaded here.
The application
In this series, we are going to use the Contoso Cookbook application, which you may know from the Hands-on Labs created by Microsoft. In these excellent labs, you can learn all the principles of Windows 8 development. However, the application isn’t built using MVVM principles, it focuses on teaching you the concepts of Windows 8 instead. That’s why I decided to rebuild Constoso Cookbook, this time using MVVM. Most functionality has been ported and I’ve added a couple of things extra (some weren’t covered in the webinar ethier).
Just for your convenience, let me quickly show you the application in case you’ve never seen it. The overview page is shown first, it contains a grouped GridView.
The landing page also contains a Semantic zoom.
When clicking on a group header, the group detail page is shown.
And below, the detail page is shown for an individual recipe.
The architecture
I’ve created an architecture for the application by setting a couple of goals for the application. One of these is Separation of concerns (SOC). The SOC principle says that we each building block should be responsible for one task and for one task only. That’s one of the things that the MVVM principle is really promoting. No longer are you building a view that contains both the actual view code and the code-behind. Instead you’ll be building a view and a separate class, the ViewModel, which is an abstraction of the view. This ViewModel is concerned with the data and the commands that the view will bind upon but isn’t linked to any controls, which are hard to test. The same goes for services. Instead of putting everything in the ViewModel, we will build separate classes, or services, which are each concerned with a specific task we can test in isolation. It doesn’t cost a thing to create a new class.
Loose coupling is another principle I’ve put forward in the design of the app. Not creating hard links between the building blocks but instead creating a loose coupling between the layers is important. Why else do we build up a layered architecture if we create hard references between our layers, making it hard to replace a layer? The loose coupling goes hand in hand with the SOC by the way.
In my architecture, I use dependency injection, which is the way I achieve loose coupling. Through an IOC container, we supply the concrete implementations to things like the ViewModels. However, it will be easy to create mocks and therefore the unit testing is easier to do.
Let’s look at the separate projects I have added to my solution. You’ll notice I have added quite a few projects. I like to keep things separated, it doesn’t cost a thing to add another project. Do keep in mind that this is my way of working, I’m not going
- ContosoCookbook.App: is the main application. It contains the locator, the views…
- ContosoCookbook.Contracts: I develop against interfaces, not concrete implementations. I place my interfaces in a separate project so I can use them from anywhere, including my unit tests.
- ContosoCookbook.Messages: contains the message classes. This application doesn’t have messages since the viewmodels don’t really need to communicate. Messaging was explained in the earlier articles though.
- ContosoCookbook.Model: contains my client model representation. These classes implement interfaces of the Contracts project.
- ContosoCookbook.Repositories: contains the data repositories for my app. Again, we’ll look at this later.
- ContosoCookbook.ServiceClient: under the motto, “a project doesn’t cost a thing”, I added another project for my service references. I don’t put these in the projects that use them, I group them in this one project. This way, only one project has dependencies on these and if service references change, I only have to update them in one location.
- ContosoCookbook.Services: contains the services that I’m using in my application. I’ve talked a bit about services in the first articles, I’ll be diving deeper in some of these here.
- ContosoCookbook.Shared: contains shared classes (among others my class that uses the container).
- ContosoCookbook.ViewModel: this is an easy one, it contains the services.
- ContosoCookbook.ViewModel.Tests: a unit test project. We’ll look at how we are testing some viewmodel methods later.
I’m now going to dive in some aspects of the architecture and setup. We’ll start with a concept that was already explained in the first series: the ViewModelLocator.
The ViewModelLocator
For my views to be able to bind on a ViewModel, I use the locator pattern. If you, like me, are a fan of MVVM Light, you’ll know the concept of a ViewModel Locator class already since that’s baked into the framework. My VMLocator contains a property for all VMs I have in my app. My app knows upfront which VMs it will contain, so using the locator is enough for my setup here. Below are a couple of properties that return VM instances. I’m returning these using my IOC container (more later on this).
public IRecipeGroupOverviewViewModel RecipeGroupViewModel
{
get
{
return InstanceFactory.GetInstance<IRecipeGroupOverviewViewModel>();
}
}
public IRecipeGroupDetailViewModel RecipeGroupDetailViewModel
{
get
{
return InstanceFactory.GetInstance<IRecipeGroupDetailViewModel>();
}
}
In the constructor of the locator, I’m using my IOC container to register all my types with the container (I use an abstraction, more on that in just a second).
public ViewModelLocator()
{
//Services
InstanceFactory.RegisterType<IRecipeDataService, RecipeDataService>();
InstanceFactory.RegisterType<INavigationService, NavigationService>();
...
//ViewModel
InstanceFactory.RegisterType<IRecipeGroupOverviewViewModel, RecipeGroupOverviewViewModel>();
InstanceFactory.RegisterType<IRecipeGroupDetailViewModel, RecipeGroupDetailViewModel>();
...
//Views
InstanceFactory.RegisterType<IRecipeGroupView, RecipeGroupView>();
InstanceFactory.RegisterType<IRecipeGroupDetailView, RecipeGroupDetailView>();
...
}
Since I create an instance of the VMLocator in Application Resources, I can be sure that this constructor is created very early in the app lifecycle and therefore I know that all my types are registered with my container before they are needed anywhere in the app.
The concrete views use the VMLocator as follows:
<Page
x:Class="ContosoCookbook.App.Views.RecipeGroupView"
DataContext="{Binding RecipeGroupViewModel, Source={StaticResource ViewModelLocator}}"
>
The Container
One of my prerequisites of the architecture is loose coupling of my components. This I achieve through the use of a DI container. As shown in the previous topic, I register my dependencies in the constructor of my dependencies. MVVM Light comes bundled with SimpleIOC. I’m a fan of MVVM Light but I find SimpleIOC a bit too limited. So I’ve been looking for a replacement on Nuget. At the point of writing, I could find MetroIOC, TinyIOC, AutoFac and some more that had support for WinRT. I’ve picked MetroIOC for my application here.
In the locator code above, you could see the InstanceFactory class being used. This class is a wrapper around my container. The reason I’m creating a wrapper instead of using the container directly from code is… loose coupling. It wouldn’t be smart to create a loosely-coupled architecture and create a hard dependency on the container. That’s why I put in another layer in the form of the InstanceFactory class. In this class, I have a dependency on the container. But, if I decide tomorrow that my MetroIOC container isn’t doing what it’s supposed to do, I only have to change my code in just one class, the InstanceFactory. Don’t let the consumer directly access the provider.
Here’s the code for the InstanceFactory class (ContosoCookbook.Shared):
public static class InstanceFactory
{
static readonly IContainer container;
public static Dictionary<Type, Type> Registrations = new Dictionary<Type, Type>();
static InstanceFactory()
{
container = new MetroContainer();
}
public static void RegisterType<T1, T2>() where T2 : class,T1
{
Registrations[typeof(T1)] = typeof(T2);
container.Register<T1, T2>(null, new Singleton());
}
public static void RegisterInstance<T1>(T1 instance)
{
container.RegisterInstance<T1>(instance);
}
public static void RegisterWithTransientLifetime<T1, T2>() where T2 : class,T1
{
Registrations[typeof(T1)] = typeof(T2);
container.Register<T1, T2>(registration: new Transient());
}
public static void RegisterNamedInstance<T1, T2>(string key) where T2 : class,T1
{
container.Register<T1, T2>(key: key);
}
public static T GetInstance<T>()
{
return container.Resolve<T>();
}
public static T GetNamedInstance<T>(string key)
{
return container.Resolve<T>(key: key);
}
}
In this application, I’ve registered all my VMs and services as singletons. However, the class also provides transient options (meaning that a new instance will be created every time an instance is requested). I agree that for the detail pages in my app, I could have used a transient instantiation of the VM.
In my ViewModels, I now use the container to resolve my dependencies (note I’m not using constructor injection here, I again agree that might have been better). You can see some code of the
public RecipeGroupOverviewViewModel()
{
navigationService = InstanceFactory.GetInstance<INavigationService>();
recipeDataService = InstanceFactory.GetInstance<IRecipeDataService>();
dialogService = InstanceFactory.GetInstance<IDialogService>();
Initialize(null);
InitializeCommands();
}
Navigation and window management
I’ll round up this first part by talking a bit more about navigation (navigation was already covered in the first series of the MVVM articles).
Navigation in Windows 8 apps is based on the concept of a Frame and pages. Every app has one root frame which can display pages, one at a time. The Frame contains methods such as GoBack() and Navigate().
In an MVVM architecture, the question where to do navigation is an interesting one. For one, the ViewModels are concerned with the flow of an application. They “decide” where to go next, for example in the Execute() command handler. But should a ViewModel contain code that directly interacts with the Frame? I would say no, since Frame is a visual object, part of WinRT. It’s hard to mock that out, so you’re basically taking a dependency on this if you decide to write code that directly calls Frame methods. Also, if you want to port your code later to Windows Phone, this would certainly complicate things.
Is it then the task of the View? Again no. The view contains view code and has no clue when to navigate and where.
The “correct” answer is using a separate service that in turn contains a reference to the Frame. This navigation service is responsible for navigation and for navigation only (remember SoC?). It’s injected into the ViewModels through the container itself. The VMs can then call methods on this navigation service to manage the flow of the application, but the actual interaction with the Frame happens only from the navigation service.
The code for the service is shown below. Notice it has a Frame property.
public class NavigationService : INavigationService
{
private Frame frame;
public Frame Frame
{
get
{
return frame;
}
set
{
frame = value;
frame.Navigated += OnFrameNavigated;
}
}
public NavigationService()
{
}
public void Navigate(Type type)
{
Frame.Navigate(type);
}
public void Navigate(Type type, object parameter)
{
Frame.Navigate(type, parameter);
}
public void Navigate(string type, object parameter)
{
switch (type)
{
case PageNames.RecipeDetailView:
Navigate<IRecipeDetailView>(parameter); break;
case PageNames.RecipeGroupDetailView:
Navigate<IRecipeGroupDetailView>(parameter);break;
case PageNames.RecipeGroupView:
Navigate<IRecipeGroupView>(parameter); break;
case PageNames.ExtendedSplashScreen:
Navigate<IExtendedSplashScreenView>(parameter); break;
case PageNames.SearchResultsView:
Navigate<ISearchResultsView>(parameter); break;
case PageNames.InfiniteRecipesView:
Navigate<IInfiniteRecipesView>(parameter); break;
}
}
private void Navigate<T>(object parameter) where T: IView
{
DisposePreviousView();
var viewType = InstanceFactory.Registrations.ContainsKey(typeof(T)) ? InstanceFactory.Registrations[typeof(T)] : typeof(T);
Frame.Navigate(viewType, parameter);
}
public IView CurrentView
{
get { return frame.Content as IView; }
}
private void DisposePreviousView()
{
try
{
if (this.CurrentView != null && ((Page)this.CurrentView).NavigationCacheMode ==
Windows.UI.Xaml.Navigation.NavigationCacheMode.Disabled)
{
var currentView = this.CurrentView;
var currentViewDisposable = currentView as IDisposable;
// if(currentView is BasePage
if (currentViewDisposable != null)
{
currentViewDisposable.Dispose();
currentViewDisposable = null;
} //view model is disposed in the view implementation
currentView = null;
GC.Collect();
}
}
catch { }
}
public void Navigate(string type)
{
Frame.Navigate(Type.GetType(type));
}
public void GoBack()
{
if (Frame.CanGoBack)
{
Frame.GoBack();
}
}
private void OnFrameNavigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
var view = e.Content as IView;
if (view == null)
return;
var viewModel = view.ViewModel;
if (viewModel != null)
{
if (!(e.NavigationMode ==
Windows.UI.Xaml.Navigation.NavigationMode.Back
&&
(((Page)e.Content).NavigationCacheMode ==
Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled ||
(((Page)e.Content).NavigationCacheMode ==
Windows.UI.Xaml.Navigation.NavigationCacheMode.Required))))
{
viewModel.Initialize(e.Parameter);
}
}
}
}
The Frame property is set from the App.xaml.cs:
protected async override void OnLaunched(LaunchActivatedEventArgs args)
{
Frame rootFrame = Window.Current.Content as Frame;
var navigationService = InstanceFactory.GetInstance<INavigationService>();
var stateService = InstanceFactory.GetInstance<IStateService>();
var dataService = InstanceFactory.GetInstance<IRecipeDataService>();
await dataService.GetAllRecipeGroups();
SearchPane.GetForCurrentView().QuerySubmitted += App_QuerySubmitted;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
navigationService.Frame = rootFrame;
if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
stateService.LoadState();
if (stateService.Parameter != null && stateService.ViewName != null)
{
navigationService.Navigate(stateService.ViewName, stateService.Parameter);
}
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
...
}
From then on, my VMs can use it to navigate, as follows.
RecipeGroupSelectedCommand = new RelayCommand<string>((recipeGroup) =>
{
navigationService.Navigate(PageNames.RecipeGroupDetailView, recipeGroup);
});
What about other types of navigation, such as showing a dialog, an error window? They too are managed using a separate service, in my app this is the DialogService. This service is also registered in the container and used in a similar way as the NavigationService.
public class DialogService : IDialogService
{
public async void ShowDialog(string message)
{
MessageDialog messageDialog = new MessageDialog(message);
await messageDialog.ShowAsync();
}
public async void ShowDeleteConfirmation()
{
MessageDialog messageDialog = new MessageDialog("The item has been deleted");
await messageDialog.ShowAsync();
}
public async void ShowAddToFavoriteConfirmation()
{
MessageDialog messageDialog = new MessageDialog("This recipe is now added to your favorites");
messageDialog.Commands.Add(new UICommand("OK, thanks!"));
messageDialog.Commands.Add(new UICommand("Undo please"));
await messageDialog.ShowAsync();
}
public async void ShowPushNotificationQuestion()
{
MessageDialog messageDialog = new MessageDialog("Enable push notifications?");
messageDialog.Commands.Add(new UICommand("Yes!", OnYesClicked));
messageDialog.Commands.Add(new UICommand("No!", OnNoClicked));
await messageDialog.ShowAsync();
}
private void OnNoClicked(IUICommand command)
{
}
private void OnYesClicked(IUICommand command)
{
var toastService = InstanceFactory.GetInstance<IPushNotificationService>();
toastService.RegisterChannelUriWithService();
}
}
Summary
We’ve already covered some parts of the Contoso Cookbook MVVM implementation. In the following articles, we will cover things such as tiles, lifecycle, push notifications and much more.
About the author
Gill Cleeren is Microsoft Regional Director, Silverlight MVP, Pluralsight trainer and Telerik MVP. He lives in Belgium where he works as .NET architect at Ordina. Gill has given many sessions, webcasts and trainings on new as well as existing technologies, such as Silverlight, ASP.NET and WPF at conferences including TechEd, TechDays, DevDays, NDC Oslo, SQL Server Saturday Switserland, Silverlight Roadshow in Sweden, Telerik RoadShow UK… Gill has written 2 books: “Silverlight 4 Data and Services Cookbook” and Silverlight 5 Data and Services Cookbook and is author of many articles for magazines and websites. You can find his blog at www.snowball.be. Twitter: @gillcleeren