Allow Users to dynamically enable Extension Modules

Topics: Prism v4 - WPF 4
Mar 16, 2011 at 5:57 PM

I believe I have an issue common to the community that has not been directly addressed.  I have a WPF application based off StocktraderRI and therefore relies heavily on MEF (AggregateCatalog).

The application will have a ConfigurationView (Popup Window) which will allow the user to choose which extension modules to enable.  The problem is that it appears that I need to have 2 containers to accomplish this goal. The Prism examples always just create 1 AggregateCatalog that is statically set via config file, XAML, or code.  Below is my best guess at a solution.  For illustration purposes assume I have 2 Modules A and B, but the user placed a check  mark  (selected) only next to Module A in the ConfigurationView.

Module MEF Container: A preliminary Module MEF pass looks at all assemblies for the “IModule” interface so the ConfigurationView can show the user all modules possibilities (Module A and B).  This is briefly used outside MefBootstrapper flow and then somehow discarded.  The Modules selected for the user are stored in a config file for use by the “Complete MEF Container”.

Complete MEF Container: This pass uses only the assemblies associated with the selected Modules (Module A assembly), which is contained in the Prism AggregateCatalog.  This is the container used within MEFBootstrapper.  Note this container will resolve everything which includes IModule, Views, ViewModules, and Services…

Questions:

1) First is my approach the best strategy for use with the Prism framework?

2) In my proposed solution how do I create an IModuleCatalog and ModuleManager for the sole purpose of getting a list/description of available modules (Will use DirectoryModuleCatalog)?  I do not want the Module initialize routines called, and will discard the ModuleManager somehow once the list is complete.  If I do not discard the ModuleManager I will have 2 of them sitting around when I later want to locate the prism IModuleManger with the servicelocator.

3)  I thought about using methods of the ModuleManager like “LoadOnDemand”, which might allow me to load modules later depending on user selection.  The problem is MEF resolves everything (Views, Services, …) so the Module methods like “Initialize()” and “LoadOnDemand()” are fairly useless in a full MEF world?

4) If the user selects or deselects a module within ConfigurationView how in the world do I recompose the container (basically an application reboot) using the prism flow.

5) What is the best way to gather a detailed description for each module to display in the ConfigurationView.  I read that ModuleCatalog a collection ModuleInfo class, which in turn has a few properties ModuleName and ModuleType.  However I would like a custom property called description that the Module creator would add to the metadata. Then I could display this custom text to the user so they can understand details of the module they are enabling/disabling.

Thanks,
aidesigner

Mar 17, 2011 at 4:06 PM

Hi,

Based on my understanding of your scenario, you could achieve your requirement by using the app.config file to configure your module catalog (you could set modules to be loaded on demand and manage dependencies between them). By following this approach, you shouldn’t have to deal with nested containers.

Therefore, you could easily retrieve module names to be shown in your view, and finally load on demand the modules selected by the user.

You might find handy the following links:

I hope you find this information useful.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic

 

Mar 17, 2011 at 9:05 PM

First thank you for your reply.  I have read all the Prism documentation and Quickstarts in search of answers.

I do not believe your approach will work because my extension modules are created by the community.  Individuals may then install a module in a specific directory and my WPF Prism application will import the modules through MEF.  Also note there is no real purpose for IModule, as the AggregateCatalog of MEFBootstrap will resolve all imports (Modules, Views, and Services) using MEF.

Problem:

It still seems I need some type of container that reveals all available modules (DirectoryModuleCatalog).  After that I can follow the normal MEFBootstrap flow, but only include the module (assemblies) selected by the user in my configuration pop-up window.  If you agree with my analysis can you answer a few of the questions above (Especially #2)...

Thanks,

aidesigner

Developer
Mar 18, 2011 at 6:15 PM

Hi,

If you specify that the modules should be loaded OnDemand, you could use the DirectoryModuleCatalog, to get the modules you have in your folder, and then launch the configuration pop-up window, in which a list of the available modules could be obtained, as you've mentioned, from the ModuleCatalog (you could pass the same instance of DirectoryModuleCatalog that you've used, which contains a list of all the modules in the directory you've specified in its Modules property). You could then use call the ModuleManager.LoadModule method (which indicates that a module should be loaded) for each module that you want to load (based on the selection the user has made in the pop up).

As for using nested containers, if you follow the aforementioned approach there should be no need to do so. The modules and the exports associated to them will only be added to the AggregateCatalog once a module is loaded.

I hope you find this helpful.

Guido Leandro Maliandi 
http://blogs.southworks.net/gmaliandi

Mar 18, 2011 at 7:51 PM

Thanks for stikcing with this issue...

LoadModule only has any meaning if the modules implement IModule and more importantly use the initialize() method to register views and services.  However with the introduction of MEF examples like the StockTraderRI MEF import everything (views, services, ...) and leave Module.initialize() empty.  A Quote from Chapter 4 "Instead of using the initialization mechanism provided by the IModule interface, the Stock Trader RI uses a declarative, attribute-based approach for registering views, services, and types."

Given these facts if I create my AggregateCatalog with all assemblies I get everything imported (views, services) for all modules.  Therefore it stills seems I have to create AggregateCatalog only utilizing the assemblies for the Modules enabled within my popup ConfigView.  Hence I need a means of creating an "available modules list" using a ModuleCatalog container that is outside of the Prisim/MEFBootstrap/Aggregate Catalog flow.  The list can then be used by the ConfigView, which in turn outputs a list (ie config.txt) of selected modules.  This selected module list is then utilized to populate the Aggregate Catalog (Contain a subset of available modules).

Also note I don't think my solution deals with nested containers.  It simply has 2 independent parallel containers 1) Container just to find available Modules (imports IModule). 2) Standard Prism AggreateContainer(Modules, views, services) in MEFBootstrap, which only uses a subset of module assemblies.

If you agree with my problem can you help me with my initial questions (Espically #2, #4, #5)...

Thanks,

Developer
Mar 21, 2011 at 8:58 PM

Hi,

The ModuleExport attribute is used by MEF to declare that the class being exported is a module (that is to say, implements IModule), therefore, when you load that module through the ModuleManager.LoadModule method, its exports will be added to the MEF container. This is true even if the Initialize method is left empty.

Therefore, there is no need to maintain separate containers, since you would only add the types of the modules you've selected to the container. The usage of the ModuleExport attribute in your case would be something like this:

[ModuleExport(typeof(MyModule), InitializationMode = InitializationMode.OnDemand)]

In order to have the list of the available modules in the ConfigView you could use the DirectoryModuleCatalog; since you will have defined that your modules should be loaded on demand, retrieving the list of the available modules in a directory won't export its types into the container. They will only be exported when the ModuleManager.LoadModule method is called.

Please let me know if this scenario doesn't fit your requirements.

I hope you find this helpful.

Guido Leandro Maliandi 
http://blogs.southworks.net/gmaliandi

Mar 22, 2011 at 2:20 PM

Allow me to summarize to make sure I got this correct.  ModuleManager finds IModule imports and then associates the respective assembly with the Module.  For "OnDemand" Modules, only when LoadModule is called is the Module assembly added to the AggregateCatalog.  For this reason all classes (ie. views, viewmodels, services) in the Module assembly will not be imported until LoadModule called.  My new understanding requires that a Module be associated with only 1 assembly (Perhaps Partial IModule Classes resolves this issue)?

If you can help a little futher I am now just left with questions 4 and 5 of the first post (Included below).  I believe the answer to #4 is that it is not possible to unload a Module.

Reposted Questions
4) If the user selects or deselects a module within ConfigurationView how in the world do I recompose the container (basically an application reboot) using the prism flow.

5) What is the best way to gather a detailed description for each module to display in the ConfigurationView.  I read that ModuleCatalog a collection ModuleInfo class, which in turn has a few properties ModuleName and ModuleType.  However I would like a custom property called description that the Module creator would add to the metadata. Then I could display this custom text to the user so they can understand details of the module they are enabling/disabling.

Thanks Again!

Developer
Mar 22, 2011 at 3:57 PM
Edited Mar 22, 2011 at 3:58 PM

Hi,

Based on my understanding, your assumption of my explanation is correct. Modules are usually associated to a single assembly. If you have different assemblies that compose a single "unit" or fulfilll a single use case, you could create a dependency between those modules. You could read more about that in the Specifying Module Dependencies of the Modularity chapter of the Prism MSDN documentation.

As for your fourth question, you're correct, it's not possible to unload a module. What you could do is to publish an event with the EventAggregator to notify the module that all its services and views should be unregistered and removed. You can read more about this in the following threads:

Regarding your fifth question, what you're mentioning isn't supported out of the box in Prism. You could extend Prism to support this, or create a custom mechanism to display such descriptions, for example retrieving the values from a database. For example, if you wish to achieve your scenario storing modules in a configuration file, you could extend the ModuleConfigurationElement and ConfigurationModuleCatalog classes located in the Modularity folder of the Prism.Desktop project, to include an additional parameter which represents the description. Note that, in that case, you should also extend the ModuleInfo class to support having such a parameter, since the ConfigurationModuleCatalog ultimately uses the information in the App.config file to build ModuleInfo instances.

You might also find the following threads useful to your purposes:

I hope you find this helpful.

Guido Leandro Maliandi 
http://blogs.southworks.net/gmaliandi