Click here to Skip to main content
Click here to Skip to main content

Plug-in Enabled Applications

, 11 Nov 2008
Rate this:
Please Sign up or sign in to vote.
Develop extensible application by enabling 3rd party plug-ins
Demo-Image.gif

Introduction

In this article I am going to demonstrate, how you can develop an extensible application using facilities provided by .NET framework. In the demo you can see how you can install and uninstall third party plugging to your existing application.  

Background

You can see in applications like Microsoft office system as well as in Visual studio people can install additional components and make use of them in their day to day activities. These third party components or mostly referred as plug-ins provide very useful features that the existing application doesn’t provide. However this extendibility doesn’t come with ease. Here application developers have to think and design the application architecture carefully to give this facility. When you give the necessary functionality third party developers and venders can make use of it and develop plug-ins, making your application more extendable and feature rich. In the demo application you can see that I converted a single data presentation which is a table view in to various other presentations like charts using the plug-ins that has charting capabilities.

 In this article I am going to show you how you can use concepts like dynamic loading, reflection, late binding and custom attribute to develop an extendible application. For sake of completeness I will define some of the words I talked about in the next two sentences. Loading external assemblies on demand is known as a dynamic loading. Late binding is a technique in which you are able to create an instance of a given type and invoke its members at runtime.

Code

I am not going to touch on each and every piece of code here. Mainly I will discuss the overall design, how to transfer application data to plug-ins, how to load and run the plug-ins and finally achieving security.

Design

In the demo application I have used mainly three entities. Obviously there has to be a host application. Then you have plug-ins. Then most important entity (assembly) that enable communication by providing common definitions and interfaces that will be implemented and referred by both former entities. First I will talk about this intermediate module namely PluginShared in my demo. In this module mainly you have IPlugin,  IPluginServiceProvider, PluginInfoAttribute, PluginDataEntity interfaces and classes.

IPlugin : every plug-in has to implement this interface to be a valid plug-in in that application. If you look at the interface closely you can understand the use.

IPluginServiceProvider: if some type wants to provide services to the plug-ins it has to implement this interface. In the demo application I have include only one method. But if plug-ins requires more data or services you can extend this interface to provide them.

PluginInfoAttribute: this class is type of System.Attribute which means if you sing the plug-ins with this attribute external entities that query the Meta data can get the information about the plug-ins.

PluginDataEntity: this class acts as data container. Data from the application is converted to this type before transferring to the plug-ins.

PluginShared module is referred by both the main application as well as the each and every plug-in. For instance BarChartPlugin has a reference to it. BarChat user control has implemented the IPlugin interface. In addition it has included the PluginInfoAttribute providing information about the plug-in. with these decleration BarChartPlugin assembly has satisfied all recruitments to be a valid plug-in.

Now in the main application I have PluginServiceProvider class which is an IPluginServiceProvider type. A single instance is used to provide services to the plug-ins. In addition PluginManager class act a important part in this design to manage plug-ins. For example loading plug-ins to the system, deleting plug-ins and calling plug-ins. In this demo I have used PluginConfig xml document to keep state of plug-ins. whenever a plug-in is installed or deleted this Xml is updated accordingly to track the states. Then PluginManager has dictionary that keeps track of current plug-ins. Following code snippet is used to load the plug-ins from the path. You can see dynamic loading and late binding concepts here.

public PluginContainer Load(string plugpath,string key)
{
    PluginContainer container = null;
    plugpath = Path.GetDirectoryName(plugpath);
    if (Directory.Exists(plugpath))
    {
        string[] paths = Directory.GetFiles(plugpath, "*.dll");
        for (int i = 0; i < paths.Length; i++)
        {
            IPlugin iPlugin;
            Assembly asm = Assembly.LoadFrom(paths[i]);
            Type[] asmTypes = asm.GetTypes();

            for (int j = 0; j < asmTypes.Length; j++)
            {
                try
                {
                    Type t = asmTypes[j].GetInterface("IPlugin");
                    if (t != null)
                    {
                        PluginInfoAttribute atrib = GetAttibs(asmTypes[j]);
                        object o = asm.CreateInstance(asmTypes[j].FullName);
                        iPlugin = o as IPlugin;
                        if (iPlugin != null)
                        {
                            container = new PluginContainer(key, iPlugin.GetTitle(),
                                plugpath,iPlugin, atrib);
                        }
                    }
                }
                catch (Exception e)
                {
                }
            }
        }
    }
    return container;
}

