Strange behavior

Mar 16, 2010 at 8:06 AM

Hi everybody!
I've a problem with a project of mine:

I've a MainWindow where I create a container and call ComposeParts because it has a MainViewModel Import

 

public MainWindow()
        {
            CompositionContainer container;

            InitializeComponent();

            container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            container.ComposeParts(this);
        }

The MainViewModel requires a IPluginManager so I use ImportingConstructor

[ImportingConstructor]
        public MainViewModel(IPluginManager pluginManager)
        {
            this.pluginManager = pluginManager;
            this.pluginManager.PluginsChanged += new EventHandler(pluginManager_PluginsChanged);

            //this.pluginManager.Initialize();
            this.LoadPlugins();
        }

This's the PluginManager, and we call the code inside the constructor "InitializingCode"
public PluginManager()
        {
            directoryCatalog = new DirectoryWatcherCatalog(Settings.Default.PluginsDirectory);
            aggregateCatalog = new AggregateCatalog(directoryCatalog);
            compositionContainer = new CompositionContainer(aggregateCatalog);
            //compositionContainer.ExportsChanged += new EventHandler<ExportsChangeEventArgs>(compositionContainer_ExportsChanged);

            compositionContainer.ComposeParts(this);
        }
It has a Plugins property

[ImportMany(AllowRecomposition = true)]
        public IEnumerable<Lazy<IPlugin, IPluginMetadata>> Plugins
        {
            get
            {
                return plugins;
            }

            set
            {
                if (plugins == value)
                    return;

                plugins = value;
                OnPluginsChanged();
            }
        }

Ok, so when I run the program the Plugins property is assigned twice: the first time with 3 plugins (there is an assembly with 3 plugins in the PLuginsDirectory),
the second time with an empty list.

But if I create an Initialize method where I put the "InitializingCode" and I call Initialize method inside the MainViewModel's constructor everything works fine
so the Plugins property is assigned once :(

Any idea?

Thanks
Federico

 

[ImportMany(AllowRecomposition = true)]
        public IEnumerable<Lazy<IPlugin, IPluginMetadata>> Plugins
        {
            get
            {
                return plugins;
            }

            set
            {
                if (plugins == value)
                    return;

                plugins = value;
                OnPluginsChanged();
      
[ImportMany(AllowRecomposition = true)]
        public IEnumerable<Lazy<IPlugin, IPluginMetadata>> Plugins
        {
            get
            {
                return plugins;
            }

            set
            {
                if (plugins == value)
                    return;

                plugins = value;
                OnPluginsChanged();
            }
        }
      }
        }
Developer
Mar 16, 2010 at 3:11 PM

The reason this is happening is because the PluginManager is being created with MEF via the importing constructor which causes the plugins property to be set after the constructor is called and it is empty because there are no plugins in the container it was constructed from, which is the one in your Mainwindow. The other reason it is initialized is by the ComposeParts call in its constructor which does have plugins in it and it is called first because it happens in the constructor as opposed to after the construction.

So the question is why do you have two catalogs and two containers? It would be best if your host (looks like MainWindow in this case) constructs the container and sets up all the catalogs. 

Mar 16, 2010 at 4:21 PM

I've used two containers because I wanted to have a "logical separation" between the MainWindow and the PluginManager, so I could move the PluginManager to another project and I don't need to change anything!

Do you thing it is wrong?

Thanks
Federico

Developer
Mar 16, 2010 at 9:10 PM

While in principal I agree with your logic, the problem comes from the fact that PluginManager is participating in two containers and so they will always fight over who sets the imports for it. 

In general catalogs and containers are managed by the host of an application as opposed to individual parts such as the PluginManager. Probably the biggest reason for this is that the host is the only one that actually knows the proper way to setup the catalog because it has more information about where to look (Plugins directory for desktop apps, bin/plugins for asp.net apps, xaps for silverlight ap). For example right now your PluginManager is only usable by a desktop application because it makes assumptions that it has access to the file system, which limits its reuse.

So my suggestion would be to eliminate the catalog/container in the PluginManager and move it into the MainWindow or perhaps even further up into the main function of the application.

Mar 17, 2010 at 4:49 AM
Edited Mar 17, 2010 at 5:48 AM

I'll follow your advice!

Thanks!
Federico