简体   繁体   中英

How can I create composition scoping by using metadata in MEF?

My goal is to share instances of objects for some part of application. There are lots of different kind of modules inside one container so there have to be dynamic way to define which instances should be shared in which scope.

The idea is working quite fine when I'm using Filtered catalogs, CompositionScopeDefinition with PartMetadata attribute. The PartMetadatas are used by 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);

Question

I have found that I can use PartMetadataAttribute only in concrete class which has exported. Is there any way to add PartMetadata from base / abstract class?

If there is no way to do that - Is there other way to do this kind of dynamic scoping?

[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
{

}

Exports and export metadata can be inherited. But I think part metadata cannot be inherited.

What you describe doesn't seem to be what I'd call dynamic scoping, since in fact the scope is fixed. It's just that you want it to automatically propagate to different parts based on what they export. Is that right?

What I've done for a very large MEF catalog in the past is not base it on what parts export, but rather what they import. For example, you have an "App" export. Anyone who imports it (transitively) automatically belong to the App scope (since they'd have to be in order to import it). Anything that doesn't get selected by that transitive import walk automatically bubbles up to the global scope. I actually did this with 4 scopes: lowest, middle, upper scopes, plus a global scope that everything else bubbles up to if they don't import the special scope-defining export. In each scope, there is a special MEF part that defines the anchor to that scope. This part is the T in ExportFactory<T> from the parent scope that creates instances of the lower scope. I use extension methods MEF already defines on FilteredCatalog to do the transitive import walk and it was actually very straightforward code.

The method below splits out a single catalog of all parts into scoped catalogs. It supports n scope depths. You can use the result to build up a 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;
}

I hope that helps. I think it will because it will get you where it sounds like you're going, which is implicitly scoped parts by virtue of what they import/export.

That said, you might consider using Microsoft.Composition from NuGet which has a more complete scoping feature. But it comes with lots of limitations too, compared to the MEF found in .NET. So it may or may not be a good fit.

Most of modules only imports things from the core libraries, but there are also assemblies which are shared between different modules. I have tried to read all the articles and blog posts related how MEF is used inside visual studio and also little bit tested Roslyn System.Reflection.Metadata - https://github.com/KirillOsenkov/MEFMetadata/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM