This project is read-only.

Bank Teller Quickstart Migration

Contents

Overview

This page describes how we migrated the Bank Teller Quickstart made with CAB to Smart Client Software Factory 2007

Prerequisites

Smart Client Software Factory May 2007
Windows SDK
WPF/WCF Extensions for Visual Studio 2005 (Visual Studio 2005 users only)

Application Overview

Workspaces and Views

The design of the new BankTeller solution user interface is the same as the one in CAB. Workspaces, views and extension sites were not changed, as shown in the figure below.
WorkspacesViews.png
Since Smart Client Software Factory introduces the concept of WorkItemControllers and ControlledWorkItems, the work item hierarchy has been changed as shown in the next section.

WorkItems

This figure illustrates the full WorkItem Hierarchy that exists during application execution.
WorkItems.png

Decisions Taken

At some point during the construction we had to decide whether to use one or two modules to separate the functionality between the BankTeller and Customer. Why did we opt for a single module?
The BankTeller module would have the functionality required to manage the customer queue and the “Customer” module would provide services and views to work with the customer. This made sense, but it was not a must-do refactoring because there was no requirement that led us to separate the modules.
We imagined the application if the Customer module would not be loaded into the shell and that would result into the application showing only the customer queue.
We decided to leave the customer views and services in the BankTeller module. If the solution grew, we would need to assess if it makes sense to split the BankTeller in two. For instance, if we wanted to allow the customer to pay their credit card through the bank teller, we would have a new module that would subscribe to the CustomerSelected event. In a similar way we could have more modules that plug into this event and provide new functionality.

Why didn’t we use the WorkItem state to store the current customer?
We wanted to have more control over the context, hence we decided to implement a custom service named “ContextService” that will provide a CurrentCustomer property. We added this context service to each child workitem as shown below.
workItem = WorkItem.WorkItems.AddNew<ControlledWorkItem<CustomerWorkItemController>>(key);
ContextService context = workItem.Services.AddNew<ContextService>();
context.CurrentCustomer = e.Data;

The ContextService will be a singleton on the context of the new controlled CustomerWorkItem. We will have access to the customer in the workitem environment (views, sub-workitems, etc).

Why aren’t we persisting the WorkItem using the IStatePersistence service?
Since we are not using the state, we are not persisting. The requirement would be fulfilled if we have a method on the PersistenceService to save all the content that needs to be persisted.

Benefits of the Migration

Once we completed the migration of the BankTeller to Smart Client Software Factory we identified the following benefits:
  • Guidance package integration: Now that we have the solution compatible with the guidance packages we don’t have to deal with the repetitive tasks we were performing manually in CAB. With the recipes the job is much easier. For example creating new views (using MVP pattern) instead of having to create a user control, an interface, a presenter, etc. The productivity increases. The same happens when we had to add event publications and subscription on the bank teller. Finally if we need to create a module in the future, this will save some time (updating the profile catalog, creating the project and adding the correct references).
  • Consistency: the solution structure that we end up having is more consistent and the code was cleaner for our eyes. Compared to the old structure where we had a couple of controllers and workitems mixed, now we have a different structure. For instance, the ModuleController is the controller for the BankTeller module that orchestrates the general use case. Then we have a CustomerWorkItemController to handle the story of ‘working with the customer’. This will also allow us having a more test-driven oriented code.
  • WPF integration: We can start migrating the solution to WPF in a seamless way. The recipe to Add WPF View will allow us to start mixing WPF content in the solution.

Correlation between solutions

CAB Solution

CabSolution.png

Smart Client 2007 Solution.

SCSFSolution.png

Shell

The new shell was implemented using the SCSF feature that creates a separate module to define the layout for the shell.
Now we have two projects:
  • Shell
  • BankTeller.Layout
Shell.png

CustomerMapExtension Module

This module stayed without any mayor change.

CustomerMapModule.png

BankTellerCommon

We maintained this project in order to show the correlation.
Another way to solve this would be putting the Business Entities into the Business Entities folder of the Infrastructure. Interface project.

