Code review on MEF / Prism integration code

Feb 7, 2010 at 2:41 PM

I created the following for my new EHR project (http://EHR.CodePlex.com).   The objective being to have both MEF/Prism capabilities - preliminary test looked good was just looking to bullet-proof it for my application (and community).    FYI:  Back in 2008 I did it this way

using System;

using System.ComponentModel.Composition;

using System.ComponentModel.Composition.Hosting;

using System.Reflection;

using System.Windows;

using CAGContrib.Base;

using GWN.Contrib.MEF.Base;

using Microsoft.Practices.Composite.Modularity;

using Microsoft.Practices.Unity;

using Microsoft.Practices.Composite.Logging;

 

namespace EHR.WPF

{

    public class AppBootstrapper : CAGBootStrapper

    {

        private CompositionContainer _container;

 

        /// <summary>

        /// Using MEF Beta - renamed Lazy<> to BeLazy<> to eliminate conflicts

        /// between beta and 4.0

        /// </summary>

        [ImportMany("ModuleContract")]

        private BeLazy<ModuleBase, IModuleMetadata>[] _views = null;

 

        /// <summary>

        /// Called when [create shell].

        /// Intended to be overridden

        /// </summary>

        /// <returns></returns>

        public override DependencyObject OnCreateShell()

        {

            return Container.Resolve<MainWindow>();

        }

 

        /// <summary>

        /// Gets the module catalog.

        /// <remarks>

        /// Note: Modules are in the EHR\Modules folder

        /// </remarks>

        /// </summary>

        /// <returns></returns>

        protected override IModuleCatalog GetModuleCatalog()

        {

            ModuleCatalog catalog = new ModuleCatalog();

            if (LoadParts())

                foreach (var mefModule in _views)

                {

                    Type moduleType = mefModule.Value.GetType();

                    string name = mefModule.Metadata.Name;

                    string fullname = moduleType.AssemblyQualifiedName;

                    string codebase = moduleType.Assembly.CodeBase;

 

                    // Populate our module info object

                    ModuleInfo info = new ModuleInfo(name, fullname);

 

                    // So the Prism typeloader won't complain

                    info.Ref = codebase;

 

                    // Add to catalog

                    catalog.AddModule(info);

                }

 

            return catalog;

        }

 

 

        /// <summary>

        /// Loads the parts.

        /// </summary>

        /// <returns></returns>

        private bool LoadParts()

        {

            var catalog = new AggregateCatalog();

            catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));

            catalog.Catalogs.Add(new DirectoryCatalog("AddIn"));

 

            _container = new CompositionContainer(catalog);

            CompositionBatch batch = new CompositionBatch();

            batch.AddPart(this);

            batch.AddExportedValue<CompositionContainer>(_container);

 

            try

            {

                _container.Compose(batch);

 

                // Register the composition container in unity so

                // we can dispose on shutdown

                Container.RegisterInstance<CompositionContainer>(_container);

            }

            catch (CompositionException compositionException)

            {

                MessageBox.Show(compositionException.ToString());

            }

            return true;

        }

 

    }

}

<!--EndFragment-->
Feb 7, 2010 at 4:44 PM

Hi Bill

Can you talk a bit about what your goals are for this bootstrapper? It looks like you are using MEF to simply deliver assemblies which you are then registering with Prism's module loader.

Glenn

Feb 8, 2010 at 2:46 AM
gblock wrote:

Hi Bill

Can you talk a bit about what your goals are for this bootstrapper? It looks like you are using MEF to simply deliver assemblies which you are then registering with Prism's module loader.

Glenn

Hi Glenn,  I've been disconnected from MEF for a while - quickly ran into what you are suggesting but have resolved it.   My goals are to have full functionality of MEF and Prism throughout the system (transparent to the modules).

Below you'll find the output from the latest source of the http://EHR.CodePlex.com project - the asterisk are the MEF implementation and the exclamation are the Unity implementation of ILoggerFacade:

 ** GWN.EHR: 9:26:58 PM Debug(None): ModulePrototype.OnRegisteredTypes
