Giving your user the opportunity to add new modules to your software on a plug & play basis during run-time improves the usability and functionality of you application marginally.

This blog will guide you on how to build your own plug & play system. The use-case we take here is a Data Acquisition module that allows us to add new Device Protocols at run-time. We have two parts to this solution – the parent application that handles data acquisition and the module device protocol. We need a shared project that allows the interaction between the two parts – introducing Interfaces.

A interface indicates the properties and methods available in a class but not its implementation which is defined in a class derived from the interface. The below interface IDevice is shared between the application and the module through a class library.

public interface IDevice
{
    object Configuration { get; }
    object Values { get; }

    bool Start();
    bool Stop();
}

Now lets build our two devices in a class library and compile it as separate DLLs. The IDevice class should be derived from the earlier class library we defined that is to be shared.


    public class RTUDevice : IDevice
    {
        public object Configuration { get; }
        public object Values { get; protected set; }

        public RTUDevice(object configuration)
        {
            Configuration = configuration;
        }

        public bool Start() { }
        public bool Stop() { }
    }

    public class TCPDevice : IDevice
    {
        public object Configuration { get; }
        public object Values { get; protected set; }

        public TCPDevice (object configuration)
        {
            Configuration = configuration;
        }

        public bool Start() { }
        public bool Stop() { }
    }

Now lets get to our application that support plug & play. For the purpose of this tutorial we will consider a new module is added to a folder that will be loaded from the application in run-time. We build this logic into our Factory Pattern that allows us to create objects for the given interface by loading them from the folder.

public static class DeviceFactory
{
    private static ILogger _logger = Log.ForContext("SourceContext", "DeviceFactory");
    private static readonly Dictionary<string, Type> Types = LoadDevicesFromAssembly(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Devices"));
    private static Dictionary<string, Version> AssemblyVersions;

    private static Dictionary<string, Type> LoadDevicesFromAssembly(string assemblyPath)
    {
        Dictionary<string, Type> deviceTypes = new Dictionary<string, Type>();
        AssemblyVersions = new Dictionary<string, Version>();

        IEnumerable<string> assemblyFiles = Directory.EnumerateFiles(assemblyPath, "*.dll", SearchOption.AllDirectories);

        foreach (string assemblyFile in assemblyFiles)
        {
            try
            {
                Assembly assembly = Assembly.LoadFile(assemblyFile);
                string name = assembly.GetName().Name;
                Version version = assembly.GetName().Version;

                if (AssemblyVersions.ContainsKey(name))
                {
                    if (AssemblyVersions[name].CompareTo(version) > 0)
                    {
                        continue;
                    }
                    else
                    {
                        deviceTypes.Remove(name);
                        AssemblyVersions[name] = version;
                    }
                }
                else
                {
                    AssemblyVersions.Add(name, version);
                }

                foreach (Type type in assembly.ExportedTypes)
                {
                    try
                    {
                        if (type.IsClass && typeof(IDevice).IsAssignableFrom(type))
                        {
                            deviceTypes.Add(name, type);
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.Error(ex, "IDevice Assignable");
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Load Device Assembly");
            }
        }

        return deviceTypes;
    }

    public static IDevice CreateDevice(
        string type,
        object configuration)
    {
        if (Types.ContainsKey(type))
        {
            try
            {
                IDevice device = Activator.CreateInstance(
                    Types[type],
                    new[] { configuration}) as IDevice;
                return device;
            }
            catch (Exception ex)
            {
                _logger.Error(ex, "Create Device for Type:{type}", deviceConfiguration.Type);
                return null;
            }
        }
        else
        {
            _logger.Debug("Module not found for {type}", deviceConfiguration.Type);
            return null;
        }
    }
}

Here we have added another feature that allows loading multiple versions of a module and the latest module can be used for initializing a class. However existing initialized objects with the older versions should be reinitialized.

Wifi vector created by stories – www.freepik.com

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *