CompositionInitializer and ExportFactory for the desktop

Mar 28, 2010 at 6:43 AM

For those of you building apps that work on SL and WPF and which use MEF, I just ported CompositionInitializer and ExportFactory to the desktop.

You can access the bits here: http://cid-f8b2fd72406fb218.skydrive.live.com/self.aspx/blog/Composition.Initialization.Desktop.zip

Read the comments at that link as there are a few differences between the Desktop and SL. Also to use ExportFactory requires a slight bit of setup code (about 3 extra lines).

Publically the APIs are identical. This allows you to reuse parts across SL and WPF which depend on CompositionInitializer or ExportFactory.

Thanks Glenn

Apr 10, 2010 at 10:41 PM
Edited Apr 10, 2010 at 11:45 PM

Much appreciated!

Is there a particular reason why this doesn't work with ImportingConstructor? I get an exception along the line that this import has to be on a member.

Currently I do something like this to work around that. I'd like to just do it in the constructor once.

 

[ImportMany]
public ICollection<ExportFactory<IRegionBehavior, IRegionBehaviorMetadata>> Behaviors { get; private set; }

public RegionBehaviorFactory()
{
    var observableBehaviors = new ObservableCollection<ExportFactory<IRegionBehavior, IRegionBehaviorMetadata>>();
    observableBehaviors.CollectionChanged += new NotifyCollectionChangedEventHandler(Behaviors_CollectionChanged);
    Behaviors = observableBehaviors;
}

void Behaviors_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Add)
    {
        foreach (ExportFactory<IRegionBehavior, IRegionBehaviorMetadata> behavior in e.NewItems)
        {
            AddIfMissing(behavior.Metadata.BehaviorKey, () => behavior.CreateExport().Value);
        }
    }
}

 

 

Developer
Apr 13, 2010 at 6:48 PM

ImportingConstructor will only work if MEF is actually constructing the object, in the case of CompositionInitializer someone else is constructing the object and then dipping it into the container via SatisfyImports.

Jun 22, 2010 at 8:59 PM

I might have misunderstood, but I was doing this

    [Export(typeof(IGenericExport))]
    class GenericExport : IGenericExport
    {
        static int X;

        int _num;

        public GenericExport()
        {
            this._num = GenericExport.X++;
        }

        public override string ToString()
        {
            return _num.ToString();
        }
    }

    public interface IGenericExport
    {

    }

    [Export]
    public class GenericClass
    {
        public IEnumerable<ExportFactory<IGenericExport>> Stuff { get; set; }

        [ImportingConstructor]
        public GenericClass([ImportMany] IEnumerable<ExportFactory<IGenericExport>> stuff)
        {
            this.Stuff = stuff;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var exportFactoryProvider = new Microsoft.ComponentModel.Composition.Hosting.ExportFactoryProvider();
            var container = new CompositionContainer(new AssemblyCatalog(typeof(Program).Assembly), exportFactoryProvider);
            exportFactoryProvider.SourceProvider = container;
            var obj = container.GetExportedValue<GenericClass>();
        }
    }

and getting this error

ImportDefinition of type 'System.ComponentModel.Composition.ReflectionModel.ReflectionParameterImportDefinition' cannot be used in this context. Only import definitions produced by the ReflectionModelServices.CreateImportDefinition based on members are supported. Use ReflectionModelServices.IsImportingParameter to determine whether a given import definition is based on a member or a parameter.
Parameter name: importDefinition

Which lead me to try the above workaround instead. Isn't this leagal MEF stuff, why would this be an error?

Jun 22, 2010 at 9:16 PM

It sounds like you've hit into a limitation of the ported ExportFactoryProvider, which is that it will not work on constructors. I will look into it.

Thanks

Glenn

Jun 22, 2010 at 9:20 PM
Edited Jun 22, 2010 at 9:20 PM

Much obliged

John

Jun 22, 2010 at 11:37 PM

Note that this problem also already existed with the PartCreator sample. I got the same exception message when trying to import a PartCreator via the constructor. I worked around it by importing PartCreators via a property instead.

Jun 23, 2010 at 12:38 AM

