Simple ViewModelLocator with MEF
December 13, 2010 6 Comments
These days design pattern #1 seems to be the MVVM pattern, at least in the Silverlight/WPF community. As soon as you start to dig deeper into MVVM you get in touch with another design pattern, the ViewModelLocator which is basically the ServiceLocator pattern but instead of locating Services it helps us to locate our ViewModels. Just bing it and you will find a wide variety of implementations. The only reason why I came up with my own implementation is simply because I couldn’t find one which meets my requirements:
- Support for design-time data
- As less as possible code to wire up the ViewModels with the ViewModelLocator
- Easy syntax to bind the ViewModel in XAML
The best implementation so far was the ViewModelLocator from John Papa. Unfortunately it doesn’t support design time data seamless, that means you have to code extra ViewModels for design-time data and set the datacontext twice with help of the designer attribute. But I borrowed some ideas from his implementation as well from Kellabytes blog post on how to use MEF with the ViewModelLocator from the famous MVVM Light Toolkit. What I learned from both is that MEF is the best option to implement a light weight ViewModelLocator.
But before we start lets talk about ViewModels and design-time data. Mostly ViewModels relying on external services to get the data they want and to polish them for the View. So the most obvious option would be to mock the ViewModel and instead of asking the service for data, it provides some fake data by itself. Since we live in a decoupled world their is one problem with this approach and that is the fact that we have to introduce interfaces for our ViewModel and to implement each ViewModel twice, one for design-time and one for run-time. I think it’s a much better approach to inject ServicProvider/Helper into the ViewModels which creates an abstraction layer between the ViewModels and the external services and gives us the possibility to foist our design-time data. Plus, a ServiceProvider/Helper is potentially used by more than one ViewModel. So it makes absolute sense to place the fake data there.
Here it comes, the easy 3+1 steps you need to get your ViewModels with full design-time support:
1. Export ServiceHelper
1: [Export(typeof(IServiceHelper))]
2: public class ServiceHelper : IServiceHelper
3: {...
4:
5: [Export(typeof(IServiceHelper))]
6: [DesignTimeExportAttribute(DesignTime = true)] //explicite mark this as design-time export
7: public class DummyServiceHelper : IServiceHelper
8: {...
2. Export ViewModel via contract name and import ServiceProvider
1: [Export(ViewModelTypes.MainViewModel)]
2: public class MainViewModel : INotifyPropertyChanged
3: {
4: ...
5: [ImportingConstructor] //lets MEF provide an implementation
6: public MainViewModel(IServiceHelper serviceHelper)
7: {
8: ...
3. Bind the ViewModel
1: <!--
2: For extended support in the designer tool add a typed version of the ViewModel to ViewModelLocator
3: and instead of the indexer use the typed property
4: DataContext="{Binding Source={StaticResource VMLocator}, Path=MainViewModel}"
5: -->
6: <Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource VMLocator}, Path=[MainViewModel]}">
4. Optional add a typed property to the ViewModelLocator if you need support for the PropertyGrid in the VS/Blend
1: ...
2: //string indexer to access the right ViewModel by contract name
3: public object this[string viewModel]
4: {
5: get
6: {
7: return _container.GetExportedValue<object>(viewModel);
8: }
9: }
10:
11: //a typed property to access the MainViewModel
12: //is not needed because the string indexer can be used directly from XAML
13: //but adds extra value to the design experience
14: public MainViewModel MainViewModel { get { return (MainViewModel)this[ViewModelTypes.MainViewModel]; } }
15: ...
If you are interested in how it works and prefer to read source code then grab it from here or if you are fine with my ESL – blog post style then keep reading.
Ok you still here, then lets start with some infrastructure that helps us to define our design-time exports easily. MEF allows us to add meta-data to an export definition. The following class implements a simple Boolean attribute which indicates whether an export is meant to provide a implementation at design-time.
1: [MetadataAttribute]
2: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
3: /// <summary>;
4: /// Marks an export as a design-time replacement for another export with the same contract.
5: /// </summary>
6: public class DesignTimeExportAttribute : ExportAttribute
7: {
8: #region Constructors
9: public DesignTimeExportAttribute()
10: {
11: DesignTime = false;
12: }
13:
14: public DesignTimeExportAttribute(Type contractType)
15: : base(contractType)
16: {
17: DesignTime = false;
18: }
19:
20: public DesignTimeExportAttribute(string contractName)
21: : base(contractName)
22: {
23: DesignTime = false;
24: }
25:
26: public DesignTimeExportAttribute(string contractName, Type contractType)
27: : base(contractName, contractType)
28: {
29: DesignTime = false;
30: }
31: #endregion
32:
33: [DefaultValue(false)]
34: public bool DesignTime
35: {
36: get; set;
37: }
38: }
Now we can take advantage of one extensibility point of MEF and code our custom ComposablePartCatalog which filters and prioritize design-time exports (the idea and part of the code comes from here). The overall goal of the strategy is to to replace a ServiceProvider implementation with a dummy implementation at design-time by utilizing the DesignTimeAttribute. Maybe a ServiceProvider doesn’t rely on external source and we don’t want to be forced to provide design-time export, that’s why exports without the DesignTimeAttribute stay valid at design-time. However, at run-time we wanna get rid of design-time data and we exclude all exports with the DesignTimeAttribute set to true.
1: /// <summary>
2: /// A design- vs run-time wrapper catalog which provides prioritizing and filtering based on the DesignTimeExportAttribute
3: /// </summary>
4: public class DRPartCatalog : ComposablePartCatalog
5: {
6: private ComposablePartCatalog _catalog;
7: private bool _designTime = false;
8: public bool DesignTime
9: {
10: get
11: {
12: return _designTime;
13: }
14: }
15:
16: /// <summary>
17: /// Creates a new DRPartCatalog around an existing catalog.
18: /// The catalog that this class decorates is provides in the constructor
19: /// paramater "catalog". The "DesignTime" property is set to false.
20: /// <summary>
21: public DRPartCatalog(ComposablePartCatalog catalog)
22: {
23: _catalog = catalog;
24: }
25:
26: /// <summary>
27: /// Creates a new DRPartCatalog around an existing catalog.
28: /// The catalog that this class decorates is provides in the constructor
29: /// paramater "catalog". The "designTime" parameter controls sets the "DesignTime"
30: /// property which is used to control the import satisfaction
31: /// <summary>
32: public DRPartCatalog(ComposablePartCatalog catalog, bool designTime)
33: {
34: _designTime = designTime;
35: _catalog = catalog;
36: }
37:
38: public override System.Linq.IQueryable<ComposablePartDefinition> Parts
39: {
40: get { return _catalog.Parts; }
41: }
42:
43: /// <summary>
44: /// Returns the exports in the catalog that match a given definition of an import.
45: /// This method is called every time MEF tries to satisfy an import.
46: ///</summary>
47: public override IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>> GetExports(ImportDefinition importDef)
48: {
49: // If ImportMany is defined and we are at design-time the use the standard bahavior and return
50: // all matching exports.
51: if (importDef.Cardinality == ImportCardinality.ZeroOrMore && DesignTime)
52: {
53: return base.GetExports(importDef);
54: }
55:
56: //otherwise we have to do our own logic
57: IList<Tuple<ComposablePartDefinition, ExportDefinition>> result
58: = new List<Tuple<ComposablePartDefinition, ExportDefinition>>();
59:
60: // Walk through all parts in that catalog...
61: foreach (ComposablePartDefinition partDef in Parts)
62: {
63: // ... and for each part, examine if any export definition matches the
64: // requested import definition.
65: foreach (ExportDefinition exportDef in partDef.ExportDefinitions)
66: {
67: if (importDef.IsConstraintSatisfiedBy(exportDef))
68: {
69: //ok the import definition is satisfied
70: Tuple<ComposablePartDefinition, ExportDefinition> matchingExport = null;
71: matchingExport = new Tuple<ComposablePartDefinition, ExportDefinition>(partDef, exportDef);
72: object designTimeMetadata;
73: exportDef.Metadata.TryGetValue("DesignTime", out designTimeMetadata);
74: //if DesignTimeAttribute is set then ToBool returns the assigend value
75: //ohterwise it returns false
76: bool hasDesignTimeAttribute = ToBool(designTimeMetadata);
77:
78: //If ImportMany is defined and we are at run-time then filter out
79: //design-time exports
80: if (importDef.Cardinality == ImportCardinality.ZeroOrMore)
81: {
82: if (DesignTime || !hasDesignTimeAttribute)
83: result.Add(matchingExport);
84: }
85: //If Import or Import(AllowDefault=true) then prioritize design-time exports
86: //at design-time
87: else
88: {
89: if (DesignTime)
90: {
91: if (result.Count == 0) //also allow run-time exports at design-time
92: result.Add(matchingExport);
93: else if (hasDesignTimeAttribute) //but prioritize design time data at design time
94: {
95: result.Clear();
96: result.Add(matchingExport);
97: }
98: }
99: else
100: {
101: if (!hasDesignTimeAttribute) //only allow run-time exports at run-time
102: result.Add(matchingExport);
103: }
104: }
105: }
106: }
107: }
108: return result;
109: }
110:
111: /// <summary>
112: /// Converts an untyped value into a bool. If the object is null
113: /// or cannot be converted to an bool, returns false.
114: /// </summary>
115: protected static bool ToBool(object value)
116: {
117: if (value == null)
118: {
119: return false;
120: }
121:
122: bool result = false;
123: bool.TryParse(value.ToString(), out result);
124: return result;
125: }
126: }
Now we have the important parts in place and we can code an example on how we take advantage of all this. In order to simulate a more real world project we going to have separated projects for the View, the ViewModel and the Model. The following interface defines the functionality for a ServiceHelper.
1: public interface IServiceHelper
2: {
3: void GetData(Action<IEnumerable<Foo>> callback);
4: }
Allright, here a “real” implementation of the interface and another one to provide fake data for designer.
1: [Export(typeof(IServiceHelper))]
2: public class ServiceHelper : IServiceHelper
3: {
4: public void GetData(Action<System.Collections.Generic.IEnumerable<Foo>> callback)
5: {
6: //lets pretend we do a external service call here
7: //which would fail at design-time
8:
9: List<Foo> result = new List<Foo> {
10: new Foo { Bar = "Runtime data 1"},
11: new Foo { Bar = "Runtime data 2"},
12: new Foo { Bar = "Runtime data 3"}
13: };
14: //and now hand-over the data via the callback
15: callback(result);
16: }
17: }
18:
19: [Export(typeof(IServiceHelper))]
20: [DesignTimeExportAttribute(DesignTime = true)] //explicite mark this as design-time export
21: public class DummyServiceHelper : IServiceHelper
22: {
23: public void GetData(Action<System.Collections.Generic.IEnumerable<Foo>> callback)
24: {
25: //we just need dummy data
26: List<Foo> result = new List<Foo> {
27: new Foo { Bar = "Design-time data 1"},
28: new Foo { Bar = "Design-time data 2"},
29: new Foo { Bar = "Design-time data 3"}
30: };
31: //and now hand-over the data via the callback
32: callback(result);
33: }
34: }
The ViewModel imports IServiceHelper but doesn’t know about run-/design-time data. It just expects an implementation of IServiceHelper and exports itself via contract name. The name of the ViewModel that is used as contract name for the export is managed in a sealed class and prevents typos within the export/import definitions.
1: [Export(ViewModelTypes.MainViewModel)] //export via contract name
2: public class MainViewModel : INotifyPropertyChanged
3: {
4: private IServiceHelper _servicHelper;
5:
6: private ObservableCollection<Foo> _foos;
7: public ObservableCollection<Foo> Foos
8: {
9: get
10: {
11: return _foos;
12: }
13: set
14: {
15: _foos = value;
16: if (PropertyChanged != null)
17: PropertyChanged(this, new PropertyChangedEventArgs("Foos"));
18: }
19: }
20:
21: [ImportingConstructor] //lets MEF provide an implementation
22: public MainViewModel(IServiceHelper serviceHelper)
23: {
24: _servicHelper = serviceHelper;
25: _servicHelper.GetData((result) =>
26: {
27: Foos = new ObservableCollection<Foo>(result);
28: });
29: }
30:
31: public event PropertyChangedEventHandler PropertyChanged;
32: }
Finally, we can have a look at the ViewModelLocator. The implementation is pretty simple and straight forward except for getting the assemblies we need for MEF. Since the standard initialization of MEF doesn’t work at design-time, we have to manually add all assemblies which contains export and/or import definitions until MEF gets a better design-time support. It’s a ugly hack, but the easiest way to get the assembly is via the type of a known class or interface. Please let me know if you know about a more elegant way that works also at design-time.
1: public class SimpleViewModelLocator
2: {
3: private static CompositionContainer _container;
4:
5: /// <summary>
6: /// Initializes a new instance of the ViewModelLocator class.
7: /// </summary>
8: public SimpleViewModelLocator()
9: {
10: //detect design-time
11: bool designTime = DesignerProperties.IsInDesignTool;
12:
13: //since the default catalog initializing in MEF doesn't work at runtime
14: //we have to create the catalog manually for now
15: var aggregatedAssemblyCatalog = new AggregateCatalog(
16: new AssemblyCatalog(typeof(MainViewModel).Assembly) //assembly that contains the ViewModels
17: , new AssemblyCatalog(typeof(IServiceHelper).Assembly) //assembly that contains the Models
18: );
19:
20: //the design/run time catalog helps to filter exports depending on a design-time attribute
21: var drpCatalog = new DRPartCatalog(aggregatedAssemblyCatalog, designTime);
22:
23: //the container to resolve exported ViewModels
24: _container = new CompositionContainer(drpCatalog);
25: }
26:
27: //string indexer to access the right ViewModel by contract name
28: public object this[string viewModel]
29: {
30: get
31: {
32: return _container.GetExportedValue<object>(viewModel);
33: }
34: }
35:
36: //a typed property to access the MainViewModel
37: //is not needed because the string indexer can be used directly from XAML
38: //but adds extra value to the design experience
39: public MainViewModel TestViewModel { get { return (MainViewModel)this[ViewModelTypes.MainViewModel]; } }
40: }
Here we go; after all the efforts we can easily wire our ViewModels in 3 + 1 steps (see above) with first class design-time support. If you prefer to have support for DataBinding in the PropertyGrid of Cider/Blend then step 4 is a matter of seconds with the following code snippet.
1: <?xml version="1.0" encoding="utf-8" ?>
2: <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
3: <CodeSnippet Format="1.0.0">
4: <Header>
5: <Title>vmltprop</Title>
6: <Shortcut>vmltprop</Shortcut>
7: <Description>Code snippet for defining a typed Property in Bitdisaster's SimpleViewModelLocator</Description>
8: <Author>Jan Hannemann</Author>
9: <SnippetTypes>
10: <SnippetType>Expansion</SnippetType>
11: </SnippetTypes>
12: </Header>
13: <Snippet>
14: <Declarations>
15: <Literal>
16: <ID>VM</ID>
17: <ToolTip>ViewModel</ToolTip>
18: <Default>ViewModel</Default>
19: </Literal>
20: </Declarations>
21: <Code Language="csharp">
22: <![CDATA[public $VM$ $VM$ {get{return ($VM$)this[ViewModelTypes.$VM$];}}
23: $end$]]>
24: </Code>
25: </Snippet>
26: </CodeSnippet>
27: </CodeSnippets>
Let me know what you think!
Nice idea, would like to see the sourcecode though.
Thanks!
I fixed the download link for the source code.
Link to Source doesn’t work
I just checked the link and it did work. The download is hosted at Dropbox. http://dl.dropbox.com/u/32658643/SimpleViewModelLocator.rar
Thank you! The link that you provide here works indeed! But in the text above where you said: “If you are interested in how it works and prefer to read source code then grab it from here or if you are fine with my ESL – blog post style then keep reading.” – ‘here’ points to some other address – http://www.sharpedtools.net/picdropbox/SimpleViewModelLocator.rar
P.S. Ohhh.. now I see the link at the top of the page!!! It points to the correct source! Thank you again!
Thanks Anastas, I have fixed the link in the text now.