Silverlight Prism On Demand Modules

Aug 9, 2011 at 12:31 PM

The example for using MefMVVM with Prism works perfectly unless modules are being loaded on demand with Silverlight. Does anyone have an example for using MefMVVM with Prism's on demand modules with Silverlight? 

Coordinator
Aug 9, 2011 at 1:07 PM
It should work well as well... all you have to do is implement an IComposer and feed it to MEFedMVVM so that MEFedMVVM uses the same Catalogs
Regards
Marlon
WPF Blog - http://marlongrech.wordpress.com/
Microsoft MVP for Client App



On Tue, Aug 9, 2011 at 1:31 PM, bjporada <notifications@codeplex.com> wrote:

From: bjporada

The example for using MefMVVM with Prism works perfectly unless modules are being loaded on demand with Silverlight. Does anyone have an example for using MefMVVM with Prism's on demand modules with Silverlight?

Read the full discussion online.

To add a post to this discussion, reply to this email (MEFedMVVM@discussions.codeplex.com)

To start a new discussion for this project, email MEFedMVVM@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com


Aug 9, 2011 at 1:56 PM

I will take another look at it. When i only implemented IComposer the containers were not being shared. When i implemented IComposer and IContainerProvider the first onDemand module was not being found in the container.

Aug 10, 2011 at 1:50 PM

I attempted it again and came up with the same results. Everything works if i do not share containers but when I share containers it does not work. It always fails when trying to find the first module. 

I have to share containers so that is the only option. I tried several variations in the bootstrapper and they all failed.

I have looked at all the examples(i think) and there does not seem to be a mefedMVVM silverlight on demand module example. There is an example with the Cinch framework for on demand modules but it is for WPF and uses DirectoryCatalog which is not available in silverlight and wouldn't work for what I would think are obvious reasons.

I have looked into the Prism bootstrapper code and it is pretty simplistic when creating the container. I think error has to do with the MefedMVVM export provider either not being able to find the Module Exports or not being able to find exports that are added after the start..... 

Any direction or help would be appreciated.

Something worth nothing. Everything worked perfectly until we switched to on demand modules. Our original silverlight solution did not use on demand modules and mefedMVVM integrated perfectly and the container was shared.

*****************Error Message I receive when sharing*****************

Unable to locate the module with type 'DashboardModule, Dashboard, Version=1.0.0.0' among the exported modules. Make sure the module name in the module catalog matches that specified on ModuleExportAttribute for the module type.

 

Here is how the ModuleCatalog is being created.

protected override IModuleCatalog CreateModuleCatalog()
        {
            // Module B, D, E and F are defined in XAML.
            return Microsoft.Practices.Prism.Modularity.ModuleCatalog
                .CreateFromXaml(new Uri("/;component/ModulesCatalog.xaml"UriKind.Relative));
        }

Here is the module class it can't find.

[ModuleExport(ModuleNames.Dashboard, typeof(DashboardModule), InitializationMode = InitializationMode.OnDemand)]
    public class DashboardModule : IModule
    {
        #region IModule Members
 
        public void  Initialize()
        {
        }
 
        #endregion
    }

 

**************XAML ModuleCatalog**************

<!-- Needed at Startup -->
    <Modularity:ModuleInfoGroup
        InitializationMode="WhenAvailable">
        <Modularity:ModuleInfo
            Ref="Dashboard.xap"
            ModuleName="Dashboard"
            ModuleType="DashboardModule, Dashboard, Version=1.0.0.0" />
    </Modularity:ModuleInfoGroup>

 

 



Aug 15, 2011 at 5:09 PM

There does not seem to be any documentation on where IContainerProvider came from. It appears to be a requirement to truly share Catalog Parts. IComposer makes it so both containers have the same parts but since there are two containers shared parts are retrieved twice.

This is for a scenario where someone does not exclusively use ExportViewModel attribute. If you have a mix of Exports and ExportsViewModel attributes there will be scenarios where shared classes are retrieved twice. Can anyone provide an example where IContainerProvider is implemented for a Silverlight Prism 4 on demand module application?

Aug 16, 2011 at 2:07 PM

A decent solution to this problem is provided in the comments section of this link http://marlongrech.wordpress.com/2011/01/03/mefedmvvm-with-prism-4/

Simply implemented IComposer is not good enough to share instances. You have to put the line below for each instance you want to share with the prism container

ViewModelRepository.Instance.Resolver.Container.ComposeExportedValue(Container.GetExportedValue<Interface>());
The better solution is to have your bootstrapper implement IContainerProvider. However, this does not seem to work with silverlight on demand modules.

To confirm it was not my solution i used the most basic setup of on demand modules.
example solution from the link below:
http://www.codeproject.com/KB/silverlight/PrismTutorial_Part1.aspx

If you add mefedmvvm and implement IContainerProvider to that solution it fails locating the first module.

 

Coordinator
Aug 16, 2011 at 2:38 PM

apologies for the IContainerProvider... it was a patch sent by a friend and I never really tested it with SL :) will try and see why it does not work... (if I find time :) right now work is taking priority over open source)

Thanks for posting a temp solution... Appreciate your help and support for MEFedMVVM 

Sep 6, 2011 at 7:20 PM

I used the IContainerProvider interface to allow my bootstrapper to share global instances with MEFedMVVM which worked well except that resolution of objects which were located in other modules than the main module. Recomposition did not work well for these giving me trouble with PRISM navigation, but I have uploaded a patch for this which is currenctly "under evalutation".

If someone is interested I could post my boostrapper code here (which is very similar to some IContainerProvider example I found somewhere else) when I get back to work in two weeks,

Sep 6, 2011 at 7:24 PM

I would be interested in seeing your bootstrapper code.

Sep 20, 2011 at 9:49 AM

Here is my bootstrapper which setup MEFedMVVM and PRISM (using a module catalog that has been downloaded at runtime).

The regions that should be of interest is "Container Initialization","Shell Creation", "IComposer,IContainerProvider impementations"

It configures both PRISM and MEFedMVVM and is a bit more complicated then requerd since it also performs some setup for EnterPriselibrary so the EnterpriseLibrary logger and exception manager is accessible through MEF.

-------------------------------------- THE BOOTSTRAPPER ----------------------------- 

namespace MyApp
{
    using System;
...
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition.Primitives;
    using System.Diagnostics;
    using MEFedMVVM.ViewModelLocator;
    using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
    using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Practices.Prism.Logging;
    using Microsoft.Practices.Prism.MefExtensions;
    using Microsoft.Practices.Prism.Modularity;
    using Microsoft.Practices.Prism.Regions;
    using Microsoft.Practices.ServiceLocation;
    using Microsoft.Practices.Unity;

    [CLSCompliant(false)]
    public class Bootstrapper : MefBootstrapper, IContainerProvider, IComposer
    {
        /// <summary>
        /// Logger created by the enterprise library
        /// </summary>
        [Dependency]
        public LogWriter logWriter {get; set;}

        /// <summary>
        /// The container for the enterprise library
        /// </summary>
        [Dependency]
        public IUnityContainer enterpriseLibraryContainer {get; set;}

        /// <summary>
        /// save module catalog stream until it is parsed.
        /// </summary>
        private string _moduleCatalog;

        #region Bootstrapper overrides
        /// <summary>
        /// Run method replacing that of PRISM's default bootstrapper.
        /// </summary>
        /// <param name="moduleCatalog">
        /// The content of a PRISM ModuleCatalog configuration in xaml format.
        /// </param>
        public void Run(string moduleCatalog)
        {
            //Save module catalog
            _moduleCatalog = moduleCatalog;

            base.Run();
        }

        /// <summary>
        /// Create the <see cref="ILoggerFacade"/> used by the bootstrapper.
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// The base implementation returns a new TextLogger.
        /// </remarks>
        protected override Microsoft.Practices.Prism.Logging.ILoggerFacade CreateLogger()
        {
            var logger = new Logging.EnterpriseLibraryLoggerFacede() { writer = logWriter };
            return logger;
        }

        #region ModuleCatalog Initialization
        /// <summary>
        /// This method parses a XAML file (as string) and return a ModuleCatalog
        /// </summary>
        /// <remarks>
        /// The implementation is based on that of Prism.Modularity.ModuleCatalog.CreateFromXAML
        /// </remarks>
        /// <param name="xamlStream">XAML file defining a ModuleCatalog, se PRISM documentation for example</param>
        /// <returns>The ModuleCatalog defined in the XAML file</returns>
        private static ModuleCatalog CatalogFromXAML(string xaml)
        {
            var root = XamlReader.Load(xaml);
            return root as ModuleCatalog;
        }

        /// <summary>
        /// Creates the <see cref="IModuleCatalog"/> used by Prism.
        /// It uses the module catalog passed to the Run method
        /// </summary>
        /// <returns></returns>
        /// <remarks>
        /// The base implementation returns a new ModuleCatalog.
        /// </remarks>
        protected override IModuleCatalog CreateModuleCatalog()
        {
            ModuleCatalog catalog = CatalogFromXAML(_moduleCatalog);
            _moduleCatalog = null;

            if (catalog == null)
            {
                var dictionary = new System.Collections.Generic.Dictionary<string, object>();
                dictionary.Add("Configuration file", "ModuleCatalog.xaml");
                logWriter.Write(ErrorResources.ErrorConfigurationCouldNotLoad, dictionary);
            }

            return catalog;
        }


        /// <summary>
        /// Configures the <see cref="IModuleCatalog"/> used by Prism.
        /// </summary>
        /// <remarks>
        ///     Configuration is specified in ModuleCatalog.xaml, so this function is very short.
        /// </remarks>
        protected override void ConfigureModuleCatalog()
        {
            base.ConfigureModuleCatalog();
        }
#endregion 


        #region Container Initialization
        