** GWN.EHR: 9:26:58 PM Debug(None): ModulePrototype.OnRegisterViews
** GWN.EHR: 9:26:58 PM Debug(None): ModuleSecurity.OnRegisteredTypes
!!! GWN.EHR: Debug(None): Exited - testing MEF/Prism loggers
** GWN.EHR: 9:26:58 PM Debug(None): ModuleSecurity.OnRegisterViews
!!! GWN.EHR: Debug(None): Exited - testing MEF/Prism loggers

The module that generated the above follows: 

using System.ComponentModel.Composition;

using CAGContrib.Base;

using GWN.Contrib.MEF.Base;

using Microsoft.Practices.Unity;

using Microsoft.Practices.Composite.Logging;

 

namespace SecurityModule

{

    [Export("ModuleContract", typeof(ModuleBase))]

    [ModuleMetadata(Name = "Security")]

    public class ModuleSecurity : ModuleBase

    {

        [Dependency]

        public ILoggerFacade Logger { get; set; }

 

        [Import("DefaultLogger", typeof(ILoggerFacade))]

        private ILoggerFacade defaultLogger = null;

 

        [ImportingConstructorAttribute]

        public ModuleSecurity()

        {

            // Flag to let us know what process instantiated the module

            IsCreatedByMEF = true;

        }

 

        public ModuleSecurity(IUnityContainer container)

            : base(container)

        {

            // Instantiated by Prism (only if MEF does

            // not instantiate the class).

        }

 

        public override void OnRegisterTypes()

        {

            defaultLogger.Log(GetType().Name + ".OnRegisteredTypes", Category.Debug, Priority.None);

            base.OnRegisterTypes();

            Logger.Log("Exited - testing MEF/Prism loggers", Category.Debug, Priority.None);

        }

 

        public override void OnRegisterViews()

        {

            defaultLogger.Log(GetType().Name + ".OnRegisterViews", Category.Debug, Priority.None);

            base.OnRegisterViews();

            Logger.Log("Exited - testing MEF/Prism loggers", Category.Debug, Priority.None);

        }

    }

}

 

 

I created a strategy and policy that basically sets the context.Existing with the value provided by MEF composition - I was pleasantly surprised to learn that if Existing is populated that Unity will simply do a BuildUp on it; not instantiate a new one. Community note: Strategy and related code is in the Infrastructure\Contrib\GWN.EHR.UnityContrib project.

 

 

    public class MEFStrategy : BuilderStrategy

    {

        /// <summary>

        ///

        /// </summary>

        /// <param name="context">The context.</param>

        public override void PreBuildUp(IBuilderContext context)

        {

            // If this is a class derived from ModuleBase then

            // MEF may have loaded it so we'll check to ensure

            // Unity doesn't stomp on it

            if (context.BuildKey.Type.BaseType.Name.Equals("ModuleBase"))

            {

                // Policy that will handle the population of the

                // context.Existing property

                IMEFStrategyPolicy policy = context.Policies

                    .Get<IMEFStrategyPolicy>(new NamedTypeBuildKey(typeof(IModule), "Module"));

 

                // If policy is not null then process for MEF.  It

                // will set the context.Existing property if it is

                // a module that was processed by MEF

                if (policy != null)

                    policy.PopulateExistingFromMEF(context);

            }

            // Create or Buildup depending on status of context.Existing

            base.PreBuildUp(context);

        }

    }

<!--EndFragment-->

 

    public class MEFStrategyPolicy : IMEFStrategyPolicy

    {

        private IUnityContainer container;

 

        /// <summary>

        /// Initializes a new instance of the <see cref="MEFStrategyPolicy"/> class.

        /// </summary>

        /// <param name="container">The container.</param>

        public MEFStrategyPolicy(IUnityContainer container)

        {

            this.container = container;

        }

 

        /// <summary>

        /// Locates the current context module from a list

        /// of modules handled by MEF - if it exist then it

        /// sets context.Existing which effectively prevents

        /// Unity from creating a new instance; instead it

        /// does a buildup of the provided one

        /// </summary>

        /// <typeparam name="T"></typeparam>

        /// <param name="context">The context.</param>

        /// <returns></returns>

        public bool PopulateExistingFromMEF(IBuilderContext context)

        {

            IMEFStrategyData data = container.Resolve<IMEFStrategyData>();

            context.Existing = data.ModuleList

                .FirstOrDefault<IModule>(

                    m => m.GetType().FullName.Equals(context.BuildKey.Type.FullName));

 

            return true;

        }

    }

