Chia sẻ qua


Module

A module in the Composite Application Library is a logical unit in your application. Modules assist in implementing a modular design. These modules are defined in such a way that they can be discovered and loaded by the application at run time. Because modules are self-contained, they promote separation of concerns in your application. Modules can communicate with other modules and access services in a loosely coupled fashion. They reduce the friction of maintaining, adding, and removing system functionality. Modules also aid in testing and deployment.

A common usage of a module is to represent different portions of the system. The following are some examples of modules:

  • A module that contains a specific application feature such as news
  • A module that contains a specific subsystem, such as purchasing, invoicing, or general ledger
  • A module that contains infrastructure services, such as logging and authorization services or Web services
  • A module that contains services that invoke line-of-business (LOB) systems, such as Siebel CRM and SAP, in addition to other internal systems

For example, there are four modules in the Stock Trader Reference Implementation (Stock Trader RI):

  • NewsModule. This module is responsible for aggregating and displaying the news related to the user's currently selected investment in their portfolio.
  • PositionModule. This module is responsible for displaying the positions and for handling buy/sell orders.
  • MarketModule. This module handles aggregating and displaying trend information for the portfolio.
  • WatchModule. This module handles displaying the Watch List, which is a list of funds the user monitors. This module also handles adding and removing items from this list.

When you develop your application in a modularized fashion, you structure the application into separate modules that can be individually developed, tested, and deployed by different teams. For more information, see the section "Team Development Using Modules" in the Modularity design concept. Modularity also helps you address separation of concerns by keeping a clean separation between the UI and business functionality.

Creating Modules

There are several ways of creating and packaging modules, but the most common way is to create a single assembly per module, which contains all the views, services, and other classes needed by the module. It also must contain a class that derives from IModule.

IModule

A module is a class that implements the IModule interface. This interface contains a single Initialize method that is called during the module's initialization process.

public interface IModule
{
    void Initialize();
}

Modules in the Application Life Cycle

Modules go through three phases during the life cycle of an application, as illustrated in Figure 1.

Ff921157.94f52b6e-f3eb-4109-b739-b6f06fcbfa6f(en-us,PandP.20).png

Figure 1

Module loading process

As illustrated in Figure 2, the module loading process includes the following:

  1. Defining/discovering modules. Information about the modules has to be added to the ModuleCatalog.
  2. Loading modules. The assemblies that contain the modules have to be loaded into memory. This might mean downloading the assemblies from the Web or otherwise retrieving them first.
  3. Initializing modules. The modules should be initialized. This means creating instances of the module classes and calling the Initialize() method on them.

Defining or Discovering Modules

The ModuleCatalog holds information about the modules that can be used by the application. Each module is described in a ModuleInfo class that records the name, type, and location, among other attributes of the module.

There are several approaches to filling the ModuleCatalog with ModuleInfo instances:

  • Defining modules in code
  • Defining modules in XAML
  • Reading module information from a configuration file
  • Loading all modules in a directory

Although most people prefer to stick with one approach, they are not mutually exclusive and can be combined for extra flexibility.

Note

If you are using the UnityBootstrapper, you must provide a module catalog instance; if you do not, a run-time exception is thrown.

Defining Modules in Code

The most straightforward way of populating the ModuleCatalog is by adding ModuleInfo instances in code. Another advantage of this approach is that you can easily add conditional logic to determine which modules to load.

