Поделиться через


How the Stock Trader RI Works

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

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.

Ff921179.86c155cd-4da6-463b-a560-f33235353d82(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 UnityContainerAdapter for use in the modules.
  3. The StockTraderRIBootstrapper creates and shows the Shell view.
  4. The Composite Application Library's StaticModuleEnumerator finds all the modules the application needs to load.
  5. The Composite Application Library's ModuleLoader 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.

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).

Note

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.

protected virtual void InitializeModules()
        {
            IModuleEnumerator moduleEnumerator = Container.TryResolve<IModuleEnumerator>();
            …
            IModuleLoader moduleLoader = Container.TryResolve<IModuleLoader>();
            …
            ModuleInfo[] moduleInfo = moduleEnumerator.GetStartupLoadedModules();
            moduleLoader.Initialize(moduleInfo);
        } 

Module Enumeration

To enumerate a module, the bootstrapper uses an IModuleEnumerator which is returned from the GetModuleEnumerator method. The bootstrapper in the Stock Trader RI overrides this method to return a StaticModuleEnumerator prepopulated with the Stock Trader RI module metadata. The StaticModuleEnumerator specifies a static list of modules that the Shell directly references.

protected override IModuleEnumerator GetModuleEnumerator()
{
    return new StaticModuleEnumerator()
        .AddModule(typeof(NewsModule))
        .AddModule(typeof(MarketModule))
        .AddModule(typeof(WatchModule), "MarketModule")
        .AddModule(typeof(PositionModule), "MarketModule", "NewsModule");
}

Module Loading

To initialize a module, the ModuleLoader service first resolves the module from the container. During this resolving process, the container will inject services into the modules 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()
{
    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.

Note

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

Modules can register views in the container, where they can be resolved. The following code shows where the WatchListView and WatchListPresentationModel 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>();
     … 
}

Note

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()
{
    … 
   IWatchListPresentationModel watchListPresentationModel = _container.Resolve<IWatchListPresentationModel>();
}

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. 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.

The following code shows where the Watch module adds the Watch List view to the "Watch Region."

public void Initialize()
{
    …
    _regionManager.Regions["WatchRegion"].Add(watchListPresentationModel.View);
    …
}

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

<StackPanel Grid.Row="1" Grid.RowSpan="2" Grid.Column="3">
    <Controls:TearOffItemsControl x:Name="TearOffControl" 
        cal:RegionManager.RegionName="WatchRegion" 
        …/>
    <ItemsControl Margin="0,20,0,0" cal:RegionManager.RegionName="NewsRegion" />
</StackPanel>

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

Ff921179.23d7283d-d1b7-4fb5-b703-01a724218daf(en-us,PandP.10).png

Figure 2
CFI Stock Trader watch list

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 button, illustrated in Figure 3, uses the AddWatchCommand, which is a DelegateCommand, to notify the WatchListService whenever a new watch item is added.

Note

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.

Ff921179.dff36959-c05d-459c-babf-b8385a10823d(en-us,PandP.10).png

Figure 3
Add To Watch List button

Using a DelegateCommand allows the service to delegate the command's CanExecute 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 = addWatchCommand;
}

The AddWatchButton then binds to the command (through the DataContext) with the command parameter binding to the AddWatchTextBox.Text property.

 <StackPanel Orientation="Horizontal">
    <TextBox Name="AddWatchTextBox" MinWidth="100"/>
    <Button Name="AddWatchButton" DockPanel.Dock="Right" Command="{Binding}" CommandParameter="{Binding Text, ElementName=AddWatchTextBox}">Add To Watchlist</Button>
</StackPanel>

This means that when the Add To Watch List button is clicked, the AddWatchCommand will be invoked, passing in the stock symbol to the WatchListService.

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.Get<TickerSymbolSelectedEvent>().Publish(e.Value);

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["NewsRegion"].Add(articlePresentationModel.View);
eventAggregator.Get<TickerSymbolSelectedEvent>().Subscribe(ShowNews, ThreadOption.UIThread);

Note

The NewsController subscribes to the event in the UI thread to safely update the UI and avoid a WPF exception.

More Information

For more information about the concepts described in this section, see the following technical concepts:

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.