Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SatisfyImportsOnce vs ComposeParts

Tags:

c#

mef

Can someone please explain the difference between SatisfyImportsOnce and ComposeParts and why one would work where the other doesn't?

Specifically I have a MVC Web application that I am using MEF in. Below is some code (from that application) that works when I use SatisfyImportsOnce but doesn't when I use ComposeParts. My understanding is that ComposeParts creates composable parts from an array of attributed objects and composes them in the specified composition container and that SatisfyImportsOnce composes the specified part by using the specified composition service. To my simple monkey brain even though the English is different they are semantically the same. Both use the CompositionContainer to spit exported types at import targets.

public class FormPartCustomatorFactory
{

    [ImportMany(typeof(ICustomRenderer), AllowRecomposition = true)]
    private readonly List<Lazy<ICustomRenderer, ICustomRendererMetaData>> _rendererImports = new List<Lazy<ICustomRenderer, ICustomRendererMetaData>>();

    private readonly Dictionary<string, Lazy<ICustomRenderer, ICustomRendererMetaData>> _renderers;

    public static readonly FormPartCustomatorFactory Instance = new FormPartCustomatorFactory();

    static CompositionContainer _container;

    private FormPartCustomatorFactory()
    {
        using (var catalog = new DirectoryCatalog(HttpRuntime.BinDirectory, "*.dll"))
        {               
            _container = new CompositionContainer(catalog);
            _container.SatisfyImportsOnce(this); // <- Works
            // _container.ComposeParts(this); // DOESN'T Work
            _renderers = _rendererImports.ToDictionary(q => q.Metadata.Name, q => q);
        }
    }

    ~FormPartCustomatorFactory()
    {
        _container.Dispose();
    }

    public static ICustomRenderer Find(string name)
    {
        return Instance._renderers[name].Value;
    }
}
like image 422
Peter Avatar asked Jun 21 '11 01:06

Peter


2 Answers

SatisyImportsOnce will compose a type without registering it for recomposition. So, if you intend to use a type without support for recomposition, you can use SatisfyImportsOnce and it will do the work as usual, but any changes in the container (new parts added, or parts removed), then your instance won't automatically be recomposed to offer up these new parts.

In your instance, you are using:

[ImportMany(typeof(ICustomRenderer), AllowRecomposition = true)]

...but through SatisfyImportsOnce, this import won't be recomposed.

If you are not worried about recomposition, you could change your code use constructor injection, so you could do:

[ImportingConstructor]
public FormPartCustomatorFactory(IEnumerable<Lazy<ICustomRenderer, ICustomRendererMetadata>> renderers)
{
    if (renderers == null)
        throw new ArgumentNullException("renderers");

    _renderers = renderers.ToDictionary(r => r.Metadata.Name, r => r);
}

The reason I would suggest constructor injection, is that the set of Lazy<ICustomRenderer, ICustomRendererMetadata> instances are an explicit dependency your type requires, so it would be better to instantiate your type in a usable state, rather than instantiate and then require an additional step to get it ready for first time use.

This makes your FormPartCustomatorFactory type much more testable. To this end, if you were to change the constructor as such, then your method of making it a singleton wouldn't work. Instead, you could take advantage of the lifetime management functionality of MEF, so possibly redesign your type as:

public interface IFormPartCustomatorFactory
{
    ICustomRenderer Find(string name);
}

[Export(typeof(IFormPartCustomerFactory)), PartCreationPolicy(CreationPolicy.Shared)]
public class FormPartCustomatorFactory : IFormPartCustomatorFactory
{
    private IEnumerable<Lazy<ICustomRenderer, ICustomRendereMetadata>> _renderers;

    [ImportingConstructor]
    public FormPartCustomatorFactory(IEnumerable<Lazy<ICustomRenderer, ICustomRendererMetadata>> renderers)
    {
        if (renderers == null)
            throw new ArgumentNullException("renderers");

        _renderers = renderers;
    }

    public ICustomRenderer Find(string name)
    {
        return _renderers
            .Where(r => r.Metadata.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)
            .Select(r => r.Value)
            .FirstOrDefault();
    }
}

Doing it this way means that your type is not dependent on MEF, it can be used without it, its more testable, and the CompositionContainer will manage the lifetime of the part, in this case the CreationPolicy.Shared (which is the default for exported types), uses a singleton lifetime strategy. You can then import an instance of IFormPartCustomator, you import the same singleton instance.

I would also argue that calling it a Factory is possibly wrong, as a factory is designed to create new instances, whereas, your type will only create one instance of each ICustomRenderer. If this is the intended behaviour, maybe it would be better called an FormPartCustomatorService, that implements an IFormPartCusomatorService interface? If you want to spin up new instances each time, you could look at ExportFactory<ICustomRenderer, ICustomRendererMetadata>.

like image 187
Matthew Abbott Avatar answered Nov 10 '22 07:11

Matthew Abbott


As Matthew mentions, SatisfyImportsOnce doesn't register the part for recomposition. This means the MEF container doesn't hold a reference to the part.

In addition, SatisfyImportsOnce only satisfies the imports of a part, but ignores any exports it has. ComposeParts would add the exports to the container too.

like image 12
Daniel Plaisted Avatar answered Nov 10 '22 07:11

Daniel Plaisted