Skip to content

Proposed Extension Mechanism

Eugene Bekker edited this page Jan 12, 2016 · 1 revision

The following code snippet defines a proposed extension mechanism called the ExtManager Pattern which is based on the MEF features of the .NET framework and common utilization pattern.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

// * Managed Extensibility Framework (MEF)
//    https://msdn.microsoft.com/en-us/library/dd460648%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396
// * Attributed Programming Model (MEF)
//    https://msdn.microsoft.com/en-us/library/ee155691(v=vs.110).aspx

// Terms:
//    * Component  - target entity instance
//    * Provider   - a factory for resolving components 
//    * Scaffold?  - the machinery for provider and compnent resolution

namespace AppDomain.FooArea
{
    /*********************************************************************
    ** The two interfaces IFoo and IFooProvider represent the contract
    ** that needs to be satified by any extension implementations of the
    ** target extensible component. 
    */

    /// <summary>
    /// Session-level interface or base-class that defines the <i>immediate
    /// interaction</i> contract between a client and the extensible component.
    /// </summary>
    /// <remarks>
    /// The terms <b>session-level</b> and <b>immediate</b> in the summary
    /// description are relative to the domain and context of use of the
    /// extensible component.  In some cases, long-running interactions
    /// (sessions) that span minutes, hours or days in real time, may be
    /// acceptable.  In other cases, a session may be confined to seconds
    /// in real world scenarios.
    /// </remarks>
    public interface IFoo : IDisposable
    {
        string HowToSayHello
        { get; set; }

        void SayHelloWorld();
    }


    /// <summary>
    /// Provider interface for generating instances of the target
    /// extensible component.
    /// </summary>
    /// <remarks>
    /// This is an Application-level interface that is constructed at
    /// the time the component instance needs to be resolved, and
    /// persists until the ext management container is disposed (at
    /// which time it would discard all providers that it instantiated
    /// during its lifetime).
    /// </remarks>
    public interface IFooProvider // : IDisposable
            /* The provider interface can optionally extend IDisposable
            ** if we want to support clean up of the provider instance,
            ** however it's important to rememeber that only the MEF
            */
    {
        /// <remarks>
        /// An optional set of named initialization parameters may be
        /// provided to further configure or qualify the Component
        /// instance that is returned.
        /// </remarks>
        IFoo GetFoo(IDictionary<string, object> initParams = null);
    }

    /*********************************************************************
    ** The remaining components represent the supporing elements that help
    ** implement this "ExtManager" pattern.  Once in place, they only need
    ** to be used by consumers of the ExtManager and by implemntors.
    */

    public interface IFooProviderInfo
            /* The property definitions of this interface should
            ** match up with the properties defined in the custom
            ** attribute defined below, except that they should
            ** only define getters as part of the property sig.
            */
    {
        [DefaultValue(null)]
        string Name
        { get; }

        string Label
        { get; }

        string Description
        { get; }
    }

    [MetadataAttribute]
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    public class FooProviderAttribute : ExportAttribute
    {
                /* Any required attribute parameters are part of the
                ** constructor signature as per usual with attributes.
                */
        public FooProviderAttribute(string name)
            : base(typeof(IFooProvider))
        {
            Name = name;
        }

        public string Name
        { get; private set; }

        public string Label
        { get; set; }

        public string Description
        { get; set; }
    }

    public static class FooExtManager
    {
        private const string EXT_DIR = "ext";

        private static Config _config;

        private static CompositionContainer _cc;

        public static string GetExtPath()
        {
            var thisAsm = Assembly.GetExecutingAssembly().Location;
            if (string.IsNullOrEmpty(thisAsm))
                return null;

            var thisDir = Path.GetDirectoryName(thisAsm);
            if (string.IsNullOrEmpty(thisDir))
                return null;

            return Path.Combine(thisDir, EXT_DIR);
        }

        /// <remarks>
        /// An optional name may be given to distinguish between different
        /// available provider implementations.  Additionally, an optional
        /// set of named initialization parameters may be provided to
        /// further configure or qualify the Provider instance that is
        /// returned and ultimately the Components that the Provider
        /// produces.
        /// </remarks>
        public static IFooProvider GetFooProvider(string name = null)
                /* The name can be an optional argument if the entity ext
                ** manager supports the notion of a "default" provider
                ** and/or entity implementation
                */
        {
            if (_config == null)
            {
                lock (typeof (Config))
                {
                    if (_config == null)
                    {
                        InitConfig();
                    }
                }
            }
            return _config[name]?.Value;
        }

        private static void InitConfig()
        {
            var aggCat = new AggregateCatalog();

            // Add the assembly that contains the current Component/Provider Scaffold
            var thisAsm = Assembly.GetExecutingAssembly();
            aggCat.Catalogs.Add(new AssemblyCatalog(thisAsm));

            // Add assemblies in the current apps path and runtime
            aggCat.Catalogs.Add(new ApplicationCatalog());

            // Add the local extension folder if it exists
            var thisExt = GetExtPath();
            if (Directory.Exists(thisExt))
                aggCat.Catalogs.Add(new DirectoryCatalog(thisExt));

            // Other possible folders to include:
            //    * Application CWD
            //    * PATH
            //    * User-specific ext folder
            //    * System-wide ext folder

            _config = new Config();

            // Guard this with a try-catch if we want to do something
            // in an error situation other than let it throw up
            _cc = new CompositionContainer(aggCat);
            _cc.ComposeParts(_config);
        }

        class Config : Dictionary<string, Lazy<IFooProvider, IFooProviderInfo>>
        {
            private IEnumerable<Lazy<IFooProvider, IFooProviderInfo>> _Providers;

            [ImportMany]
            public IEnumerable<Lazy<IFooProvider, IFooProviderInfo>> Providers
            {
                get { return _Providers; }
                set
                {
                    _Providers = value;
                    Clear();
                    foreach (var x in Providers)
                    {
                        var m = x.Metadata;
                        if (!string.IsNullOrEmpty(m?.Name))
                            this[m.Name] = x;
                    }
                }
            }
        }
    }
}

namespace AppDomain.FooArea.Impl1
{
    [FooProvider("FooImplName",
            Label = "Foo Impl",
            Description = "This is a Foo implementation.")]
    [PartCreationPolicy(CreationPolicy.Shared)]
            // This is an optional attribute to control creation lifetime
    public class FooImplProvider : IFooProvider
    {
        public IFoo GetFoo(IDictionary<string, object> initParams = null)
        {
            return new FooImpl();
        }

        public void Dispose()
        {
            // Cleanup Provider if the provider extends an IDisposable interface
        }
    }

    public class FooImpl : IFoo
    {
        public string HowToSayHello { get; set; }

        public void SayHelloWorld()
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
            // Cleanup Component
        }
    }
}

The following snippet shows a typical use case of a consumer of the ExtManager pattern.

using AppDomain.FooArea;
using System.Collections.Generic;

namespace ClientAppDomain.FooClient
{

    public class FooClient
    {
        public static void Main(string[] args)
        {
            var fooProv = FooExtManager.GetProvider("someName");
            var fooParams = new Dictionary<string, object>
            {
                ["param1"] = "val1",
                ["param2"] = "val2",
                ["param3"] = "val3",
            };

            using (var foo = fooProv.GetFoo(fooParams))
            {
                foo.SayHelloWorld();
            }
        }
    }
}
Clone this wiki locally