Dotneteers.net
All for .net, .net for all!

AutoView – the Missing Link for a Good ViewModel First Approach

VBandi's blog

Syndication

Subscribe

Generic Content

Whether you are developing a WPF, Silverlight, Windows Phone or Windows Store app using XAML, you will probably be told that MVVM is the way to go. With MVVM, you have to make a relationship between the View and the ViewModel for things to work. Essentially, there are two ways to make this connection. You either create the View first and the ViewModel second, or the other way around. Both approaches have their pros and cons.

For a long time, I’ve been in the View First camp. I’ve always valued the design time experience very high in projects, and the View First approach works better in this case: you just define the DataContext in the XAML of the View and make sure your ViewModel behaves properly in design time – e.g. it does not make server calls and perhaps even fills up some ObservableCollections and other properties with data to fine tune your UI with in Blend, without even launching the application.

NOTE: all samples in this post are for a Windows Store app, but they can easily be used in other XAML frameworks with minimal or even zero changes.

The Problems with View-First

However, I’ve always been somewhat uneasy with this approach. It works good in theory, but you have to jump through some hoops for a View-first navigation. E.g. You may have to use IOC containers to match the Views and ViewModels, it is hard to pass data from one screen login (ViewModel) to the next one, invoke initialization methods, etc. For example, let’s assume you have a Product List screen, and clicking on one of the products should take the user to a Product Details screen. Pretty typical problem, isn’t it? For the View-First approach, you would have to store the selected product somewhere (e.q. a querystring or a global variable), navigate to the next View, wait until it creates the proper ViewModel and then retrieve the selected product from the global variable so that you can display its data or perform a query to the server. All of this extra work, when what I really want to do is something like this:

public void ProductClicked(Product product)

NavigateTo(new ProductDetailsViewModel(product); }

The other big drawback is the difficult testability of View-first applications. Usually you don’t unit test Views, but you probably want to test the ViewModels. If you’re doing navigation with the ViewModel-first approach, you can easily verify that the right calls are made and the right navigation happens – even the arguments passed to the ViewModel constructor can be verified using the awesome Visual Studio Fakes framework - or if you prefer, through interfaces and IOC containers.

My ViewModel-First Architecture

All of this finally made me re-examine the VM-first approach. I am pretty sure that i am not the first to come up with this solution, but I am happy with it, so please let me share it with you as well.

At first, an AppViewModel object is created as soon in the app’s lifecycle as possible. Some people call it ApplicationContext – the idea is that AppViewModel is a global, singleton class of which one, and only one exists throughout the entire lifetime of the application. It can store things like the logged in user’s name and Id, cache certain lookup tables from the server, and other global things. And it also has a property called MainViewModel. Here is a typical snippet of the AppViewModel class:

public class AppViewModel : ViewModelBase
{
    private AppViewModel()
    {
        //initialize cache
    }
        
    private static readonly AppViewModel _instance = new AppViewModel();
    public static AppViewModel Instance
    {
        get { return _instance; }
    }
 
    private ViewModelBase _mainViewModel;
 
    public ViewModelBase MainViewModel
    {
        get { return _mainViewModel; }
        set { Set(() => MainViewModel, ref _mainViewModel, value); }
    }
}

The important thing here is that the MainViewModel property is supported by an INotifyPropertyChanged infrastructure which makes it possible for the XAML framework to react to the changes. Here I am using Laurent’s excellent MVVM Light Toolkit, for the INPC implementation, but you can use any other approach.

The AppViewModel class here is a singleton class, and it always exists throughout the entire lifetime of the application. Your code can always access the one and only AppViewModel instance by typing AppViewModel.Instance.

The MainPage of the application is very simple. It may contain some constant elements (e.g. styling, a logo, app name, etc), or the name of the logged in user – but the most important thing is that its DataContext is set to the AppViewModel Instance. The easiest way to do this is to simply set the DataContext in the MainPage.xaml.cs file:

public MainPage()
{
    DataContext = AppViewModel.Instance;
    InitializeComponent();
}

 

