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

Add run-time functionality to your application by providing a plug-in mechanism

By , 26 May 2003
 

Sample screenshot

Sample screenshot

Issues covered:
  • Using the Activator Class
  • Using custom configuration sections by implementing IConfigurationSectionHandler
  • Designing a simple Plug-in architecture

What you’ll need:

  • VS.Net 2003 (If you have 2002, use this utility to convert the files to the previous format)

Why do we need a plug-in framework for our application?

People usually add plug-in support in their applications for the following reasons:

  • We want to allow our application to be extended with more functionality without the need to re-compile and distribute it to customers
  • We need to add functionality on site
  • We need to fix a bug on site
  • The business rules for the application change frequently, or new rules are added frequently

Case Study

In our case study, we will build a very simple text editor, composed of only one form. The only thing that text editor can do is display text in a single textbox in the middle of the form. Once this application is ready, we will create a simple plug-in which will be added to the application. That plug-in will be able to read the text currently in the textbox , parse it for valid email addresses, and return a string containing only those emails. We will then put this text inside the text box.

As you can see, there are a number of “unknowns” in our case study:

  • How do we find the plug-in from within the application?
  • How does the plug-in know what text is in the text box?
  • How do we activate this plug-in?

We will answer all of these questions when we encounter them as we build the solution.

Step 1 – Create a simple text editor

OK. I won’t bore you with the details of this. It’s all in the source code download. Just a simple form showing a lump of text.

I’ll assume from this moment that you have created this simple application.

Step 2 – Create the Plug-in SDK

Now that we have an application, we want it to be able to talk with external plug-ins. How do we make this happen?

The solution is for the application to work against a published interface – a set of public members and methods which will be implemented by all custom plug-ins.

We’ll call this interface IPlugin. From now on, any developer that would like to create a plug-in for our application will have to implement this interface.

This interface will be located at a shared library, which both our application and any custom plug-ins will reference.

Let’s define this interface then. We need very little data from our simple plug-in - its name , and a method which would instruct it to perform a generic action based upon the data in our application.

interface IPlugin
{
    string Name{get;}
    void PerformAction(IPluginContext context);
}

The code is pretty straight forward, but I’ll explain why I’m sending an IPluginContext interface to the PerformAction. The reason I’m sending an interface rather than just a string is because I want to allow more flexibility in the matter of what object will I be able to send. Currently, this interface is very simple:

<PRE><PRE lang=cs>public interface IPluginContext
{
    string CurrentDocumentText{get;set;}
}

Now, all I have to do is implement this interface in one or more objects, and send this to any plug-in to receive a result. In the future this will allow me to chane the string of not just a textbox, but any object I like.

Step 3 – Creating our custom Plug-in

All we have to do now is :

  • Create a separate class library object
  • Create a class that implements the IPlugin Interface
  • Compile that class and place it in the same folder as our main application

Here’s the EmailPlugin Class, in full:

public class EmailPlugin:IPlugin
{
   public EmailPlugin()
   {
   }

   /// The single point of entry to our plugin
   /// Acepts an IPluginContext object
   /// which holds the current
   /// context of the running editor.
   /// It then parses the text found inside the editor
   /// and changes it to reflect any 
   /// email addresses that are found.
   public void PerformAction(IPluginContext context)
   {
      context.CurrentDocumentText =ParseEmails(context.CurrentDocumentText);
   }

   /// The name of the plugin as it will appear 
   /// under the editor's "Plugins" menu
   public string Name
   {
      get
      {
         return "Email Parsing Plugin";
      }
   }

   /// Parse the given string for any emails using the Regex Class
   /// and return a string containing only email addresses
   private string ParseEmails(string text)
   {
      const string emailPattern = @"\w+@\w+\.\w+((\.\w+)*)?";
      MatchCollection emails = Regex.Matches(text,emailPattern,
                                             RegexOptions.IgnoreCase);
      StringBuilder emailString = new StringBuilder();
      foreach(Match email in emails) 
      {
         emailString.Append(
         email.Value + Environment.NewLine);
      }
      return emailString.ToString();
   }
}

Step 4 – Letting our application know about the new plug-in

Once we have compile out plug-in, how do we let our application know about it?

The solution is simple :

  • Create an application configuration file
  • Create a section in the config file that lists all the available plugins
  • Create a parser for this config section

OK. To take care of step one, Just add an XML file to the Main application.

