Export (0) Print
Expand All

How the Stock Trader RI Works

The Stock Trader RI is a composite application, which is composed of a set of modules that are initialized at run time. Figure 1 illustrates the application's startup process, which includes the initialization of modules. The next sections provide details about each of these steps.

Ff650525.f0037c22-0c56-4207-a5fa-62970f7986f4(en-us,PandP.10).png

Figure 1

Stock Trader RI startup process

The Stock Trader RI startup process is the following:

  1. The application uses the StockTraderRIBootstrapper, which inherits from the Composite Application Library's UnityBootstrapper for its initialization.
  2. Initializes the Composite Application Library's UnityServiceLocatorAdapter for use in the modules.
  3. The StockTraderRIBootstrapper creates and shows the Shell view.
  4. The Composite Application Library's ModuleCatalog finds all the modules the application needs to load.
  5. The Composite Application Library's ModuleManager loads and initializes each of the modules.
  6. Modules use the Composite Application Library's RegionManager service to add a view to a region.
  7. The Composite Application Library's Region displays the view.

Modules

A module is a logical unit of separation in the application. In the Stock Trader RI, each module exists in a separate assembly, but this is not an absolute requirement. The advantage of having this separation is that it makes the application more maintainable and enables distributed teams to work on different modules with minimal overlap on the files being updated in the source control system.

The application does not direct each module; instead, each module contributes content to the Shell view and interacts with other modules. The final system is composed of the aggregation of the modules' contributions. By using this composability, you can create applications with emergent behaviors—this refers to the application being able to scale up in complexity and requirements as it grows.

The modules are loosely coupled. This means they do not directly reference each other, which promotes separation of concerns and allows modules to be individually developed, tested, and deployed by different teams.

Services and Containers

This is possible through a set of application services that the modules have access to. Modules do not directly reference one another to access these services. In the Stock Trader RI, a dependency injection (DI) container (referred to as the container) injects these services into modules during their initialization (the Stock Trader RI, uses the Unity container).

Ff650525.note(en-us,PandP.10).gifNote:
For an introduction to dependency injection and Inversion of Control, see the article Loosen Up - Tame Your Software Dependencies for More Flexible Apps by James Kovacs.

Bootstrapping the Application

Modules get initialized during a bootstrapping process by a class named UnityBootstrapper. The UnityBootstrapper is responsible for starting the core composition services used in an application created with the Composite Application Library. For more information, see the Bootstrapper technical concept. The following code from the UnityBootstrapper class shows the Module Manager is located from the container. The Module Manager manages the process of validating the module catalog, retrieving modules if they are remote, loading the modules into the application domain, and calling the IModule.Initialize method.

protected virtual void InitializeModules()
{
    IModuleManager manager;

    try
    {
        manager = this.Container.Resolve<IModuleManager>();
    }
    catch (ResolutionFailedException ex)
    {
     ...
    }

    manager.Run();
} 

Module Catalog

The StockTraderRIBootstrapper class defines the ModuleCatalog in code. In this case, the shell has direct references to all the modules, so the Bootstrapper can directly add them to the ModuleCatalog.

protected override IModuleCatalog GetModuleCatalog()
{
    var catalog = new ModuleCatalog();
    catalog.AddModule(typeof(MarketModule))
    .AddModule(typeof(PositionModule), "MarketModule")
    .AddModule(typeof(WatchModule), "MarketModule")
    .AddModule(typeof(NewsModule));

    return catalog;
}

Module Loading

After the ModuleCatalog is populated, the modules are ready to be loaded and initialized. To initialize a module, the ModuleInitializer service first resolves the module from the container. During this resolving process, the container will inject services into the module's constructor. The following code shows that the region manager is injected.

public WatchModule(IUnityContainer container, IRegionManager regionManager)
{
    _container = container;
    _regionManager = regionManager;
}

After that, the ModuleLoader calls the module's Initialize method, as shown here.

public void Initialize()
{
    this.RegisterViewsAndServices();
    ...
}

Views

