tags:

views:

117

answers:

1

I am trying to get MEF to recompose all the parts that it knows about when I update an instance that is exported. Essentially I want to have MEF update all my parts that import a connection string configuration value when it is changed. Everything looks good up to the point where I want to change the instance. If I try to ComposeParts with the value updated it seems to add a second part instance into the container, and then my imports are updated, but to null.

Can anyone point out where I am going wrong? Or should I even be trying to use MEF in this fashion?

I am using MEF preview 9 and targetting .net framework 3.5, and WPF.

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Linq;
using System.Text;
using Shouldly;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            MainClass main = new MainClass();
            main.RunTest();
        }
    }

    public class MainClass
    {
        [ImportMany]
        public IEnumerable<Settings> Settings { get; set; }

        public void RunTest()
        {
            AggregateCatalog catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new AssemblyCatalog(typeof(Settings).Assembly));

            CompositionContainer container = new CompositionContainer(catalog);

            container.SatisfyImportsOnce(this);

            Config cfg = new Config
            {
                Settings = new Settings { ConnectionString = "Value1" },
            };

            // result is returned with a null settings value
            UsesSettings result = container.GetExportedValue<UsesSettings>();

            // this recomposes everything with the new value, result changes to have settings of Value1
            container.ComposeParts(cfg);

            // this line results in my import many enumerable returning 2 parts the Value1 setting and null
            container.SatisfyImportsOnce(this);

            result.TheSettings.ConnectionString.ShouldBe("Value1");

            cfg.Settings = new Settings { ConnectionString = "Value2" };

            // how do I tell the container to recompose now I have changed the config object,
            // or how do I replace the part value with the new value?

            // this line causes the result.Settings to return null
            container.ComposeParts(cfg);

            // this updates the ImportMany to 3 values, Value1, Value2 and null
            container.SatisfyImportsOnce(this);
        }
    }

    public class Settings
    {
        public string ConnectionString = "default value";
    }

    public class Config
    {
        [Export(typeof(Settings))]
        public Settings Settings { get; set; }
    }

    [Export(typeof(UsesSettings))]
    public class UsesSettings
    {
        [Import(typeof(Settings), AllowRecomposition = true)]
        public Settings TheSettings { get; set; }
    }
}
+1  A: 

You have a few things a little off for the scenario you are trying to accomplish.

First: If you want to add/remove/change a particular export it must not be in the catalog itself so you should remove your Config class which has the Export settings property. That is being created by the CatalogExportProvider and is the reason you are seeing a null value in your collection.

Try the following:

public class Program
{
    [ImportMany(AllowRecomposition=true)]
    public IEnumerable<Settings> Settings { get; set; }

    public void RunTest()
    {
        AggregateCatalog catalog = new AggregateCatalog();
        catalog.Catalogs.Add(new AssemblyCatalog(typeof(Settings).Assembly));

        CompositionContainer container = new CompositionContainer(catalog);

        // Settings should have 0 values.
        container.ComposeParts(this);
        Contract.Assert(this.Settings.Count() == 0);

        CompositionBatch batch = new CompositionBatch();

        // Store the settingsPart for later removal...
        ComposablePart settingsPart = 
            batch.AddExportedValue(new Settings { ConnectionString = "Value1" });

        container.Compose(batch);

        // Settings should have "Value1"
        UsesSettings result = container.GetExportedValue<UsesSettings>();
        Contract.Assert(result.TheSettings.ConnectionString == "Value1");

        // Settings should have 1 value which is "Value1";
        Contract.Assert(this.Settings.Count() == 1);
        Contract.Assert(this.Settings.First().ConnectionString == "Value1");

        // Remove the old settings and replace it with a new one.
        batch = new CompositionBatch();
        batch.RemovePart(settingsPart);
        batch.AddExportedValue(new Settings { ConnectionString = "Value2" });
        container.Compose(batch);

        // result.Settings should have "Value2" now.
        Contract.Assert(result.TheSettings.ConnectionString == "Value2");

        // Settings should have 1 value which is "Value2"
        Contract.Assert(this.Settings.Count() == 1);
        Contract.Assert(this.Settings.First().ConnectionString == "Value2");
    }
}
public class Settings
{
    public string ConnectionString = "default value";
}

[Export(typeof(UsesSettings))]
public class UsesSettings
{
    [Import(typeof(Settings), AllowRecomposition = true)]
    public Settings TheSettings { get; set; }
}
Wes Haggard
Thanks Wes! I changed my code to hold onto the composable part which allowed me to update the part in the container, rather than just adding more and more instances, works great now.
Matt