Click here to Skip to main content
15,885,125 members
Articles / Programming Languages / C#

Creating a Simple Plugin System with .NET

Rate me:
Please Sign up or sign in to vote.
4.93/5 (26 votes)
9 Nov 2015CPOL5 min read 65.6K   62   29
How to create a simple plugin system with .NET

I’ve uploaded the code for the tutorial on GitHub: https://github.com/dukeofharen/tutorials/tree/master/DotNet.Plugin

At some point in the development of your (.NET) application, you might feel the need of supplying some sort of plugin system, either for yourself or for creating a plugin ecosystem where other people can create plugins. There are some frameworks (like MEF) to handle plugins in .NET, but I’d rather make such a system myself, because it doesn’t require a lot of code.

What Steps Do We Need to Take?

  1. In order to make your application ready for plugins, you need to have a project in your application that exposes the functionality you want to be available for the plugins.
  2. You need to define an interface, with some basic methods and properties, which will be implemented by every plugin class.
  3. When you’ve created a class library (DLL) which contains the plugin class, this library needs to be loaded into the running application in some way. .NET exposes some functionality to load DLLs into memory, runtime.
  4. The DLLs are loaded into memory, and that’s it, nothing happens. We need to do something to detect the loaded plugins and do something with them. We know one thing: every plugin in memory implements the specific interface we’ve created. We need to perform some reflection to find all these classes and instantiate them.

In computer science, reflection is the ability of a computer program to examine and modify its own structure and behavior (specifically the values, meta-data, properties and functions) at runtime.

Building It

I think building a simple console application is a nice scenario for a simple plugin system. The console application will enable you to fill in a command (the name of the plugin) and fill in some additional parameters. The plugin class corresponding to the command name will be loaded and the corresponding Go method will be executed. So let us get started.

Setting Up the Project Structure

I’ve set up a new solution in Visual Studio with these 2 projects:

  • DotNet.Plugin: This is the main Console project
  • DotNet.Plugin.Business: This is a class library that contains common functionality that will be used across all plugins

In the Business project, I’ve defined an interface: IPlugin. Every plugin that will be made will implement this interface.

C#
public interface IPlugin
{
    string Name { get; }
    string Explanation { get; }
    void Go(string parameters);
}

The Name property will be used to look for the plugin when the console app is up and running. The Explanation property will return a bit of explanation for the plugin and the Go method accepts parameters and does some action based on these parameters.

Next, we’ll make a simple static class called Constants in the business project which will contain some simple constants.

C#
public static class Constants
{
    //The folder name which contains the plugin DLLs
    public const string FolderName = "Plugins";
}

Right now, only the FolderName is available in the constants. This is the name of the folder in which the plugin DLLs will reside. This folder needs to be placed in the folder that holds the EXE of the console app.

Next up is the PluginLoader class. This class loads all DLL files from the plugins folder and subsequently looks into the memory to get all classes implementing interface IPlugin and instantiating them.

C#
public class PluginLoader
{
    public static List<IPlugin> Plugins { get; set; }

    public void LoadPlugins()
    {
        Plugins = new List<IPlugin>();

        //Load the DLLs from the Plugins directory
        if (Directory.Exists(Constants.FolderName))
        {
            string[] files = Directory.GetFiles(Constants.FolderName);
            foreach (string file in files)
            {
                if (file.EndsWith(".dll"))
                {
                    Assembly.LoadFile(Path.GetFullPath(file));
                }
            }
        }

        Type interfaceType = typeof(IPlugin);
        //Fetch all types that implement the interface IPlugin and are a class
        Type[] types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(a => a.GetTypes())
            .Where(p => interfaceType.IsAssignableFrom(p) && p.IsClass)
            .ToArray();
        foreach (Type type in types)
        {
            //Create a new instance of all found types
            Plugins.Add((IPlugin)Activator.CreateInstance(type));
        }
    }
}

Let’s create the first plugin for the system. This plugin will just be a class within the Business project, and is called Help. This plugin, when called from the command line, will sum all loaded plugins by their name and explanation, like this:

C#
public class Help : IPlugin
{
    public void Go (string parameters)
    {
        foreach(IPlugin plugin in PluginLoader.Plugins)
        {
            Console.WriteLine("{0}: {1}", plugin.Name, plugin.Explanation);
        }
    }

    public string Name
    {
        get
        {
            return "help";
        }
    }

    public string Explanation
    {
        get
        {
            return "This plugin shows all loaded plugins and their explanations";
        }
    }
}

The help command

The help command that lists all plugins

The Business project is done right now. The only thing left to do is edit the console project and actually create a plugin. The Main method in the Program class in the console project should look like this:

C#
public static void Main (string[] args)
{
    Console.WriteLine ("Started plugin app..");
    try
    {
        PluginLoader loader = new PluginLoader();
        loader.LoadPlugins();
    }
    catch(Exception e)
    {
        Console.WriteLine(string.Format("Plugins couldn't be loaded: {0}", e.Message));
        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
        Environment.Exit(0);
    }
    while (true)
    {
        try
        {
            //Let the user fill in an plugin name
            Console.Write("> ");
            string line = Console.ReadLine();
            string name = line.Split(new char[] { ' ' }).FirstOrDefault();
            if(line == "exit")
            {
                Environment.Exit(0);
            }
            IPlugin plugin = PluginLoader.Plugins.Where(p => p.Name == name).FirstOrDefault();
            if (plugin != null)
            {
                //If the plugin is found, execute it
                string parameters = line.Replace(string.Format("{0} ", name), string.Empty);
                plugin.Go(parameters);
            }
            else
            {
                Console.WriteLine(string.Format("No plugin found with name '{0}'", name));
            }
        }
        catch(Exception e)
        {
            Console.WriteLine (string.Format ("Caught exception: {0}", e.Message));
        }
    }
}

At the top of the method, the PluginLoader class will be instantiated and the LoadPlugins method will be called. Next, the method will let the user fill in a command. The name of the plugin always comes first, optionally postfixed with some parameters (e.g., strlength these are parameters). If the command equals “exit”, the application will close. Next, the method will look in the loaded plugins if there is one with Name being equal to the command you’ve typed in. If not, an error will be shown. If the plugin was found, the plugin’s Go method will be executed with the optional parameters you typed in.

Next up, it’s time to create a real plugin and put the DLL of the compiled plugin in the Plugins folder. I’ve created a new project within the solution called DotNet.Plugin.StringPlugins. This solution needs to have a reference to the Business project, because we need the IPlugin interface.

C#
public class StringLength : IPlugin
{
    public string Explanation
    {
        get
        {
            return "Gets a string as parameter and returns the string length in characters.";
        }
    }

    public string Name
    {
        get
        {
            return "strlength";
        }
    }

    public void Go(string parameters)
    {
        Console.WriteLine(parameters.Length);
    }
}

This is the plugin StringLength. It does exactly what the name says it does: it accepts a string and calculates the total string length.

After this, the project needs to be compiled and placed in the Plugins folder in the same folder as where the Console .EXE file is.

The result

The console app now loads the plugin and lets me execute the strlength command

Well, actually that’s it. This is a very nice and clean way to extend your application using a simple plugin system.

And Now?

This was a very small example, but you can use the same technique in other types of .NET applications.

  • Are you developing a CMS using MVC, Web API, Web Forms or any other .NET web technology? You can use this system so others can extend your CMS.
  • Also when you develop a desktop application (using WPF or WinForms), a system like this might open new options for external developers.

License

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



Comments and Discussions

 
QuestionPlugin vs Class library Pin
dataskull4-Apr-22 8:10
dataskull4-Apr-22 8:10 
QuestionOne question Pin
githubcatw19-Jan-20 0:46
githubcatw19-Jan-20 0:46 
AnswerRe: One question Pin
AyesC5-Oct-20 17:31
AyesC5-Oct-20 17:31 
GeneralMy vote of 5 Pin
Santhakumar M4-Dec-15 8:13
professionalSanthakumar M4-Dec-15 8:13 
PraiseGood article Pin
KLPounds9-Nov-15 10:18
KLPounds9-Nov-15 10:18 
Questionwhat about of runtime exceptions ? Pin
webmaster4429-Nov-15 8:48
webmaster4429-Nov-15 8:48 
AnswerRe: what about of runtime exceptions ? Pin
Duke Of Haren9-Nov-15 9:51
Duke Of Haren9-Nov-15 9:51 
QuestionA Useful Technique Pin
Member 25759449-Nov-15 5:52
Member 25759449-Nov-15 5:52 
AnswerRe: A Useful Technique Pin
Duke Of Haren9-Nov-15 9:12
Duke Of Haren9-Nov-15 9:12 
QuestionIndeed, why not MEF or PRISM or ... Pin
GerVenson9-Nov-15 1:21
professionalGerVenson9-Nov-15 1:21 
AnswerRe: Indeed, why not MEF or PRISON or ... Pin
#realJSOP9-Nov-15 5:59
mve#realJSOP9-Nov-15 5:59 
GeneralRe: Indeed, why not MEF or PRISM or ... Pin
GerVenson9-Nov-15 20:22
professionalGerVenson9-Nov-15 20:22 
GeneralRe: Indeed, why not MEF or PRISM or ... Pin
#realJSOP9-Nov-15 23:51
mve#realJSOP9-Nov-15 23:51 
GeneralRe: Indeed, why not MEF or PRISM or ... Pin
GerVenson10-Nov-15 0:03
professionalGerVenson10-Nov-15 0:03 
(And you sir did very well) Laugh | :laugh:

GeneralRe: Indeed, why not MEF or PRISM or ... Pin
#realJSOP10-Nov-15 1:30
mve#realJSOP10-Nov-15 1:30 
GeneralRe: Indeed, why not MEF or PRISM or ... Pin
GerVenson10-Nov-15 3:07
professionalGerVenson10-Nov-15 3:07 
AnswerRe: Indeed, why not MEF or PRISON or ... Pin
Duke Of Haren9-Nov-15 9:53
Duke Of Haren9-Nov-15 9:53 
GeneralRe: Indeed, why not MEF or PRISON or ... Pin
GerVenson9-Nov-15 22:05
professionalGerVenson9-Nov-15 22:05 
QuestionWhy not MEF? Pin
Tomas Takac8-Nov-15 23:23
Tomas Takac8-Nov-15 23:23 
QuestionDynamic install / uninstall Pin
w.jian8-Nov-15 21:49
w.jian8-Nov-15 21:49 
AnswerRe: Dynamic install / uninstall Pin
roks nicolas8-Nov-15 23:01
professionalroks nicolas8-Nov-15 23:01 
GeneralRe: Dynamic install / uninstall Pin
Petoj879-Nov-15 0:06
Petoj879-Nov-15 0:06 
GeneralRe: Dynamic install / uninstall Pin
roks nicolas9-Nov-15 2:04
professionalroks nicolas9-Nov-15 2:04 
AnswerRe: Dynamic install / uninstall Pin
#realJSOP9-Nov-15 3:25
mve#realJSOP9-Nov-15 3:25 
GeneralRe: Dynamic install / uninstall Pin
roks nicolas9-Nov-15 4:42
professionalroks nicolas9-Nov-15 4:42 

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

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