Click here to Skip to main content
15,886,063 members
Articles / Programming Languages / C#

A Flexible Plugin System

Rate me:
Please Sign up or sign in to vote.
4.98/5 (25 votes)
3 Sep 2008LGPL34 min read 130.8K   1.8K   163  
A generic plugin system used to load and manage plugins
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security;
using System.Security.Policy;

namespace Fadd.Plugins
{
    /// <summary>
    /// This class is responsible of loading all modules in a system.
    /// </summary>
    public class PluginManager<T> : IApplication where T : class, IPlugin
    {
        private const string DefaultPath = "*.dll";
        private readonly IApplication _pluginManager;
        private readonly List<T> _plugins = new List<T>();
        private string _pluginPath = DefaultPath;

        private static readonly Evidence DefaultEvidence = new Evidence(
            new object[] {new Zone(SecurityZone.Internet)},
            new object[] {});

        /// <summary>
        /// Initializes a new instance of the <see cref="PluginManager&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="application">Interface exposed to plugins.</param>
        public PluginManager(IApplication application)
        {
            _pluginManager = application;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PluginManager&lt;T&gt;"/> class.
        /// </summary>
        public PluginManager() : this(null)
        {
        }

        /// <summary>
        /// Access a loaded plugin.
        /// </summary>
        /// <value>plugin if found; otherwise null.</value>
        public T this[string pluginName]
        {
            get
            {
                foreach (T plugin in _plugins)
                {
                    if (string.Compare(plugin.PluginName, pluginName, true) == 0)
                        return plugin;
                }

                return null;
            }
        }

        /// <summary>
        /// Folder in which plugins resides.
        /// </summary>
        /// <remarks>Can also contain wildcards</remarks>
        /// <example>
        /// <code>
        /// pluginMgr.Path = "plugins\\plugin*.dll";
        /// </code>
        /// </example>
        public string PluginPath
        {
            get { return _pluginPath; }
            set { _pluginPath = value; }
        }

        /// <summary>
        /// Called when a plugin assembly is about to be loaded.
        /// </summary>
        /// <param name="typeInfo">Information about the assembly.</param>
        /// <returns>Evidence that the <see cref="Assembly"/> should be loaded with.</returns>
        /// <remarks>
        /// The assembly is used to determine which security settings a plugin <see cref="Assembly"/> should be loaded with.
        /// </remarks>
        protected virtual Evidence GetEvidence(PluginTypeInfo typeInfo)
        {
            EvidenceRequestedEventArgs eventArgs = new EvidenceRequestedEventArgs(typeInfo);
            EvidenceRequested(this, eventArgs);
            return eventArgs.Evidence ?? DefaultEvidence;
        }

        /// <summary>
        /// Loads modules with the correct <see cref="Evidence"/> and correct protection level.
        /// </summary>
        protected void LoadModules()
        {
            PluginFinder finder = new PluginFinder();
            try
            {
                IList<Type> type = new List<Type>();
                type.Add(typeof (T));
                finder.Find(_pluginPath, type);
            }
            catch (ReflectionTypeLoadException err)
            {
                Console.WriteLine(err);
                foreach (Exception e in err.LoaderExceptions)
                    Console.WriteLine(e);
            }
            catch (TypeLoadException err)
            {
                Console.WriteLine(err);
            }

            foreach (PluginTypeInfo typeInfo in finder.Plugins)
            {
                Evidence evidence = GetEvidence(typeInfo);
                Assembly assembly = Assembly.Load(typeInfo.Location, evidence);
                foreach (Type type in typeInfo.Types)
                {
                    if (!typeof (T).IsAssignableFrom(type))
                        continue;

                    T loadedPlugin = (T) Activator.CreateInstance(type);
                    OnPluginLoaded(loadedPlugin, assembly, typeInfo);
                    _plugins.Add(loadedPlugin);
                }
            }
        }

        /// <summary>
        /// Loads all modules that can be loaded (their dependencies have been loaded.)
        /// </summary>
        /// <param name="loaded"></param>
        /// <param name="dependent"></param>
        /// <returns></returns>
        /// <remarks>Call this method as long as it returns true.</remarks>
        private bool LoadModules(IDictionary<string, T> loaded, IDictionary<string, T> dependent)
        {
            foreach (KeyValuePair<string, T> pair in dependent)
            {
                bool dependenciesMissing = false;
                foreach (string dependency in pair.Value.Dependencies)
                {
                    if (loaded.ContainsKey(dependency))
                        continue;
                    dependenciesMissing = true;
                    break;
                }
                if (dependenciesMissing)
                    continue;

                dependent.Remove(pair.Value.PluginName);
                loaded.Add(pair.Value.PluginName, pair.Value);
                StartPlugin(pair.Value);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Called when a plugin have been loaded.
        /// </summary>
        /// <param name="plugin">Module being loaded.</param>
        /// <param name="assembly">Assembly that the plugin exist in.</param>
        /// <param name="typeInfo">information about the plugin</param>
        /// <remarks>Plugin have not been started yet.</remarks>
        protected virtual void OnPluginLoaded(T plugin, Assembly assembly, PluginTypeInfo typeInfo)
        {
            PluginLoaded(this, new PluginLoadedHandlerEventArgs<T>(plugin, assembly, typeInfo));
        }

        /// <summary>
        /// Loads all modules
        /// </summary>
        public void Start()
        {
            LoadModules();

            Dictionary<string, T> loadedModules = new Dictionary<string, T>(),
                                  dependencyModules = new Dictionary<string, T>();

            // Put all modules in the dependent side
            foreach (T plugin in _plugins)
                dependencyModules.Add(plugin.PluginName, plugin);

            // Load a plugin at a time (since foreach breaks when something is removed)
#pragma warning disable 642
            while (LoadModules(loadedModules, dependencyModules)) ;
#pragma warning restore 642

            foreach (KeyValuePair<string, T> pair in dependencyModules)
                throw new InvalidOperationException("Module '" + pair.Key + "' is missing dependency.");
        }

        /// <summary>
        /// Calls start in a <see cref="IPlugin"/>.
        /// </summary>
        /// <param name="plugin">plugin to start</param>
        /// <exception cref="ArgumentNullException"></exception>
        protected virtual void StartPlugin(T plugin)
        {
            Check.Require(plugin, "plugin");

            ApplicationRequestedEventArgs<T> args = new ApplicationRequestedEventArgs<T>(plugin);
            ApplicationRequested(this, args);
            plugin.Start(args.Application ?? _pluginManager ?? this);
        }

        /// <summary>
        /// A plugin have been loaded into the system
        /// </summary>
        public event PluginLoadedHandler<T> PluginLoaded = delegate { };

        /// <summary>
        /// A plugin have been started.
        /// </summary>
        public event PluginStartedHandler<T> PluginStarted = delegate { };

        /// <summary>
        /// An plugin assembly is about to be loaded and the system needs to know which <see cref="Evidence"/> the plugin assembly should be loaded with.
        /// </summary>
        /// <remarks>
        /// Plugins are loaded into the Internet zone per default.
        /// </remarks>
        public event EvidenceRequestedHandler EvidenceRequested = delegate { };

        /// <summary>
        /// Called when a plugin is about to be started and we need to get the application interface
        /// that should be used when starting the plugin.
        /// </summary>
        public event ApplicationRequestedHandler<T> ApplicationRequested = delegate{};
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Founder 1TCompany AB
Sweden Sweden

Comments and Discussions