After a module is initialized, it can use these services to access the Shell (essentially, the top-level window, which contains all the content) where it can add views. A view is any content that a module contributes to the UI.

Ff650525.note(en-us,PandP.10).gifNote:
In the Stock Trader RI, views are usually user controls. However, data templates in WPF are an alternative approach to rendering a view.

View Registration in the Container

Modules can register views in the container, where they can be resolved. The following code shows where the WatchListView,WatchListPresentationModel, and AddWatchPresenter are registered with the container. In the Stock Trader RI, this registration generally happens in the module's RegisterViewsAndServices method.

protected void RegisterViewsAndServices()
{
     …
     _container.RegisterType<IWatchListView, WatchListView>();
     _container.RegisterType<IWatchListPresentationModel, WatchListPresentationModel>();
     _container.RegisterType<IAddWatchPresenter, AddWatchPresenter>();
     … 
}
Ff650525.note(en-us,PandP.10).gifNote:
You can also register views and services using configuration instead of code.

After they are registered, they can be retrieved from the container either by explicitly resolving them or through constructor injection.

public void Initialize()
{
    ...
    this._regionManager.RegisterViewWithRegion(RegionNames.MainToolBarRegion, () => this._container.Resolve<IAddWatchPresenter>().View);
}

Presentation Model

The Stock Trader RI uses several UI design patterns for separated presentation. One of them is the Presentation Model pattern. Using the Presentation Model, you can separate out the UI rendering (the view) from the UI business logic (the presenter or, in this case, presentation model). Doing this allows the presentation model to be unit tested, because the view can be mocked. It also makes the UI logic more maintainable.

In our implementation of the Presentation Model, the view is injected into the presentation model during its creation. The caller who created the presentation model (in this case, the module) can access the View property to get the view.

Regions and the RegionManager

After the view is created, it needs to be shown in the shell. In an application created with the Composite Application Library, you use a region, which is a named location in the UI, for this purpose. There are two ways to show views inside a region; they are described in the following sections:

  • Adding a View to a Region Instance
  • Registering View Types with Region Names

Adding a View to a Region instance

Using the RegionManager, a module gets a region and adds, shows, or remove views. The module accesses the region through an IRegion interface. It does not have direct knowledge of how the region will handle displaying the view. This is known as view injection. For more information about view injection, see the UI Composition technical concept.

The following code shows where the NewsController adds an ArticleView to the ResearchRegion.

public void Run()
{
    ...
this.regionManager.Regions[RegionNames.ResearchRegion].Add(articlePresentationModel.View);
    ...
}

This region was defined in the Shell in its XAML using the RegionName attached property, as shown here.

<Controls:ResearchControl Regions:RegionManager.RegionName="ResearchRegion">

Figure 2 shows how the watch list appears in the application.

Ff650525.d14aa027-f2da-469b-9c3a-965e12416e25(en-us,PandP.10).png

Figure 2

CFI Stock Trader Articles View

Registering View Types with Region Names

Another way to display views within a region is by associating the type of a view with a region name. Whenever a region with that name displays, the registered views will be automatically created and added to the region. This is known as view discovery. For more information about view discovery, see the UI Composition technical concept.

The following code example shows how the PositionPieChartView is associated with the region name "ResearchRegion."

public void Initialize()
{
...
this._regionManager.RegisterViewWithRegion(RegionNames.ResearchRegion, () => _container.Resolve<IPositionPieChartPresentationModel>().View);
}
Ff650525.note(en-us,PandP.10).gifNote:
In the preceding example, the presenter creates the view. Therefore, instead of registering the type of the view, a delegate is registered that can create and return the view.

Service Registration

Modules can also register services so they can be accessed either by the same module or other modules in a loosely coupled fashion. In the following code, the WatchListService, which manages the list of watch items, is registered by the Watch module.

protected void RegisterViewsAndServices()
{
    _container.RegisterType<IWatchListService, WatchListService>(new ContainerControlledLifetimeManager());
    …
}

