Plugin – part 1

Background

The concept of a software plugin is something that’s been around since at least the early ‘70s – maybe longer. I personally remember writing plugins in C and C++ as far back as the late ‘80s or early ‘90s. When I finally made the switch to .NET, I remember porting an old C++ plugin library to C# and using it in numerous customer projects after that. Fast forward to today and I have a .NET based NUGET package for plugins named: CG.Plugin that comprises my “go-to” tool for adding plugins to almost any kind of project. I also extended CG.Plugin with two other packages: CG.Plugin.FileSystem and CG.Plugin.Reflection. Each contains a strategy for loading plugins at runtime. Using my CG.Plugin package with either the CG.Plugin.Reflection, or CG.Plugin.FileSystem packages is a great way to quickly and easily add plugins to your next project.

 

Example Usage

Here’s an example of how to use my plugin library:

Let’s look at what this code does. It starts by making a dictionary of named arguments. The command objects that are contained in the plugin use those arguments for anything that must be passed in at runtime. The arguments can be anything. Their verbs can also be anything but they should be something that makes sense to your command(s).

Next, a PlugInBuilder object is created. I left the configuration of the builder hanging because it’s just a sample, but as I mentioned earlier, I currently have two strategies for loading command objects on behalf of a builder. Either of those strategies could have been added into this sample, at this point.

The plugin object itself is created using the builder’s Build method. The plugin exposes a collection of ICommand objects, but another (nicer) way to find commands is by verb and we demonstrate that in this sample. Here we see the ByVerb method returning a collection of commands with verbs that match either “verb1”, or “verb2”, or both. This allows for commands with more than one verb – as well as verbs that are implemented by more than one command.

Once a collection of commands is available, the code simply iterates through the collection and executes each one.

So what do these commands do? The answer is, anything you like! Any capability you want for your software that you may not use often, or features you may need to switch out with other versions after your code is deployed, or even features that weren’t conceived of when your product was delivered. Plugins are a very flexible way to extend the capabilities of your code without requiring recompiling or reconfiguring.

So hopefully, I’ve peaked your interest in plugins, at least a bit.

 

Library Internals

My plugin library starts with the IPlugin interface, which is shown here:

Any object that implements IPlugin exposes a collection of ICommand objects through the Commands property. There is also a Refresh method, which rebuilds that command collection using whatever strategy was added to the builder.

The ICommand interface is pretty simple. Here is the trimmed down source:

Any object implementing ICommand will expose a collection of strings in the Verbs property. Most commands will only have a single verb but it’s possible for them to have many verbs. Multiple verbs come in handy if a command is to be executed under more than one scenario.

All commands also expose a property named Priority, which is used to order the commands for any given plugin.

The only method on the ICommand interface is the ExecuteAsync method. This method is used to execute a command for a plugin. It accepts a collection of arguments and an optional cancellation token.

The class that implements the IPlugin interface is called PluginProduct and here is a trimmed down look at its source code:

The first thing to note is that the PluginProduct class is a Builder Product, so we know it will be created internally, by a PluginBuilder object. The class contains two properties: (1) Cache, which is a local copy of all the currently loaded commands, and (2) Strategies, which is a collection of all the loading strategies that were passed in from the builder.

The Commands property is implemented using the Cache property, with the code checking for an empty Cache collection, then using lazy loading, and the collection of loader strategies in the Strategies property, to fill the command collection on an as-needed basis. Before the contents of the Cache are returned, they are ordered using the Priority of each ICommand object.

The Refresh method simply clears out any existing ICommand objects in the Cache. That Cache will be refilled the next to a caller asks for the contents of the Commands property.

Like any Builder Product, the PluginProduct class is initialized through the OnInitialize method. In this method we start by looking for any and all IPluginLoaderProvider objects in the Providers collection. If you read about my Builder library before you’ll know that the Providers collection is populated by the Builder at build-time. It’s a collection of Provider objects injected into the Product by the Builder. Here, the Providers collection contains our collection of loader providers. We get those providers then we iterate through them and ask each one for a IPluginLoaderStrategy object. Those strategy objects are then stored in the Strategies property.

The only other method is the Dispose override, which simply cleans up any local resources when called by the .NET framework.

I mentioned two interfaces while walking through the PluginProduct class: (1) IPluginLoaderProvider, and (2) IPluginLoaderStrategy. There is a third interface that I didn’t mention but it’s related to the others: IPluginLoaderSetup. Together, these three interfaces complete the integration with the Builder library. Let’s look at the Setup interface first:

This interface says that any plugin loader related setup object is going to include a black and/or white list for assemblies, and another black and/or white list for commands. That means that any loader strategy will support very fine grained control over which assemblies get loaded and searched for command objects. It also means that any loader strategy can apply that same level of control over which individual command objects get loaded – regardless of which assembly they come from. Together, these four lists make it very easy to ensure that a plugin never loads anything it isn’t supposed to.

The class that corresponds to this interface is called PluginLoaderSetupBase. The source for that is shown here:

The class includes a full base implementation of the IPluginLoaderSetup interface and is intended as the starting point for creating concrete setup classes in other packages. Notice how the class also creates an association with a provider type. The Builder library will use that association to create the provider instance, at runtime.

The IPluginLoaderProvider interface is shown next:

Not that much going on here, the provider defers all the logic to strategy objects at runtime. Here is the code for the corresponding PluginLoaderProvider class:

This class is intended as a base for crafting concrete provider classes in other packages.

The only interface left to show is the one for the loader strategy:

So here we see that all plugin loader strategy objects support loading a collection of ICommand objects through a Load method.

Here is the source for the corresponding base strategy class:

Once again, this base class is intended as a starting point for crafting concrete loader strategy objects in other packages. Notice how the base class creates an association between the strategy, provider and setup types. That will come in handy in derived classes for obtaining strong references to any of the associated objects at runtime.

I haven’t really talked about how providers, setups, builders, and strategies work together. For that I highly encourage everyone to go look at the articles I wrote for my Builder library. I’ll focus on the plugin library itself here and assume that everyone understands how the Builder parts work.

The CG.Plugin package also contains a class called IPluginExtensions, which contains extension method related to the IPlugin type. That’s where we implement the ByVerb method, which we used in the sample code at the beginning of this article. Here is the trimmed down source for that class:

In this code we’re accepting an IPlugin object and using it’s Commands property to get a list of ICommand objects. From there, we use a LINQ query to filter that list down to commands whose

Verbs property contains at least one of the verbs passed into the method. Once we have that list, we prioritize the results using the Priority property on the ICommand type, then we return everything as a list.

 

Wrapup

That’s pretty much it for the CG.Plugin package itself. The loader strategies are implemented in the CG.Plugin.FileSystem and CG.Plugin.Reflection packages, not in the CG.Plugin package. Factoring that code out into separate packages keeps the CG.Plugin package itself light and focused. Of course, the downside of factoring that code into separate packages is that there are more packages to deal with. That’s a design compromise that I suspect most NUGET developers face, at one point or another.

I’ll cover the implementation of the two existing loader strategies in my next article, then I’ll cover how to add those strategies when constructing a plugin through a builder.

 

See ya next time.

 

The code for this article is part of my NUGET package CG.Plugin, which can be downloaded for free at

https://github.com/CodeGator/CG.Plugin

The source code for the CG.Plugin project lives on Github and can be obtained for free at

https://www.nuget.org/packages/CG.Plugin