        /// <summary>
        /// Create a single composition container shared by MEFedMVVM and PRISM
        /// </summary>
        /// <remarks>
        /// Code taken from http://mefedmvvm.codeplex.com/workitem/15391
        /// </remarks>
        /// <returns>A composition container shared with MEFedMVVM</returns>
        protected override CompositionContainer CreateContainer()
        {
            if (this.Container == null)
            {
                var mefedProvider = new MEFedMVVMExportProvider(MEFedMVVMCatalog.CreateCatalog(AggregateCatalog));
                var providersList = new List<ExportProvider>();

                //check if there are any custom export providers
                var providers = ((IComposer)this).GetCustomExportProviders();
                if (providers != null && providers.Any())
                    providersList.AddRange(providers);

                //add mefedMVVM provider
                providersList.Add(mefedProvider);

                var container = new CompositionContainer(providersList.ToArray());
                foreach (var imefedProvider in container.Providers.OfType<MEFedMVVMExportProvider>())
                {
                    imefedProvider.SourceProvider = container;
                }

                return container;
            }
            return Container;
        }
        
        /// <summary>
        /// Configures the <see cref="Container"/>. May be overwritten in a derived class to add specific
        /// type mappings required by the application.
        /// </summary>
        protected override void ConfigureContainer()
        {
            base.ConfigureContainer();


            //Register enterprise library types
            this.Container.ComposeExportedValue<LogWriter>(this.logWriter);
            try
            {
                var exceptionManager = enterpriseLibraryContainer.Resolve<ExceptionManager>();
                this.Container.ComposeExportedValue<ExceptionManager>(exceptionManager);
            }
            catch(Exception)
            {
                //For now it is ok if the exception manager is not configured
            }
           
        }

        #endregion


        #region Shell Creation
        /// <summary>
        /// Creates the shell or main window of the application.
        /// </summary>
        /// <returns>The shell of the application.</returns>
        /// <remarks>
        /// If the returned instance is a <see cref="DependencyObject"/>, the
        /// <see cref="Bootstrapper"/> will attach the default <seealso cref="Microsoft.Practices.Composite.Regions.IRegionManager"/> of
        /// the application in its <see cref="Microsoft.Practices.Composite.Presentation.Regions.RegionManager.RegionManagerProperty"/> attached property
        /// in order to be able to add regions by using the <seealso cref="Microsoft.Practices.Composite.Presentation.Regions.RegionManager.RegionNameProperty"/>
        /// attached property from XAML.
        /// </remarks>
        protected override DependencyObject CreateShell()
        {
            //Setup this as MEFedMVVM composer
            MEFedMVVM.ViewModelLocator.LocatorBootstrapper.ApplyComposer((IComposer)this);

            //Create a view and return it
            return Container.GetExportedValue<Shell>();
        }

        /// <summary>
        /// Initializes the shell.
        /// </summary>
        /// <remarks>
        /// The base implemention ensures the shell is composed in the container.
        /// </remarks>
        protected override void InitializeShell()
        {
            base.InitializeShell();

            BusyIndicator root = Application.Current.RootVisual as BusyIndicator;
            if (root != null)
                root.Content = (UIElement)this.Shell;
            else
                Application.Current.RootVisual = (UIElement)this.Shell;
        }        
        #endregion


        #region PRISM region initialization
        protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
        {
            var mappings = base.ConfigureRegionAdapterMappings();            
            mappings.RegisterMapping(typeof(System.Windows.Controls.Frame), 
                                new Navigation.FrameRegionAdapter(ServiceLocator.Current.GetInstance<IRegionBehaviorFactory>()));

            return mappings;
        }
        #endregion
        /// <summary>
        /// Configures the <see cref="AggregateCatalog"/> used by MEF.
        /// </summary>
        /// <remarks>
        /// The base implementation does nothing.
        /// </remarks>
        protected override void ConfigureAggregateCatalog()
        {
            base.ConfigureAggregateCatalog();

            //Add this assembly to the list of known assemblies
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Bootstrapper).Assembly));
        }

        #endregion


        #region MEFedMVVM IComposer,IContainerProvider impementations

        /// <summary>
        /// Return the same composition contaiener to MEFedMVVM as to PRISM
        /// </summary>
        /// <returns>The composition container shared by PRISM</returns>
        CompositionContainer IContainerProvider.CreateContainer()
        {
            Debug.Assert(this.Container != null); //Should already be created

            return this.Container;
        }


        /// <summary>
        /// Use the same catalog used by PRISM
        /// </summary>
        /// <returns></returns>
        ComposablePartCatalog IComposer.InitializeContainer()
        {
            return this.AggregateCatalog;
        }

        /// <summary>
        /// Add any custom ExportsProvided for composition usage
        /// </summary>
        /// <returns>null to indicate that no custom providers are privided</returns>
        IEnumerable<ExportProvider> IComposer.GetCustomExportProviders()
        {
            return null;
        }
        #endregion 
    }
}


---- MODULE CATALOG ----
<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                          xmlns:sys="clr-namespace:System;assembly=mscorlib"
                          xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">

    <!-- Module info without a group -->
    <Modularity:ModuleInfo Ref="MyApp.Settings.xap" ModuleName="Settings" ModuleType="MyApp.Settings.SettingsModule, MyApp.Settings, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

</Modularity:ModuleCatalog>


Sep 20, 2011 at 1:03 PM

Thanks!