HowTo: load CAB modules on demand

This article describes an approach to loading CAB modules on demand. You can Download the solution.

Introduction

Modules are composed of a set of services, WorkItems, SmartParts, controllers/presenters, business entities, and last but not least Module Initialization class, which is used for initializing and running the module's WorkItems

When the application starts, CAB loads the ProfileCatalog.xml file and determines which modules it needs to load. After it retrieves the list of modules, it begins loading them and initializing them. If any of the modules contain a ModuleInit class, it will construct the class and call the appropriate Load and/or AddServices methods.

Loading modules is an expensive operation because it entails loading assemblies, creating services, registering WorkItemExtensions, and calling ModuleInit methods.

Let’s analyze this in detail and how to mitigate the expensive task.

Enumerating modules

CAB has a built-in service that provides module enumeration: the IModuleEnumerator. This service definition is very simple. It returns an array of module information (assembly name, roles and update location):

public interface IModuleEnumerator
{
///
/// Gets an array of enumerated from the source the
/// enumerator is processing.
///
/// An array of instances.
IModuleInfo[] EnumerateModules();
}

The default implementation is the FileCatalogModuleEnumerator. You can use the FileCatalogModuleEnumerator class to read the catalog of modules from an XML file specified in the application configuration file. If no file is specified, the class searches for a file named ProfileCatalog.xml in the application base folder. Here is a sample ProfileCatalog file:

<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile>
<Modules>
<ModuleInfo AssemblyFile="Module1.dll">
<Roles>
<Role Allow="Users"/>
</Roles>
</ModuleInfo>
<ModuleInfo AssemblyFile=" Module2.dll">
<Roles>
<Role Allow="Sales"/>
<Role Allow="Administrators"/>
</Roles>
</ModuleInfo>
</Modules>
</SolutionProfile>

<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile>
<Modules>
<ModuleInfo AssemblyFile="Module1.dll">
<Roles>
<Role Allow="Users"/>
</Roles>
</ModuleInfo>
<ModuleInfo AssemblyFile=" Module2.dll">
<Roles>
<Role Allow="Sales"/>
<Role Allow="Administrators"/>
</Roles>
</ModuleInfo>
</Modules>
</SolutionProfile>

<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile>
<Modules>
<ModuleInfo AssemblyFile="Module1.dll">
<Roles>
<Role Allow="Users"/>
</Roles>
</ModuleInfo>
<ModuleInfo AssemblyFile=" Module2.dll">
<Roles>
<Role Allow="Sales"/>
<Role Allow="Administrators"/>
</Roles>
</ModuleInfo>
</Modules>
</SolutionProfile>

Loading modules

Now that we know how to enumerate the available modules, let’s look at how CAB loads them. The IModuleLoaderService is the service in charge of this:

public interface IModuleLoaderService
{
///
/// Returns a list of the loaded modules.
///
IList LoadedModules { get; }

///
/// Loads the specified list of modules.
///
/// The that will host the modules.
/// The list of modules to load.
void Load(WorkItem workItem, params IModuleInfo[] modules);

///
/// Loads assemblies as modules.
///
/// The that will host the modules.
/// The list of assemblies to load as modules.
void Load(WorkItem workItem, params Assembly[] assemblies);

///
/// The event that is fired when a module has been loaded by the service.
///
event EventHandler<DataEventArgs> ModuleLoaded;
}

The default implementation is the ModuleLoaderService and performs these steps:

  1. Uses the information returned from the IModuleEnumerator to determine which modules should be loaded at run time.
  2. Loads the specified assemblies, checks them for any ModuleDependency attributes and builds a list of modules to initialize.
  3. It then iterates over each module in the sequence, loading the services associated with the module (including creating instances of classes decorated with the [Service] attribute)
  4. It iterates again over the whole sequence calling the Load method on any classes that extend ModuleInit.
  5. Registers any types with the WorkItemExtension attribute into the WorkItemExtensionService.
  6. Finally, it notifies about loaded modules.

Loading on demand

Now that you know how modules are loaded let’s see how we can change the default behavior to load only the necessary modules on startup and load others on demand. The solution consists of having two profile catalogs and a bootstrap module called arbitrary HomeModule.
The ProfileCatalog.xml has the modules loaded on startup and the ProfileCatalogOnDemand.xml has the modules to be loaded on demand.

loadcabondemand1

Now we need a way to enumerate the available modules to be loaded on demand. We will reuse the CAB FileCatalogEnumerator to achieve this. The schema of both the ProfileCatalogOnDemand xml and the ProfileCatalog are the same. The only thing we need to change is which catalog might be read. Fortunately, this service has a constructor that accepts a catalog file path.

new FileCatalogModuleEnumerator( "ProfileCatalogOnDemand.xml" )

new FileCatalogModuleEnumerator( "ProfileCatalogOnDemand.xml" )

new FileCatalogModuleEnumerator( "ProfileCatalogOnDemand.xml" )

The bootstrap module will host this service so we need to add it to the root work item of the module.

WorkItem.Services.Add( 
new FileCatalogModuleEnumerator("ProfileCatalogOndemand.xml") );

WorkItem.Services.Add( 
new FileCatalogModuleEnumerator("ProfileCatalogOndemand.xml") );

WorkItem.Services.Add( 
new FileCatalogModuleEnumerator("ProfileCatalogOndemand.xml") );

Finally, we might be able to consume this service from within any workitem, view, presenter in the hierarchy. In conjunction with the IModuleLoaderService, loading modules on demand from any part of the CAB application will be easy.

void LoadFirstAvailableModuleTest()
{
IModuleInfo[] modules = _modEnumerator.EnumerateModules()
_moduleLoaderService.Load(_workItem, modules[0]);
}

loadcabondemand2

Sample

The sample that demonstrates these concepts has been written following the guidelines of SCBAT. Indeed, the Guidance Package has been used to create modules and views.
This is the Shell and the HomeModule loaded. The HomeModule inserts a Modules-> Load Module… menu item.

loadcabondemand3

When the Load Module command is executed, a dialog shows the modules available and the modules already loaded. We need to use the module enumerator and module loader services, so they are injected on the presenter.

[InjectionConstructor]
public ModuleLoaderDialogPresenter
(
[ServiceDependency] IModuleLoaderService loader,
[ServiceDependency] FileCatalogModuleEnumerator modEnumerator,
[ServiceDependency] WorkItem workItem
)
{
_moduleLoaderService = loader;
_modEnumerator = modEnumerator;
_workItem = workItem;
}

loadcabondemand4
 
Then we select the Module1 and load it. When ModuleLoaderService finish the process it will raise a ModuleLoaded event.

loadcabondemand5

The Module1 adds a View on the right workspace.

loadcabondemand6

What we can do from here?

This sample serves as a starting point to take module loading to a next step

  • We could change the ProfileCatalogOnDemand schema and include configuration to load modules conditionally
  • The module enumerator could be part of the Shell and get rid of the bootstrap module, however we need to be able to identify the IModuleEnumerator service to retrieve from the ObjectBuilder container.

Download the solution

Published: February 27 2006

blog comments powered by Disqus