How to manage discovery and composition as 2 separ

2019-03-04 15:22发布

问题:

I have set up an assembly catalog:

  private CompositionContainer GetContainer() {
     // initialize directory info
     ExtensionDirectory = new DirectoryInfo(settings.ExtensionsPath);
     // directory catalog
     var dirCatalog = new DirectoryCatalog(ExtensionDirectory.FullName);

     return new CompositionContainer(dirCatalog);
  }

The contents of the container will load up all the assemblies in the directory as expected. I do not want to actually compose anything yet because I have constructors that will be injected with dependencies.

What I want to do is use the AssemblyCatalog as a repository; query for a specific export, pass the constructor dependency, then compose only the parts involved in this process.

From what I understand, if I were to call

_container.ComposeParts(this);

...without providing exports for the [ImportingConstructor]s, then none of the parts would be included in the _container.

In order to facilitate queries to the container, I have a method as follows:

  public Lazy<IEntity> GetPart(Func<Lazy<IEntity, IEntityMetaData>, bool> selector) {
     var entity = _container.GetExports<IEntity, IEntityMetaData>()
                            .Where(selector)
                            .Select(e => e as Lazy<IEntity>)
                            .FirstOrDefault();

     return entity; // this will be passed up to the composition service
  }

It seems that GetExports<T, M>() will not return an export containing an [ImportingConstructor] if the part which would satisfy the dependency is not included in the container.

My approach is to have an extension container/catalog at a low level; a higher level composition service will receive all parts and compose the final object. I decided on this approach so we would be able to add/extend the types of catalogs available in the future.

回答1:

I think these concerns are already separated: discovery is handled by catalogs, and composition is done by export providers.

In the typical case, you just pass a catalog directly to the container and for convenience it will automatically take care of creating an CatalogExportProvider for it.

But you can also create one or more export providers yourself and pass them to the container with this constructor overload. (You may also have to set the SourceProvider to point back at the container after that, so that the export providers can use each other.)

You can create your own ExportProvider implementations, and they don't even have to be backed by catalogs.



回答2:

In order to satisfy the requirements, I created 3 classes:

public sealed class CompositionFactory {
  [Import("Provider")]
  private IProvider importProvider;

  /* MEF initialization */
}

[Export("Provider")]
public sealed class AssemblyProvider : IProvider {
  private CatalogExportProvider _provider;
}

internal sealed class ComposableAggregate { }

The CompositionFactory initializes MEF to discover the AssemblyProvider. When the provider initializes:

private CatalogExportProvider InitializeProvider() {
   // directory catalog
   var dirCatalog = new DirectoryCatalog(ExtensionDirectory.FullName);
   return new CatalogExportProvider(dirCatalog);
}

...we return a CatalogExportProvider. I can now use an API to the CompositionFactory:

public ISomething GetSomething(string ContractName, object ContractParam) {
   // implementation
}

...to query for the correct composable part using a contract name:

public ComposablePartDefinition GetPartDefinition(string ContractName) {
   return _provider.Catalog.Parts
                   .Where(p => p.ExportDefinitions
                                .Select(e => e.ContractName)
                                .Any(c => c == ContractName))
                   .FirstOrDefault();
}

The work is then completed in the ComposableAggregate helper class:

internal ISomething Value {
   get {
      return _container.GetExport<IEntity>(_contractName).Value;
   }
}

private CompositionBatch CreateBatch() {
   CompositionBatch batch = new CompositionBatch();
   // create composable part from definition
   ComposablePart importDef = CreatePart(_contractName);
   batch.AddPart(importDef);
   return batch;
}

private ComposablePart CreatePart(string ContractName) {
   // get part definition from catalog
   return _provider.GetPartDefinition(ContractName).CreatePart();
}