MEF + MVVM:如何使用非导入或混合参数实例化类

时间:2013-02-20 11:31:42

标签: parameters constructor viewmodel mef factory

我一直在试验MEF和MVVM。我想让MEF用字符串构造函数参数初始化一个NonShared ViewModel实例,如下所示:

// BarViewModel's constructor has one single string parameter
IBarViewModel bar = container.GetExportedValue<IBarViewModel>("bar title");

显然,MEF不允许我这样做。

我用谷歌搜索,有人说ExportFactory是正确的工具,但没有语法示例。我无法弄清楚如何使用ExportFactory初始化带有构造函数参数的实例(或者我应该说非导入的参数)。

然后我尝试使用ViewModelFactory来实现这一点。我引用了这个article

并且出现了这样的事情:

[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;
    }

基本上我在这里做的是(1)从其他地方导入一个Container(2)新的VM up with parameter(3)使用一个临时容器来解决新实例的依赖关系。

这段代码似乎工作正常但我后来发现(a)BarViewModel不能再有[ImportingConstructor](b)我不能在其构造函数中使用BarViewModel的[Import]属性,因为它们在ctor范围内为null。

这意味着ViewModel的使用非常有限,也意味着无法使用MEF初始化这样的类:

[Import]
public ILogger Logger {get;set;}

[ImportingConstructor]
public SomeClass(IDataService service, string text)
{
    Logger.Trace(text);
}

现在我不知道如何用MEF实例化这个类。我想这是一个非常常见的情况,所以我想知道MEF是否无法处理这个问题?

1 个答案:

答案 0 :(得分:2)

有两种方法可以做你想要做的事情:

  • 创建工厂并将所有必要的部件导入工厂,然后将其传递到类的构造函数中。为此,您必须让工厂了解实现IBarViewModel接口的不同类以及实例化所需的部分,或者使用反射将它们放入某个列表中。

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实施:

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实施:

[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; }
}

对于这两种情况,我选择使用字符串文本来区分SuperBarViewModel(“x”)和BarViewModel(“”),但您几乎可以选择要帮助选择的元数据之一。

用法示例(适用于两种情况):

[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...
    }
}

显然,除此之外,您还可以做更多事情,例如创建自己的自定义ExportAttribute,而不是使用默认的ExportMetadataAttribute进行带元数据的导出。这将允许您创建更复杂的元数据。此外,如果您需要,SatisfyImports / SatisfyImportsOnce可以查看。基本上,您可以扩展此答案以自定义您的解决方案。