Log in

Subscribe to site updates.

Easy C# Plugins with Caraway - Part Two

Posted by Nicholas Blumhardt | August 09, 2007 13:37

This is part two of a series discussing the implementation of plugin architectures using the Caraway IoC container. If you haven't already read part one, hop on over there now and get up to speed :)

There are two things that almost always come up when implementing plugins:

  1. Plugins need to call back into different parts of the host application, and
  2. Plugins need their own configuration parameters and these will be different for each plugin

Let's see how to deal with these issues using Caraway.

Exposing Application Services to Plugins

Not all calculations that our calculator can perform will give correct results. Division, for instance, cannot be performed with a divisor of zero.

Rather than having our plugins throw exceptions in these circumstances, we'd like them to use a notification service than can determine the correct behaviour based on the context in which the application is running.

We define our notification service as an INotifier interface:

namespace Caraway.Example.Calculator.Api
{
    public interface INotifier
    {
        void Notify(string message);
    }
}

This is pretty simple and not something you'd see in a real application, but you can imagine in this situation we have plenty of options for implementing our notifier that can be wired into the app: we could throw an exception with the provided message, print the message to the command line, or show a message box.

For this example, the application provides a MessageBoxNotifier that simply displays the provided message:

namespace Caraway.Example.Calculator
{
    class MessageBoxNotifier : INotifier
    {
        public void Notify(string message)
        {
            MessageBox.Show(message);
        }
    }
}

Now in the application startup code we'll register our MessageBoxNotifier as the implementation for the INotifier service:

container.Register<INotifier>(typeof(MessageBoxNotifier));

The result of this registration is that any other component registered in the same container can access the INotifier service by declaring it as a construction parameter. In our division plugin:

public Divide(INotifier notifier)
{
    _notifier = notifier;
    ...

The division plugin is registered through the application configuration file, just like the addition plugin:

<component
    type="Caraway.Example.Calculator.Division.Divide, Caraway.Example.Calculator.Division"
    service="Caraway.Example.Calculator.Api.IOperation" />

When the container creates our division plugin, it will see the dependency on an INotifier service and locate the MessageBoxNotifier instance that we have registered. Our division plugin simply uses the notifier like it would if we'd passed the service in explicitly:

public double Apply(double lhs, double rhs)
{
    if (rhs == 0.0)
    {
        _notifier.Notify("Cannot divide by zero.");
        return 0.0;
    }
    ...

(Notice that we've cheated a bit since the last article and changed the type handled by our calculator from int to double: more on that in the next section!)

What Have We Achieved?

Our division plugin can access the notification service provided by the application, while our addition plugin remains unchanged. Plugins can even access services exposed by other plugins, and our application doesn't need to be concerned with any of the plumbing at all - the container does this for us.

Passing Configuration Parameters

Configuration of individual plugins is handled in a similar way: just add the configuration parameters to the plugin's constructor. In this case we'd like the results of our division operations rounded to a fixed number of decimal places. The complete division plugin is listed below:

namespace Caraway.Example.Calculator.Division
{
    public class Divide : IOperation
    {
        INotifier _notifier;
        int _places;

        public Divide(INotifier notifier, int places)
        {
            if (notifier == null)
                throw new ArgumentNullException("notifier");

            _notifier = notifier;
            _places = places;
        }

        #region IOperation Members

        public string Operator
        {
            get
            {
                return "/";
            }
        }

        public double Apply(double lhs, double rhs)
        {
            if (rhs == 0.0)
            {
                _notifier.Notify("Cannot divide by zero.");
                return 0.0;
            }

            return Math.Round(lhs / rhs, _places);
        }

        #endregion    
    }
}

To pass the places parameter to the plugin, we add it to the configuration file:

<component
    type="Caraway.Example.Calculator.Division.Divide, Caraway.Example.Calculator.Division"
    service="Caraway.Example.Calculator.Api.IOperation" >
    <parameters>
        <parameter name="places" value="4" />
    </parameters>
</component>

Crank up the calculator and see the results! The example is included in the beta 2 distribution of the project.

What about optional parameters and dependencies? Coming soon...

Comments

RSS feeds only Intro ...

Posted by G|oS|co | August 11, 2007 10:41

The RSS feed of this article on contains the intro and not the whole article. Is that by design?

Posted by Nicholas Blumhardt | August 12, 2007 11:32

Yes - these articles seemed a bit long to make you scroll through in Reader... I can change that though if you like..?

Your Comment





Reset

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.