Friday, November 26, 2004 8:29 PM bart

Dynamic assembly loading (useful when developing plug-in driven frameworks)

Reflection is one of the damn great technologies in the .NET Framework. Currently, I'm creating some demos on how to create a plug-in driven architecture for an application framework. What it basically should do, is allowing a user to load modules at runtime (e.g. by specifying a series of assemblies/types in a configuration file). Thus, without recompiling the original solution, it should still be possible to integrate new functionality in the application just by using configuration. Let's show you how...

 

1. Create interfaces for the plug-ins you want to support

An example looks as follows:

using System;

namespace
Demo.Interfaces
{
     ///
<summary>
     ///
Summary description for Class1.
     ///
</summary>
     public interface
IPlugin
     {
          int Calculate(int
input);
     }
}

Create this class in a separate class library (so that it becomes a separate assembly file). In this example, I'm just using an interface with one method in order to calculate a left-associative calculation on integer values. Basically, what I want to do is something like this:

(((((input operation1) operation2) operation3) operation4) operation5)

Where every operation is execute by a class implementing the IPlugin interface. An example of some implementations is this:

using System;
using Demo.Interfaces;

namespace Demo.Test
{
      /// <summary>
      /// Summary description for Class1.
      /// </summary>
      public class Test1 : IPlugin
      {
            public int Calculate(int input)
            {
                  return input * 2;
            }
      }

      public
class
Test2 : IPlugin
      {
            public int Calculate(int input)
            {
                  return input + 3;
            }
      }
}

 

2. Create a loader application

Next, we need to create the application that will use the plug-ins. For the sake of simplicity you can just go ahead and create a Windows Forms application.

 

2.a. Specifying the configuration in an XML file

One of the things we want to do is load the implementations of the IPlugin dynamically. Therefore, we need to have a file that contains the information needed to load the assembly and instantiate the class (that should implement IPlugin). For this purpose, I created a simple sample XML file:

<?xml version="1.0" standalone="yes"?>
<
Plugins xmlns
="http://tempuri.org/Plugins.xsd">
     <Calculators
>
          <File>test1</File
>
          <Type>Demo.Test.Test1</Type
>
     </Calculators
>
     <Calculators
>
          <File>test1</File
>
          <Type>Demo.Test.Test2</Type
>
     </Calculators
>
</Plugins>

In order to load this file in a nice and smooth way, you can create a typed DataSet with the following XSD schema:

<?xml version="1.0" encoding="utf-8" ?>
<
xs:schema id="Plugins" targetNamespace="http://tempuri.org/Plugins.xsd" elementFormDefault="qualified" attributeFormDefault="qualified" xmlns="http://tempuri.org/Plugins.xsd" xmlns:mstns=http://tempuri.org/Plugins.xsd xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata
="urn:schemas-microsoft-com:xml-msdata">
     <xs:element name="Plugins" msdata:IsDataSet
="true">
          <xs:complexType
>
               <xs:choice maxOccurs
="unbounded">
                    <xs:element name
="Calculators">
                         <xs:complexType
>
                              <xs:sequence
>
                                   <xs:element name="Type" type="xs:string" minOccurs="0"
/>
                                   <xs:element name="File" type="xs:string" minOccurs="0"
/>
                              </xs:sequence
>
                         </xs:complexType
>
                    </xs:element
>
               </xs:choice
>
          </xs:complexType
>
     </xs:element
>
</
xs:schema>

The code to load the XML file then looks like this:

plugins.ReadXml("plugins.xml"); //plugins is a DataSet control on the Windows Form of the type of the typed dataset.

This code can be placed in the event handler for a button or just in the Form_Load event handler. Note: for better performance, XmlReader implementations would be better (instead of using the DataSet approach).

 

2.b. Load the assemblies and instantiate the types

Once we have loaded the XML configuration file, we can go ahead to instantiate the types that we want to execute dynamically. A code snippet looks like this:

private ArrayList lst;