Not surprising as it is almost identical code :-)

Jul 9, 2010 at 8:10 AM

hi, i've a problem using the ExportFactory<>. i'm new to mef  so i hope for some help:) i've the following code:

[PartCreationPolicy(CreationPolicy.NonShared)]
[Export(typeof(INonShared))]
[ExportMetadata("Muster","030")]
public sealed class NonShared1 : INPCBase, INonShared
{...}


[Export(typeof(IMusterFacService))]
public class MusterFacService : IMusterFacService
{
        [ImportMany(typeof(INonShared))]
        public IEnumerable<ExportFactory<INonShared, Dictionary<string, object>>> Fac { get; private set;}

...}


public partial class App : Application
{
...

var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
catalog.Catalogs.Add(new DirectoryCatalog(AppDomain.CurrentDomain.BaseDirectory));


var exportFactoryProvider = new Microsoft.ComponentModel.Composition.Hosting.ExportFactoryProvider();
_container = new CompositionContainer(catalog, exportFactoryProvider));
exportFactoryProvider.SourceProvider = _container;
 _container.ComposeParts(this);

...}

and i always got the following error:

Der Export "TestMef.NonShared1 (ContractName="TestMef.INonShared")" kann nicht dem Typ "System.ComponentModel.Composition.ExportFactory`2[[TestMef.INonShared, TestMef, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]" zugewiesen werden.

i use the Microsoft.ComponentModel.Composition.Initialization.Desktop.dll from glenns link above (bin\4.0)

 

Developer
Jul 9, 2010 at 4:22 PM

MEF tries to get the proper contract name from the type on you are Importing in your case the type should be ExportFactory<INonShared, Dic> but you are overriding it by doing ImportMany(typeof(INonShared)), try removing the typeof(INonShared) parameter and simply have [ImportMany].

Jul 12, 2010 at 7:14 AM
Edited Jul 12, 2010 at 11:12 AM

thx for he tip weshaggard. but unfortunately i get no exports now, the collection is empty. if i switch back to lazy instead of exportfactory the the collection counts 2. i think i miss anything :) is there a sample for wpf with exportfactory<> out there?

edit: as far as i remove the metadata from my exports and imports it works. so i have to investigate into this now.

edit2: it seems my generic metadata was the problem, i use now custom metadata and all works like expected.

 public interface IMetadatInterfaceNonShared
    {
        string MusterKey { get;}
    }

    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class MyExportAttributes : ExportAttribute, IMetadatInterfaceNonShared
    {
        public MyExportAttributes() :base(typeof(INonShared))
        {
            
        }

        public string MusterKey { get; set; }
    }
Aug 25, 2010 at 4:21 PM
Edited Aug 25, 2010 at 4:23 PM

Hi,

Will this CompositionInitializer class for the desktop be in MEF 2 ?
I didn't see it in the MEF 2 Preview 1...

Sep 14, 2010 at 5:59 AM

Hi, like kakone i would like to know if this will go to MEF 2 too?

Sep 23, 2010 at 7:15 PM

Hi,

I vote for this to be included in MEF2 Preview 2, since it fixes many problems while Working with WPF.

Sep 24, 2010 at 9:21 AM

This thread is quite old, most of this has already been integrated into the MEF 2 Preview 1

CompositionInitializer is a Silverlight thing

Sep 24, 2010 at 4:57 PM

If CompositionInitializer is a Silverlight thing, then what is the Class that does the same for desktop WPF Apps?

Or can you direct me to the patterns that should be used for desktop WPF development.

Sep 26, 2010 at 3:07 PM

Unfortunately I can not. My understanding of the CompositionInitializer class is limited, although I do not believe it was originally built with the desktop in mind.

This blog post explains things in more detail, maybe you'll find it more helpful:

http://reedcopsey.com/2010/03/26/mef-compositioninitializer-for-wpf/

Sep 26, 2010 at 7:07 PM

CompositionInitializer can work on the desktop just as it worked in Silverlight. The quesiton is however is it the right thing. We designed CI for three reasons, one to reduce the concept count and get people off the ground really quickly with MEF, two to enable a simple path for composite parts that are newed yup the framework itself or in custom code, such as a user control, and three to enable third parties to depend on MEF being present without requring the host to do any configuration.

The thing we never liked about CI was that it requires the part to tell itself to compose, and that it couples the part to a static container. As long as the parts being imported are singletons throughout the application, CI can work, as soon as you start getting into more complex instance scenarios, or scoping of containers, it falls down very quickly.

We evaluted aternatives to CI however the cost of developing them was too great and required deeper platform changes that we could not accomplish in V1 / Silverlight.

CI was however far easier to implement and although there were several cons, we felt the benefits outweighed the cons, and so we shipped it for Silverlight.

We decided to not ship it for the desktop (at least not initially) and rather to explore alternative ways to address the scenarios we built CI that would not have the negatives I mentioned above.

Cheers

Glenn

Sep 26, 2010 at 9:31 PM

Thanks for the explanation Glenn, but the problem with WPF on desktop remains for the time being.

I really dislike having to use a static singleton throughout my code, but if I must use one I would rather use one provided by the framework.

Is there any ETA for the alternative ways you are talking about?

 

cheers

Leonard

Nov 2, 2010 at 11:27 AM
Edited Nov 2, 2010 at 11:28 AM

I use MEF in very specific cases, not for the construction of all my objects, only when there is no alternative. CompositionInitializer is very convenient in this case and, sorry, I don't find the alternative ways you are talking about for the desktop.

Cordially,
Stéphane. 

 

Nov 2, 2010 at 6:44 PM

while CompositionInitializer is convenient it creates a tight coupling between your type and MEF. it shouldn't be used unless there's no other way.

Nov 4, 2010 at 7:49 AM

Stephane

What specific scenarios are you trying to address? This post might help in some way: http://blogs.msdn.com/b/gblock/archive/2010/02/01/hosting-mef-within-application-and-libraries.aspx

Glenn

Jul 15, 2011 at 2:25 AM

Is MEF intended to perform 100% of class instantiations?  If not then is CompositionInitializer the best way to get ILogger into the classes that are not parts.  The only other alternative is to use ServiceLocator, but that would have to imported as well.

Basically my understanding is have everything be a part (Only use keyword "new" in factories) or bring in CompositionInitializer?

Thanks,

aidesigner

Aug 22, 2011 at 3:17 PM

Hi Glenn,

 

I've used the ported ExportFactory in an application, this application creates few hundreds of exports during its instantiation.

Sometimes I get a "Collection was modified; enumeration operation may not execute." exception while composing

this.Container.ComposeParts(this.Shell)

It seems like a threading issue to me, the stack trace is as below
   at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   at System.Collections.Generic.List`1.Enumerator.MoveNext()
   at Microsoft.Internal.Collections.WeakReferenceCollection`1.AliveItemsToList()
   at System.ComponentModel.Composition.Hosting.ImportEngine.RecompositionManager.UpdateImportIndex()
   at System.ComponentModel.Composition.Hosting.ImportEngine.RecompositionManager.GetAffectedParts(IEnumerable`1 changedContractNames)
   at System.ComponentModel.Composition.Hosting.ImportEngine.OnExportsChanging(Object sender, ExportsChangeEventArgs e)
   at System.ComponentModel.Composition.Hosting.CompositionServices.TryFire[TEventArgs](EventHandler`1 _delegate, Object sender, TEventArgs e)
   at System.ComponentModel.Composition.Hosting.ExportProvider.OnExportsChanging(ExportsChangeEventArgs e)
   at System.ComponentModel.Composition.Hosting.CompositionContainer.OnExportsChangingInternal(Object sender, ExportsChangeEventArgs e)
   at System.ComponentModel.Composition.Hosting.CompositionServices.TryFire[TEventArgs](EventHandler`1 _delegate, Object sender, TEventArgs e)
   at System.ComponentModel.Composition.Hosting.ExportProvider.OnExportsChanging(ExportsChangeEventArgs e)
   at System.ComponentModel.Composition.Hosting.AggregateExportProvider.OnExportChangingInternal(Object sender, ExportsChangeEventArgs e)
   at System.ComponentModel.Composition.Hosting.CompositionServices.TryFire[TEventArgs](EventHandler`1 _delegate, Object sender, TEventArgs e)
   at System.ComponentModel.Composition.Hosting.ExportProvider.OnExportsChanging(ExportsChangeEventArgs e)
   at System.ComponentModel.Composition.Hosting.ComposablePartExportProvider.Recompose(CompositionBatch batch, AtomicComposition atomicComposition)
   at System.ComponentModel.Composition.Hosting.ComposablePartExportProvider.Compose(CompositionBatch batch)
   at System.ComponentModel.Composition.Hosting.CompositionContainer.Compose(CompositionBatch batch)
   at System.ComponentModel.Composition.AttributedModelServices.ComposeParts(CompositionContainer container, Object[] attributedParts)
   at EFG.MarketBeat.RealtimeDataFeeder.BootStrapper.InitializeShell()

Ahmed
Aug 23, 2011 at 9:34 AM

well glenn it's a bit strange to me but, moving the heavy load to a new method (initialize) and calling it after starting the application fixed this issue

this.Container.ComposeParts(this.Shell);
Application.Run(this.Shell);
this.Shell.Initialize();

Shell.Initialize triggers some initialization logic in the view model, calling Shell.Initliaze() before Application.Run usually produces an exception in the below method inside ExportFactoryImport.cs

static Func<ExportLifetimeContext<T>> CreatePartLifetimeContextCreator<T>(ImportDefinition productImport, ExportProvider sourceProvider, ExportDefinition productDefinition)
        {
            Func<ExportLifetimeContext<T>> creator = () =>
            {
                var product = sourceProvider.GetExports(productImport).Single(e => e.Definition == productDefinition);
                return new ExportLifetimeContext<T>((T)(product.Value), () =>
                {
                    if (product is IDisposable)
                        ((IDisposable)product).Dispose();
                });
            };
            return creator;
        }

with the following stack trace

   at Microsoft.Internal.Collections.WeakReferenceCollection`1.CleanupDeadReferences()
   at Microsoft.Internal.Collections.WeakReferenceCollection`1.Add(T item)
   at System.ComponentModel.Composition.Hosting.ImportEngine.TrySatisfyImports(PartManager partManager, ComposablePart part, Boolean shouldTrackImports)
   at System.ComponentModel.Composition.Hosting.ImportEngine.SatisfyImports(ComposablePart part)
   at System.ComponentModel.Composition.Hosting.CompositionServices.GetExportedValueFromComposedPart(ImportEngine engine, ComposablePart part, ExportDefinition definition)
   at System.ComponentModel.Composition.Hosting.CatalogExportProvider.GetExportedValue(ComposablePart part, ExportDefinition export, Boolean isSharedPart)
   at System.ComponentModel.Composition.Hosting.CatalogExportProvider.CatalogExport.GetExportedValueCore()
   at System.ComponentModel.Composition.Primitives.Export.get_Value()
   at Microsoft.ComponentModel.Composition.Hosting.ExportFactoryImport.<>c__DisplayClassa`1.<CreatePartLifetimeContextCreator>b__7() in C:\SourceSafe2010\Marketbeat\Marketbeat_DEV\RDF\Common\Composition.Initialization.Desktop\ComponentModel.Composition.Initialization.Desktop\Microsoft\ComponentModel\Composition\Hosting\ExportFactoryImport.cs:line 173
   at Microsoft.ComponentModel.Composition.Hosting.ExportFactoryInternal`1.CreateExport() in C:\SourceSafe2010\Marketbeat\Marketbeat_DEV\RDF\Common\Composition.Initialization.Desktop\ComponentModel.Composition.Initialization.Desktop\Microsoft\ComponentModel\Composition\Hosting\ExportFactoryOfT.cs:line 22
   at EFG.MarketBeat.RealtimeDataFeeder.Transmitters.TransmitterFactory.<GetTransmitters>b__5(<>f__AnonymousType0`2 <>h__TransparentIdentifier0)

 

Your help is appreciated