Tip: Name this file App.Config. If you do that, every time you build your application VS.NET will automatically copy this file into the build output folder and rename it to <yourApp>.Config ,saving you the hassle.

Now, We want the plug-in developer to easily add an entry in the Config file to publish each plug-in he has created.

Here’s how the Config file should look:


<configuration>
   <configSections>
      <section name="plugins"
               type="Royo.PluggableApp.PluginSectionHandler, PluggableApp" />
   </configSections>
   <plugins>
      <plugin type="Royo.Plugins.Custom.EmailPlugin, CustomPlugin" />
   </plugins>
</configuration>

Notice the configSections Tag. We tell the application Configuration settings that we have an unidentifies section in this config file, but that we have a parser for this section. This parser resides in the class Royo.PluggableApp.PluginSectionHandler,which in an assembly named PluggableApp

I’ll show you the code for this class in the next.

Next, we have the Plugins section of the config file, which lists , for every plug-in, the class name and the assembly name in which it resides.

We will use this information when we instantiate the plug-in, later on.

OK. Once the Config file is done, we basically have finished one end of the circle. The plug-in is ready to rock, and has published itself to all the necessary channels. All we have left to do now is to allow our application to read in this information, and instantiate the published plugins according to this info.

Step 5 – Parse the config file using IConfigurationSectionHandler

In order to parse out the plugins that are found within the Config file of our application, The framework provides a very simple mechanism that enables us to register a specific class as a "handler" for a specific portion in our config file. We must have a handler for any portion in the file that is not automatically parsed by the framework, otherwise we get a ConfigurationException thrown.

In order to provide the class that parses the "plugins" section, all we need to do is to implement the System.Configuration.IConfigurationSectionHandler interface.

The interface itself is very simple:

public interface IConfigurationSectionHandler
{
   public object Create(object parent, object configContext, 
                        System.Xml.XmlNode section);
}

All we have to do is override the "create" method in our custom class, and parse the XML node which is provided to us. This xml node, in our case, will be the "Plugins" XML node. Once we have that, we have all the information we need in order instantiate the plugins for our application.

Our custom class must provide a default constructor, since it is instantiated automatically by the framework at run time, and than the "Create" method is called on it.

Here's the code for the Plugins Section Handler class:

public class PluginSectionHandler:IConfigurationSectionHandler
{
   public PluginSectionHandler()
   {
   }


   /// Iterate through all the child nodes
   /// of the XMLNode that was passed in and create instances
   /// of the specified Types by reading the attribite values of the nodes
   /// we use a try/Catch here because some of the nodes
   /// might contain an invalid reference to a plugin type
   public object Create(object parent, object configContext, 
                        System.Xml.XmlNode section)
   {
      PluginCollection plugins = new PluginCollection();
      foreach(XmlNode node in section.ChildNodes)
      {
         //Code goes here to instantiate
         //and invoke the plugins
         ...
   }
   return plugins;
}

As you can see in the config file mentioned earlier, We provide the data the framework needs in order to handle the plugins section using the configSection tag prior to the actual plugins tags.

<configuration>
   <configSections>
       <section name="plugins" type="Royo.PluggableApp.PluginSectionHandler, 
                PluggableApp"/>

   </configSections>
   ...

Notice how we specify the class ; The string is somposed of two sections : The full name of the class(including encapsulating namespaces), comma, The name of the Assembly in which this class is located. This is all the framework needs in order to instantiate a class, and unsurprisingly, this is exactly the information we require for any plugins to register for our application.

Instantiating and invoking the plugins

Let's see how we actually instantiate an instance of a plug-in given the following string:

String ClassName = "Royo.Plugins.MyCustomPlugin, MyCustomPlugin"
IPlugin plugin = (IPlugin )Activator.CreateInstance(Type.GetType(ClassName));

Let's explain what's happening here.

Since our application does not a direct reference to the assembly of the custom plug-in, we use the System.Activator class . Activator is a special kind of class which is able to create instances of object given any number of specific parameters. It can even create COM Instances of objects and return them. If you have ever coded in ASP or VB, you remember the CreateObject() function which was used to instantiate and return objects based on the CLSID of a class. Activator operates on the same idea, yet uses different arguments, and returns a System.Object instance, not a variant….

In this call to Activator, I pass in as a parameter the Type which I want to instantiate. I use the Type.GetType() method to return an instance of a Type which matches the Type of the plug-in. Notice that the Type.GetType method accepts as a parameter exactly the string which was put inside the plugins tag, which describes the name of the class and the assembly it resides in.

Once I have an instance of the plug-in, I cast it to an IPlugin interface, and put it inside my plug-in object. A Try-Catch block must be put on this line, since we cannot be sure that the plug-in that is described there actually exists, or does in fact support the IPlugin interface we need.

Once we have the instance of the plug-in, we add it to the ArrayList of our application plugins, and move on to the next XML node.

Here’s the code from our application:

public object Create(object parent, object configContext, 
                     System.Xml.XmlNode section)
{
   //Derived from CollectionBase
   PluginCollection plugins = new PluginCollection();
   foreach(XmlNode node in section.ChildNodes)
   {
      try
      {
         //Use the Activator class's 'CreateInstance' method
         //to try and create an instance of the plugin by
         //passing in the type name specified in the attribute value
         object plugObject = Activator.CreateInstance(
                             Type.GetType(node.Attributes["type"].Value));

         //Cast this to an IPlugin interface and add to the collection
         IPlugin plugin = (IPlugin)plugObject;
         plugins.Add(plugin);
      }
      catch(Exception e)
      {
         //Catch any exceptions
         //but continue iterating for more plugins
      }
   }

   return plugins;
}

Invoking the plugins

After all this work is done, we can now use the plugins.One more thing is missing, though. Remember that the IPlugin.PerformAction() requires an argument of type IPluginContext which holds all the necessary data for the Plug-in to do its work. We'll implement a simple class which implements this interface, which we send to the PerformAction method whenever we call a plug-in. Here's the code for the class:

public interface IPluginContext
{
    string CurrentDocumentText{get;set;}
}

public class EditorContext:IPluginContext
{
    private string m_CurrentText= string.Empty;
    public EditorContext(string CurrentEditorText)
    {
        m_CurrentText = CurrentEditorText;
    }

    public string CurrentDocumentText
    {
        get{return m_CurrentText;}
        set{m_CurrentText = value;}
    }
}


Once this class is ready, we can just perform an action on the current editor text like so:
private void ExecutePlugin(IPlugin plugin)
{
	//create a context object to pass to the plugin
	EditorContext context = new EditorContext(txtText.Text);

	//The plugin Changes the Text property of the context
	plugin.PerformAction(context);

	txtText.Text= context.CurrentDocumentText;
}

Summary

We've seen that it's pretty simple to support plugins in your application.

  • Create A shared interfaces library
  • Create Custom plugins implementing the custom interfaces
  • Create Context arguments to pass to the plugins
  • Create a section in your config file to hold plug-in names
  • Instantiate plugins using an IConfigurationSectionHandler implementer class
  • Call your plugins!
  • Go home and spend some quality time away from your computer

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Roy Osherove
Web Developer
Israel Israel
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionRemote plugin Security IssuesmemberHermaphrodyte10 Sep '08 - 22:48 
Hi,
 
I've implemented a plugin architecture which is working well.
However, when one of the plugins resides on a server (rather than on the local machine) an IO security exception is thrown when the plugin attampts an IO.
 
Any idea how to resolve this issue?
 
Dave
 
Dave Leighton

AnswerRe: Remote plugin Security IssuesmemberHermaphrodyte11 Sep '08 - 2:45 
OK, I figured it out.
Gve the assembly a strong name and then hike the permissions for it.
 
Dave
 
Dave Leighton

GeneralWell done Roy, simple orthodox code :)membermeaningoflights24 Nov '07 - 14:14 
Not only is the article well written, the delivery is exceptional and the nature of the topic promotes collaboration among developers with SOA.
GeneralBackwards CompatibilitymemberDeveng9 Apr '07 - 8:56 
How do you avoid the "public key token mismatch" error everytime you release an update? Won't you get this error everytime your application updates the dll that contains the IPlugin interface? How do you avoid having to rebuild the plugin for every update?
Generalother options and about unmanaged dllmembersot26 Nov '06 - 5:45 
i liked the way you presented this article. actually im also in the middle of experimenting things around plugins in .Net. Took me some time to get to Reflection.Assembly namespace. Got stuck for the whole day on the InvalidCastException problem. Searched google for hours until I found an article (or two) why it failed on casting. And the answer is that you have to have reference to the same interface assembly. the way of doing this is either the the plugins refers to the main application.exe files (only in VS 2005) or splitting the interface into separate dll and have the engine and plugins referenced into that dll (thus they are refering to the same assembly).
 
all is well in the .net section, only now, i want to have more flexibility and enable people to not only create plugins in .net framework but also in unmanaged .dll. And that's when I got bunch of 'unsolved' problems. Let me give you a brief description. Basically, I have an interface class ITest that have to be implemented by all plugins (managed or unmanaged). for the unmanaged part, I just created an abstract class similar to the managed counterpart. I derived from that class, create a static function that will return a pointer to the derived class. And then I use P/Invoke on LoadLibrary and GetProcAddress to get the pointer to the function and execute it inside c#. it all went well, if I only execute the static function (have confirmed it using a static void function that only pops a message box). the problem arise when I want to cast the pointer i get (that contains the derived object) into the managed ITest interface. I tried various methods in Marshal namespace but without any luck. Instead, I get the FatalExecutionEngineError message. It said that the stack is corrupt. It seems that I don't cast it right and instead I executed the object as function (which is not correct and surely will corrupt the stack). Can you help? Is there a way to cast IntPtr to a unmanaged class object?
 
thanks in advance.
GeneralNice one !memberTrance Junkie18 May '05 - 3:01 
Just what i was looking 4 ! Thanks
see--> www.maxxor.co.za
Nothing useful but im starting to work as a dev 4 da 1st time !!!! Cant wait !
Laugh | :laugh: Laugh | :laugh: Laugh | :laugh: Laugh | :laugh: Laugh | :laugh: Laugh | :laugh: Laugh | :laugh: Laugh | :laugh: Laugh | :laugh: Laugh | :laugh: Laugh | :laugh:
GeneralNice article - useful informationmemberMike Ellison21 Nov '03 - 7:30 
Thanks for laying out these issues so well, Roy.
 
With regards to the conversation regarding dynamic discovery of plugins: if you were to write your application from scratch today, would you implement dynamic discovery of some kind, or would you use the App.config approach you originally described? I'm just curious what your point of view is on this now.
GeneralRe: Nice article - useful informationmemberRoy Osherove21 Nov '03 - 10:22 
Hi Mike.
If I had to do it from scratch (and I am doing it within another application) I would go with a config file approach , although I'd keep the plugin config section in a different XML file for ease of use by plugin developers.
The dynamic discovery (which I did do in a different article) is cool but it's always best to "keep it simple" unless something else is required. in this case it's simpler and easier to implement a config based approach.
GeneralRe: Nice article - useful informationmemberMike Ellison21 Nov '03 - 10:30 
Roy Osherove wrote:
If I had to do it from scratch (and I am doing it within another application) I would go with a config file approach , although I'd keep the plugin config section in a different XML file for ease of use by plugin developers.
 
Hi Roy. Do you mean that each plugin would have its own configuration xml file?
GeneralRe: Nice article - useful informationsussRoy Osherove21 Nov '03 - 13:10 
No. I mean there would be a central xml config file where all addins would register in. It should be separate from the app.config, to prevent any errors to non-related configs.
GeneralRe: Nice article - useful informationmemberMike Ellison21 Nov '03 - 10:37 
Roy - where is the other article where you did dynamic discovery? I would love to read it.
GeneralRe: Nice article - useful informationsussRoy Osherove21 Nov '03 - 13:08 
http://msdnaa.net/search.aspx?SearchText=Roy&ResourceTypes=all&Mode=Search&Archives=False
GeneralRe: Nice article - useful informationmemberMike Ellison21 Nov '03 - 13:14 
Thank you. Nice work.
GeneralHelp!memberTim McCurdy13 Aug '03 - 7:21 
Ok, I took this example and really extended it for a program I am writing. Basically, everything is working great except for one aspect.
 
Each Plugin DLL that I write Implements the same properties from the IPlugin interface. However, some of the Properties that are Implemented have sub Properties. I am trying to display these Objects within a Property Grid on my Form at runtime. Does anyone know how to get the Property Grid to display the "ExpandableObjectConverter"? It is not working for this even though it works fine on my custom Control projects!? Unsure | :~
 
Also, is it possible (maybe using Reflection?) to convert the Class that is being loaded from the plugin DLL (i.e. the one that implements IPlugin) into it's "real" object type to display Properties in the Property Grid that are specific to that Class? For example, even though each Class Implements IPlugin so that I can call the same Method, there may be different user-customizable Properties per Plugin object. WTF | :WTF:
GeneralThank you...editorHeath Stewart28 May '03 - 2:31 
I just wanted to thank you for doing something that no other plug-in article on this site has done - used the built-in support for configuration files! I've been doing it this way since the beta days of 1.0 and never quite understood why anyone would want to usurp what's already available and easy to access (not quite as easy to modify, but an app usually shouldn't modify its own plug-in configuration). Sure, searching directories or using a different files works, but why would anyone want to maintain two different configuration files or have to worry about deployment problems with subdirectories?
 
So, nice article and good research into harnessing what's already available in the Framework!
 

Reminiscent of my younger years...
10 LOAD "SCISSORS"
20 RUN

GeneralRe: Thank you...memberRoy Osherove28 May '03 - 3:58 
Thanks Smile | :)
I'm totally flattered..
I will try doing this with AppDomains , though, Just to learn how to do it Smile | :)
GeneralNice articlemembershivpal28 May '03 - 0:26 
H;)i, Its a nice article.
GeneralRe: Nice articlememberRoy Osherove28 May '03 - 1:55 
Thanks Smile | :)
GeneralSorry for the bad formattingmemberRoy Osherove27 May '03 - 20:20 
Sorry for the (initially) bad formatting.
It's the first article I've handed out to CodeProject which required Code highlighting, and I misunderstood the directions in the "submissions" wizard on the site.
 
On that note, how about fixing the directions for users submitting articles through the site? It still says "add pre and code tags" when in reality you highlight the text and select "formatted" from the combo...
QuestionPlug-In Context?memberAmber Star27 May '03 - 11:30 
Thanks for the Article,
 
However I have one question.. How can you ever expect the application to know the context of the Plug-in? This should never be assumed.. that the main program knows about the Plug-in's needs. Even if it is a generic Object.. What Object do you send to the Plug-in. A better aproach is to send A reference to an IServiceProvider, and allow the Plug-in to store the reference,allowing the Plug-in to request what it needs.
 
Just a sugestion.. What do you think?
 
Amber Star
Visit My WebLog..

AnswerRe: Plug-In Context?memberRoy Osherove27 May '03 - 20:17 
I can see your point, and I agree. I guess you could say that IPluginContext in essence is an IServiceProvider. The naming might be wrong, as it imples other things..
second, This is just a generic example. You could extrapolate a better design decision in which IPluginConext(Or IServiceProvider for that matter) would allow you to dynamically retrieve types of specific data without assuming the plugin will just need a "EditorText" property...
GeneralDynamic discoverymembermordejai27 May '03 - 8:04 
One thing I would expect from a plugin architecture, is being able to load plugins by just dropping them in the program or plugin folder.
 
It's easy, you just have to load the assemblies from that folder and check for types implementing IPlugin.
 
That avoids the config file step.
GeneralRe: Dynamic discoverymemberAmber Star27 May '03 - 12:40 
Your idea sounds neat, but once you load an assembly into an AppDomain, you can't unload it. So is it worth loading every Assembly to see if implements IPlugin, for the sake of Dynamic Disco?
 
I wish you could unload assemblies!
WTF | :WTF:
 
Amber Star
Visit My WebLog..

GeneralRe: Dynamic discoverymemberAdam Turner27 May '03 - 14:00 
You can unload assemblies. If you create a different AppDomain, and then load the assemblies in that, then unloading the AppDomain will unload the assemblies.
 
Also, a nice enhancement would be an expiration on the AppDomain. So that if no assembly is loaded/executed in a specific AppDomain, then after a certain time-period that AppDomain is unloaded. Also it would be good to create an AppDomain for a group of similar plugins instead of all plugins in 1 AppDomain or a single AppDomain for each plugin.
GeneralRe: Dynamic discoverymembermordejai27 May '03 - 15:54 
Amber Star wrote:
Your idea sounds neat, but once you load an assembly into an AppDomain, you can't unload it. So is it worth loading every Assembly to see if implements IPlugin, for the sake of Dynamic Disco?
 
As Adam pointed out, you can load the assemblies in a different AppDomain.
 
I would load all the assemblies in the folder on a separate AppDomain to check for IPlugins, make a list, then unload the aux AppDomain and load those in the current.
 
Amber Star wrote:
I wish you could unload assemblies!
You can with this workaround... I'm sure there's a good implementation reason to avoid arbitrary unloading of assemblies.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 27 May 2003
Article Copyright 2003 by Roy Osherove
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid