I have been mulling over different approaches for implementing the ViewModel (MVVM) pattern and feel that I’m close to a solution that I’m happy with. I have the following criteria:
- View First. I don’t like the idea of the ViewModel having even the slightest dependency on the view. That way I can easily isolate it and test it. The view I have no intention of (unit) testing as it will not have any code.
- MEF only. For now, I’m focusing on lean and simple. I was a big fan of Prism, but I’m shying away from big, frameworks that force me down a path. I’m liking light-weight utility libraries better. Sure I have to write more code, but I have full control and much more flexibility to adapt the solution to the requirements.
- Blendable design data. The first thing that excited me about WPF/e (original Silverlight code name) was the improvement in Designer/Developer work flow. Giving the designer a better experience and more control over the interface design is definitely an advantage.
I originally thought it would be nice to have the ViewModel discoverable at design time (as discussed in this article by John Papa, but I had trouble getting the ViewModel to be registered by MEF at design time. I then explored the new ViewModel support in Expression Blend, and I liked it.
In my experience the Designer typically creates the prototypes, hands them off to the developer and then needs to go back to update the design. This has always been a painful process, especially in an ASP.Net web forms application.
- The prototypes come as a Visio file, or html web pages.
- The developer needs to spend a lot of time transforming this into an ASP.Net web page, and falls short of recreating the exact design.
- The designer then has a really hard time working with the ASP.Net source and updates the original prototype.
- The Developer needs to update the application page to match the new prototypes
The result is friction between the two teams, and a lot of UI bugs generated by QA. Ideally, the developer and designer work should with the same files and do not conflict with each. In the world of Silverlight 4, the following is possible:
- The designer creates the interface in Expression Blend and attaches sample data to the forms
- The developer adds functionality to the forms and Visual Studio, only modifying the bindings in the presentation layer
- The designer can continue to make modifications to the presentation layer in Expression Blend even after the developer has worked with it
One of the challenges I faced working with MEF was troubleshooting import failures. I managed to resolve all except the timing for import of the composition container itself in the base view. I use this to resolve a viewmodel by name:
public static class ExportProviderExtensions
{public static object GetExport(this ExportProvider provider, string typeName){return GetExport(provider, typeName, typeName);}
}
And then use this in the base View class:
public class View : Page{private string _viewModelName;public string ViewModelName{get { return _viewModelName; }set{_viewModelName = value;ViewModel = ContainerLocator.Container.GetExport(ViewModelName) as ViewModelBase;}}public ViewModelBase ViewModel{get { return (ViewModelBase)DataContext; }set { DataContext = value; }}}
The ContainerLocator just stores the CompositionContainer in a static property.
The sample view model simply creates some data:
[Export]public class MainViewModel : ViewModelBase{public Collection<Thing> List { get; set; }public MainViewModel(){List = new Collection<Thing>(){new Thing {Value = "One"}, new Thing {Value = "Two"}, new Thing {Value = "Three"}};RaisePropertyChanged("List");}}
The view model name is set in the view xaml:
<rf:View x:Class="Silverlight4.Composite.MainView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:rf="clr-namespace:Refract.Views;assembly=Refract"ViewModelName="Silverlight4.Composite.MainViewModel">
I could not get this to work in the designer, because the designer host did not properly register exported types in the assembly, but I realized that it is not necessary. I can use Expression Blend to create a data source from a class. Point the wizard to the view model and voilĂ , design time data that is customizable by the designer:
<Silverlight4_Composite:MainViewModelData="Aliquam cras duis integer" IsInDesignMode="True" ><Silverlight4_Composite:MainViewModel.List><Silverlight4_Composite:Thing Value="Curabitur maecenas phasellus class aenean"/><Silverlight4_Composite:Thing Value="Praesent nunc curae nam"/><Silverlight4_Composite:Thing Value="Quisque vivamus accumsan"/></Silverlight4_Composite:MainViewModel.List></Silverlight4_Composite:MainViewModel>
Blend sets the data context on the first control you bind to, but I moved it to the root of the xaml:
<rf:View …d:DataContext="{d:DesignData /SampleData/MainViewModelSampleData.xaml}" …
The result (In Blend):
… and in the browser: