MEF构造函数注入

43

我正在尝试理解MEF的构造函数注入属性。我不知道如何告诉它加载构造函数的参数。

我要加载的是这个属性

[ImportMany(typeof(BUsers))]
public IEnumerable<BUsers> LoadBUsers { get; set; }

这是我用来导入程序集的代码。

try
{
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly()));
    catalog.Catalogs.Add(new DirectoryCatalog("DI")); 
    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
}

这里是我尝试加载的类

[Serializable]
[Export(typeof(BUsers))]
public class EditProfile : BUsers
{
    [ImportingConstructor]
    public EditProfile(string Method, string Version)
    {            
        Version = "2";
        Action = "Edit";
        TypeName = "EditProfile";
    }
3个回答

60

使用ImportingConstructor属性时,构造函数的参数将变为导入项。默认情况下,你要导入的内容(契约名称)基于你导入到的参数或属性类型。因此,在这种情况下,两个导入项的契约类型都是string,并且第一个参数和第二个参数之间没有实际区别。

看起来你正在尝试使用导入项来提供配置值,这可能不是它设计的目的。为了让它按照你的要求工作,你应该覆盖每个参数的契约名称,像这样:

[ImportingConstructor]
public EditProfile([Import("Method")] string Method, [Import("Version")] string Version)
{ }

那么您需要在容器中为方法和版本添加导出项。一种方法是直接添加它们:

var container = new CompositionContainer(catalog);
container.ComposeExportedValue("Method", "MethodValue");
container.ComposeExportedValue("Version", "2.0");
container.ComposeParts(this);
(请注意,ComposeExportedValue实际上是定义在静态AttributedModelServices类上的扩展方法。) 如果您想从某种类型的配置文件中读取这些值,您可以创建自己的导出提供程序,该提供程序读取配置并将其中的值作为导出提供给容器。 另一种处理方式是仅导入一个通过名称提供对配置值访问权限的接口,并从构造函数的主体中获取所需的值。

我刚在 CodePlex 下载了最新的版本。ComposeExportedValue() 方法不在 CompositionContainer 类中。它在哪里? - David.Chu.ca
我认为我找到了方法。它在AttributedModelServices类中,在该类中,该方法被定义为CompositionContainer类的扩展方法。 - David.Chu.ca
1
@David.Chu.ca 是的,ComposeExportedValue是AttributedModelServices类的扩展方法。 - Daniel Plaisted
1
好的回答。但如果我有一个需要配置的“非共享”导出,该怎么办? 导入配置获取器接口是有效的(如您所写)但我应该使用什么_名称_? 在对象创建和OnImportsSatisfied调用之间没有拦截的方法来设置这个_名称_ - 但在我的情况下,在OnImportsSatisfied中访问配置是有效的。 - 我对你的回答还有另一个问题:container.ComposeParts(this)中的_this_是什么? 我不认为这是当前创建的EditProfile实例。 - Sebastian Schumann

26

我喜欢Daniel的解决方案;然而,我只看到一个问题,那就是参数名称在演员(创建CompopositionContrainer())和具有[ImportingConstructor]的导出部分之间的紧密耦合。例如,“Method”必须在两个位置上匹配。如果演员和导出部分位于不同的项目中,则很难维护导出部分。

如果可能的话,我会向导出部分类添加第二个CTOR。例如:

[Export(typeof(BUsers))] 
public class EditProfile : BUsers
{
    [ImportingConstructor]
    public EditProfile(EditProfileParameters ctorPars)
    : this(ctorPars.Method, ctorPars.Version) {}

    public EditProfile(string Method, string Version)
    {
        Version = "2";
        Action = "Edit";
        TypeName = "EditProfile";
    }

编辑个人资料参数类(EditProfileParameters)应该很简单:包含两个属性,分别是方法(Method)和版本(Version):

[Export]
public class EditProfileParameters{
   public string Method { get; set; }
   public string Version { get; set; }
}
关键点是为该类添加Export属性。然后,MEF应该能够将此类映射到EditProfile的CTOR参数。
以下是将Export部分添加到容器的示例:
var container = new CompositionContainer(catalog);
var instance1 = new EditProfileParameters();
// set property values from config or other resources
container.ComposeExportedValue(instance1);
container.ComposeParts(this);

2
尽管有些晚,这里提供了另一种利用MEF的较少知名功能——属性导出的方法。
public class ObjectMother
{
    [Export]
    public static EditProfile DefaultEditProfile
    {
        get
        {
            var method = ConfigurationManager.AppSettings["method"];
            var version = ConfigurationManager.AppSettings["version"];

            return new EditProfile(method,version);
        }
    }
}

没有必要为ObjectMother添加用法,也不需要在EditProfile上添加任何属性,即可使其正常工作。

不错的功能,但如果EditProfile包含任何导入项怎么办?在这种情况下,您需要在容器上调用SatisfyImportsOnce。那很麻烦。目前我对这个问题没有真正的解决方案。 - Sebastian Schumann
在这种情况下,我们正在构建一个工厂,手动实例化您的EditProfile。如果EditProfile在构造函数中需要其他依赖项,您可以始终通过ImportingConstructor将这些依赖项带入工厂。我应该指出,如果您向EditProfile添加其他依赖项,则这是应用程序对此构造函数的唯一引用,并且您将获得编译时错误。在我看来,这是一个公平的交易。 - bryanbcook

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接