If you look closer you can see all the information related to plug-in is taken and added in to the plug-in collection. Then in the PluginManager class we have the following code to populate the menu items.

public List<ToolStripMenuItem> GetPluginManuItemList()
{
    GetPlugins();
    List<ToolStripMenuItem> menuList = new List<ToolStripMenuItem>();
    ToolStripMenuItem toolStripMenuItem = null;

    foreach (string var in plugContainerCollection.Keys)
    {
        PluginContainer cont = plugContainerCollection[var];
        IPlugin p = cont.Plugin;
        toolStripMenuItem = new ToolStripMenuItem();
        toolStripMenuItem.Name = p.GetTitle();
        toolStripMenuItem.Size = new System.Drawing.Size(127, 22);
        toolStripMenuItem.Text = p.GetTitle();
        toolStripMenuItem.Click += new  System.EventHandler(this.OnPluginItemClick);
        toolStripMenuItem.Tag = p;
        menuList.Add(toolStripMenuItem);
    }
    return menuList;
}

You can clearly see that in this we have used definitions in the IPlugin interface to populate the text of the menu items and so on. Here one important thing to note is I have used Tag property to keep track of the related plug-in and all plug-ins will come to OnPluginItemClick method when the click event occur.

private void OnPluginItemClick(object sender, EventArgs e)
{
    ToolStripMenuItem ts = sender as ToolStripMenuItem;
    if (ts != null)
    {
        IPlugin p = ts.Tag as IPlugin;

        if (p != null)
        {
            p.SetService(PluginServiceProvider.Instance);
            p.Initialize();
            Control c = p.GetView();
            Form f = new Form();
            f.Controls.Add(c);
            c.Dock = DockStyle.Fill;
            f.MdiParent = this.mainForm;
            f.Width = 600;
            f.Height = 450;
            f.Text = p.GetTitle();
            f.Show();
        }
        else
        {
            MessageBox.Show("Cant find the plugin.");
        }
    }
}

Above code snippet shows why we used Tag property as a container of the plug-in. This is the time where user is going to use the plug-in. Now we have to provide IPluginServiceProvider and initialize the plug-in before loading the UI. Here we open a form with the Control provided by IPlugin type.

In the demo, I have extended the capability to install and uninstall the plug-ins. To make this possible I made plug-ins wrapped in a very elegant way. Plug-in and referenced assemblies are bundled as a zip file and make the extension myplugin or any extension name you want. When user installs this plug-in, we unzip the bundled assemblies and check whether it has valid plug-in implementations or if it’s already installed. If not we load the new plug-in and update the Xml. When extracting the zip file I used temp folder in the Plugins folder where all plug-ins are installed. If the plug-in is valid one we copy the folder in to Plugins folder. Folders are named with a unique name to help the proper management. When uninstalling, I used similar pattern where we delete the uninstalled plug-in from the collection and update the Xml. Main concern is deletion of temp folder and uninstalled plug-in folders in runtime. Since .NET doesn’t facilitate unload of assemblies after loading, I used the deferent technique to clean up the temp. That is when application restarts I deleted temp folder and uninstalled plug-ins.

Another concern you may have is, how to provide integrity of data in your application. What if a plug-in edits data in the main application? If that happens data shown in the data grid in this demo application will be inconsistent and invalid. We have to avoid this. Here I converted the main application data type in to plug-in data type which is in the PluginShared module. Then I send this converted data into each and every plug-in. So if any plug-in changes input data it will not be reflected to main application or other plug-in.

Now you can see using these strategies you can design a good extendible application.

Conclusion 

Using this design you can develop extensible applications with rich features. Also you can tackle the limitations exists.

Acknowledgment

Special thanks to following gentlemen. You can find the charting controls in following locations.

Hamed Mosavi: http://www.codeproject.com/KB/miscctrl/HBarChart.aspx

Julijan Sribar:  http://www.codeproject.com/KB/graphics/julijanpiechart.aspx

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

PhD, BSc(Eng), MCP (Web Development)

Comments and Discussions

 
Questionjuan Pinmemberabdi1911889-May-12 5:04 
QuestionWeb app Pinmemberraulhmacias17-Nov-08 11:31 
AnswerRe: Web app PinmemberTharindu Nishad Patikirikorala26-Nov-08 8:05 
GeneralRe: Web app Pinmemberwayne.net1-Dec-08 8:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 11 Nov 2008
Article Copyright 2008 by Tharindu Nishad Patikirikorala
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid