Today, we’re going to embark on a new journey: building WPF applications that support plugins. There are a number of ways to build plugable applications, and I recently had the opportunity to play around with a few ideas. In this series we’ll explore some of our options and their merits. But first, let’s look at what a plug-in is.
Plug-ins: what are they, where do they come from, and what do they taste like?
Odds are you know what a plug-in is, from a user point of view, but many of us (yes, “us” means “developers” here) haven’t stopped to think about how they work.
A plug-in is a chunk of code, supplied by a library not directly part of the application, that extends (or alters) the application in some way. Plug-ins can be written by the application’s developer or by a third party. Either way, the application must be written to incorporate plug-ins at run time.
The most common method for supporting plugins is designating a folder into which plugins will be installed. The application then searches for specific basic functionality that allows the application to hook in the plug-in and run the functionality it contains.
While extensibility is probably the main reason that a plug-in architecture is used, another advantage is separate deployables. If an application is determined to have a bug, a plug-in architecture allows you to deploy a new version of the plug-in without having to redeploy the entire application. In a way, it’s like the Single Responsibility Principle applied at the application level.
What does a plugin look like for my application?
You’re probably ahead of me on this. If the application is to hook in plug-ins based on some basic functionality, it probably needs to know what that basic functionality is.
To facilitate this, the application author writes an abstraction that defines the required functionality to hook into the application, and then publishes it separately from the application. Then the application is coded to search a specified plug-in directory for implementations of that abstraction.
Because the abstraction only defines the contract, the plug-in is free to do just about anything once it’s loaded.
Let’s create a simple console application that uses a plug-in architecture. Our application will present the classic numerically-indexed menu which will present a number of options to print Hello, World! in a variety of languages.
Several plug-in projects, each one providing a single plug-in implementation.
To start, we need to define our plug-in. For us, this means an interface.
This completes the plug-in project. Well done.
The main application will build the menu, interpret the responses, and coordinate the plugins. We’ll also want it to have some built-in languages as “out of the box” functionality.
In building the menu, we want to present our built-in options first, then enumerate the options supported by the plug-ins.
Because the two most common languages here in Texas are English and Spanish, we’ll build those in. The plugins will provide other language options.
// worried about letting the user quit.
// be sure you validate!
Pretty simple. But what is that _LoadPlugins() method? I’ll give you a hint… it loads the plugins. To do that, we’ll need to get our plugin directory, scan the assemblies, and instantiate any classes that implement IHelloPlugin.
NOTE Ordinarily, I’d break this up into smaller methods like _PrintMenu() and _ProcessSelection() (or even dependency-injected, SOLID-compliant classes), but given this context, I think it’s better to be explicit than architecturally sound.
NOTE We could also implement English and Spanish as plug-ins that happen to ship with the application, but I wanted to showcase that the application will usually have inherent functionality instead of merely being a plug-in host.
Scan any assemblies for implementations of IHelloPlugin.
Return a collection of instances.
Getting the plugin directory is simple. Let’s just put it in an app setting.
That’s it. The main application is done. If we were to run it now, we’d see a menu with two options.
From here, we pick a language and get the expected output.
We have our framework, now we need to pretend to be a third-party developer who wants to augment this application to add more languages.
In one of the plug-in projects, we’ll create a new class to implement IHelloPlugin.
We can repeat this ad nauseum to support other languages.
We’ve created a fairly useless application which is completely extensible via plugins. Furthermore, if any language is incorrect (except for English and Spanish), we just need to deploy an update for that plug-in; we don’t need to redeploy the entire application.
What’s not completely evident in this example is that the plugin doesn’t have to merely return the message. It has full control of the application once GetMessage() is called. This means that the developer is free to prompt the user for their age, make web service calls, or just about anything else.
NOTE This brings up an important issue. Since the plugin has control, it really can do anything. This includes actions that may pose a security threat to the end user. This is an issue that needs to be considered when creating a plug-in-based application. You may want to consider an approval process to certify plug-ins for use with the application.
Next time, we’ll look at the primary issue with this approach as well as its solution. Then we’ll discuss the benefits and consequences of each.
Hi Greg, I think that should be typeof(IHelloPlugin).IsAssignableFrom(t).
Thanks, Ian. The post has been updated, now complete with pro tip!