Don’t worry, we won’t have to do this for any other views. You can keep those codebehinds as clean as you want.

So, how will our MainPage.xaml look after this? Well, it will be very simple. Apart from the logo, etc I mentioned above, all it will have is a single AutoView control – the topic of this entire blog post. Like this:

<local:AutoView DataContext="{Binding MainViewModel}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" />
 

All the magic happens within this AutoView control. As you can see, its DataContext is bound to the MainViewModel property of the AppViewModel singleton object. What the AutoView control does is that whenever its DataContext changes, it tries to locate a View for the ViewModel and display that. Since the DataContext property is inherited down the Visual Tree, the newly created View will have the same DataContext – e.g. ViewModel as what was set in the AppViewModel.MainViewModel property.

image

So, how do you perform navigation now? If we go back to the Product List – Product details example we had earlier, here is the one line you have to do:

AppViewModel.Instance.MainViewModel = new ProductDetailsViewModel(SelecteProduct);

As soon as the new ViewModel gets created, the AutoView locates the associated View, and displays it. Boom, all done.

If you need more sophisticated navigation (e.g. handling a back stack, caching ViewModels, etc), you can change the AppViewModel: instead of setting its MainViewModel property, call a NavigateTo method which takes care of all the navigation infrastructure you need.

Unit testing this navigation also couldn’t be easier. All you need to do is this:

//Act
productListViewModel.SelectProduct(myProduct) //or just invoke a command

//Verify
var pdvm = AppViewModel.Instance.MainViewModel as ProductDetailsViewModel;
Assert.IsNotNull(pdvm);
Assert.AreEqual(myProduct, pdvm.Product);

First, we invoke the command for selecting a specific product on the product list screen, then we make sure that the right ViewModel has been created for the next screen, and its Product property has been set accordingly. You can even use Fakes here if you want.

Finding the Right View

So, how does the AutoView find the right View for the ViewModel? I prefer to use convention here. The specifics may change from project to project, but my most common approach is to place the AutoView in the same assembly where all the other views are, and look for a class in that assembly that has similar name as the ViewModel, but without the Model. Here is a typical implementation for the CreateView method of the AutoView control:

/// <summary>
/// Creates the view for the specified viewmodel based on convention (in the View Namespace, 
/// same class name but without the "Model" at the end.
/// </summary>
/// <param name="vm">The viewmodel to locate the view for</param>
/// <returns>The newly instantiated view</returns>
private FrameworkElement CreateView(object vm)
{
    var classname = vm.GetType().Name;
    if (!classname.EndsWith("ViewModel"))
        throw new ArgumentException("ViewModel class name should end with 'ViewModel' for CreateView to work");

    classname = classname.Remove(classname.Length - "Model".Length);

    //View has to be in same assembly as AutoView
    var vmAssembly = GetType().GetTypeInfo().Assembly;
    var viewType = vmAssembly.GetType("DemoApp.Views." + classname);

    if (viewType == null)
        viewType = typeof (ViewNotFound);

    var result = (FrameworkElement) (Activator.CreateInstance(viewType));
    result.DataContext = vm;

    return result;
}

First, we make sure that the ViewModel’s class name ends with “Model”. Then we strip this “Model” (sounds kinky, eh?), and look for a View in the DemoApp.Views namespace that has the same class name. For example, a ProductListViewModel will find its match in the ProductListView class. If the View is found, it is created, its DataContext is set, and returned.

The rest of the AutoView control is pretty straightforward:

public class AutoView : ContentControl
{
    public AutoView()
    {
        DataContextChanged += OnDataContextChanged;
    }

