Dynamic Loaded Modules and Eventing

Topics: Prism v2 - Silverlight 4
Jul 9, 2010 at 9:27 PM

Hello,

I am using Prism to load modules via a ModuleCatalog in a xaml file. I have 2 modules, ModuleA and ModuleB. Right now both are set to InitializationMode="WhenAvailable". In ModuleA's ViewModel I can fire an event like this:

EventAggregator.GetEvent<ShowModuleBEvent>().Publish(EventArgs.Empty); 
This fires the event and ModuleB's IModule class contains :
_eventAggregator.GetEvent<ShowModuleBEvent>().Subscribe(HandleShowModuleB, ThreadOption.UIThread, true);

All this works just great. The problem which I am sure you can see is that if I make ModuleB load dynamically, then the IModule in ModuleB will not be loaded at start-up and so the event subscription of course wont catch the event to load ModuleB's view. My question is (finally), where should I be placing event subscriptions to load dynamic modules? What class (or project) is the recommended  place to handle the loading and unloading of modules when they are dynamically loaded?

Thanks!!

-Jeff

Jul 12, 2010 at 5:08 AM
Edited Jul 12, 2010 at 5:09 AM

In the case of my http://EHR.CodePlex.com (ModularityWithMef.Desktop folder) using Prism V4 Drop 3 w/MEF container - I subscribe to the ModuleEvent in my MainWindowPresenter (the PresenterBase imports the Shell, EventAggregator, ModuleManager, etc).

using System.ComponentModel.Composition;
using System.Globalization;
using GWN.Library.WPF.Base;
using GWN.Library.WPF.Events;
using Microsoft.Practices.Composite.Logging;
using Microsoft.Practices.Composite.Modularity;

namespace ModularityWithMef.Desktop.Presenters
{
    [Export]
    public class MainWindowPresenter : PresenterBase, IPartImportsSatisfiedNotification
    {
        [Import]
        public CallbackLogger Logger { get; set; }

        protected override void OnEventAggregatorSet()
        {
            EventAggregator.GetEvent<ModuleEvent>().Subscribe(e => ProcessModuleLoad(e));
        }
       
        public void ProcessModuleLoad(ModuleEventArgs e)
        {
            // Remove spaces and load the module
           
ModuleManager.LoadModule(e.ModuleControl.ModuleName.Replace(" ", ""));
        }

        public void Log(string message, Category category, Priority priority)
        {
            var mainWindow = Shell as MainWindow;
            mainWindow.TraceTextBox.Text =
                string.Format(CultureInfo.CurrentUICulture,
                "[{0}][{1}] {2}\r\n{3}",
                    category, priority,
                    message, mainWindow.TraceTextBox.Text);
        }

        public void OnImportsSatisfied()
        {
            Shell.DataContext = ModuleTracker;
            this.Logger.Callback = Log;
            this.Logger.CallbackSavedLogs();
        }
    }
}

In my case I'm using MVPVM however if you are using MVVM I would probably import the EventAggregator/ModuleManager into the Bootstrapper (similiar to how I import the MainWindowPresenter below) and subscribe to it there.
 
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Windows;
using Microsoft.Practices.Composite.Logging;
using Microsoft.Practices.Composite.MefExtensions;
using Microsoft.Practices.Composite.Modularity;
using ModularityWithMef.Desktop.Presenters;
using ModularityWithMef.Infrastructure;


namespace ModularityWithMef.Desktop
{
    public class Bootstrapper : MefBootstrapper
    {
        private CallbackLogger callbackLogger = new CallbackLogger();

        [Import]
        MainWindowPresenter mainWindowPresenter { get; set; }

        protected override System.Windows.DependencyObject CreateShell()
        {
            Container.ComposeParts(this);
            return (DependencyObject) mainWindowPresenter.Shell;
        }

        protected override void InitializeShell()
        {
            Application.Current.MainWindow = (MainWindow)this.Shell;
            Application.Current.MainWindow.Show();
        }

