views:

866

answers:

1

I'm experimenting with MEF and created a test program to call "plugins" that implement some given interface, which follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ProbeContract
{
    public interface IProbe
    {
        int DoProbe(string what);
        List<string> GetCapabilities();
    }
}

I created a sample console program which loads the "plugins" from its own assembly and, if any found, from a diretory in which one puts additional DLLs. The program works OK whether the plugins directory is empty (only the "native" plugins are called) or it has compatible DLLs to start with. BUT... if a new DLL is added between loop iterations, the Refresh() method of DirectoryCatalog throws a ChangeRejectedException, which is explained thusly:

The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced a single composition error. The root cause is provided below. Review the CompositionException.Errors property for more detailed information.

1) Change in exports prevented by non-recomposable import 'MEFTest.Program.ProberSet (ContractName="ProbeContract.IProbe")' on part 'MEFTest.Program'.

The program is below, follow by the code for the DLL I try to add. What am I doing wrong?

using System;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ProbeContract;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace MEFTest
{
    class Program
    {
        [ImportMany]
        IEnumerable<IProbe> ProberSet { get; set; }

        CompositionContainer exportContainer;
        DirectoryCatalog pluginCatalog;
        AggregateCatalog catalog;

        private void Run()
        {
            catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
            string myExecName = Assembly.GetExecutingAssembly().Location;
            string myPath = Path.GetDirectoryName(myExecName);
            pluginCatalog = new DirectoryCatalog(myPath + "/Plugins");
            catalog.Catalogs.Add(pluginCatalog);
            exportContainer = new CompositionContainer(catalog);

            CompositionBatch compBatch = new CompositionBatch();
            compBatch.AddPart(this);
            compBatch.AddPart(catalog);
            exportContainer.Compose(compBatch);

            for (; ; )
            {
                Console.Write("Press any key to run all probes: ");
                Console.ReadKey(true);
                Console.WriteLine();
                pluginCatalog.Refresh();
                foreach (var Prober in ProberSet)
                {
                    Prober.DoProbe("gizmo");
                }
            }
        }

        static void Main(string[] args)
        {
            Program p = new Program();
            p.Run();
        }
    }
}

The plugin. The other two plugins are similar, the only difference being they reside in the same assembly as the main program:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using ProbeContract;

namespace OtherProbes
{
    [Export(typeof(IProbe))]
    public class SpankyNewProber : IProbe
    {
        public int DoProbe(string what)
        {
            Console.WriteLine("I'm Spanky and New and I'm probing [{0}]", what);
            return 0;
        }

        public List<string> GetCapabilities()
        {
            List<string> retVal = new List<string>();
            retVal.Add("spanky");
            retVal.Add("new");
            return retVal;
        }
    }
}
+9  A: 

I'm assuming you are using MEF preview 6 because you are seeing rejection exceptions. The reason you are seeing the change being rejected is because your ProberSet is not recomposable. Try changing your ProberSet import to:

[ImportMany(AllowRecomposition=true)]        
IEnumerable<IProbe> ProberSet { get; set; }

Doing so will allow for new IProbe exports to be introduced into the Catalog/Container after this import has already been composed.

The idea here is that once you get a stable composition we reject any changes that could potentially destablize that composition and in your case you stated you want a set of non-recomposable IProbe objects so adding new IProbe's after it was intially set would violate that requirement.

Wes Haggard
Yep, now it works. Thanks!
JCCyC