    private void OnDataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
    {
        if (DataContext == null)
        {
            Content = null;
            return;
        }

        try
        {
            var content = CreateView(DataContext);
            content.VerticalAlignment = VerticalAlignment.Stretch;
            content.HorizontalAlignment = HorizontalAlignment.Stretch;
            Content = content;

        }
        catch (Exception exception)
        {
            Content = exception.ToString();
            throw;
        }
    }
The constructor subscribes to the DataContextChanged event, and when that happens, tries to locate and create a correspoding View. Since the AutoView control is a descendant of the ContentControl, it has a Content property, which can simply be set to the resulting View and it will be put in the Visual Tree and displayed. Finally, if there was any exception during the creation of the View, we display that in the place of the Content.

Debugging Helpers

Returning our attention to the CreateView method, you can see that when the View could not be located, it creates a special View called ViewNotFound, which looks like this:

image

 

This is essentially a placeholder, and warns you if a View is not found. Convention based approaches often can’t verify these kinds of things during compile time, so a debugging tool such as this one is very useful. The important part of the XAML source for this control looks like this:

<Grid>
    <TextBlock HorizontalAlignment="Center" Margin="0,10,0,0" TextWrapping="Wrap" Text="View not Found. ViewModel:" VerticalAlignment="Top" />
    <TextBlock HorizontalAlignment="Center" Margin="0,150,0,0" TextWrapping="Wrap" Text="{Binding}" VerticalAlignment="Top" />
</Grid>

Another thing that can help us to get our bearings while developing is a little keyboard helper that tells you what View and ViewModel are currently on the screen. If your application has dozens of screens and ViewModels and you find an issue on one of the screens, it may take a while to figure out which View or ViewModel you need to edit – even if you used the world’s most clever names for them. I often add a small helper to the AutoView control which displays the current View and ViewModel at a secret keystroke. Here is an example:

#if DEBUG
    protected override void OnKeyDown(KeyRoutedEventArgs e)
    {
        base.OnKeyDown(e);
        if (e.Key == VirtualKey.F1)
        {
            MessageDialog d = new MessageDialog("DataContext: " + (DataContext != null ? DataContext.GetType().ToString() : "null"), 
                "View: " + (Content!= null ? Content.GetType().ToString() : "null"));
            d.ShowAsync();
        }
    }
#endif

This will show a MessageBox when I press the F1 key. You can set it to a Ctrl+Alt+MouseRightClick combo, whatever you like. The #if DEBUG part makes sure that this little code snippet doesn’t make it into production. We don’t want to show our internals to the customers, do we?

Summary

For me, this AutoView control has been the missing link for embracing the ViewModel-first approach, and I haven’t looked back since. It really helped with VM testability and a proper architecture that is easy to explain to members of my team. Hope this helps you, too – let me know in the comments what you think!


Posted Mar 25 2014, 11:18 AM by vbandi

Comments

AutoView ??? the Missing Link for a Good ViewModel First Approach wrote AutoView ??? the Missing Link for a Good ViewModel First Approach
on Wed, Mar 26 2014 9:43

Pingback from  AutoView ??? the Missing Link for a Good ViewModel First Approach

David Evans wrote re: AutoView – the Missing Link for a Good ViewModel First Approach
on Thu, Mar 27 2014 10:01

why not get ride of the View (XAML) completely and use data templates to dynamically resolve the XAML -  <DataTemplate DataType="{x:Type ca:ConfigurationViewModel}">

Also why aren't you using a IoC container?

vbandi wrote re: AutoView – the Missing Link for a Good ViewModel First Approach
on Thu, Mar 27 2014 11:36

David: DataTemplate.DataType is not supported for WinRT, and I think it is not supported on Windows Phone either. It would be a good choice otherways, although I like convention based approach. Also, it would be hard to do the stuff I described in the debugging helper section.

As for IoCs, I don't like the extra work of defining interfaces, registering stuff, and especially explaining junior devs that they can't just say "new XYZ()". Still, I do use IoC sometimes, for example for faking repositories for testing. But not for ViewModels.

Livebinding einer FMX TListView an TStrings - Seite 2 - Delphi-PRAXiS wrote Livebinding einer FMX TListView an TStrings - Seite 2 - Delphi-PRAXiS
on Fri, May 22 2015 9:39

Pingback from  Livebinding einer FMX TListView an TStrings - Seite 2 - Delphi-PRAXiS