public class Bootstrapper : UnityBootstrapper
  {
      protected override IModuleCatalog GetModuleCatalog()
      {
          ModuleCatalog catalog = new ModuleCatalog();

          // Adding modules that are referenced by the shell using typeof():
          catalog.AddModule(typeof (ModuleA), "ModuleD")
              .AddModule(typeof (ModuleB))
              .AddModule(typeof (ModuleC), InitializationMode.OnDemand)
              ;

          // Adding modules in code that are not directly referenced by the shell: 
          const string moduleBAssemblyQualifiedName = "Modules.ModuleB.ModuleB, Modules.Silverlight, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null";
              
          catalog.AddModule(new ModuleInfo("ModuleB", moduleBAssemblyQualifiedName));

          return catalog;
      }

In the preceding example, most of the modules are directly referenced by the shell. That is why this example uses typeof(Module) to add modules to the catalog.

To see another example of defining the module catalog in code, see the StockTraderRIBootstrapper.cs in the Stock Trader RI. For more information, see How the Stock Trader RI Works.

Defining Modules in XAML

It is also possible to declaratively specify what kind of module catalog to create and which modules to add to it by creating a ModuleCatalog.xaml file, as shown here.

<Modularity:ModuleCatalog xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:sys="clr-namespace:System;assembly=mscorlib"
               xmlns:Modularity="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite.Silverlight">
    <Modularity:ModuleInfoGroup Ref="ModuleX.Silverlight.xap" InitializationMode="OnDemand">
        <Modularity:ModuleInfo ModuleName="ModuleX" ModuleType="ModuleX.ModuleX, ModuleX.Silverlight, Version=1.0.0.0" />
    </Modularity:ModuleInfoGroup>
    <Modularity:ModuleInfoGroup Ref="ModulesWY.Silverlight.xap" InitializationMode="WhenAvailable">
        <Modularity:ModuleInfo ModuleName="ModuleY" ModuleType="ModuleY.ModuleY, ModulesWY.Silverlight, Version=1.0.0.0">
            <Modularity:ModuleInfo.DependsOn>
                <sys:String>ModuleW</sys:String>
            </Modularity:ModuleInfo.DependsOn>
        </Modularity:ModuleInfo>
        <Modularity:ModuleInfo ModuleName="ModuleW" ModuleType="ModuleW.ModuleW, ModulesWY.Silverlight, Version=1.0.0.0">
        </Modularity:ModuleInfo>
    </Modularity:ModuleInfoGroup>
    <!-- Module info without a group -->
    <Modularity:ModuleInfo Ref="ModuleZ.Silverlight.xap" ModuleName="ModuleZ" ModuleType="ModuleZ.ModuleZ, ModuleZ.Silverlight, Version=1.0.0.0" />
</Modularity:ModuleCatalog>

Usually, the .xaml file is added as a resource to your shell, which, from a technical perspective, makes this approach very similar to defining the ModuleCatalog in code.

Note

ModuleInfoGroups are used mainly for validating XAP files during deployment. They are used to simplify the naming of the XAP to download and when to load the modules within the XAP into the application domain. Dependencies between modules can be defined within modules in the same ModuleInfoGroup; however, you cannot have dependencies between modules in different ModuleInfoGroups.

In the bootstrapper, you also need to specify that the XAML file is the source for your ModuleCatalog, as shown in the following code example.

protected override IModuleCatalog GetModuleCatalog()
        {
            return
                ModuleCatalog.CreateFromXaml(
                    new Uri("/RemoteModuleLoading.Silverlight;component/ModulesCatalog.xaml", UriKind.Relative));

Reading Module Information from a Configuration File

In Windows Presentation Foundation (WPF), it is also possible to specify the module information in the App.config file, as shown in the following example. The advantage of this approach is that this file is not compiled with the application. This makes it very easy to add new modules without recompiling the shell.

  <modules>
    <module assemblyFile="Modules/ModuleD.dll" moduleType="ModuleD.ModuleD, ModuleD" moduleName="ModuleD">
      <dependencies>
        <dependency moduleName="ModuleB"/>
      </dependencies>
    </module>
    <module assemblyFile="Modules/ModuleB.dll" moduleType="ModuleB.ModuleB, ModuleB" moduleName="ModuleB"/>
    <module assemblyFile="Modules/ModuleA.dll" moduleType="ModuleA.ModuleA, ModuleA" moduleName="ModuleA">
      <dependencies>
        <dependency moduleName="ModuleD"/>
      </dependencies>
    </module>
    <module assemblyFile="Modules/ModuleC.dll" moduleType="ModuleC.ModuleC, ModuleC" moduleName="ModuleC" InitializeMode="OnDemand"/>
  </modules>

After specifying module information in the App.config file, you would use the ConfigurationModuleCatalog to create your module catalog, as shown in the following bootstrapper code.

protected override IModuleCatalog GetModuleCatalog()
    {
        ModuleCatalog catalog = new ConfigurationModuleCatalog();
        return catalog;
    }

Note

You can still add modules to a ConfigurationModuleCatalog in code. You can use this to define modules that your application absolutely needs to function.

Note

Silverlight does not support using configuration files. If you want to use a configuration-style approach for Silverlight, the recommended approach is to create your own ModuleCatalog that reads the module configuration from a Web service on the server.

Discovering All Modules in a Directory

You can declare the modules in a directory and use the DirectoryModuleCatalog to look for modules in a designated folder. This is the easiest way to add and remove modules from your application.

To use this approach, you first need to decorate your modules with the module names and dependencies, as shown in the following code example. This allows the DirectoryModuleCatalog to determine which modules to load and the load order.

[Module(ModuleName = "ModuleA")]
public class ModuleA : IModule
{
   
}

[Module(ModuleName = "ModuleB")]
[ModuleDependency("ModuleA")]
public class ModuleB : IModule
{
   
}

After you decorate your modules with the module names and dependencies, you would use the DirectoryModuleCatalog to create your module catalog, as shown in the following bootstrapper code.

protected override IModuleCatalog GetModuleCatalog()
    {
        return new DirectoryModuleCatalog() {ModulePath = @".\Modules"};
    }

For an example of a module catalog definition in a directory, see the Modularity (DirectoryLookup) QuickStart topic in Modularity QuickStarts for WPF.

Note

This functionality is not supported in Silverlight, because the Silverlight security model does not allow you to load assemblies from the file system.

Loading Modules

After the ModuleCatalog is populated, the modules are ready to be loaded and initialized. Module loading means that the module assembly is transferred from disk into memory. If the assembly is not present on disk, it might have to be retrieved first. An example of this is downloading assemblies from the Web using Silverlight XAP files. The ModuleManager is responsible for coordinating the whole loading and initialization process.

Initializing Modules

Finally, the modules have to be initialized. This means, an instance of the module classes must be created and their Initialize() method must be called. Initialization is the place to do the following:

  • Register types with the container.
  • Register view types with region names.
  • Integrate the module with the application.

Note

After a module is loaded and initialized, a module cannot be unloaded.

Registering Types to the Container

During initialization, a module can register views and services. Registration allows their dependencies to be provided through the container and allows them to be accessed from other modules.

To do this, the module will need to have the container injected into the module constructor (for more information, see "Specifying Module Dependencies" later in this topic). The registration of types is often done in a RegisterViewsAndServices method. The following code shows how the MarketModule in the Stock Trader RI registers a MarketHistoryService, MarketFeedService, and TrendLineView.

protected void RegisterViewsAndServices()
{
    _container.RegisterType<IMarketHistoryService, MarketHistoryService>(new ContainerControlledLifetimeManager());
    _container.RegisterType<IMarketFeedService, MarketFeedService>(new ContainerControlledLifetimeManager());
    _container.RegisterType<ITrendLineView, TrendLineView>();
    _container.RegisterType<ITrendLinePresenter, TrendLinePresenter>();
}

Depending on which container you use, registration can also be performed outside of the code through configuration.

Note

The advantage of registering in code, compared to configuration, is that the registration happens only if the module loads.

Registering View for a Region

In the module's Initialize method, a typical action is to register views to appear in a region. There are two approaches to this, view injection and view discovery, discussed in more detail in the UI Composition technical concept. The following code example shows an example of registering a view type to appear in a region using the view discovery approach. With this approach, instances of all the view types registered against a region will appear when the region is displayed.

this.regionViewRegistry.RegisterViewWithRegion(RegionNames.SelectionRegion
                ,typeof(EmployeesView));

In this example, the view type (EmployeesView) is created by the registered container whenever a region with the specified name is displayed. The view is now responsible for creating any presenters or presentation models it might use.

Integrate the Module with the Application

The last step in the module initialization method would be to integrate the module with the rest of the application. The way you do this will vary, depending on the structure of your application. The following are common things to do to integrate your module into your application:

  • Add the module's views to the applications navigation structure. It is likely that most of the views in your modules will not have to be displayed directly, but only after some action by the user. Depending on the style of application, you may want to use menus, toolbars, or other navigation strategies for your users to access views. In the initialization method of the module, you can also register with the application's navigation structure. In the event handlers of your navigation structure (that is, when a user clicks on a menu item), you might use View Injection techniques to add views to the appropriate regions.

    For other navigation styles, you might want to immediately show some initial views in the shell. For example, the Stock Trader RI demonstrates how to directly add views to regions in the shell, using View Injection techniques.

  • Subscribing to application level events or services. Often, applications expose application specific services and/or events that your module is interested in. The initialize method would be the place where you can add the module's functionality to those application-level events and services. For example, the application might raise an event when it is shutting down and your module wants to react to that event. It is also possible that your module must provide some data to an application level service. For example, if you have created a MenuService (it is responsible for adding and removing menu items), the module's Initialize method is where you would add the correct menu items.

Considerations for Modules

When creating your modules, consider the following:

  • Consider putting each module in a separate namespace.
  • Consider not having one module directly accessing concrete types from another module. Use interfaces instead that come from a shared library. This reduces coupling, thereby increasing testability and prevents circular references between modules.
  • When the modules are not on the local host computer, you should minimize application start-up time; this has implications for which modules you should load and when you should load them.
  • You only need to think about loading modules for WPF applications if you want your modules retrieved from an external location, such as a database or remote share. In this case, the Composite Application Library modularity architecture is pluggable, so you can implement your own strategies.
  • Minimize your trips to download and balance by logically structuring your application. Package as many modules as you can in the same XAP file.

Module Options

You can specify dependencies between modules and load modules on demand. If you want to do either of these, you must add some information to your modules, as described in this section.

Specifying Module Dependencies

Modules may depend on other modules. If Module A depends on Module B, Module B must be initialized before Module A can be initialized. The ModuleManager keeps track of these dependencies and initializes the modules accordingly. Depending on how you defined your module catalog, you can define your module dependencies in code, configuration, or XAML.

The following code shows where Module A and Module B are defined and that Module B depends on Module A.

[Module(ModuleName = "ModuleA")]
public class ModuleA : IModule
{
   
}

[Module(ModuleName = "ModuleB")]
[ModuleDependency("ModuleA")]
public class ModuleB : IModule
{
   
}

The following example App.config file shows where Module D depends on Module B.

  <modules>
    <module assemblyFile="Modules/ModuleD.dll" moduleType="ModuleD.ModuleD, ModuleD" moduleName="ModuleD">
      <dependencies>
        <dependency moduleName="ModuleB"/>
      </dependencies>
    </module>

The following XAML shows where Module Y depends on Module W.

        <Modularity:ModuleInfo ModuleName="ModuleY" ModuleType="ModuleY.ModuleY, ModulesWY.Silverlight, Version=1.0.0.0">
            <Modularity:ModuleInfo.DependsOn>
                <sys:String>ModuleW</sys:String>
            </Modularity:ModuleInfo.DependsOn>

Loading Modules on Demand

To load modules on demand, you need to specify that they should be loaded into the module catalog with the InitializationMode set to OnDemand. After you do that, you need to provide the code for when the module should be loaded into your application.

  • You can specify the InitializationMode.OnDemand when you define your module catalog in code, as shown in the following code example.
catalog.AddModule(typeof (ModuleC), InitializationMode.OnDemand);

You can specify the InitializationMode.OnDemand when you define your module catalog in code, as shown in the following code example.

...
<Modularity:ModuleInfoGroup Ref="ModuleX.Silverlight.xap" InitializationMode="OnDemand">
...

You can specify the InitializationMode.OnDemand when you define your module catalog in the App.config file, as shown in the following code example.

...
<module assemblyFile="Modules/ModuleC.dll" moduleType="ModuleC.ModuleC, ModuleC" moduleName="ModuleC" InitializeMode="OnDemand"/>
...

Module C can then be loaded on demand; for example, it can be loaded when a user clicks a button, as shown in the following code.

private void OnLoadModuleCClick(object sender, RoutedEventArgs e)
        {
            moduleManager.LoadModule("ModuleC");
        }

Modularity Internals

This section discusses the main classes and interfaces involved with defining the module catalog, module loading, and module initialization.

Module Manager

Figure 2 provides an overview of the main classes and interfaces that the ModuleManager uses to define the module catalog, load the module assemblies, and initialize the modules.

Ff921157.6f13a019-d9f3-48c5-930b-9b0c1f96d6bd(en-us,PandP.20).png

Figure 2

ModuleManager class diagram

The ModuleManager is the main class that manages the process of validating the module catalog, retrieving modules if they are remote, loading the modules into the application domain, and invoking the module's Initialize method. The ModuleManager works with the following elements to accomplish these activities:

  • UnityBootstrapper and Application Bootstrapper. The application bootstrapper overrides the GetModuleCatalog method and defines the approach to building the ModuleCatalog. The ModuleCatalog can be defined in code, XAML file, configuration file, or in a directory.
  • ModuleCatalog. The module catalog defines the modules that the end user needs to run the application. The module catalog knows where the modules are located and the module's dependencies.
  • IModuleTypeLoader. The process of downloading the modules, if needed, to the local host computer and loading the assembly into the application domain. For Silverlight, the loading process downloads a XAP file from a Web server. For WPF, the module usually resides on the local host computer or it may be retrieved from a file share, database, or Web service. The modules may be loaded when the application starts or on-demand. For modules that are statically referenced and, therefore, already loaded into the application domain, no IModuleTypeLoader will be used.
  • ModuleInitializer. This calls the Module.Initialize method.

Figure 3 describes the module loading sequence of activities.

Ff921157.313448c6-fa05-446f-8497-a8ac7002f440(en-us,PandP.20).png

Figure 3

Module loading process

Module loading in the Composite Application Library is a six-step process:

  1. The application bootstrapper populates the modules in the ModuleCatalog. You can define the modules in code, define the modules in XAML, read module information from a configuration file, or load all modules in a directory.
  2. The UnityBootstrapper calls the Run method on the ModuleManager.
  3. The ModuleManager validates the catalog and gets the list of modules to load.
  4. The ModuleManager asks the ModuleCatalog for the module load order. The load order is based on module dependencies.
  5. The ModuleManager loads the modules and brings their types into the application domain, unless they are to be loaded on-demand. For Silverlight applications, if the modules are remote, the ModuleManager retrieves the modules asynchronously using the XapModuleTypeLoader and loads their types into the application domain. For WPF, the modules are assumed to be local, but they may need to be loaded into the application domain using the FileModuleTypeLoader. If the modules are marked as on-demand, they will be retrieved and loaded when specifically requested.
  6. The ModuleManager calls the module's Initialize method through the ModuleInitializer.

Module Catalog

The ModuleCatalog is responsible for maintaining the metadata for modules and modules groups in the application. You can define the modules in code, define the modules in XAML, read module information from a configuration file, or load all modules in a directory.

To set up the module catalog, the GetModuleCatalog method has to be overridden to return a valid instance of the catalog. The ModuleCatalog will use the ModuleDependencySolver to parse the list of modules and create the order of the modules in the catalog based on the module dependencies that have been defined.

Module Info

The ModuleCatalog returns a list of module metadata named ModuleInfo to the ModuleManager that will be used to load and initialize. The following code shows the ModuleInfo constructor and DependsOn method.

public ModuleInfo(string name, string type, params string[] dependsOn)
{
    this.ModuleName = name;
    this.ModuleType = type;
    this.DependsOn = new Collection<string>();
    foreach(string dependency in dependsOn)
        {
           this.DependsOn.Add(dependency);
        }
}
...
public Collection<string> DependsOn { get; set; }

In addition to information about the module, the preceding code shows that you can obtain or provide a collection of module names that the modules depend on. The module catalog will order its modules based on these dependencies.

Extensibility

The Composite Application Library for WPF and Silverlight currently ships only one retriever: the XapModuleTypeLoader that downloads Silverlight modules. Retrievers are an important extensibility point; you can create and plug in your own retrieval strategy.

Another extensibility point is the creation of a custom ModuleCatalog if you have the need to store your module configuration in a different location, such as in a database.

More Information

For more information about modules, see the following resources:

For more information about other Composite Application Guidance technical concepts, see the following topics:

Home page on MSDN | Community site