如何使用 MEF 中的元数据创建合成范围



我的目标是为应用程序的某些部分共享对象的实例。一个容器中有很多不同类型的模块,因此必须有动态的方式来定义哪些实例应该在哪个范围内共享。

当我使用过滤目录时,这个想法工作得很好,CompositionScopeDefinition与PartMetadata属性。PartMetadatas 由 FilteredCatalogs 使用。

var fullCatalog = new AggregateCatalog();
// lots of different kind of modules added inside aggregate catalog here
var globalLevelCatalog = fullCatalog.Filter(cpd => 
    !cpd.ContainsPartMetadataWithKey("Scope.App") || 
    cpd.ContainsPartMetadataWithKey("Scope.Global")
);
var appLevelCatalog = fullCatalog.Filter(cpd => 
    cpd.ContainsPartMetadataWithKey("Scope.App")
);
var appLevelDefinition = new CompositionScopeDefinition(appLevelCatalog, null);
var globalDefinition = new CompositionScopeDefinition(
    globalLevelCatalog, new[] { appLevelDefinition }
);
var container = new CompositionContainer(globalDefinition);

问题

发现我只能在已导出的具体类中使用 PartMetadataAttribute。有没有办法从基/抽象类添加部分元数据?

如果没有办法做到这一点 - 有没有其他方法可以做这种动态范围?

[PartMetadata("Scope.App", true)]  // will not be part of Metadata
public abstract class AppBase : IApp
{
}
// The PartMetadata has to be defined here so it is part of Metadata
[Export(typeof(IApp))]
public class TheApp
{
}

导出和导出元数据可以继承。但我认为部分元数据不能继承。

你所描述的似乎不是我所说的动态范围,因为实际上范围是固定的。只是您希望它根据导出的内容自动传播到不同的部分。是吗?

我过去为一个非常大的 MEF 目录所做的不是基于导出哪些部件,而是基于它们导入的内容。例如,您有一个"应用"导出。导入它(可传递)的任何人都自动属于应用范围(因为他们必须这样做才能导入它)。任何未被该传递导入遍历选择的内容都会自动冒泡到全局范围。我实际上使用 4 个作用域来执行此操作:最低、中等、上作用域,再加上一个全局作用域,如果它们不导入特殊的作用域定义导出,其他所有内容都会冒泡到全局作用域。在每个作用域中,都有一个特殊的 MEF 部件,用于定义该作用域的定位点。此部分是创建较低作用域实例的父作用域中ExportFactory<T> T。我使用 MEF 已经在 FilteredCatalog 上定义的扩展方法来执行传递导入遍历,它实际上是非常简单的代码。

下面的方法将所有部件的单个目录拆分为作用域目录。它支持 n 个范围深度。您可以使用结果来构建CompositionScopeDefinition

private static IImmutableDictionary<string, ComposablePartCatalog> GetScopedCatalogs(ComposablePartCatalog fullCatalog)
{
    Requires.NotNull(fullCatalog, "fullCatalog");
    // define the scopes, in order of lowest scope to highest.
    // The implicit one is the "" scope, which is global.
    string[] scopes = { typeof(DeepestScopePart).FullName, typeof(MiddleScopePart).FullName, typeof(UpperScopePart).FullName };
    var scopesAndCatalogs = ImmutableDictionary.Create<string, ComposablePartCatalog>().WithComparers(StringComparer.Ordinal);
    var catalog = fullCatalog; // start with the full catalog
    foreach (string scope in scopes)
    {
        // Pull out this scoped contract and everything that depends on it.
        var scopedCatalog = catalog
            .Filter(part => part.Exports(scope))
            .IncludeDependents(def => def.Cardinality != ImportCardinality.ZeroOrMore);
        scopesAndCatalogs = scopesAndCatalogs.Add(scope, scopedCatalog);
        // What's left over goes to the next scope up.
        catalog = scopedCatalog.Complement;
    }
    // define the implicit one (global catches every part that doesn't need a lower scope).
    scopesAndCatalogs = scopesAndCatalogs.Add(String.Empty, catalog);
    return scopesAndCatalogs;
}

我希望这有所帮助。我认为会的,因为它会让你到达听起来像你要去的地方,这是由于它们导入/导出的内容而隐含的范围部分。

也就是说,你可以考虑使用 NuGet 的 Microsoft.Composition,它具有更完整的范围功能。但与 .NET 中的 MEF 相比,它也有很多限制。所以它可能是一个合适的,也可能不是一个合适的。

大多数模块只从核心库中导入内容,但也有一些程序集在不同模块之间共享。我试图阅读所有与如何在Visual Studio中使用MEF相关的文章和博客文章,并且还对Roslyn System.Reflection.Metadata 进行了一些测试-https://github.com/KirillOsenkov/MEFMetadata/