Den Delimarsky

I am an engineer working on API documentation, security and machine learning.

github twitter linkedin rss

Implementing application extensibility

May 10, 2010
5 minutes read

There are cases, when the functionality provided with the application is just not enough. Or maybe it is enough, but some people want to use it a bit different – either adapt it to their own needs or implement something that would add value to existing features.

I actually encountered the extensibility problem in one of my projects, WeatherBar. Initially, I built it around the Google Weather API. Fair enough, it worked well for a lot of locations and it was quite simple. However, later people reported that they cannot find their location, but it was available on other weather services. Another group of people mentioned the fact that the reported weather conditions weren’t precise enough. So I thought – I am not able to integrate all possible weather API implementations in one single application. Besides, why would I do it if someone is using only one of them?

The solution to this is quite simple – plugins. An application can be extended by separating some of its functionality to external assemblies. In my specific case, for the next release of WeatherBar I decided not to hardcode the weather service API calls. Now, it will be available as a plugin. I am still bundling the base assemblies that include the Google Weather plugin, but it is optional. If there is another plugin that can show other weather conditions, I am perfectly fine with users consuming the data provided by it instead of mine.

However, there are several restrictions I am implementing, and I would generally recommend having them implemented when providing basic extensibility.

Set a template for the plugin

By this I mean that there should be a well-defined plugin structure. For my application, I implemented an interface that sets the base properties and methods that must be implemented in the plugin assembly. I generally don’t care about any other methods or properties exposed by that DLL, however the ones outlined in the interface have to be there for the plugin to work with my app. Later on, some developers might want to implement some specific restrictions (for example, no windows can be opened by the plugin or a specific security signature must be used for the plugin to be recognized), however at this point, I see no need in such limitations (mostly because of the size and purpose of the application).

Have a well-defined plugin architecture

By this I mean a well know structure, that defines the way the plugins are developed and used. In my case, I used a three-layered approach:

Image has been lost

The DevKit is the foundation – it offers the inherited interfaces as well as some helper methods and functions. It is included in the application and it is referenced when a plugin is developed. In the WeatherBar build I am currently working on, I am checking the passed assemblies for types that inherit the needed interface, that is supported by the DevKit. If it is found, the a valid plugin is available.

Here is the basic structure of the IWeather interface that I declare to provide extensibility for weather API providers:

public interface IWeather
{
    void Get(string location);
    string CurrentTemp { get; set; }
    string Condition { get; set; }
    string Wind { get; set; }
    string Time { get; set; }
    Dictionary<string, string> Forecast { get; set; }
    string Scale { get; set; }
    string Humidity { get; set; }
}

As you see, there is nothing complicated here. However, the plugin developer will need to specify all implementations for the application to work correctly.

Then, in the main application I am loading the needed assemblies this way:

List<object> LoadPlugins()
{
    List<object> plugins = new List<object>();
    DirectoryInfo directory = new DirectoryInfo(Directory.GetCurrentDirectory() +"\\plugins");

    foreach (DirectoryInfo dir in directory.GetDirectories())
    {
        FileInfo[] files = dir.GetFiles("plugin.dll");
        Assembly assembly = Assembly.LoadFrom(files[0].FullName);
        foreach (Type type in assembly.GetTypes())
        {
            if (typeof(IWeather).IsAssignableFrom(type) && type.IsAbstract == false)
            {
                IWeather weather = type.InvokeMember(null, BindingFlags.CreateInstance, null, null, null) as IWeather;
                plugins.Add(weather);
            }
        }
    }

    return plugins;
}

By default, I consider that a plugin name is the name of the folder it is placed in and the plugin assembly is called plugin.dll. The architecture can differ, but this is how I decided to go in this case. Later on, I will add a XML manifest here as well.

Also, naming and description convention is needed. My idea for this was to bundle a XML manifest to carry the plugin description. Unique names for the plugins will be set by unique folders (that will contain the DLL and the manifest) that will be placed inside one, main – plugins folder.

Having a well planned architecture will ensure the usability and will help you avoid conflicts (for example, for plugins with the same name).

Think ahead

Chances are, you want to preserve the plugin architecture for a while. And even if you don’t you don’t want a small change to render other plugins unusable. Therefore, think carefully when defining the interfaces. An additional property or method will basically require all other plugins to be rewritten to adapt to new conditions. Detailed planning will save headaches later.

I just started with introducing extensibility to my app, and honestly – I feel like this is going to be one of the biggest additions to the project, that should make the application easily adaptable to individual situations.

Not every project should have extensibility features, though, so I don’t recommend splitting one big thing just for the sake of splitting it and bundling multiple DLLs (I know there are people who do that). It won’t add much to the overall project value, but will increase the number of possible compatibility and interoperation issues.


Back to posts