Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

A Flexible Plugin System

, 3 Sep 2008
A generic plugin system used to load and manage plugins
fadd-15373.zip
trunk
dlls
xunit.dll
Examples
Plugins
ExampleApplication.Shared
Properties
ExampleApplication
Properties
ExamplePlugin.Shared
Properties
ExamplePlugin
Properties
Fadd.Globalization.Yaml
fadd.snk
Properties
Tests
fadd
Commands
Net
Tests
fadd.snk
Globalization
Tests
Logging
Plugins
Properties
Tests
Validation
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)

About the Author

jgauffin
Founder Gauffin Interactive AB
Sweden Sweden
Founder of OneTrueError, a .NET service which captures, analyzes and provide possible solutions for exceptions.
 
blog | twitter
Follow on   Twitter   LinkedIn

| Advertise | Privacy | Mobile
Web04 | 2.8.140718.1 | Last Updated 3 Sep 2008
Article Copyright 2008 by jgauffin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid