John Papa and Glen Block have recently posted on a topic that touched home for me.  They put together a solution to find view models for a given view in a view-first fashion to support Blendability and a clean MVVM presentation pattern.  Funny, I put this very pattern together last week after being motivated by what I saw from Rob Eisenberg and Laurent Bugnion at MIX the week before.  I too came to the conclusion that it makes a lot of sense to go with a View-first approach if you’re going to make Blend a first class citizen in your development process (as I believe you should).

What John and Glen put together is of course MEF based, but what I put together is IoC container based.  In my case I’m using Unity, but it could be any IoC container (Windsor, Ninject, StructureMap, etc.).  The primary thing I’m trying to solve it to make the View designer-friendly so that we can leverage tools like Blend and the designer within Visual Studio (aka Cider).  I refer to this as “Blendability”.  Typically what makes a View not Blendable is the fact that the ViewModel is trying to do things like call Web Services.  This  is a necessity for a real application, but it’s breaks the designer as these functions are explicitly blocked.  So, I’ve strictly separated ViewModels that are designer-friendly from the real production ViewModels and I use a service locator pattern to discover the correct implementation at runtime.  My approach can be summarized with:

  • ViewModels will expose a well known interface
  • Views will bind to a ViewModel’s interface – not it’s implementation
  • Create designable ViewModel implementations to accompany the real ViewModel
  • Leverage the Design-Time Attributes in the Silverlight Designer to wire up the designable ViewModel in the View’s XAML
  • Use a simple convention and IoC container to discover the View’s corresponding ViewModel at runtime and bind accordingly

This approach provides us with a consistent makeup of all Views and ViewModels, minimizes error prone ceremony code, and makes the View very Blendable.  Below is an example of the pattern.

The ViewModel interface

    public interface IOrderHistoryViewModel
    {
        HistoricalOrderCollection Orders { get; set; }
        ParameterlessMarkupBindingCommand SelectCommand { get; set; }
        ParameterlessMarkupBindingCommand CancelCommand { get; set; }
    }

The (designable) ViewModel

    public class DesignableOrderHistoryViewModel : IOrderHistoryViewModel
    {
        public HistoricalOrderCollection Orders { get; set; }
        
        public ParameterlessMarkupBindingCommand SelectCommand { get; set; } // nothing to do here for the designer
        public ParameterlessMarkupBindingCommand CancelCommand { get; set; } // nothing to do here for the designer

        public DesignableOrderHistoryViewModel()
        {
            // create some data to display in the designer
            Orders = new HistoricalOrderCollection();
            Orders.Add(new HistoricalOrder{SubmitDate = DateTime.Now, TotalAmount = 1000.00});
            Orders.Add(new HistoricalOrder{SubmitDate = DateTime.Now, TotalAmount = 2000.00});
            Orders.Add(new HistoricalOrder{SubmitDate = DateTime.Now, TotalAmount = 3000.00});
            Orders.Add(new HistoricalOrder{SubmitDate = DateTime.Now, TotalAmount = 4000.00});
        }
    }

The (real) ViewModel

    public class OrderHistoryViewModel :  DefaultViewModel, IOrderHistoryViewModel
    {
        public ParameterlessMarkupBindingCommand SelectCommand { get; set; }
        public ParameterlessMarkupBindingCommand CancelCommand { get; set; }

        public DesignableOrderHistoryViewModel()
        {
            this.SelectCommand = new ParameterlessMarkupBindingCommand(() => { // real impl here });
            this.CancelCommand = new ParameterlessMarkupBindingCommand(() => { // real impl here });
            
            // fetch Orders from a web service call, etc.
        }
    }

The View (XAML)

<PresentationModel:DefaultView x:Class="Demo.Views.OrderHistoryView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="400" d:DesignWidth="300"
    xmlns:DesignProviders="clr-namespace:Demo.Views" 
    xmlns:PresentationModel="clr-namespace:Brownsberger.Commons.PresentationModel;assembly=Brownsberger.Commons.Silverlight" 
    d:DataContext="{d:DesignInstance DesignProviders:DesignableOrderHistoryViewModel, IsDesignTimeCreatable=True}" >
    <StackPanel>
        <!-- bind to orders, etc. -->
    </StackPanel>
</PresentationModel:DefaultView>

The View (code-behind)

    public partial class OrderHistoryView : DefaultView
    {
        public OrderHistoryView()
        {
            InitializeComponent();
            this.DataContext = ViewModelLocator.Locate(this);
        }
    }
 

Again, we’re leveraging the Design-Time Attributes for the Silverlight designer (highlighted above).  The means that the designer will automatically instantiate our designable ViewModel and bind it to the View while in the context of the designer.  No code-behind code will be invoked.  However when the application is running, the code-behind logic will of course execute and we will get the production-like behavior.  This is exactly what we want as it keeps the real ViewModel out of the way of the designer and we have full control over of the designable ViewModel to do whatever we want to support the design process.

The part I’ve glossed over is the ViewModelLocator that you see in the code-behind implementation.  It is build on a simple naming convention:  The View and the ViewModel will always reside in the same namespaces within the same assembly and the View will always be suffixed with xxxxxView and the ViewModel will be of the same name, but suffixed with xxxxxViewModel.  In our example above:  OrderHistoryView and OrderHistoryViewModel.  With that convention in place, and our IoC container properly initialized with all available types, the ViewModelLocator can do some simple reflection code to discover the ViewModel type corresponding to this view, instantiate and return it.  We then of course set it to the View’s DataContext.  Here is what the ViewModelLocator looks like:

    public class ViewModelLocator
    {
        public static object Locate(object view)
        {
            try
            {
                Check.ForNullArg(view, "view");

                Type viewType = view.GetType();
                string viewTypeName = viewType.Name;

                Logger.WriteInfo("Attempting to auto-resolve the view model from the view (type: {0})", viewTypeName);

                if (string.IsNullOrEmpty(viewTypeName) || !viewTypeName.EndsWith("View"))
                {
                    throw new PresentationModelException("Attempting to locate a view model instance for an invalid {0} implementation - expected the type name to be suffixed with 'View'", typeof(IView).Name);
                }

                string viewModelTypeName = string.Concat(viewTypeName, "Model");

                var viewModelTypeFullName = viewType.AssemblyQualifiedName.Replace(viewTypeName, viewModelTypeName);

                var viewModelType = Type.GetType(viewModelTypeFullName);

                if (viewModelType == null)
                {
                    throw new PresentationModelException("Could not find view model type '{0}' in namespace '{1}' within assembly '{2}'.  Auto resolution of the view model requires both the view and the view model to reside within the same namespace.", viewModelTypeName, viewType.Namespace, viewType.Assembly.FullName);
                }

                object resolved = IoC.Resolve(viewModelType);

                if (resolved == null)
                {
                    throw new PresentationModelException("Failed to resolve the view model type '{0}'", viewModelTypeFullName);
                }

                Logger.WriteInfo("Successfully auto-resolved view model (type {0})", viewModelTypeFullName);

                return resolved;
            }
            catch (Exception ex)
            {
                string viewType = view != null ? view.GetType().FullName : "NULL";

                throw new PresentationModelException(ex, "The attempt to auto-instantiate the view model for the view '{0}' threw an exception: {1}.  See the inner exception for details.", viewType, ex.Message);
            }
        }