Pages

Saturday, August 27, 2011

Steps to write a plugin based application with MEF

I have already written a blog on Managed Extensibility Framework few days ago, and you must wonder why I am writing again. Well actually today I have been creating an application that could be easily plugged into a host application. In this blog lets show you in steps how you could easily create your own plugin based application and later change itself easily using MEF.




Steps to create a Plugin based application :

  1. Before we proceed lets look how the User Interface for the application that I am building looks like :
  2. The UI contains two Panels, one to show the UI loader control, ideally will list a Button, and another container which will load the actual UI for the application. I have used normal windows based application to make you understand better.  Initially it will show you a button, and when you click on the button, it will load the actual usercontrol.
  3. To start creating a plugin based application, we need to create a contract that lies between the Modules and the Host. The contract restricts the Plugins to be loaded on the Host.
  4. Lets add a Class Library to the project and name it as PluginContract. We add 3 interfaces.
    public interface IProcessRunner
    {
        void Process();
    }
    
    
  5. The IProcess interface includes some portion of code that you need to execute when the application is getting loaded. This interface acts as a process which the plugin needs to execute whenever the object is loaded. The idea is to separate the actual process code from the User Interface.
  6. Next we define IPlugin interface which maps the actual User Interface of the Module. The definition of the IPlugin interface looks like :
    public interface IPlugin
    {
        UserControl GreeterControl { get; set; }
    
        bool IsPluginCreated { get; set; }
        void CreatePlugin();
        UserControl GetPlugin();
    
    }
    
    
  7. The final Interface represents the Interface attacher when connects the plugin with the application and also giving a special interface to run the plugin and run custom code. The interface looks like :
    public interface IInterfaceAttacher
    {
        Control InterfaceControl { get; set; }
        IProcessRunner Runner { get; set; }
        Control GetInterfaceObject();
        IPlugin Plugin { get; set; }
        Control Container { get; set; }
    }
  8. Once you are done, you can add a new Class Library and add one UserControl in it. We call it as GreetControl. Add reference to the Contract library and add the implementation of the interfaces.
  9. IProcess is the most simple interface that I have added to the system. It implements only Process method. The idea is to invoke a sequence of steps in the Process to generate the UserControl. For simplicity we put a messageBox in it. 
  10. The IPlugin interface on the other hand actually used to create an object of my plugin and return back the object. The implementation looks like :
    public class Plugin : IPlugin
    {
        public UserControl GreeterControl { get; set; }
    
        public bool IsPluginCreated { get; set; }
        public void CreatePlugin()
        {
            if (this.GreeterControl == null)
                this.GreeterControl = new ucGreetMessage();
    
            //Initialize
    
            this.IsPluginCreated = true;
        }
    
        public UserControl GetPlugin()
        {
            if (!this.IsPluginCreated)
                this.CreatePlugin();
    
            return this.GreeterControl;
        }
    }

    Here we create the object of GreeterControl and return the object of it when the GetPlugin is called. This contract element should be used from the Host to load the interface.
  11. InterfaceAttacher is created to aggregate all the elements into a single object. It creates an object of IPlugin, an object of IProcess, so it is the main contract for the whole plugin. Lets see how I implement the interface :
    public class InterfaceAttacher : IInterfaceAttacher, IDisposable
    {
    
        private void Initialize()
        {
            this.InterfaceControl.Text = "Load Greeter";
        }
    
        public Control InterfaceControl { get; set; }
        public IProcessRunner Runner { get; set; }
        public IPlugin Plugin { get; set; }
    
        public Control Container { get; set; }
    
        public Control GetInterfaceObject()
        {
            if (this.InterfaceControl == null)
            {
                this.InterfaceControl = new Button();
                this.InterfaceControl.Click += new EventHandler(InterfaceControl_Click);
            }
            this.Initialize();
    
            return this.InterfaceControl;
        }
    
        void InterfaceControl_Click(object sender, EventArgs e)
        {
            if (this.Container == null)
                throw new ApplicationException("You cannot load a module without specifying the container");
    
            if (this.Runner == null)
                this.Runner = new Processor();
    
            this.Runner.Process();
    
            //Load Plugin
            if (this.Plugin == null)
                this.Plugin = new Plugin();
    
            this.Container.Controls.Add(this.Plugin.GetPlugin());
        }
    
        #region IDisposable Members
    
        public void Dispose()
        {
            if (this.InterfaceControl != null)
                this.InterfaceControl.Dispose();
        }
    
        #endregion
    }

    Here the InterfaceControl is an individual control which will be loaded on the system and used to invoke a load operation of the Plugin. Here for simplicity I have used a Button to load the plugin, but ideally it should be a menu item. Thus when the object is Clicked, it will invoke certain events to call the Process of IProcess and load the IPlugin to the Container which is provided by the Host.

Certainly, you might be thinking why I am showing this crappy unusable code to you. Actually I have written this code long ago, may be at least 2 years ago when I was actually creating plugin based application for the first time. Here is to show you how I have actually developed the system, and how easy is to modify it to use MEF.


Creating the Host

Finally lets create the Host for this Plugin.  The plugin host will host two container (in our case) one to load all the buttons that are coming for each individual plugin which loads the actual plugin to the system, and another is to host the actual plugin interface. Lets take two panels in the Form and name it pnlLoadControls  for the panel which loads the InterfaceLoader (Button) and pnlContainer which loads the actual UserControl coming from plugin.

