views:

184

answers:

5

I'm working on a system monitoring application similar to Nagios in C#. I have a plugin interface defined as:

public interface IPlugin
{
    PluginResult Execute();
}

Each plugin, depending on its functionality, will have a variable number of arguments. As an example, a ping plugin might take a hostname, # of packets, timeout value, etc. I want the user to be able to define these arguments per service in my user interface, but obviously these arguments won't be known until the application discovers which plugins are available. I'm curious as to how others might design a plugin such that these variable arguments would be discoverable by the application.

Right now, as an example, I've got a ping plugin:

public class PingPlugin : IPlugin
{
    private const string RESULT_MESSAGE = "Average ms: {0}; Packet loss: {1}";

    private string _hostname;
    private int _packets;
    private int _timeout;
    private int _warningTimeThreshold;
    private int _warningLossThreshold;
    private int _errorTimeThreshold;
    private int _errorLossThreshold;

    public PingPlugin(
        string hostname,
        int packets,
        int timeout,
        int warningTimeThreshold,
        int warningLossThreshold,
        int errorTimeThreshold,
        int errorLossThreshold)
    {
        _hostname = hostname;
        _packets = packets;
        _timeout = timeout;
        _warningTimeThreshold = warningTimeThreshold;
        _warningLossThreshold = warningLossThreshold;
        _errorTimeThreshold = errorTimeThreshold;
        _errorLossThreshold = errorLossThreshold;
    }

    public PluginResult Execute()
    {
        // execute the plugin
    }
}

I thought I might be able to discover the constructor parameters using reflection and present the user with a property grid to allow the configuration of the plugin, but I'm not sure the best way to provide a set of default values with this design. What might some alternatives be?

+14  A: 

Have you considered looking at the Managed Extensibility Framework?

Mystere Man
Vote up for this, MEF is amazing. No need to reinvent the wheel here, seriously.
gaearon
Can't support this enough! Why does everyone insist on inventing **YET ANOTHER PLUG-IN** system, even after MEF?? <shakes head in disbelief>
marc_s
MEF allows for discovery and injection, but it does not answer the fundamental question, which is how to expose and wire up parameters dynamically to the plugin object. MEF is not, in and of itself, a plugin system. There is still architectural work to do in deciding which services should be exposed and how the user will interact with the plugins.
Dan Bryant
A plugin system doesn't have to be overly complex. Why use MEF if I don't need all that it has to offer? If I can keep my plugin code to a couple of small(ish) class files, it makes it that much easier for another developer to pick up maintenence of the app later without having to know MEF or understand how I'm using it. I understand the recommendation, and appreciate it, but I really can't understand the hostility that goes along with...
Chris
@Chris: That is how it always begins. The problem is feature creep is almost unavoidable. So the next developer may have to wad through 30 class files thinking to himself "Why isn't he using MEF?"
Guvante
+1  A: 

You can apply the [DefaultValue] attribute to the parameters.

In C# for, you can use new syntax for this: int warningLossThreshold = 30,

SLaks
A: 

I voted +1 for the MEF answer too, it will solve many of your problems.

However, if you want to do it without MEF, it seems to me that you are missing some way to have the plugins tell your application via metadata, about the parameters it require.

One possible design could be this: Have an IPluginProvider interface, which your application can discover. This should have a parameterless constructor, so you can easily new up an instance. It should then have methods that return whatever metadata is needed (such as "pretty names" for the parameters, which are required, what are some sensible defaults, and so on). It should then include CreateInstance method, which takes the actual parameters as IDictionary<string,object> and returns the actual IPlugin instance.

driis
+3  A: 

Rather than have a Plugin constructor determine the parameters, you might consider something like this:

public interface IPlugin
{
    PluginResult Execute(Object parameters);
}

public class PingParameters
{
    //Various parameters here, including [Description] and [DisplayName] attributes if you wish
}

public class ParametersTypeAttribute : Attribute
{
    public Type Type { get; private set; }

    public ParametersTypeAttribute(Type type)
    {
        Type = type;
    }
}

[ParametersType(typeof(PingParameters))]
public class PingPlugin : IPlugin
{
    public PluginResult Execute(Object parameters)
    {
        return Execute((PingParameters) parameters);
    }

    private PluginResult Execute(PingParameters parameters)
    {
        //Your execution code here
    }
}

This gives you more flexibility for the parameters, as you can add attributes, provide setter validation and even specify designer/converter integration for the property grid. The property grid hooks up directly to the parameters object.

Dan Bryant
I really like this idea and will explore it further.
Chris
This is a very simple idea, I like that. I looked at MEF once. It was way too much for a simple plugin architecture.
Chris Lively
@Chris, MEF is definitely worth looking into, as it does a very good job of handling discovery and instantiation/injection. There's also a good chance that it will become the standard way of doing this, since it is now included in the framework. If your plugin requirements become more complicated, with the plugin wanting to interact with the host environment through service interfaces, MEF is a good way to go. Of course, even if you use MEF to do the wiring, you'll still need something like this to encapsulate your parameters.
Dan Bryant
@Dan: The real question is in what the host app needs to know about the plugin. For me, it's typically not very much. My plugins add pages to a asp.net app. The Host in this case is simply a shell that just maintains security checks. So, all my host needs to know is which plugins are authorized and the assembly name to load.. Which is handled by a DB call. The plugins handle everything else. The parameters the host does pass is just the well defined security tokens.
Chris Lively
I don't have a need for individual plugins to communicate with each other through a service interface at all. Because the plugin part is simply the UI. I've separated all of the code from them into a common set of library assemblies that any module can directly reference. From this perspective, MEF was complete overkill.
Chris Lively
@Chris, the main benefits come when you start performing dependency injection of shared discovered services for looser coupling, which is where MEF shines. As your focus shifts toward making the code support unit tests, you will naturally tend toward injection of dependencies in addition to plugin discovery, at which point it's very helpful to have a standard infrastructure to lean on. It sounds like it's not useful yet, but it's worth thinking about. MEF is actually not all that complicated, but there is a fundamental shift in perspective it encourages that takes some time to integrate.
Dan Bryant
A: 

I haven't looked at the MEF (will do now).

I had a problem almost identical to yours, I solved it with Attributes.
I have a UI which (calls BL which) uses reflection to show all the available "services" (nothing more than appropriately decorated classes).

When the user selects a "service" further attributes drive the UI. The attribute "schema" is fairly straight forward, and allows for any number of parameters with any name. By introducing constants (with the attribute definition) you can standardise common things like "name" so that your services are consistent.

All the data is then stored in a Key-Value pair table.

The great thing about this is that you can just dump new / modified "service" assemblies in teh bin dir - no extra work required. The only dependency is the attribute definitions assembly - so keep this lean.

Source code is at CodePlex if you want to "steal" some :)

Adrian K