3
votes

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?

1
MEF in .NET 4.0 so ExportFactory is not supported out of box. I'm using Glen's port.Charlie

1 Answers

2
votes

There are two ways to do what you're looking to do:

  • Create a factory and import all necessary parts into the factory, then pass that into the constructor of the classes. For this to work you must make your factory aware of the different classes that implement the 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; }
}

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.