Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF - Dynamically Loading User Control Plugins

0.00/5 (No votes)
17 Feb 2010 1  
This article describes about methods to load User Control plug-ins.

Introduction

Lot of WPF UserControls are nowadays available through third parties, either for free or based on a fee. This article explains ways to integrate those third party user control plug-ins into the current user application.

Background

Before going to the code details, let's see the exact requirements of the problem we are going to solve. We have two plug-ins: UI.dll, ImageViewer.exe, which are stored in the plug-in store called "ExtPlugins".

  • UI.dll - shows some first name and last name strings
  • ImageViewer.exe - shows an image, and the path is the same as the ExtPlugins folder

The current application has to do two things:

  1. Read the User Control from the external module
  2. Load the appropriate data used by the User Control

Let's explore these two steps in more detail:

  1. Read the User Control from the external module: For this, we need to know the exact name of the User Control that resides in the external plug-in. Because, there may be many User Controls that exist in the module. We need to know which one we are interested in.
  2. Load the appropriate data used by the User Control.

The second requirement is where things get much more interesting. The first question is how the user control expects the data, i.e., in which format. Of course, we know that the external User Control has some kind of data -binding mechanism already defined.

Let's take one of the external plug-ins we have here: "ImageViewer". The documentation of the module says that the current image path should be set through the property. There is a property "CurrentImagePath" available in the UserControl, and when we set this property, the UI syncs and displays the appropriate image in the control.

Now, take another external plug-in we have here: "UI". This module does not have any setter property like for the first one. The User Control does not have properties like "FirstName", "LastName" defined within the external module. Rather it is data bound to an object with two properties defined for "FirstName", "LastName". How do we solve this problem?

Approach (1): Using our knowledge of the external plug-in. In the user application, define a custom class with two properties. This needs apriori knowledge about the plug-in during coding time. The advantage of this approach is an easy to define class and structure during the coding stage of the user application. The disadvantage is the user needs to change the C# code when the plug-in requirements change; e.g., if the external plug-in adds another property, then we need to change the C# code.

Approach (2): Using the C# Reflection emit feature. Here, we read the external plug-in requirements (the best way is to use XML syntax to define the requirements), and then create a typebuilder on the fly and add properties to it with the property builder. The advantage is easy adaptability with the new requirements change in the external plug-in. We are not changing the C# code for the new properties added or deleted. And it is extensible to load different types of plug-ins. The only thing is you need a mechanism to describe the plug-in requirements. Disadvantage: a little extra code for using Reflection techniques.

Now, let's dive into the C# code.

Using the Code

This the code-behind file for the main window:

public Window1()
{
    InitializeComponent();
    mainframe = new Mainframe();
    mainframe.LoadPlugins();
    AddPluginNames();
}

During main-window startup, we load the plug-ins and read the required User Controls and their corresponding data:

// Event Handlers
private void btnShow_Click(object sender, RoutedEventArgs e)
{
    this.stkCanvas.Children.Add(PluginControl);
    // assign data context to control
    this.DataContext = PluginData;
}

In the main window, we handle the button click event and add the User Ccontrol dynamically:

UserControl LoadUserControlPlugin(string plguinRelativePath)
{
    UserControl myControl = null;
    try
    {
        Assembly asm = Assembly.LoadFile(plguinRelativePath);
        Type[] tlist = asm.GetTypes();
        foreach (Type t in tlist)
        {
            if(t.BaseType == typeof(UserControl))
            {
                myControl = Activator.CreateInstance(t) as UserControl;
                break; // get only one control to show up
            }               
        }
    }
    catch (Exception ex) { myControl = null; }
    return myControl;
}

This is a very straightforward approach in C# to load a library, read the types, and create an instance of a type. Here, we are interested in the User Control type. Reading the User Control is kind of a crude technique, but I have only shown this for example purposes. If the user has the exact name of the User Control he is interested in, then he can use that name to directly read a particular User Control.

namespace Main
{
    class MyModel
    { 
        public object InitializePluginData()
        {
            // create Module
            ModuleBuilder modBuilder = CreateModule("PDAssembly", "PD");
        
            // custom type creation - custom
            TypeBuilder tb = modBuilder.DefineType("PDT", TypeAttributes.Public);
            CreatePluginDataObject(tb);
                
            // Test generated type
            Type t = tb.CreateType();
            object obj = Activator.CreateInstance(t);
            t.GetProperty("FirstName").SetValue(obj, "Adam", new object[0]);
            t.GetProperty("LastName").SetValue(obj, "Smith", new object[0]);
            return obj;
        }

        public void CreatePluginDataObject(TypeBuilder tb)
        {
            ////////////////////////////
            CreateProperty<string>(tb, "FirstName");
            CreateProperty<string>(tb, "LastName");
            ////////////////////////////
        }

        public ModuleBuilder CreateModule(string nameAssembly, string nameModule)
        {
            // Build Assembly
            AppDomain assdomain = AppDomain.CurrentDomain;
            AssemblyName assName = new AssemblyName(nameAssembly);
            AssemblyBuilder assBuilder = 
              assdomain.DefineDynamicAssembly(assName, 
              AssemblyBuilderAccess.RunAndSave);
            // Build module
            string nameDll = nameModule + ".dll";
            ModuleBuilder modBuilder = 
              assBuilder.DefineDynamicModule(nameModule, nameDll);
            return modBuilder;
        }
 
        // Generic Method - useful in creating different types of properties
        public void CreateProperty <proptype>(TypeBuilder tb, 
                    string propName, Type propType)
        {
            // Define required fileds here
            string filedName = "_" + propName;
            FieldBuilder fdBuilder = tb.DefineField(filedName,
              typeof(propType), FieldAttributes.Private);
            PropertyBuilder prBuilder = tb.DefineProperty(propName, 
              PropertyAttributes.None, typeof(propType), new Type[0]);
        
            MethodBuilder getter = tb.DefineMethod(
                "get", // Method name
                MethodAttributes.Public | 
                  MethodAttributes.SpecialName,
                typeof(propType), // Return type
                new Type[0]); // Parameter types

            ILGenerator getGen = getter.GetILGenerator();
            // Load "this" onto eval stack
            getGen.Emit(OpCodes.Ldarg_0);
            // Load field value onto eval stack
            getGen.Emit(OpCodes.Ldfld, fdBuilder);
            getGen.Emit(OpCodes.Ret); // Return

            MethodBuilder setter = tb.DefineMethod(
                "set",
                MethodAttributes.Assembly | 
                  MethodAttributes.SpecialName,
                null, // Return type
                new Type[] { typeof(propType) }); // Parameter types
        
            ILGenerator setGen = setter.GetILGenerator();
            // Load "this" onto eval stack
            setGen.Emit(OpCodes.Ldarg_0);
            // Load 2nd arg, i.e., value
            setGen.Emit(OpCodes.Ldarg_1);
            // Store value into field
            setGen.Emit(OpCodes.Stfld, fdBuilder);
            setGen.Emit(OpCodes.Ret); // return
        
            // Link the get method and property
            prBuilder.SetGetMethod(getter);
            // Link the set method and property
            prBuilder.SetSetMethod(setter);
        }
    }
}

Here, I used the Reflection emit technique to create the appropriate plug-in data object dynamically. I used the TypeBuilder, PropertyBuilder to assign the required properties to a class. Another point here is CreateProperty() is a generic method and can create different types of properties.

// Private Methods
public void LoadPluginA(string name)
{
    object myData = null;
    UserControl myControl;
    // data
    // Approach: with reflection emit
    MyModel mymodel = new MyModel();
    myData = mymodel.InitializePluginData();
    // Approach: with Custom defined structure
    // myData = new Person("Adam","Smith");
    _dictPluginData.Add(name, myData);

    // Control
    string fullpath = Path.GetFullPath(@"../../../ExtPlugin/UI.dll");
    myControl = LoadUserControlPlugin(fullpath);
    _dictUserControl.Add(name, myControl);
}


public void LoadPluginB(string name)
{
    //object myData = null;
    UserControl myControl;

    // Load Control
    string fullpath = Path.GetFullPath(
      @"../../../ExtPlugin/ImageViewer.exe");
    myControl = LoadUserControlPlugin(fullpath);
    _dictUserControl.Add(name, myControl);

    // Load Data
    string pathImage = Path.GetFullPath(@"../../../ExtPlugin/" + 
           @"funny-pictures-cat-plans-your-doom.jpg");
    if (myControl != null)
    {
        // set some custom data for this plugin only
        Type t2 = myControl.GetType();
        t2.GetProperty("CurrentImagePath").SetValue(
                          myControl, pathImage, null);
    }
    _dictPluginData.Add(name, null);
}

Here, the basic approach is similar. First, get the User Control type and then set the data it is expecting. In LoadPluginB, we use the GetProperty, SerValue functions to set the appropriate data to the property in the external plug-in object. In LoadPluginA, we use Reflection.Emit to create a custom object to pass to the User Control. The interesting point is the User Control datacontext is expecting the object type to be set.

this.DataContext = PluginData // here PluginData is of type object.

The WPF framework provides us more flexibility to solve interesting problems.

Points of Interest

Setting data to an external User Control is an interesting topic, and there are many ways of solving this problem. Here, we looked at two approaches:

  1. Using the knowledge about the external plug-in. In the user main application, define a custom class.
  2. Using C# Reflection, we create a generic class to create classes depending on the plug-in requirements.

In my opinion the second approach is more elagant interms of maintainabilty of plugins in large projects. And also it is more scalable with requirmets of plugins.

References

The ImageViewer plug-in was downloaded from this website, and the plug-in module belongs to the author: http://mel-green.com/2009/09/wpf-simple-vista-image-viewer/.

History

  • 02/17/2010 - Initial version.

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