Easy C# Plugins with Caraway
Posted by Nicholas Blumhardt | July 24, 2007 14:39
This is the first part in a series of tutorials on implementing pluggable architectures with the Caraway IoC container.
The Application
The program we're going to write is a kind of calculator. Its admittedly a contrived example but anything big enough to be interesting in the real-world sense would be too complex to make a good article. Bear with me :)
Our calculator accepts two numbers and applies our chosen operator to give a result.
We could choose a fairly large set of operators to hard-code into our program, but to make things interesting, we're going to create an API allowing new operators to be plugged in to our calculator.
The completely uninspired interface looks like this:

Core
Under the hood the ICalculator facade will present the core functionality to the UI layer:
interface ICalculator
{
IEnumerable<string> AvailableOperators { get; }
int ApplyOperator(string op, int lhs, int rhs);
}
From each of our 'operation' plugins we need something along these lines:
interface IOperation
{
string Operator { get; }
int Apply(int lhs, int rhs);
}
The implementation of the calculator just pulls together the various operations:
class Calculator : ICalculator
{
IDictionary<string, IOperation> _operations = new Dictionary<string, IOperation>();
public Calculator(IEnumerable<IOperation> operations)
{
foreach (var operation in operations)
_operations[operation.Operator] = operation;
}
public IEnumerable<string> AvailableOperators
{
get
{
return _operations.Keys;
}
}
public int ApplyOperator(string op, int lhs, int rhs)
{
return _operations[op].Apply(lhs, rhs);
}
}
Assemblies
So, our calculator takes a bunch of operations defined somewhere. While we could do ourselves some favours right now and come up with a good deployment plan, we'll do the bare minimum and create just what we need:
| Assembly | References | Purpose |
|---|---|---|
| Caraway.Example.Calculator |
| Main program and UI |
| Caraway.Example.Calculator.Api | Interfaces shared between the main program and the plugins, e.g. IOperation | |
| Caraway.Example.Calculator.Addition |
| Plugin implementing the + operation |
We separate the IOperation interface into the API DLL so that it can be shared between our calculator and
the plugins. The important things to note here are that the program does not depend on the plugins, and neither the plugins
nor the API depend on Caraway at all.
A Plugin for Addition
So, let's implement our first plugin - addition:
public class Add : IOperation
{
public string Operator
{
get
{
return "+";
}
}
public int Apply(int lhs, int rhs)
{
return lhs + rhs;
}
}
Configuration
By now you're probably wondering where the container comes into this. We've described our architecture without even referring to it, and without even using the word plugin in our definitions. That's the neat thing about this approach - your code stays focussed on implementing the application features rather than plumbing.
The magic is all in our startup code:
public static void Main(string[] args)
{
using (IInjectionContainer container = new InjectionContainer())
{
container.Register<ICalculator>(
c => new Calculator(c.Resolve<IEnumerable<IOperation>>()));
container.RegisterCollection(new CollectionRegistration<IOperation>()));
var config = new ContainerConfiguration("calculator");
config.Configure(container);
var calculator = container.Resolve<ICalculator>();
Application.Run(new CalculatorForm(calculator));
}
}
IInjectionContainer.Register<T>() makes our ICalculator service available through the Calculator component. Using a lambda to register the component gives us lazy instantiation without sacrificing type safety. We could more simply have written:
container.Register<ICalculator>(typeof(Calculator));
...in C# 2.0 or if we didn't mind having the service created by the container using reflection. If you're not used to programming with functional constructs like lambdas in C# 3.0, just step through the code in a debugger and all will become clear very quickly.
The next line, container.RegisterCollection(new CollectionRegistration<IOperation>())) tells the container that when we register components exposing the IOperation service, it should keep all of them in a list rather than just the most recent single registration as it would do by default.
The configuration file is where the action's at. The important line is this:
<component type="Caraway.Example.Calculator.Addition.Add, Caraway.Example.Calculator.Addition" service="Caraway.Example.Calculator.Api.IOperation" />
That enlists our Add operation as a plugin supporting the IOperation interface.
Running the application you should now be able to perform the remarkable feat of integer addition!
So what have we achieved?
I hope it comes across in the examples above that we've been building an application, rather than some kind of framework. The whole purpose of these techniques is to ditch the endless plumbing code, metadata and imperative, attribute-driven gunk that turns up in real-life applications and leverage the very powerful base features of the C# language to build clean architectures.
In the next article, we'll build a 'division' plugin that shows off the features that make Caraway really great for simple plugin architectures:
- Giving plugins access to services exposed by the application
- Passing configuration parameters to plugins
The source code for this article contains a pre-built version of Caraway to get you started. You'll need to have a copy of Visual Studio Codename "Orcas" to use it.
Files
Comments
There are no comments on this article.
Disclaimer: These articles represent the opinions of the authors and may not match the official position of Ubik Systems Pty. Ltd. Confirmation should be sought on all matters involving professional advice.

Your Comment
Reset