Architecting Composite Smart Clients Using CAB and SCSF
by Mario Szpuszta
Summary: Microsoft's offerings for building composite smart clients include Composite UI Application Block (CAB) and the Smart Client Software Factory (SCSF) from the Patterns & Practices Group. This article unveils the architectural details of CAB and SCSF, and shows you how to design composite smart clients using CAB and SCSF. The examples are from an integrated bank desktop smart-client project undertaken at RACON Software GmbH, a software house of the Raiffeisen Banking Group in Upper Austria.
Architectural Guidance for Composite Smart Clients
Composite Smart Clients: Real-World Scenario
Composite UI Application Block
Designing Your Shell and Infrastructure Services
Designing and Packaging WorkItems
Module Controller WorkItems
Exclusion of Use Cases
Sub-WorkItem or WorkItem
WorkItem Identification: Summary
Smart Client Software Factory
About the Author
Smart clients are extremely useful if you want to reach a well-known set of users with high user experience requirements. Very often, we tend to reduce the advantages of smart clients to easy aspects, such as a cool-looking user interface. While this is definitely important, when taking a look at the definition on MSDN, there are more vital factors. According to this definition, a smart client is a Windows application compliant with the following criteria:
- In addition to providing rich user interfaces, a smart client uses local resources available to the clients, including hardware resources, as well as components and locally installed applications.
- It is a connected application exchanging information with services on the Internet or the local enterprise network.
- Although a connected application, a smart client is offline—capable of making connectivity as transparent as possible to the user.
- Last but not least, intelligent deployment and updating are key criteria for a smart client. This includes mechanisms such as ClickOnce (no-touch) deployment and automatic updates.
While these general concepts apply to smart clients, enterprises often have additional requirements. Their users want to work with integrated desktops—a common shell that hosts different types of applications, with seamless integration and a common, streamlined way of working with them. Such types of smart clients are called composite smart clients. A composite smart client allows the client solution to be composed of a number of discrete functional pieces—so-called modules or plug-ins—that are integrated within a common host environment.
I was personally involved in creating the architecture for a common bank desktop for Raiffeisen Banking Group, Upper Austria, in Linz. The Raiffeisen Banking Group is the largest private bank group in Austria with about 2,600 bank branches. Software development for banking applications is primarily managed by RACON Software GmbH, which is mainly responsible for software of all the Raiffeisen banks in Upper Austria. Typically, these banks have lots of different applications, such as management of loan processes or applications for transactions in securities and foreign-exchange businesses. A requirement is that these applications are able to share common aspects, such as customer management, including search and access to their accounts.
After consolidating the services-side landscape into a large set of Web services, RACON Software GmbH realized it had a client-side landscape built with lots of different applications using different infrastructures. The result of this was duplicated functionality and the propagation of many different flavors of user interfaces throughout the bank. The goal was to introduce a common desktop as a new foundation for all of these applications to streamline the user experience and the maintenance of their banking applications. The primary business drivers included the need to lower the training and maintenance costs by minimizing the number of different applications and by consolidating common client services into a single infrastructure.
The idea was to create a "bank-shell" as a central entry point hosting all applications. Application access would be role-based, although the fundamental look and feel, and behavior of all applications hosted in the shell should be the same for every user. As some banks are equipped with low-bandwidth connections to the backbone only, intelligent caching strategies were also required to keep the applications responsive. In addition, for some employees a disconnected, offline capability was a requirement. Finally, they required a rich user interface, as well as integration with Microsoft Office applications installed locally on the client. With this setup of requirements, although RACON Software GmbH wanted to integrate existing Web applications into the bank-desktop, a pure Web application was not an option.
As we started to investigate the technical aspects, we found that the requirements for the architecture of the smart client needed to support the following aspects:
- Pluggable architecture to enable modules being loaded dynamically, based on configuration and user/role assignment on application startup or even later at runtime
- An infrastructure for central management, registration, and configuration of common services, such as Web service agents required by all or the majority of the modules
- Support for loosely coupled communication between modules and components of these modules, based on a publish/subscribe pattern
- An existing infrastructure for common patterns, such as Model-View-Controller, Model-View-Presenter, or the Command Pattern
With the requirements in place, we started to look at frameworks that we could build upon to realize this solution.
Released in November 2005 by the Microsoft Patterns & Practices team, the Composite UI Application Block (CAB) is a framework for implementing composite smart clients. It supports most of the requirements mentioned previously and was our initial choice for implementing the "bank-shell" of Raiffeisen Banking Group in Upper Austria. The CAB provides the following functionality:
- Dynamically loading independent yet cooperating modules into a common shell based on a central configuration
- Support of the composition pattern at several levels for functional pieces, such as user-interface elements, user-interface processes, or client-side services
- Event-Broker for loosely coupled communication between functional pieces loaded into the client application
- Ready-to-use command-pattern implementation
- Base classes for MVC implementations
- Framework for pluggable infrastructure services, such as authentication services, authorization services, module location, and module-loading services
Unlike many other client-side application frameworks, the CAB is not based on a predefined shell application that is extensible via plug-ins; it is much more a framework for building both the extensible shell application and the modules that can be dynamically plugged into this shell. For this project, it was useful, as it put the CAB into the role of a meta-framework on top of which you can build your own frameworks. Finally, the CAB makes it possible for you to decide how much you want to use from its offerings and how much you want to extend or adopt from them.
We decided that the overall design of a composite smart client based on CAB should adhere to a layered approach where CAB provides the fundamental framework with all the necessary functionality for building a pluggable, extensible architecture. The shell and the infrastructure libraries are your application foundation (your framework on top of CAB), whereas it uses CAB-functionality to dynamically load plug-ins (called modules) containing the implementations of the business use cases. Figure 1 outlines a typical architecture of a CAB-based composite smart client.
Another interesting point is that the CAB object model enables a way of designing composite smart clients with a use-case-driven approach. Its object model clearly separates use-case controller classes from other components, such as views and their controller—or presenter—classes. The use-case controller classes, called WorkItem, are responsible for putting all the necessary aspects for a use case together. This means that they are responsible for managing the necessary state of a use case; for putting together the necessary views and controllers; for managing the workflow to complete a use case; and, finally, for providing the managed state to all of these components. Together with the layered architecture introduced in Figure 1, for our project it helped us better align the structure of the project team with the development processes for creating composite smart clients using the CAB.
Figure 1. Layering of CAB-based smart clients (Click on the picture for a larger image)
We learned that three aspects affected the development process for CAB-based smart clients: the shell, the infrastructure services, and the actual use cases. We therefore recommended three primary iterations for development:
- Start achieving a high-level understanding of requirements common to most of the use cases. Common UI-related requirements are candidates for being integrated into the shell directly or at least affecting the shell's layout and design. Non-UI related functionality that is common to all (or most of) the use cases is a candidate for central services.
- Create detailed use-case diagrams. Use cases are good candidates for becoming WorkItems (and sub-WorkItems) in your application design. Relationships between use cases are candidates for either using the command-pattern or the event-broker subsystem of CAB. Logically close-related WorkItems, such as WorkItems implementing use cases for the same actors (roles of users in your company) are good candidates for being packaged into modules together.
- Refine the use-case diagrams; analyze relationships between use cases, as well as reusability and security aspects of your use cases (WorkItems). During this refinement, you might need to refactor your WorkItem packaging slightly. For example, typical findings are WorkItems that are reused independently of others and therefore should be packaged into a separate module.
The use cases identified during the detailed use-case analysis are the foundation for identifying WorkItems in CAB. When taking the use-case-driven approach for designing WorkItems, each use case maps to a single WorkItem. Any relationship between use cases in the diagram is an indicator for two things: First, it can mean a containment of one WorkItem within another one; second, it definitely leads to communication between these WorkItems. Communication can be implemented via services, the event broker, or commands. Typically, a refinement and a detailed analysis of the relationships between use cases are indicators of which type a relationship exists (parent-child or just communication between the WorkItems) and therefore will help in deciding which communication mechanism to use. This refinement will affect your strategy in packaging WorkItems, too. Therefore, it is essential to keep this refinement-iteration in mind before you start developing the broad range of WorkItems. There is more on WorkItem identification and packaging later in this article.
As outlined previously, one of the first tasks is the design and implementation of the shell. The shell is the actual application and is responsible for loading and initializing base client-services, loading and initializing modules, as well as providing the basic UI of the application and hosting the views loaded by WorkItems. The CAB provides all the necessary base classes for supporting such functionality—a base class for a shell application that is able to authenticate users based on a central CAB-authentication service and that is able to dynamically load modules based on the user's role from a so-called profile catalog, as mentioned earlier.
As the basic user interface of an application is provided by the shell and is equal for each and every module loaded into the application, the shell needs to have some extension points and must fulfill requirements common to all use cases. Typical examples for common user-interface elements that are good candidates to be integrated into the shell are menus, toolbars, task panes, and navigation panes. The important point here is that some parts of the shell will just be extended (such as adding a menu to the menu bar) and some will be replaced completely as modules are dynamically loaded into the application. For these purposes, the CAB provides two types of shell-extensibility points that you can use when designing your own shell: UIExtensionSites and Workspaces. Workspaces are used for completely replacing parts of the user interface, whereas a UIExtensionSite is used for extending existing parts of the shell, such as adding menu entries or adding tool-strip controls. Figure 2 shows a typical example for a shell designed in Visual Studio 2005.
Figure 2. A typical example for a shell with Workspaces and UIExtensionSites
Workspaces and UIExtensionSites are publicly available to all services and modules loaded into the smart client application. The CAB allows you to access these in a very generic fashion, as shown here.
workItem.UIExtensionSites["SiteName"]Add<ToolStripButton>( new ToolStripButton());
However, to avoid tight coupling and errors, I recommend a layer of indirection between the shell and components that want to add the shell. Based on the functionality the shell needs to provide to other components, you can design an interface implemented by the shell (or the shell's main view presenter) for extending the shell and register the shell as a central service used by other components of the CAB, as shown in Figure 3.
Figure 3. Shell implementing your custom IShellExtension interface provided as a central service
This is a very simple yet powerful concept, because the components loaded into the smart client (either modules with WorkItems or modules with central services) do not need to know details about Workspace and UIExtensionSite names. Components loaded into the smart client can access these services, as shown here.
IShellExtensionService ShellService = WorkItem.Services.Get<IShellExtensionService>(); ShellService.AddNavigationExtension( "Some new Menu", null, someCommand);
Still, the implementation of this IShellExtension interface can leverage the existing CAB infrastructure to keep the shell-UI design decoupled from the shell-service implementation, but the developers creating the broad masses of WorkItems do not need to know any details, such as names of UIExtensionSites or similar shell details. And you can abstract other shell-related functionality through such a shell service—for example, a messaging or a help system integrated into the shell. But remember: It is important that the shell interface expose shell-UI-related functionality only. That means it is the right point of access to allow components loaded into the smart client to extend menus, tool-strips, a task bar displayed in the left part of the Window, or add a message to a common message pane (as introduced in the examples earlier in this section).
As mentioned earlier, infrastructure services encapsulate common functionality to modules and components loaded into the smart client. As opposed to shell infrastructure, these services are not bound to UI-specific tasks. More often, they encapsulate client-side logic. Out-of-the-box CAB introduces many infrastructure services, such as an authentication service and a service for loading a catalog of modules configured somewhere (by default, in the ProfileCatalog.xml file stored in your application directory). Of course, you can introduce your own services, too. Typical examples for central infrastructure services not introduced by CAB are:
- Business functionality-related authorization service (CAB introduces authorization for loading modules only, but not for specific actions that can be performed within modules and the application).
- Web service agents and proxies encapsulating calls to Web services and offline capability.
- Context services to manage user-context across WorkItems (see the section "Context and WorkItems," later in this article).
- Services for accessing application-centric configurations.
- Deployment services using ClickOnce behind-the-scenes for programmatic, automatic updates.
We learned that it is always important to start with defining interfaces for your common services, as CAB allows you to register central services based on types, as shown here.
MessageDisplayService svc = new MessageDisplayService(_rootWorkItem); _rootWorkItem.Services. Add<IMessageDisplayService>(svc);
Infrastructure services need to be encapsulated into separate infrastructure modules loaded before other modules with actual use-case implementations will be loaded.
WorkItems are components responsible for encapsulating all the logic for specific tasks the user wants to complete with the application. As such, WorkItems are the central and most important parts of your composite smart client, as they provide the actual business functionality to the users. CAB is able to manage a hierarchy of WorkItems that are responsible for completing work together. For this purpose, a WorkItem contains or knows about one or more views (called SmartParts) with their controller classes and models. The WorkItem knows which SmartParts need to be displayed at which time and which sub-WorkItems need to be launched at which time. Furthermore, a parent WorkItem is the entry point into a specific task. Finally, it manages the state required across SmartParts and sub-WorkItems.
During development we found that there are two ways to identify WorkItems for your composite smart client: a use-case-driven approach and a business-entity-driven approach. The best way to explain this is with some examples. (Please note that for the purposes of this article these are not representative of the project I was involved in, or any other banking group.)
One of the biggest advantages of the architecture of CAB is that it allows you to design your WorkItems based on use-case diagrams. Often, you will have a one-to-one mapping between use cases and WorkItems; typically, one use case will be encapsulated into a WorkItem. Therefore, WorkItems are nothing but use-case controllers implementing the user interface processes necessary for completing a use case (task) and putting all the required parts together for doing so. The use-case diagram shown in Figure 4 was designed for a stock-management system that has been used for consulting with customers about their securities transactions (stock business) and fulfilling customer securities transaction requests.
Figure 4. A sample use-case diagram
The application implementing the use cases in Figure 4 supports front office employees for consulting customers and supports back office employees for completing the securities transactions. To achieve this, first you map one use case to one CAB-WorkItem. Relationships between use cases can be of two flavors: Either a use case is a sub-use case of another one, or a use case is used by many other use cases other than its own parent. Of course, a use case that is really just a sub-use case not reused by others results in a sub-WorkItem. Pure sub-use cases are sub-WorkItems that are not accessible from outside their parents, whereas use cases used by many other use cases in addition to their own parents need to be accessible either directly or through their parent WorkItem.
Parent use cases are the entry points for all their sub-WorkItems. Typically, a module has a root-parent WorkItem called Module Use Case Controller. This one is responsible for managing the state required for direct sub-WorkItems and providing the right context for these. Module Use Case Controllers typically add the services a module can offer to others, retrieve service-references to other services they require, register UIExtenionSites and commands for launching one of their sub-WorkItems, and launch their sub-WorkItems. Simple WorkItems without sub-WorkItems or sub-WorkItems themselves typically execute the same steps, except that they should register services that are available within their hierarchy level, only. The Module Use Case Controller WorkItems should be created whenever a module is loaded into the application.
When taking a closer look at the use-case diagram in Figure 5, you will figure out that not all use cases will result in WorkItems. Take a look at the "Bank Contact Request" use case. Remember that you want to design a smart client used by bank employees only. A customer requesting a stock-transaction consultation (expressed through the "Bank Contact Request") can either go to the front desk directly or use an Internet-banking or Net-banking solution (or something else). In any case, this use case is nothing that needs to be integrated into the smart client for the bank employees, just the opposite. "Customer Identification" needs to be part of the smart client you are designing now.
Another example is the "Offline Consultation" use case. What does it mean to your smart client? Is it really going to be a separate WorkItem? Does it change anything in the business logic? No, therefore it won't be a separate WorkItem. But this use case is an indicator for something completely different; it's an indicator for the need of offline support. This is something you therefore need to verify against the infrastructure services identified earlier: Web service agents, for example, need to support connection detection, offline reference data stores, and update message queues.
Figure 5. Excluding use cases
Some WorkItems will become parents, too, although they appear just as sub-WorkItems in the use-case diagram. This typically happens when a use case logically belongs to a parent use case, but in a detailed analysis you figure out that it does not share state, context, or anything else with its original parent or other sub-use cases and it does not depend on other commonalities, either. In that case, to avoid unnecessary overhead, you should make them parent WorkItems, too. Take a close look at the use cases "Find Stock" and "Find Business" in the use-case diagram of Figure 6. These use cases are just used for finding stock papers or ordered business transactions. They do not depend on shared state or on context of their parent WorkItem. So they are perfect candidates for becoming parent WorkItems themselves called through commands registered by the module (more exactly, the Module Controller WorkItem) to which they belong.
Figure 6. Sub-WorkItem or parent WorkItem
Finally, after creating your use-case diagrams, mapping use cases to WorkItems, and then excluding use cases and refactoring the hierarchy of your WorkItems, you will get a complete WorkItem object hierarchy for your composite smart client similar to Figure 7. For the sake of performance and simplicity, I definitely recommend keeping the use-case diagrams and therefore the WorkItem as simple as possible. The advantage you get with the approach described above is a clear, structured way for identifying WorkItems and especially identifying reusability-aspects of WorkItems.
Figure 7. Class diagram showing the WorkItems for the previous use cases
A much simpler approach for smaller, simple applications is identifying and structuring WorkItems based on the business entities processed by the smart client application. You create a list of business entities processed by your application. Typical examples are Customer, Product, Stock, StockCredit, StockDebit, and StockTransfer. For each of these entities you create a WorkItem. As StockTransfer is a combination of both, it uses StockCredit and StockDebit as sub-WorkItems.
After you have identified your WorkItems, you need to package them into modules. A module is a unit of deployment for CAB-based smart clients. Basically, you package logically related WorkItems addressing the same business space into a module. Taking the original use-case diagram from Figure 4, that would mean that you create a module for stock consultation, one for stock-business WorkItems, and a last one for stock management, as shown in Figure 8.
Figure 8. Packaging WorkItems into modules (Click on the picture for a larger image)
But there are some additional criteria for deciding on the packaging of WorkItems into modules. These additional criteria are:
First and foremost, modules are configured in a profile-catalog containing a list of CAB modules that need to be loaded dynamically when your composite smart client starts. These modules can be configured based on a user's role-membership. Therefore, security plays a central role in deciding on how to package WorkItems into modules. Let's suppose a user is not allowed to manage any stocks, as shown in the previous use cases. But, definitely, a user not allowed to create new stock papers or locking stock papers will need to find these while completing the tasks. When configuring your stock-management module (which contains the "Find Stock" WorkItem according to the packaging in Figure 8) in a way that prevents bank employees from the back office from using it, they are also not allowed to use the "Find Stock" WorkItem. But as they need this WorkItem for other tasks, it is useful to package it into a separate module. If you want to reuse WorkItems independently from others, it makes sense to encapsulate them into separate modules. Likewise, if you need to configure WorkItems independently, also put them into separate modules.
In the example demonstrated in Figure 8, this is true for "Find Stock," "Find Business," and "Stock Order Collection" WorkItems. Therefore, it is useful to encapsulate them into separate modules, as shown in Figure 9.
If you figure out that configuration, security, and reusability requirements are equal (or nearly the same) for some of the WorkItems outsourced into separate modules according to Figure 9, you can package them into one module, instead of three modules, too.
Figure 9. New packaging, according to security and reusability requirements (Click on the picture for a larger image)
For example, if the security, configuration, and reusability requirements of all "Find X" WorkItems are equal, you can create one module containing all the "Find X" WorkItems.
Finally, it's important to note that each module will result in a separate .NET assembly. After you have created your shell and infrastructure services and have identified all WorkItems, including their packaging structure, your development teams can start with the development of the modules and their WorkItems.
Although CAB provides a great infrastructure, the learning curve for some can be high. Furthermore, working with CAB requires developers completing many manual steps such as creating classes inherited from the WorkItem base class to create a use-case controller or to create Controller-classes, View-classes, and Model-classes manually. In real-world projects with large teams, this can result in many different practices for completing these steps, as every developer has separate preferences and working styles.
The Smart Client Software Factory (SCSF) is an extension to Visual Studio 2005 Professional (or higher) for automating and streamlining these tasks. The SCSF is based on the Guidance Automation Extensions also provided by the Microsoft Patterns & Practices team. The Guidance Automation Extensions are an infrastructure allowing architects and lead developers to easily create extensions in Visual Studio for automating typical development tasks with the primary target of enforcing and ensuring common directives and guidelines for their projects. SCSF provides such guidelines for CAB-based smart clients and automates things like creation of new modules, views based on the MVP pattern, and event publications and subscriptions. For more information on SCSF and Guidance Automation Extensions, refer to "Guidance Automation Extensions and Guidance Automation Toolkit," "Introduction to the Guidance Automation Toolkit," and "Smart Client Software Factory" in the MSDN Library.
The SCSF is a very useful tool that supports developers in creating CAB-based smart clients while adhering to architectural decisions—and also increasing developer productivity. because of its integrated and automated developer tasks in Visual Studio 2005.
With the Composite UI Application block and Smart Client Software Factory, the Microsoft Patterns & Practices team provides a powerful infrastructure for creating composite smart clients. The use-case-driven analysis process for identifying WorkItems packaged into modules dynamically loaded by your shell enables a powerful way for identifying reusable, central functionality for larger composite smart clients. This avoids implementing things twice and therefore lowers costs for maintenance. While CAB provides all the necessary base classes within its framework, the Smart Client Software Factory adds the necessary guidance packages for automating certain developer tasks and providing guidance to developers. Together, they enable you to create rich composite smart clients in an agile and highly productive fashion.
RACON Software GmbH. is currently implementing the first large set of modules with more than 150 WorkItems, integrated into the common bank desktop framework. The first reviews of the bank desktop framework have shown that the approaches to design and infrastructure work well for making development as easy as possible, especially for line-of-business developers. The Shell-Extension service pattern used makes the integration of more complex views built with Windows Presentation Foundation (WPF) easier because the line-of-business developers do not need to worry about Windows Forms and WPF interoperability, since this functionality is encapsulated as a feature into the Shell-Extension service. Finally, the use-case-driven approach offered an optimum solution for identifying WorkItems that are reused across different sets of modules. Overall, the CAB and SCSF took the development team forward in finding a pragmatic approach for creating the common, extensible bank desktop framework for RACON Software GmbH.
Mario Szpuszta works in the Developer and Platform Group of Microsoft Austria and supports software architects of enterprise accounts in Austria. Mario always focused on working with Microsoft technologies and started working with the .NET Framework very early. In the past two years, he focused on secure software development, ASP.NET Web development, and Web services, as well as integration of Microsoft Office in custom applications using Microsoft .NET based on Visual Studio Tools for Office, XML, and Smart Documents. Mario worked with Ingo Rammer as coauthor on writing Advanced .NET Remoting, 2nd Edition, and participated in writing Pro ASP.NET 2.0 in C# with Matthew MacDonald.
This article was published in the Architecture Journal, a print and online publication produced by Microsoft. For more articles from this publication, please visit the Architecture Journal website.