        protected override void ConfigureAggregateCatalog()
        {
            base.ConfigureAggregateCatalog();
            
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Bootstrapper).Assembly));
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ModuleA).Assembly));
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(ModuleC).Assembly));
            this.AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoaderClass).Assembly));

            DirectoryCatalog catalog = new DirectoryCatalog("DirectoryModules");            
            this.AggregateCatalog.Catalogs.Add(catalog);            
        }

        protected override void ConfigureContainer()
        {
            base.ConfigureContainer();
            this.Container.ComposeExportedValue<CallbackLogger>(this.callbackLogger);
        }

        protected override IModuleCatalog CreateModuleCatalog()
        {
            // Module C is referenced by this project and in the configuration
            // Module E only referenced in the configuration
            this.ModuleCatalog = new ConfigurationModuleCatalog();
            return ModuleCatalog;
        }

        protected override ILoggerFacade CreateLogger()
        {
            this.Logger = this.callbackLogger;
            return callbackLogger;
        }

    }
}

Jul 12, 2010 at 3:11 PM

I am not familiar with the [Import] attribute. I am new to Prism like I said. So, if I create a property for the event aggregator as you did with your MainWindowPresenter, what actually calls that property to pass in the eventAggregator object?

Also, it seems to me at first glance that the responsibility of the boot strapper class is to kick off the application. After all, it basically replaces the a call to a main form to run. Is it really the correct place to handle loading and unloading of views. After the application "boots" I would think that class's responsibility is finished. I was thinking that I should create a module that held a controller class and it could be the "manager" of views. What do you think of that? I just don't want to re-invent the wheel, and I want my code to be easy to follow for others using Prism should anyone else have to maintain my code.

thoughts?

-J

 

Jul 12, 2010 at 3:48 PM
Savij wrote:

I am not familiar with the [Import] attribute. I am new to Prism like I said. So, if I create a property for the event aggregator as you did with your MainWindowPresenter, what actually calls that property to pass in the eventAggregator object?

Dependency Injection (DI) - currently Prism uses a Unity Container.  At a very high level you can look at a DI container as a lookup dictionary that contains interfaces and the implementation that was registered for it.  For example in the Bootstrapper base class you'll find that IEventAggregator was registered to use the EventAggregator class so when you do the following:

[Dependency]                         <=== Unity
public IEventAggregator EventAggregator {get;set;}

Dependency Injection will automagically populate the property with an instance of EventAggregator for you.   The above is referred to as "Setter injection", there is also "constructor injection" which allows you to specify constructor parameters such as   myclass(IEventAggregator eventAggregator) which will effectively provide you an instance of EventAggregator.   As long as your class was instantiated using constructor or setting injection those classes will be propagating the DI allowing them to also use setter or constructor injection.

In the case of Prism Version 4 Drop 3 they provide both MEF and Unity.   MEF does things differently than Unity.   MEF allows you to do an [Import] and expects that somewhere else there is a matching [Export].   In my case the baseclass for the bootstrapper exported the IEventAggregator so I can simply do the following:

[Import]
protected IEventAggregator EventAggregator {get;set;} and it will automagically be populated. 

 

Savij wrote:

Also, it seems to me at first glance that the responsibility of the boot strapper class is to kick off the application. After all, it basically replaces the a call to a main form to run. Is it really the correct place to handle loading and unloading of views. After the application "boots" I would think that class's responsibility is finished. I was thinking that I should create a module that held a controller class and it could be the "manager" of views. What do you think of that? I just don't want to re-invent the wheel, and I want my code to be easy to follow for others using Prism should anyone else have to maintain my code.

I agree with you whole-heartedly; that would be a far better solution than placing it in the bootstrapper which is, as you said, only responsible for booting up the application - your way provides a clear separation of  concerns.

Your module will be resolved by the Unity Container (under the hood) so it will provide you the ability to specify the following:


** MODULE **

[Dependency]
public IModuleManager ModuleManager {get;set;}

private IEventAggregator eventAggregator;
[Dependency]                        
public IEventAggregator EventAggregator
{
     get { return eventAggregator; }
     set {
            eventAggregator = value;
            OnEventAggregatorSet();
     }
}

private void OnEventAggregatorSet()
{
        EventAggregator.GetEvent<myEvent>().Subscribe(MyEventHandler);
}

public void MyEventHandler(MyEventHandlerEventArgs e)      <== Must be public for Unity Subscriptions!
{
     ModuleManager.LoadModule(e.ModuleName);    
}

 

 

Jul 13, 2010 at 12:09 AM

Bill,

That is pretty awsome stuff. I can't wait to play a little and implement those changes!! Thanks so much for the insight!

-Jeff