C# Plugin Manager

C# Plugin Manager

Plugins and modules are widely used in almost any type of application. They enhance them with additional features. Beside adding features, plugins have the big advantage, that they separate code in a very clean and testable way. Even hundreds of plugins can be managed without problems, as their functionality is encapsuled in a single place.

This tutorial will explain you, how to implement a C# plugin- and module application-architecure, which is quite simple, clean and easy to maintain!

Differences between plugins and modules

Plugins add small functionalities to a software. An example could be a plugin which adds business-logic to a CRM- or an ERP-system. The registration is very lightweight normally.

Modules have more functionality. Possibly adding a whole new part to a system like i.e. a logistics-module. The registration comes often with some meta-data about the module and brings resources such as icons, menupoints etc.

Application Plugin Architecture

The following diagram shows, how a plugin/module based architecture looks like. It is made up of our application with the pluginmanager, the plugins and the SDK. The pluginmanager searches for, loads, registers and executes available plugins.

Application Plugin Architecture

Notice that the application and the plugins share the SDK, which provides everything the plugin needs to know (i.E. interfaces and parameter-classes). It is normally implemented in a separate library and referenced in each plugin-project.

Solution files tree

First Implementing an AppSDK in C#

we create AppSDK as a library project then we should create an interface for any plugin (AppSDK/interfaces.cs)

public interface IPlugin
{
    string pluginName();
    void run();
    void load(IConfig conf);
    void unload();
}

so each plugin should have to implement pluginName, run , load with configurations, unload methods.

We are going to create some other interfaces (AppSDK/interfaces.cs)

namespace AppSDK
{
    public interface ILoger
    {
        void log(string message);
    }
    public interface IUser
    {
        int getUserId();
        string getUserName();
    }
    public interface IUserManager
    {
        IUser getUser();
    }
    public interface IConfig
    {
        ILoger getLoger();
        IUserManager getUserManager();
        string getDbConnectionString();
    }

}

Then we will implement theses interfaces in our AppSDK with the default implementation but we are later can use alternative implementations coming from the plugins we will attach.(AppSDK/App.cs)

public class Loger : ILoger
 {
     public void log(string message)
     {
         Console.WriteLine("Loger: " + message);
     }
 }
 public class Config : IConfig
 {
     public ILoger getLoger()
     {
         return new Loger();
     }

     public IUserManager getUserManager()
     {
         throw new NotImplementedException();
     }

     public string getDbConnectionString()
     {
         return @"Data Source=.\SQLEXPRESS;Initial Catalog=AppDb;Integrated Security=True;";
     }
 }

Implementing the Plugin Manager (AppSDK/App.cs)

public static class PluginManager
   {
       private static Dictionary<string, IPlugin> _plugins = new Dictionary<string, IPlugin>();
       private static Config _config = new Config();
       public static bool LoadPlugin(string dllFile)
       {
           try
           {
               Assembly.LoadFrom(dllFile);
               foreach(Assembly a in AppDomain.CurrentDomain.GetAssemblies())
               {
                   foreach(Type t in a.GetTypes())
                   {
                       if(t.GetInterface("IPlugin") != null)
                       {
                           IPlugin plugin = Activator.CreateInstance(t) as IPlugin;
                           _plugins.Add(plugin.pluginName(), plugin);
                           _plugins[plugin.pluginName()].load(_config);
                       }
                   }
               }
           }
           catch (Exception ex) { return false; }
           return true;
       }
       public static Dictionary<string, IPlugin> listPlugins()
       {
           return _plugins;
       }
   }

as we can see each plugin needs some configurations to work like userManager class, DbConnectionString, LoggerClass so we used IoC to attach these required objects with out configuration object.

Second Implementing a plugin in C#

we are going to implement a simple plugin that will be used to get connection string from the app and then connect to MS SQL Server and print console message.

we create this plugin as a library project  (SQLServerClient_plugin/SQLServerClient.cs)

using System;
using AppSDK;
using System.Data.SqlClient;

namespace SQLServerClient_plugin
{
    public class SQLServerClient : IPlugin
    {
        private ILoger _logger;
        private string _connectionString;
        private IUserManager _userManager;

        public void load(IConfig conf)
        {
            _logger = conf.getLoger();
            _connectionString = conf.getDbConnectionString();
        }
        public void setConnectionString(string connectionString)
        {
            _connectionString = connectionString;
        }

        public string pluginName()
        {
            return "SQLServerClient";
        }
           
        public void run()
        {
            connect(_connectionString);
        }
        public void connect(string connectionString)
        {
            try
            {
                SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
                Console.Write("Connecting to SQL Server ... ");
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    connection.Open();
                    Console.WriteLine("Done.");
                }
            }
            catch (SqlException e)
            {
                Console.WriteLine(e.ToString());
            }

            Console.WriteLine("All done. Press any key to finish...");
            Console.ReadKey(true);
        }

        public void unload()
        {
            
        }
    }
}

we can see that each plugin should implement IPlugin interface and therefore it should add AppSDK reference in its dependencies.

The main logic and work of the plugin will be in run method.

Third Implementing a App Client in C#

Now we created the AppSDK and implemented one or more plugins, so now we are going to create Application Client  as console application which will use the AppSDK and will request to load some plugins and will run theses plugins.

using System;
using System.Collections.Generic;
using AppSDK;

namespace App
{
    class Program
    {
        static void Main(string[] args)
        {
            PluginManager.LoadPlugin(@"C:\Users\Administrator\Documents\Visual Studio 2015\Projects\AppSDK\SQLServerClient_plugin\bin\Debug\SQLServerClient_plugin.dll");
            Dictionary<string, IPlugin> plugins = PluginManager.listPlugins();
            foreach (KeyValuePair<string, IPlugin> p in plugins)
            {
                Console.WriteLine(p.Key);
                p.Value.run();
            }
            Console.ReadKey();
        }
    }
}

As we can see we called PluginManager to load the SQLServerClient_plugin dll file, then we called a method in the AppSDK to retrieve all loaded plugins and we are going to console each plugin name and then run it.

Final Step compile and run

To compile this project we have to configure Visual Studio to compile all projects in the workspace with the following steps.

From Tools -> Options -> Project and Solutions -> Build and Run ->

 

Then set the default application to startup to App project and then build the project

Then Run the project

Congratulations, We succeeded to load a plugin on the fly and execute this plugin logic.

Source Code pushed to https://gitlab.com/eramax/pluginsystem

Best Wishes.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.