Click here to Skip to main content
15,883,705 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.6K   1.8K   163  
A generic plugin system used to load and manage plugins
using System;
using System.Collections.Generic;

namespace Fadd.Commands
{
    /// <summary>
    /// <para>
    /// This dispatcher is used to be able to specify which commands that can be invoked.
    /// </para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// A typical usage would be if you have a plugin system and you do not want to allow
    /// all plugins to be able to execute all commands. Then you create a proxy dispatcher
    /// and expose it to the plugins, instead of the core dispatcher which contains all command 
    /// mappings. In this way, your core system it still protected while plugins can access
    /// a subset of it.
    /// </para>
    /// </remarks>
    public class ProxyDispatcher : ICommandDispatcher
    {
        private static readonly List<Type> EmptyList = new List<Type>();
        private readonly ICommandDispatcher _dispatcher;
        private readonly List<Type> _executeCommands;
        private readonly List<Type> _handledCommands;
        private readonly ICommandDispatcher _parent;
        /// <summary>
        /// Occurs when an unhandled exception have been caught.
        /// </summary>
        public event UnhandledExceptionDelegate UnhandledExceptionThrown;

        /// <summary>
        /// This event is invoked when someone tries to add a handler for a command.
        /// </summary>
        public event ProxyHandler AddRequested;

        /// <summary>
        /// This event is invoked when someone tries to invoke a command.
        /// </summary>
        public event ProxyHandler InvokeRequested;

        /// <summary>
        /// Initializes a new instance of the <see cref="ProxyDispatcher"/> class.
        /// </summary>
        /// <param name="parent">Parent dispatcher (that is used by the rest of the system).</param>
        /// <param name="executeCommands">Commands that may be invoked.</param>
        /// <param name="handledCommands">Commands that may be handled.</param>
        public ProxyDispatcher(ICommandDispatcher parent, List<Type> executeCommands, List<Type> handledCommands)
        {
            if (parent == null)
                throw new ArgumentNullException("parent");
            if (executeCommands == null)
                throw new ArgumentNullException("executeCommands");
            if (handledCommands == null)
                throw new ArgumentNullException("handledCommands");

            _parent = parent;
            _dispatcher = new CommandManager(null, false);
            _executeCommands = executeCommands;
            _handledCommands = handledCommands;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ProxyDispatcher"/> class.
        /// </summary>
        /// <param name="parent">Parent dispatcher (that is used by the rest of the system).</param>
        /// <remarks>No commands are allowed unless you use the <see cref="InvokeRequested"/> event.</remarks>
        public ProxyDispatcher(ICommandDispatcher parent)
            : this(parent, EmptyList, EmptyList)
        {
        }

        #region ICommandDispatcher Members

        /// <summary>
        /// Add a command handler.
        /// </summary>
        /// <param name="type">Must be a class, an attribute.</param>
        /// <param name="handler">handler handling the command</param>
        /// <exception cref="ArgumentException">If handler have been added to that type already.</exception>
        /// <exception cref="InvalidOperationException">The specified command may not be handled.</exception>
        /// <example>
        /// Program.Commands.Add(typeof(MyCommand), OnMyCommand);
        /// </example>
        public void Add(Type type, CommandHandler handler)
        {
            if (!typeof (Command).IsAssignableFrom(type))
                throw new ArgumentException("Only commands may be added in this implementation.");
            if (AddRequested != null)
                AddRequested(this, new ProxyHandlerEventArgs(type));
            else if (!_handledCommands.Contains(type))
                throw new InvalidOperationException("You may not handle that command.");

            bool exists = _dispatcher.Contains(type);

            // Add the handler
            _dispatcher.Add(type, handler);

            // Add it to our real system wide dispatcher if it has not been added.
            if (!exists)
                _parent.Add(type, OnCommand);
        }

        /// <summary>
        /// Remove a command handler.
        /// </summary>
        /// <param name="type">type to remove</param>
        /// <param name="handler">delegated that was mapped to the type.</param>
        public void Remove(Type type, CommandHandler handler)
        {
            _dispatcher.Remove(type, handler);

            // Unhook this proxy if no handlers are left.
            if (!_dispatcher.Contains(type))
                _parent.Remove(type, OnCommand);
        }

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="command">command to invoke.</param>
        /// <param name="ignoreMe">Handled that should not be invoked.</param>
        /// <returns>true if command was handled.</returns>
        public bool Invoke(object source, Command command, CommandHandler ignoreMe)
        {
            Type type = command.GetType();
            if (InvokeRequested != null)
                InvokeRequested(this, new ProxyHandlerEventArgs(type));
            else if (!_executeCommands.Contains(type))
                throw new InvalidOperationException("Unauthorized attempt to invoke " + command.GetType().FullName + ".");

            // we will be calling ourself too since we've hooked up all requested commands
            return _parent.Invoke(source ?? this, command, ignoreMe);
        }

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="command">command to invoke.</param>
        /// <returns>true if command was handled.</returns>
        /// <exception cref="InvalidOperationException">If you may not invoke the specified command.</exception>
        public bool Invoke(Command command)
        {
            return Invoke(command, null);
        }

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="command">command to invoke.</param>
        /// <param name="ignoreMe">Handled that should not be invoked.</param>
        /// <returns>true if command was handled.</returns>
        public bool Invoke(Command command, CommandHandler ignoreMe)
        {
            return Invoke(null, command, ignoreMe);
        }

        /// <summary>
        /// Invoke a command.
        /// </summary>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="command">command to invoke.</param>
        /// <returns>true if command was handled.</returns>
        public bool Invoke(object source, Command command)
        {
            return Invoke(source, command, null);
        }

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="command">Command to invoke</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <exception cref="InvalidOperationException">If you may not invoke the specified command.</exception>
        public IAsyncResult BeginInvoke(object source, Command command, AsyncCallback callback, object state)
        {
            return BeginInvoke(source, command, null, callback, state);
        }

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <param name="source">object that is invoking the command.</param>
        /// <param name="ignoreMe">Handler that should not receive the command.</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        public IAsyncResult BeginInvoke(object source, Command command, CommandHandler ignoreMe, AsyncCallback callback, object state)
        {
            Type type = command.GetType();
            if (InvokeRequested != null)
                InvokeRequested(this, new ProxyHandlerEventArgs(type));
            else if (!_executeCommands.Contains(type))
                throw new InvalidOperationException("Unauthorized attempt to invoke " + command.GetType().FullName + ".");

            return _parent.BeginInvoke(source, command, ignoreMe, callback, state);
        }

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        public IAsyncResult BeginInvoke(Command command, AsyncCallback callback, object state)
        {
            return BeginInvoke(null, command, null, callback, state);
        }

        /// <summary>
        /// Invoke a command asynchronously
        /// </summary>
        /// <param name="command">Command to invoke</param>
        /// <returns>IAsyncResult if command was invoked successfully; otherwise null.</returns>
        /// <param name="ignoreMe">Handler that should not receive the command.</param>
        /// <param name="callback">Callback that is invoked then the command completes.</param>
        /// <param name="state">object that you can use to identify the command in the <see cref="AsyncCallback"/>-method.</param>
        public IAsyncResult BeginInvoke(Command command, CommandHandler ignoreMe, AsyncCallback callback, object state)
        {
            return BeginInvoke(null, command, ignoreMe, callback, state);
        }

        /// <summary>
        /// Invoke this method when the command is complete, or if you want to wait
        /// on the command.
        /// </summary>
        /// <param name="res"></param>
        /// <returns></returns>
        public Command EndInvoke(IAsyncResult res)
        {
            return _parent.EndInvoke(res);
        }

        /// <summary>
        /// Tells us if we have a handler for the specified type.
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>True if a handler have been registered otherwise false.</returns>
        public bool Contains(Type type)
        {
            return _dispatcher.Contains(type) || _parent.Contains(type);
        }

        /// <summary>
        /// Handler for unhandled commands.
        /// </summary>
        /// <remarks>returning true will make the command look like it was handled</remarks>
        public event CommandHandler Unhandled
        {
            add { _dispatcher.Unhandled += value; }
            remove { _dispatcher.Unhandled -= value; }
        }

        #endregion

        private bool OnCommand(object source, CommandEventArgs args)
        {
            try
            {
                return _dispatcher.Invoke(source, args.Command);    
            }
            catch (Exception err)
            {
                if (UnhandledExceptionThrown == null)
                    throw;

                UnhandledExceptionThrown(this, new UnhandledExceptionEventArgs(err));
                return false;
            }
        }
    }
}

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