BankTellerCommon.png

BankTeller Module

This module had the major changes. We explain them below

BankTellerModuleOverview.png
The changes we made in the new solution are:
  • Implemented MVP pattern for the views.
  • The views do not have controllers anymore; we put this kind of logic into the corresponding presenter.
  • The CustomerWorkItem now is replaced by the CustomerWorkItemController.
  • The BankTellerModuleInit now is replaced by the Module.
  • The BankTellerWorkItem now is replaced by the ModuleController
BankTellerModuleDetails.png

Relevant Code Sections

When a user clicks on a customer in their customer queue the CustomerViewPresenter call this method to tell us to start working with the customer.
Class: ModuleController
Project: BankTellerModule
[EventSubscription(EventTopicNames.CustomerSelected, ThreadOption.UserInterface)]
public void WorkWithCustomer(object sender, EventArgs<Customer> e)
{
    // Construct a key to register the work item in ourselves
    string key = string.Format("Customer#{0}", e.Data.ID);

    // Have we already made the work item for this customer?
    // If so, return the existing one.
    ControlledWorkItem<CustomerWorkItemController> workItem = WorkItem.WorkItems.Get<ControlledWorkItem<CustomerWorkItemController>>(key);
            
    if (workItem == null)
    {
       workItem = WorkItem.WorkItems.AddNew<ControlledWorkItem<CustomerWorkItemController>>(key);
               
       ContextService context = workItem.Services.AddNew<ContextService>();
       context.CurrentCustomer = e.Data;
    }
            
    workItem.Controller.Run();
}

Context Service store the current customer. Replaces the state stuff.
Class: ContextService
Project: BankTellerModule
public class ContextService
{
    private Customer _customer;

    public Customer CurrentCustomer
    {
        get { return _customer; }
        set { _customer = value; }
    }
}

CustomerQueueViewPresenter replaces the CustomerQueueController
Class: CustomerQueueViewPresenter
Project: BankTellerModule
public partial class CustomerQueueViewPresenter : Presenter<ICustomerQueueView>
{
    [EventPublication(EventTopicNames.CustomerSelected, PublicationScope.Global)]
    public event EventHandler<EventArgs> CustomerSelected;

    // We depend on the customer queue service to tell us which customer is next
    private Services.CustomerQueueService customerQueueService;

    [ServiceDependency]
    public Services.CustomerQueueService CustomerQueueService
    {
        set { customerQueueService = value; }
    }

    /// <summary>
    /// This method is a placeholder that will be called by the view when it has been loaded.
    /// </summary>
    public override void OnViewReady()
    {
        base.OnViewReady();
    }

    /// <summary>
    /// Close the view
    /// </summary>
    public void OnCloseView()
    {
        base.CloseView();
    }

    public Customer GetNextCustomerInQueue()
    {
        return customerQueueService.GetNext();
    }

    public void WorkWithCustomer(Customer customer)
    {
        if (CustomerSelected != null)
            CustomerSelected(this, new EventArgs<Customer>(customer));
    }

    [CommandHandler(Constants.CommandNames.AcceptCustomer)]
    public void OnAcceptCustomer(object sender, EventArgs e)
    {
       Customer customer = GetNextCustomerInQueue();

        if (customer == null)
        {
            View.ShowMessageBox("There are no more customers in the queue.", "Bank Teller");
            return;
        }

        View.AddToCustomersList(customer);
    }
}

CustomerDetailViewPresenter replaces the CustomerDetailController
Class: CustomerDetailViewPresenter
Project: BankTellerModule
public partial class CustomerDetailViewPresenter : Presenter<ICustomerDetailView>
{
    private ContextService _contextService;

    [InjectionConstructor]
    public CustomerDetailViewPresenter
    (
        [ServiceDependency] ContextService contextService
    )
    {
        _contextService = contextService;
    }

    private CustomerCommentsView commentsView;
    /// <summary>
    /// This method is a placeholder that will be called by the view when it has been loaded.
    /// </summary>
    public override void OnViewReady()
    {
        View.SetCustomer(GetCustomer());
        base.OnViewReady();
    }

