Plugin – part 3

Review

Last time I covered the internals of the CG.Plugin.FileSystem NUGET package, which contains a loader strategy for the CG.Plugin NUGET package. This time I’ll cover another loader strategy, this time one from the CG.Plugin.Reflection package. That package uses reflection to look for command types in the assemblies that are already loaded into the current AppDomain.

 

CG.Plugin.Reflection

Let’s start our review with the ReflectionSetup class, whose code is shown here:

Usually, we start with a public interface and then write an abstract base class to go with it but this time, we aren’t adding any new setup properties to the IPluginLoaderSetupBase type, so we don’t need to extend anything with an additional interface.

Let’s go look at the provider class next. The trimmed down code for the FileSystemProvider class is shown here:

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

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

The strategy class is the only non-trivial class in the package. Because there’s some complexity here, let’s cover things one method at a time.

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

The Load method starts by calling a method named DiscoverAssemblies. That method is reponsible for looking in the current AppDomain and finding all the loaded assemblies, then filtering those assemblies using the optional black and while lists in the Setup.

Once a 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 three sub-methods, DiscoverAssemblies, DiscoverTypes and CreateCommands, in more detail.

DiscoverAssemblies looks in the current AppDomain and returns a raw list of loaded assemblies. This list constitutes everything that the current application has previously loaded into memory. From there, the list is further filtered using optional black and/or white lists from the setup. Notice that any errors in this method are collected and then thrown together at the end. This allows us to know all the reflection related errors before we stop the process and to report them all together at the end.

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 ReflectionSetup type to a builder before the Build method is called. Let’s look at that class now:

So here we create a ReflectionSetup 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 the first article I wrote for the CG.Plugin package, I supplied a quick example of how to create a plugin object. 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 so that the builder uses our ReflectionStrategy type. Let’s look at the code again. Here was the original code:

Here is the same code, modified to use our reflection loader strategy:

That example now loads the proper loader strategy, which looks in the current AppDomain, at all the previously loaded assemblies, and creates instances of all the types that implement ICommand.

Final Thoughts

I’ve used plugins in one form or another for many years. I’ve created a few .NET specific plugin libraries in the past. This plugin library, along with it’s assorted loader strategies, is different in that it integrates closely with my builder library, which makes it very easy to add plugins in places where I would normally have to go invent mechanisms for configuration, creation, lifetime management, and more, just to load a simple plugin into my code. I won’t claim this plugin mechanism is remarkably better than others that are out there. I’ll only claim that this one works well and integrates nicely with my other CODEGATOR NUGET packages.

 

I hope you’ve enjoyed the article. Have fun with the code!

 

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

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