private void button1_Click(object sender, System.EventArgs e)
{
     lst =
new ArrayList();

    
foreach(Plugins.CalculatorsRow r in plugins2.Calculators)
     {
          IPlugin p = (IPlugin) Activator.CreateInstance(Assembly.Load(r.File).GetType(r.Type));
          lst.Add(p);
     }
}

The ArrayList (unfortunately not using generics yet because of the v1.x coverage) can be seen as a cache for the instances of the plug-ins. For a better framework, you would have a IsReusable property defined in the interface, so that you can ask the plug-in whether it can be used multiple times or not. If not, you need to instantiate the type every time you want to use the plug-in's functionality. However, this would lead us too far for now.

Basically, what happens is this. The assembly is loaded based on the "File" column of our DataTable (thus the <File> element in the XML file). This value contains the name of the assembly file that contains the type specified in the <Type> element. Note that the file name should not have an extension (.dll nor .exe). Next, we can ask the assembly to get the specified type by using GetType. This gives us a Type instance that can be used by the Activator in order to instantiate it (in this case without constructor arguments). The result of this method call - finally - has to be casted to the appropriate type, which is of course the IPlugin interface type. This is where the magic happens. All this stuff requires System.Reflection to be imported in the class definition.

Note: in order to execute all this code, the plugins.xml file needs to be in the same folder as the executable. Furthermore, the assemblies specified in this file (thus the implementations of IPlugin) need to live in that same folder as well.

 

2.c. Calling the plug-ins in the application

Finally, we can use the loaded plug-in instances to do some calculations as defined in the plug-ins. Look at the following code snippet for an example of this:

private void btnCalculate_Click(object sender, System.EventArgs e)
{
     int input;

     try
     {
          input =
int.Parse(txtInput.Text);
     }
     catch
     {
          MessageBox.Show("Invalid input value specified!");
          return;
     }

     StringBuilder sb =
new StringBuilder();
     sb.AppendFormat("Start value: {0}\r\n\r\n", input);

     foreach
(object o in
lst)
     {
          IPlugin p = (IPlugin) o;
          input = p.Calculate(input);
          sb.AppendFormat("Plugin: {0}\r\nNew value: {1}\r\n\r\n", p.ToString(), input);
     }

     txtOutput.Text = sb.ToString();
     MessageBox.Show("Result: " + input);
}

So, actually this is a kind of chained model. The same kind of technique can be used to implement a filter as well (compare with the way HttpModules work in ASP.NET, which also are specified in machine.config or web.config and which are loaded dynamically as well).

 

3. Testing it yourself

The code of this sample can be found on www.bartdesmet.net/download/pluginssample.zip. It's actually a complete Visual Studio .NET 2003 solution with the right configuration in order to compile and execute in the development environment.

Please note it's just a simple and stupid sample in order to illustrate the principle. It has not been fully tested and further exception handling needs to be in place to make it fool-proof (e.g. to avoid loading a type that does not implement the IPlugin interface). Additional improvements can be made as well.

 

For further questions, you can drop me a mail or send in some feedback.

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# re: Dynamic assembly loading (useful when developing plug-in driven frameworks)

Saturday, November 27, 2004 12:03 AM by bart

Mmmmm, Reflection; I love it! Nice example!

One year ago I did a POC project that was a framework for sort of plug-ins. We used a lot of custom attributes to "glue" everything together.

cu
Jan

# re: Dynamic assembly loading (useful when developing plug-in driven frameworks)

Friday, December 03, 2004 5:29 AM by bart

Thanks for sharing this example. For an application that I'm currently writing, I was looking for a way to accomplish this "plugin" functionality without spending a lot of development time (deadlines, you know...), and this is indeed an easy but nevertheless powerfull way.

# re: Dynamic assembly loading (useful when developing plug-in driven frameworks)

Monday, February 28, 2005 11:01 AM by bart

thx for sharing this ! It is a great example!

# re: Dynamic assembly loading (useful when developing plug-in driven frameworks)

Friday, May 12, 2006 4:27 PM by musthafa manikkoth

I was wondering if there's anyway to load dynamically without knowing the type of concrete implementation. i.e., all that i know is that assembly "test1" implements IPlugin. Does anyone know how to do this? or Is this doable?
Thanks
Musthafa