    /// <summary>
    /// Close the view
    /// </summary>
    public void OnCloseView()
    {
        base.CloseView();
    }

    public void ShowCustomerComments()
    {
        CreateCommentsView();

        IWorkspace ws = WorkItem.Workspaces[WorkspaceNames.CustomerDetailTabWorkspace];

        if (ws != null)
        {
            ws.Show(commentsView);
        }
    }

    private void CreateCommentsView()
    {
        commentsView = commentsView ?? WorkItem.Items.AddNew<CustomerCommentsView>();
        ISmartPartInfo info = new TabSmartPartInfo();
        info.Title = "Comments";
        WorkItem.RegisterSmartPartInfo(commentsView, info);
    }

    private Customer GetCustomer()
    {
        return _contextService.CurrentCustomer;
    }
}

WPF Solution

We have decided to create a WPF version of the Bank Teller application using the WPF integration provided by SCSF 2007.

The decoupling between the view and the presenter made the job easier. Thus, we were able to reuse the view interface and the presenter and we focused on creating the WPF view.
Winforms -> WPF

WPFSolution.png

Screenshots

Note: The styles are based on Family.Show application.

WPFScreenshot1.png
WPFScreenshot2.png
WPFScreenshot4.png

Binding

Winforms
public void SetCustomer(Customer customer)
{
    if (!DesignMode)
    {
        customerBindingSource.Add(customer);
    }
}

this.txtFirstName.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.customerBindingSource, "FirstName", true));

WPF
public void SetCustomer(Customer customer)
{
   this.DataContext = customer;
}

<TextBox IsEnabled="True" Margin="88,0,11,0"  Name="txtFirstName" 
IsReadOnly="True" Text="{Binding Path=FirstName}" VerticalAlignment="Center" HorizontalAlignment="Left" Width="140"/>

Decisions Taken

Why did we use the TabWorkspace provided by the WPFCab project instead of using the WPF implementation of the TabWorkspace included on SCSF May 2007?
The TabWorkspace provided by the WPFCab project is a pure WPF control, allowing us, for example, to set styles.

WPFTab.png
You can get this project by downloading the SCSF Contribution.

How did we use the TabWorkspace provided by the WPFCab project? (see CustomerInfoView.xaml)
  • Add a reference to SCSFContrib.CompositeUI.WPF in the project where you have the view that is going to use the TabWorkspace.
  • Create an xmlns assignment (the equivalent of the using statement) into the view that is going to use the TabWorkspace as shown below:
xmlns:workspaces="clr-namespace:SCSFContrib.CompositeUI.WPF.Workspaces;assembly=SCSFContrib.CompositeUI.WPF"

Note that the use of “workspaces” is a matter of choice – it’s what you’ll use to prefix your elements from that namespace. We could have replaced all the “workspaces” strings with “wks” and it would have worked.
  • Add the TabWorkspace and define the view of the tab items.
<workspaces:TabWorkspace x:Name="tabbedWorkspace1" Grid.Row="1" Style="{DynamicResource TabControl}">
    <TabItem Header="Summary">
        <TabItem.Content>
            <views:CustomerDetailView/>
        </TabItem.Content>
    </TabItem>
    <TabItem Header="Accounts">
        <TabItem.Content>
            <views:CustomerAccountView/>
        </TabItem.Content>
    </TabItem>
</workspaces:TabWorkspace>

Why is the TabWorkspace being injected if it is a child of the WPF View?
The magic is done by the new builder strategy included in the WPF extension of SCSF called WPFControlSmartPartStrategy. This strategy walks the control containment chain looking for child controls that are smart parts, placeholders or workspaces, so that they all get added to the WorkItem. To go deeper please see this post: http://staff.southworks.net/blogs/msaez/archive/2007/04/29/WPF-Support-in-SCSF.aspx

Last edited Jul 31, 2007 at 2:36 AM by kentcb, version 36

Comments

Android Jul 15, 2008 at 3:45 PM 
Southworks blogs change location? Last link is dead now.