Plugin – part 2

Review

Last time I covered the internals of the CG.Plugin NUGET package, which contains a plugin mechanism for .NET applications. In that article I left out the implementation of the loader strategies, which is the code that loads the command objects into the plugin, since the loaders live in their own NUGET packages.

This time I’ll cover the loader strategy that lives in the CG.Plugin.FileSystem NUGET package.

 

CG.Plugin.FileSystem

Let’s start our review with the IFileSystemSetup interface, whose code is shown here:

So here we see that the setup for this package contains a single property that contains a path to a disk folder. The strategy will use that folder at runtime to load assemblies, so that they can be searched for any objects that implement the ICommand interface. This property is required and the strategy will fail if it doesn’t point to a valid directory.

Here is the trimmed down code for the corresponding FileSystemSetup class:

No surprises here, just code to set and get the root folder path.

The trimmed down code for the FileSystemProvider class is shown next:

The only thing this provider does is hand out instances of the FileSystemStrategy class. It does that in the implementation for the GetStrategy method, as shown above.

The FileSystemStrategy class itself has a little more going on, as shown below:

There’s a bit of code here so let’s cover things one method at a time.

The constructor simply passes the product and provider to the base class. Nothing else is going on there.

The Load method starts by getting the root folder path from the setup, then it uses the Path.GetFullPath method to ensure that the results are expanded into a complete path – even if the caller specified a relative path in the setup. The results are stored into the folderPath variable.

From there the code calls the DiscoverFiles method, which is used to find all the file-names in the path specified by the folderPath variable (the DiscoverFiles method is covered in detail later on).

Once the DiscoverFiles method has returned a list of file-names, that information is then passed into the LoadAssemblies method, which just like its name suggests, is used to load a collection of assemblies from disk (the LoadAssemblies method is covered in detail later on).

Once the collection of assemblies is available, that information is passed to the DiscoverTypes method, which is used to search each assembly for types that implement the ICommand interface (the DiscoverTypes method is covered in detail later on).

Once the collection of types is available it is then passed to the CreateCommands method, which is used to create an instance of each command type. That command collection is then returned to the caller.

Now, let’s look at the four sub-methods, DiscoverFiles, LoadAssemblies, DiscoverTypes and CreateCommands, in more detail.

DiscoverFiles uses the folderPath parameter to specify a disk location to use for the operation. We use the Directory.GetFiles method to enumerate the contents of the disk location, passing back a collection of DLL names in the process. Once we have that raw list of file-names we can optionally apply a white list, from the setup, to narrow the results down even more. If a white-list is available, the collection of file-names will be filtered down to only those files that were on the white-list AND where found in the root folder.

After we’ve applied any white-list to the file-name collection we can then turn around and apply another optional list, this time a blacklist, to perform even more filtering. If a black-list is available, the collection of file-names will be filtered down to only those files that were on the white-list AND where found in the root folder AND were not on the black-list.

Once the collection of file-names has been filtered out the wazoo we return it for further processing.

LoadAssemblies iterates through the list of file-names and loads each assembly into memory. This is performed as a separate step from the DiscoverFiles method so that we can report any errors in the assembly load separately from any errors we encounter while enumerating files in the root folder. Notice that any errors in this method are collected and then thrown together at the end. This allows us to catch all the errors before we stop the process and to report them at the end.

Assuming there are no errors during the assembly load, then the list of assembly references is passed back to the caller for further processing.

DiscoverTypes takes the list of assembly references and iterates through them, using reflection on each one, to produce a list of types that implement the ICommand interface. We find out which types implement ICommand with a simple LINQ query that looks for types are assignable from ICommand, are a class, are not abstract, and aren’t generic. We return the list of types for further processing. Notice again that, just like in LoadAssemblies, we collect any errors that occur during this process and throw them all together at the end.

CreateCommands is where we finally try to create actual command objects. This method uses the collection of types from DiscoverTypes and iterates through them, one at a time, using the Activator to creates each instance. Afterwards, the instances are collected into a list of ICommand references and returned to the caller. Here again, we collect any errors that occur during this process and throw them all together at the end.

That’s it for the strategy! The only other class to look at is the IBuilderExtensions class, which is used to house extensions methods for the IBuilder type. The method on this class adds our FileSystemSetup type to a builder before the Build method is called. Let’s look at that class now:

So here we create a FileSystemSetup object, add it to the builder, then return the reference so the caller can use it to configure the strategy.

Let’s pull everything together now and see how to use the strategy with a plugin…

 

Using this strategy

In my last article I supplied a quick example of how to create a plugin object and use it. In that example I left out the code that would have configured the builder to use a loader strategy. I’ll fix that now by adding a couple of lines of code to use our FileSystemStrategy type. Let’s look at the code again. Here was the original code:

Here is the same code, modified to use our file-system loader strategy:

That example now loads the proper loader strategy, which looks in your root folder and loads command objects dynamically from any assemblies in the folder – unless you’ve narrowed the range of options using a while or black list in the setup, of course.

Next time, I’ll go through the CG.Plugin.Reflection package, which contains another loader strategy that is similar to this one except that it looks for command in the assemblies that are already loaded into the current AppDomain.

 

See you then.

 

The code for this article is part of my NUGET package CG.Plugin.FileSystem, which can be downloaded for free at https://github.com/CodeGator/CG.Plugin.FileSystem

The source code for the CG.Plugin.FileSystem project lives on Github and can be obtained for free at https://www.nuget.org/packages/CG.Plugin.FileSystem