Business Modules And Interfaces In The SCSF Smart Client Solution (Introduction To CAB/SCSF Part 19)

Introduction

Part 18 gave a brief introduction to the Smart Client Software Factory. This article continues that discussion by looking at business modules, and also examining how the various modules in a Smart Client solution are expected to interact.

Recap on the Smart Client Application

In part 18 we saw that a ‘Guidance Automation’ package in the Smart Client Software Factory lets you create a base solution for a smart client program. It sets up four projects, three of which are infrastructure projects.

One of the projects is an empty ‘Infrastructure.Module’ project. Infrastructure.Module is a CAB module as described earlier in this series of articles: it isn’t directly referenced by the other projects in the solution but can be used to write infrastructural code for the solution without any tight-coupling with the rest of the solution. We’ll examine this in a little more detail below.

Business Modules

It isn’t intended that we put business logic into the Infrastructure projects discussed above. Instead we are meant to create ‘business modules’.

To create a business module we use another of the Guidance Automation packages: we right-click the solution in Solution Explorer, select Smart Client Factory/Add Business Module (C#), click ‘OK’ in the ‘Add New Project’ window and then click ‘Finish’ in the ‘Add Business Module’ window.

This gives us two new projects in the solution with default names Module1 and Interface.Module1 as below:

scsfprojectmodule.jpg

Once again here Module1 is a Composite Application Block module, and is not referenced by any other project in the solution. However, Module1.dll IS added to the ProfileCatalog (which is in Shell). This means that the Load method of a class inheriting ModuleInit in Module1 will get called by the CAB at start up, as described in part 1 of this series of articles. The class with the Load method in Module1 is called ‘Module’. We’ll look at what the Load method is doing in the next article in this series.

Note here that the Module and ModuleController classes are identical to those in Infrastructure.Module. Note also that there’s really no code at all in Module1.Interface: there are just some empty classes in a folder called Constants.

Business Module Interaction with the Rest of the Smart Client Project

As discussed in part 1 of this series, a ‘module’ is a standalone project to be used in a composite user interface. So our business module here is intended to be a slice of business functionality that can potentially be developed independently of the other modules in the application. Because the business module isn’t directly referenced by other modules a separate development team could potentially work on it and change it. It can then in theory be plugged in to the containing framework without the need for code changes in the framework. The other project’s libraries might not even need to be recompiled since they don’t actually reference the business module directly.

Clearly in practice it’s likely that the business module will have to interact with the rest of the Smart Client solution on some level. There will be a need for:

  1. The business module to use the infrastructure components: for example it might need to put a toolstrip into the Shell form.
  2. Other components in the Smart Client solution to use some of the business module functionality. As a simple example we might have a business module that deals with customers and a back-end customer database. It might have screens to show customer data and allow updates. Another business module might want to display these screens in response to a request: an Orders module might allow a double-click on a customer name to show the customer.

We want to achieve the interaction described above in a way that’s as loosely-coupled as possible, so that we can change the system easily. To do this we make sure that all interaction is through the Interface projects.

We now examine each of these possible scenarios in more detail:

1. The Business Module Using Infrastructure Components

For this scenario in our example solution Module1 references Infrastructure.Interface directly. It is set up to do this by default when you add the business module to the solution. Note that Infrastructure.Interface is intended to (mainly) contain .NET interfaces: it is not meant to contain large amounts of code.

Note that Module1 does not reference Infrastructure.Module or Infrastructure.Library directly, nor should it under any circumstances. These projects may well be under the control of a separate development team from our business module team, and they may need to be updated independently of the business modules. So we reference the interface project, and that handles our interaction with the Infrastructure libraries.

This seems to be a concept that developers working on these projects have difficulty with: almost every member of my development team at work has added one of these libraries to a business module at some stage.

I think the confusion arises because it’s not necessarily obvious how we do this. If my module just references an interface how can I actually call any functionality using just the interface? The answer is that we are once again using the dependency inversion and dependency injection concepts described in part 3 and part 4 of this series of articles.

An example here may help.

Example

We’ll use the WorkspaceLocator service that the SCSF adds into the Infrastructure.Library component when we create a Smart Client solution. The WorkspaceLocator service lets you find the Workspace a SmartPart is being displayed in, although this isn’t relevant for this discussion: all we’re interested in is how to invoke the service from a business module.

There’s a class called WorkspaceLocator that actually does the work in SmartClientDevelopmentSolution.Infrastructure.Library.Services. There’s also an interface in Infrastructure.Interface as below:

namespace SmartClientDevelopmentSolution.Infrastructure.Interface.Services
{
    public interface IWorkspaceLocatorService
    {
        IWorkspace FindContainingWorkspace(WorkItem workItem, object smartPart);
    }
}

Note that Infrastructure.Library references Infrastructure.Interface and so WorkspaceLocator can implement this interface. Note also that our business module, Module1, also references Infrastructure.Interface but NOT Infrastructure.Library. So it can’t see the WorkspaceLocator class directly and thus can’t call FindContainingWorkspace on it directly. So how do we use the service?

The answer is that this is the standard CAB dependency inversion pattern using WorkItem containers to access objects.

At start up the solution creates an instance of the WorkspaceLocator service and adds it into the Services collection of the root WorkItem, referencing it by the type of the interface:

RootWorkItem.Services.AddNew<WorkspaceLocatorService, IWorkspaceLocatorService>();

This actually happens in the new SmartClientApplication class mentioned in part 18, but all we really need to know is that the service will be available on the root WorkItem.

Now, in our module we know we can get a reference to the root WorkItem in our new module by dependency injection in a class:

        private WorkItem _rootWorkItem;
 
        [InjectionConstructor]
        public Module([ServiceDependency] WorkItem rootWorkItem)
        {
            _rootWorkItem = rootWorkItem;
        }

Our module also knows about the IWorkspaceLocator interface since it references Infrastructure.Interface. So it can retrieve the WorkspaceLocator service object from the root WorkItem using the interface, and can then call the FindContainingWorkspace method on that object:

            IWorkspaceLocatorService locator = _rootWorkItem.Services.Get<IWorkspaceLocatorService>();
            IWorkspace wks = locator.FindContainingWorkspace(_rootWorkItem, control);
            MessageBox.Show("Workspace located: " + wks.ToString());

In summary, as long as our module knows the interface to the functionality it needs, and knows how to retrieve an object that implements that interface from a WorkItem collection of some kind, it doesn’t need to have direct access to the underlying class to use the object. This was explained in more detail in earlier articles in this series.

2. Other Components Using the Business Module Functionality

For other components to use our business module functionality we are expected to work out what functionality our business module should expose to the rest of the solution. We should then define interfaces that allow access to that functionality and put them into our Module1.Interface component.

Other components in the solution can then reference Module1.Interface and call the functionality. Note that to allow them to do this we need to ensure that the correct objects are available in a WorkItem, as described above. Once again other components should NOT reference Module1. We can then change Module1 without impacting the other components.

We may of course need to change the interfaces. In this case it may be sensible to retain the old version of the interface component itself so not all other components have to upgrade, and to add a new version with the changed interfaces in as well. The old interface can then be disabled when everyone has upgraded.

Conclusion

This article has examined modules in a Smart Client solution, and discussed how they should interact.

Part 20 of this series of articles will look in a little more detail at some of the new code structures in modules in a Smart Client solution.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值