Using MEF with C#, how do I call methods on the host, from the plugin?
Asked Answered
B

2

5

I am trying to add plugin extensibility to my C# application using the Managed Extensibility Framework (MEF) framework, and so far it is going ok; I have my main/host application loading plugins from a defined folder, and can call their methods etc. from the main application. Both the host application and the plugins reference a seperate dll assembly which contains the interfaces common to all projects.

This is working fine and I can call/interact with the plugins from the main application. However, I also would like to be able to interact with the host application from the plugins, but can't seem to find out how this is done.

I would like to be able to get/set/execute exported properties and methods in the main app from my plugins. Currently I am only able to 'speak' to the plugins from the main app, not the other way around as well.

My code thus far:

Interface DLL

namespace MefContracts
{
    [InheritedExport]
    public interface IPlugin
    {
        String DoWork();
    }

    public class Class1
    {
        public IPlugin plugin { get; set; }
    }
}

Main/Host Application

namespace MyMEF
{
    class clsMEF
    {
        private CompositionContainer _container;

        [Import(typeof(MefContracts.IPlugin))]
        public MefContracts.IPlugin plugin;

        public clsMEF()
        {
            Compose();
        }

        void Compose()
        {
            var catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new DirectoryCatalog("..\\..\\Extensions"));
            _container = new CompositionContainer(catalog);
            try
            {
                this._container.ComposeParts(this);
            }
            catch (CompositionException compositionException)
            {
                Console.WriteLine(compositionException.ToString());
            }
        }
    }

    void Main()
    {
        clsMEF myMef = new clsMEF();
        MessageBox.Show(myMef.plugin.DoWork());
    }
}

Plugin

namespace MefPlugin
{
    [Export]
    public class Class1 : MefContracts.IPlugin
    {

        public String DoWork()
        {
            return "Plugin called";
        }
    }
}
Blanket answered 24/10, 2013 at 17:31 Comment(0)
B
3

After much playing and trial and error, I found the issue I was having was I had not added the current executing assembly (System.Reflection.Assembly.GetExecutingAssembly()) to the host's assembly catalogue along with the plugin's assemblies.

Many thanks to @PanosRontogiannis who got me on the right lines - that answer worked brilliantly once the assembly was properly added.

Here is the working code for others in need:

Interface DLL

using System.ComponentModel.Composition;

namespace MefContracts
{
    [InheritedExport]
    public interface IPlugin
    {
        String Work(String input);
    }

    [InheritedExport]
    public interface IHost
    {
        string Version { get; }
    }
}

Host Application

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace MyMEF
{

    [Export]
    public class Class1 : MefContracts.IHost
    {
        public String Version
        {
            get { return "v1.00"; }
        }
    }

    class clsMEF
    {
        private CompositionContainer _container;

        [Import(typeof(MefContracts.IPlugin))]
        public MefContracts.IPlugin plugin;

        public clsMEF()
        {
            Compose();
        }

        void Compose()
        {
            var catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new DirectoryCatalog("..\\..\\Extensions"));
            catalog.Catalogs.Add(new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly())); // <-- THIS WAS THE MISSING PIECE
            _container = new CompositionContainer(catalog);
            try
            {
                this._container.ComposeParts(this);
            }
            catch (CompositionException compositionException)
            {
                Console.WriteLine(compositionException.ToString());
            }

        }
    }
    static class Program
    {
        static void Main()
        {
            clsMEF myMef = new clsMEF();
            MessageBox.Show(myMef.plugin.Work("My Input"));
        }
    }
}

Plugin

using System.ComponentModel.Composition;

namespace MefPlugin
{
    [Export]
    public class Class2 : MefContracts.IPlugin
    {
        [Import(typeof(MefContracts.IHost))]
        public MefContracts.IHost Host;

        public String Work(String input)
        {
            return "Plugin Called (Input: " + input + "). Host Application Version: " + input + Host.Version;
        }
    }
}
Blanket answered 28/10, 2013 at 13:5 Comment(2)
My host member is never populated. Did you ever encounter that?Varlet
@Varlet - Sorry for the late reply, but I think this was the initial problem I was facing at this time. If you haven't already solved this and would like to post your code on another question and link here I will be happy to take a look.Blanket
G
4

You could add a host interface in the contracts assembly. For example:

[InheritedExport]
public interface IHost
{
    string Version { get; }
}

Then add a property of type IHost to the IPlugin interface:

[InheritedExport]
public interface IPlugin
{
    IHost Host { get; }
    String DoWork();
}

Finally each plug-in will need to decorate the Host property with MEF's ImportAttribute:

[Import(typeof(IHost))]
public IHost Host { get; }
Grimes answered 27/10, 2013 at 10:35 Comment(1)
Thank you for your reply. I've just had a go with this but unfortunately without success (no errors, just not getting anything back). I think I must be missing something; what is the right implementation of IHost in the host application? how should it be exposed?Blanket
B
3

After much playing and trial and error, I found the issue I was having was I had not added the current executing assembly (System.Reflection.Assembly.GetExecutingAssembly()) to the host's assembly catalogue along with the plugin's assemblies.

Many thanks to @PanosRontogiannis who got me on the right lines - that answer worked brilliantly once the assembly was properly added.

Here is the working code for others in need:

Interface DLL

using System.ComponentModel.Composition;

namespace MefContracts
{
    [InheritedExport]
    public interface IPlugin
    {
        String Work(String input);
    }

    [InheritedExport]
    public interface IHost
    {
        string Version { get; }
    }
}

Host Application

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace MyMEF
{

    [Export]
    public class Class1 : MefContracts.IHost
    {
        public String Version
        {
            get { return "v1.00"; }
        }
    }

    class clsMEF
    {
        private CompositionContainer _container;

        [Import(typeof(MefContracts.IPlugin))]
        public MefContracts.IPlugin plugin;

        public clsMEF()
        {
            Compose();
        }

        void Compose()
        {
            var catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new DirectoryCatalog("..\\..\\Extensions"));
            catalog.Catalogs.Add(new AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly())); // <-- THIS WAS THE MISSING PIECE
            _container = new CompositionContainer(catalog);
            try
            {
                this._container.ComposeParts(this);
            }
            catch (CompositionException compositionException)
            {
                Console.WriteLine(compositionException.ToString());
            }

        }
    }
    static class Program
    {
        static void Main()
        {
            clsMEF myMef = new clsMEF();
            MessageBox.Show(myMef.plugin.Work("My Input"));
        }
    }
}

Plugin

using System.ComponentModel.Composition;

namespace MefPlugin
{
    [Export]
    public class Class2 : MefContracts.IPlugin
    {
        [Import(typeof(MefContracts.IHost))]
        public MefContracts.IHost Host;

        public String Work(String input)
        {
            return "Plugin Called (Input: " + input + "). Host Application Version: " + input + Host.Version;
        }
    }
}
Blanket answered 28/10, 2013 at 13:5 Comment(2)
My host member is never populated. Did you ever encounter that?Varlet
@Varlet - Sorry for the late reply, but I think this was the initial problem I was facing at this time. If you haven't already solved this and would like to post your code on another question and link here I will be happy to take a look.Blanket

© 2022 - 2024 — McMap. All rights reserved.