<!--EndFragment-->

 

 

The ApplicationBootStrapper looks as follows now:

 

using System;

using System.ComponentModel.Composition;

using System.ComponentModel.Composition.Hosting;

using System.Reflection;

using System.Windows;

using CAGContrib.Base;

using GWN.Contrib.MEF.Base;

using GWN.Contrib.Unity.Strategy;

using Microsoft.Practices.Composite.Modularity;

using Microsoft.Practices.Unity;

 

namespace EHR.WPF

{

    public class AppBootstrapper : CAGBootStrapper

    {

        private CompositionContainer _container;

 

        /// <summary>

        /// Using MEF Beta - renamed Lazy<> to BeLazy<> to eliminate conflicts

        /// between beta and 4.0

        /// </summary>

        [ImportMany("ModuleContract")]

        private BeLazy<ModuleBase, IModuleMetadata>[] _views = null;

 

 

        /// <summary>

        /// Creates the container.

        /// </summary>

        /// <returns></returns>

        protected override IUnityContainer CreateContainer()

        {

            // Use standard container creation

            IUnityContainer container = base.CreateContainer();

 

            // Create singleton for MefModules so that MEFStrategyPolicy can reference

            // it to set context.Existing

            container.RegisterType<IMEFStrategyData, MEFStrategyData>(

                new ContainerControlledLifetimeManager());

 

            // Plug in our MEFStrategy via the Extension

            container.AddNewExtension<MEFStrategyExtension>();

 

            // Return the class for bootstrapper baseclass use

            return container;

        }

 

 

        /// <summary>

        /// Called when [create shell].

        /// Intended to be overridden

        /// </summary>

        /// <returns></returns>

        public override DependencyObject OnCreateShell()

        {

            return Container.Resolve<MainWindow>();

        }

 

        /// <summary>

        /// Gets the module catalog.

        /// <remarks>

        /// Note: Modules are in the EHR\Modules folder

        /// </remarks>

        /// </summary>

        /// <returns></returns>

        protected override IModuleCatalog GetModuleCatalog()

        {

            ModuleCatalog catalog = new ModuleCatalog();

            if (LoadParts())

                foreach (var mefModule in _views)

                {

                    // The constructor for the module will

                    // be entered during the following step

                    Type moduleType = mefModule.Value.GetType();

 

                    // We'll need to let the ModuleLoader know

                    // informaiton about this module

                    string name = mefModule.Metadata.Name;

                    string fullname = moduleType.AssemblyQualifiedName;

                    string codebase = moduleType.Assembly.CodeBase;

 

                    // Populate our module info object

                    ModuleInfo info = new ModuleInfo(name, fullname);

 

                    // So the Prism typeloader won't complain.  Because

                    // of the MEFStrategy registered in CreateContainer

                    // the ModuleLoader will never instantiate a new

                    // class - it will use the instance provided by

                    // MEF in the ModuleList we'll provide below

                    info.Ref = codebase;

 

                    // Add to catalog

                    catalog.AddModule(info);

 

                    // Add the module to our list so that it

                    // can be referenced by the MEFStrategyPolicy

                    Container.Resolve<IMEFStrategyData>()

                        .ModuleList.Add(mefModule.Value);

                }

 

            return catalog;

        }

 

 

        /// <summary>

        /// Loads the parts.

        /// </summary>

        /// <returns></returns>

        private bool LoadParts()

        {

            var catalog = new AggregateCatalog();

            catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));

            catalog.Catalogs.Add(new DirectoryCatalog("AddIn"));

 

            _container = new CompositionContainer(catalog);

            CompositionBatch batch = new CompositionBatch();

            batch.AddPart(this);

            batch.AddExportedValue<CompositionContainer>(_container);

 

            try

            {

                _container.Compose(batch);

 

                // Register the composition container in unity so

                // we can dispose on shutdown

                Container.RegisterInstance<CompositionContainer>(_container);

            }

            catch (CompositionException compositionException)

            {

                MessageBox.Show(compositionException.ToString());

            }

            return true;

        }

 

    }

}

<!--EndFragment-->

 

 

<!--EndFragment-->