After that, the container injects the WatchListService into the Watch module's WatchListPresentationModel, which accesses it through the IWatchListService interface.

        public WatchListPresentationModel(IWatchListView view, IWatchListService watchListService, IMarketFeedService marketFeedService, IEventAggregator eventAggregator)
        {
           …
          this.watchList = watchListService.RetrieveWatchList();
           …
        }

Commands

Views can communicate with presenters and services in a loosely coupled fashion by using commands. The Add To Watch List control, illustrated in Figure 3, uses the AddWatchCommand, which is a DelegateCommand, to notify the WatchListService whenever a new watch item is added.

Ff650525.note(en-us,PandP.10).gifNote:
The DelegateCommand is one kind of command that the Composite Application Library provides. For more information about commanding in the Composite Application Guidance, see the Commands technical concept.

Ff650525.13f497a8-02d0-4de3-9c9b-0dbbc53abb6b(en-us,PandP.10).png

Figure 3

Add To Watch List control

Using a DelegateCommand allows the service to delegate the command's Execute method to the service's AddWatch method, as shown in the following code.

public WatchListService(IMarketFeedService marketFeedService)
{
    …
    AddWatchCommand = new DelegateCommand<string>(AddWatch);
    …
}

private void AddWatch(string tickerSymbol)
{
    …     
}

The WatchListService is also injected into the AddWatchPresenter, which calls the view's SetAddWatchCommand method in its constructor, as shown here.

public class AddWatchPresenter : IAddWatchPresenter
    {
        public AddWatchPresenter(IAddWatchView view, IWatchListService service)
        {
            View = view;
            View.SetAddWatchCommand(service.AddWatchCommand);
        }
        public IAddWatchView View { get; private set; }
    }

This method sets the AddWatchView's DataContext to the command, as shown here.

public void SetAddWatchCommand(ICommand addWatchCommand)
{
    this.DataContext = new AddWatchPresentationModel { AddWatchCommand = addWatchCommand };
}

The AddWatchButton then binds to the command (through the DataContext) with the command parameter that is set with the value from the AddWatchTextBox.Text property.

<StackPanel Orientation="Horizontal">
    <TextBox Name="AddWatchTextBox" MinWidth="100" Style="{StaticResource CustomTextBoxStyle}" 
        Infrastructure:ReturnKey.Command="{Binding Path=AddWatchCommand}" 
        Infrastructure:ReturnKey.DefaultTextAfterCommandExecution="Add to Watch List" 
        Text="Add to Watch List" 
        AutomationProperties.AutomationId="TextBoxBlock" Margin="5,0,0,0"/>
</StackPanel>

This is using an attached behavior on the Add To Watch List text box, so when the user enters a stock symbol and then presses ENTER, the AddWatchCommand will be invoked, passing in the stock symbol to the WatchListService. For more information about attached behaviors, see "Extending Command Support" in the Commands technical concept.

Event Aggregator

The Event Aggregator pattern channels events from multiple objects through a single object to simplify registration for clients. In the Composite Application Library, a variation of the Event Aggregator pattern allows multiple objects to locate and publish or subscribe to events.

In the Stock Trader RI, the event aggregator is used to communicate between modules. The subscriber tells the event aggregator to receive notifications on the UI thread. For example, when the user selects a symbol in the Position tab, the PositionSummaryPresentationModel in the Position module raises an event that specifies the symbol that was selected, as shown in the following code.

eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish(CurrentPositionSummaryItem.TickerSymbol);

The NewsController in the News module listens to the event and notifies the ArticlePresentationModel to display the news related to the selected symbol, as shown in the following code.

this.regionManager.Regions[RegionNames.ResearchRegion].Add(articlePresentationModel.View);
eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews, ThreadOption.UIThread);
Ff650525.note(en-us,PandP.10).gifNote:
The NewsController subscribes to the event in the UI thread to safely update the UI and avoid a WPF exception.

More Information

To continue exploring the reference implementation, see the following topics:

For more information about the Composite Application Guidance technical concepts described in this section, see the following topics:

For information about other concepts that this topic does not address but were also influenced by the Stock Trader RI, see the following topics:

Home page on MSDN | Community site

Show:
© 2014 Microsoft