Reputation: 794
I have been experimenting MEF and MVVM. I wanted to let MEF initialize a NonShared ViewModel instance with a string constructor parameter, i.e something like this:
// BarViewModel's constructor has one single string parameter
IBarViewModel bar = container.GetExportedValue<IBarViewModel>("bar title");
Obviously MEF wouldn't let me do this.
I googled and some say ExportFactory is the right tool for this but there are no syntax samples. I have not been able to figure out how to use ExportFactory to initialize an instance with constructor parameters (or should I say non-imported parameter).
So then I tried to use a ViewModelFactory to achieve this. I referenced this article
and came up something like this:
[Export(typeof(IBarViewModelFactory))]
public class BarViewModelFactory : IBarViewModelFactory
{
[Import]
public Lazy<CompositionContainer> Container { get; set; }
public IBarViewModel CreateBarViewModel(string text)
{
IBarViewModel result = null;
var tempContainer = CreateTemporaryDisposableContainer(Container.Value);
try
{
result = new BarViewModel(text);
tempContainer.ComposeParts(result);
}
catch (Exception ex)
{
}
finally
{
}
return result;
}
Basically what I'm doing here is (1) import a Container from somewhere else (2) new the VM up with parameter (3) use a temporary container to settle the dependencies of the new'ed instance.
This code seems working OK but I then discovered that (a) BarViewModel can no longer have [ImportingConstructor] (b) I cannot use [Import] properties of BarViewModel in its constructor because they are null in the ctor scope.
This mean the ViewModel is very limited in usage and also it means it is not possible to initialize a class like this with MEF:
[Import]
public ILogger Logger {get;set;}
[ImportingConstructor]
public SomeClass(IDataService service, string text)
{
Logger.Trace(text);
}
Now I have no idea how to instantiate this class with MEF. I guess this is a quite common scenario so I wonder if MEF is not capable of handling this?
Upvotes: 3
Views: 2830
Reputation: 32515
There are two ways to do what you're looking to do:
IBarViewModel
interface and the parts they require for instantiation, or use reflection to get them in some list.BarViewModelFactory:
[Export(typeof(IBarViewModelFactory))]
public class BarViewModelFactory : IBarViewModelFactory
{
[Import] private IServiceA ServiceA { get; set; }
[Import] private IServiceB ServiceB { get; set; }
// val is some metadata value to help decide which class to instantiate.
public IBarViewModel CreateBarViewModel(string val, string text)
{
IBarViewModel result = null;
try
{
// Select using metadata...
if (String.Equals(val, "x", StringComparison.OrdinalIgnoreCase))
result = new SuperBarViewModel(ServiceA, ServiceB, text);
else
result = new BarViewModel(ServiceA, text);
}
catch (Exception ex) { ... }
finally { ... }
return result;
}
}
IBarViewModel implementations:
public sealed class BarViewModel : ViewModelBase, IBarViewModel
{
public BarViewModel(IServiceA svcA, string text)
{
ServiceA = svcA;
// Do something with text, etc...
}
private IServiceA ServiceA { get; set; }
}
public sealed class SuperBarViewModel : ViewModelBase, IBarViewModel
{
public BarViewModel(IServiceA svcA, IServiceB svcB, string text)
{
ServiceA = svcA;
ServiceB = svcB;
// Do something with text, etc...
}
private IServiceA ServiceA { get; set; }
private IServiceB ServiceB { get; set; }
}
ExportFactory<T, TMetadata>
. It is part of MEF (Silverlight), MEF Codeplex library (ver 2), and Microsoft.Net 4.5. Note that an ExportFactory cannot instantiate a class and pass parameters into the constuctor, so you're forced to use the text
by setting a property (or calling a method) after the instance has been created. Also, here is some information on the Using ExportMetadataAttribute.BarViewModelFactory:
[Export(typeof(IBarViewModelFactory))]
public class BarViewModelFactory : IBarViewModelFactory
{
[ImportMany]
private IEnumerable<ExportFactory<IBarViewModel, IBarViewModelMetadata>> Factories { get; set; }
// val is some metadata value to help decide which class to return.
public IBarViewModel CreateBarViewModel(string val, string text)
{
IBarViewModel result = null;
try
{
result = Factories.Single(x => String.Equals(x.Metadata.Value, val, StringComparison.OrdinalIgnoreCase))
.CreateExport()
.Value;
result.Text = text;
}
catch (Exception ex) { ... }
finally { ... }
return result;
}
}
IBarViewModel implementations:
[ExportMetadata(typeof(IBarViewModel, ""))]
public sealed class BarViewModel : ViewModelBase, IBarViewModel
{
[Import] private IServiceA ServiceA { get; set; }
public string Text { get; set; }
}
[ExportMetadata(typeof(IBarViewModel, "x"))]
public sealed class SuperBarViewModel : ViewModelBase, IBarViewModel
{
[Import] private IServiceA ServiceA { get; set; }
[Import] private IServiceB ServiceB { get; set; }
public string Text { get; set; }
}
For both scenarios, I chose to use a string text to differentiate between SuperBarViewModel
("x") and BarViewModel
(""), but you can choose practically whatever metadata you want to help select one.
Usage Example (works for both scenarios):
[Export]
public sealed class SomeClass
{
[Import]
private IBarViewModelFactory BarViewModelFactory { get; set; }
public void SomeMethod()
{
// Get the "Super" version by passing in "x" (metadata).
var barVM = BarViewModelFactory.CreateBarViewModel("x", "my text");
// etc...
}
}
Obviously, there is a lot more you can do on top of this, such as creating your own custom ExportAttribute
instead of using the default ExportMetadataAttribute
for exports with metadata. This will allow you to create more complex metadata. Also, SatisfyImports
/SatisfyImportsOnce
is something to look into if you need it. Basically, you can expand on this answer to customize your solution.
Upvotes: 2