Once the interface is created we need to write some code to actually load all the interface elements when form is loaded and upon user interaction, the actual UserInterface is loaded. Hence we write like this :

private void Form1_Load(object sender, EventArgs e)
{
    foreach (string fpath in Directory.GetFiles("plugins", "*.dll"))
    {
        this.LoadAssembly(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fpath));
    }
}

private void LoadAssembly(string filepath)
{
    Assembly asm = Assembly.LoadFile(filepath);
    var items = asm.GetTypes().Where(t => typeof(IInterfaceAttacher).IsAssignableFrom(t));

            

    foreach (var item in items)
    {
        IInterfaceAttacher attacher = Activator.CreateInstance(item) as IInterfaceAttacher;
        attacher.Container = this.pnlContainer;
        this.pnlLoadControls.Controls.Add(attacher.GetInterfaceObject());
    }
}

Hmm, looks quite complex to you, isnt it ? Its just a code that loads the assemblies from the plugins directory and GetInterfaceObject from all the IInterfaceAttacher type that is found within the plugin. Hence the code will place the button called Load in the interface. You can see, that we pass the Container to load the actual interface when the InterfaceControl is clicked.

So this is all we need to create our first plugin based application.


Limitations of this approach:


  1. This approach is very tightly coupled with the host. The Plugin needs to follow exactly what the rules that is created by the Host.
  2. The plugins are not flexible enough to create interdependencies. That means, you cannot have interdependent plugins so that one plugin can easily host a component coming from another plugin.
  3. The approach requires some sort of standardization so that once a plugin is created it can be re used in another host. 
  4. The approach forms a strict rule that a Plugin cannot spread itself in multiple dlls. I mean the IPlugin should always reside within the Plugin attacher.

To address these situations we use MEF. 

Lets introduce MEF to the System 

If you are new to MEF, please feel free to read my previous blog about MEF to get the idea. MEF is actually a IOC container that relate each Exports with Imports. The idea of MEF is to mark an interface with  Export when it needs to be plugged in to a system, and Mark as Import when we need to host some sort of plugin that might be present in the system. 

Export: 

We mark all methods, properties, types as Export when we want to export the functionality to the external world.

Import

If you are a host of Plugin you need to Import the plugin. Hence Import is used to import a functionality to the system.

Compose

The CompositionContainer is a IOC container that maps individual Export with its appropriate Import. 

You can read about them from my article here

Steps to change the existing application to use MEF
  1. Add System.CompositionModel.Composition to your Plugins and add Export attribute to the IInterfaceAttacher.

    Adding Export(typeofIInterfaceAttacher) will enable that the Interface object will be viable to export from the system to some external agent which wants to import it. 
  2. In the Actual Host we add a new class which creates a property of IInterfaceAttacher and Import the object.  The class looks like :
    public class InterfaceBuilder
    {
        [ImportMany(typeof(IInterfaceAttacher))]
        public IEnumerable<IInterfaceAttacher> Attachers { get; set; }
    }
    We use ImportMany as we can have multiple plugin in the dlls. So the property Attachers will list all the plugins into a single IEnumerable.
  3. In the Form, lets remove all the code that I have written to invoke the Type from assembly and change it to something like this :
  4. private void Form1_Load(object sender, EventArgs e)
    {
        DirectoryCatalog catalog = new DirectoryCatalog("plugins");
               
        InterfaceBuilder builder = new InterfaceBuilder();
        CompositionContainer container = new CompositionContainer(catalog);
        container.ComposeParts(builder);
    
    
        foreach (IInterfaceAttacher attacher in builder.Attachers)
        {
            this.pnlLoadControls.Controls.Add(attacher.GetInterfaceObject());
            attacher.Container = this.pnlContainer;
        }
    }

    So here I have just created an object of DirectoryCatalog. DirectoryCatalog actually lists all the Exports and Imports present in the dlls present in a particular directory. In our case the directory is plugins. We create an object of InterfaceBuilder which holds the IEnumerable of IInterfaceAttacher. Next we create an object of CompositionContainer and pass our InterfaceBuilder object to compose its parts. 
  5. Now if you run the code, it runs just fine. 
Thus you can see that the Exported plugin is actually created automatically using CompositionContainer and works as we have implemented earlier. 

Benefits ? 

Yes. There are lots of benefits of using MEF.  

  1. As it forms a standard, the same dll can plugin to any system that imports something that the plugin is going to export. 
  2. As we do not create dlls manually and rely completely on CompositionContainer, it is quite capable of handling errors, and reporting critical errors. 
  3. Interdependency can also be achieved in this approach. The plugins can spread into multiple dlls easily. Lets look how :

    We made the Runner and Plugin in InterfaceAttacher as Import capable and removed the object creation from InterfaceControl_Click event handler.  And Export the actual classes. Even you can do the same for the UserControl too.

    Simple huh ? 
  4. Yes now you can spread the types into assemblies, even your plugins can import types from outside the assemblies, even from the host

I hope this post comes to you handy. Even though you should change it in your practical plugin based application, but this will help you to start.

Thank you.

No comments:

Post a Comment

Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.