最近开发需要用到Smart Client Software Factory(SCSF)进行Smartclient的开发,SCSF其实是基于CAB和企业库的一个软件工厂开发工具包,核心就是CAB,所以了解SCSF其实就是需要对CAB进行了解,在网上找了些资料,放在这里以便查阅:
文章引用:http://www.cnblogs.com/mixiaobo/archive/2008/01.html
微软开发了一套开源的企业库 (Enterprise Library),通过使用这套企业库里面提供的各种应用程序块可以极大的提高应用程序的开发效率和缩短开发周期,也由此得到了大家的广泛应用。
企业库包括大家熟知的如下应用程序块:
Caching Application Block.
Cryptography Application Block.
Data Access Application Block.
Exception Handling Application Block.
Logging Application Block.
Security Application Block.
Validation Application Block.
Policy Injection Application Block.
这些应用程序块都是一些常用的非业务相关的公共模块,相关资料在网上搜一下就一大堆,所以很多使用过企业库的朋友应该并不陌生。所以这里也就不再多说。
我今天要给大家介绍的其实也是一个应用程序块,但是是独立于企业库单独安装的。网上也能搜到一些相关资料,但总觉不够全面,如果不看安装程序提供的帮助文档,网上查到的很多中文文章,还真让人看的一知半解。知其然,不知其所以然,所以本人就干脆看着帮助文档来细细研究。先给个下载地址:http://www.microsoft.com/downloads/details.aspx?FamilyId=7B9BA1A7-DD6D-4144-8AC6-DF88223AEE19&displaylang=en
下载后会有一个CAB_CS的安装文件,安装即可。
按照微软官方的说法,Composite UI Application Block (CAB)使用了目前商业客户端应用程序常用的设计模式来构建了一个灵活的基础框架。基于这个框架可以很容易的帮助你编写运行在microsoft .net平台上的具有复杂用户交互界面的Windows Form 应用程序。那么他有哪些优点呢?
1:允许构建由各个具有协作关系的独立模块组合成的复杂应用。
2:分离关注点,能将各个模块的开发的关注点和Shell的开发分离开来。
3:提供了一个能支持高质量的桌面应用开发的框架
4:提高了生产力和节约了开发时间,进一步巩固了架构师和开发人员的劳动成果。
这些特点如何体现出来呢?我后面的文章将和大家一同来分析。
应用程序架构元素
CAB. Composite UI Application Block的缩写
Module. 应用程序的组成部件,其中包含SmartParts,支持Service,业务逻辑和配置信息等
ObjectBuilder.通过策略和配置信息自动创建对象实例的对象构造器。
Shell. 承载了用户接口元素,SmartPart,服务的外壳宿主程序。
Visualizer. CAB中提供的,可以查看应用程序中的WorkItem的动态分级视图的工具。
外壳元素
shell application. 承载了用户接口元素,SmartPart,服务的外壳宿主程序。
SmartPart. 一个展现数据的视图,比如控件,Windows 窗体或是一个向导页。
SmartPartInfo. 存放SmartPart的相关信息的类,以便被workspace所使用,比如可以在Smartpartinfo中设置SmartPart的显示标题。
UIElement. 一个以Shell作为宿主的控件,该控件能被多个Module所共享,这样的控件有:toolbar button , menuitem , status panel等
UIElement adapter. 管理特殊类型的UIElement的显示的类
Workspace. 封装了控件和SmartParts的某种可视化的布局的组件,比如以Tab方式显示页面。
相关模式
Blackboard. 通过提供一个共享信息的地方,使其他组件能设置或获取这个地方的信息,已达到信息共享的模式。
Builder/Inversion of Control/Dependency Injection. 该模式通过组件之间的依赖关系进行运行时的注入,来达到组件重用和松散耦合的目的。
Event Broker. 允许松散耦合的组件通过发布订阅的方式进行通讯的模式。
Memento.持久化一个对象的内部状态状态,并在需要的时候可以在后期对状态进行恢复的模式。
Model-View-Controller (MVC). 该模式将领域模型,视图和基于用户输入产生的行为(控制器)分成3个不同的组成部分。视图部分提供给用户进行交互,交互信息通过视图传到控制器,控制器更新模型,模型引发事件,从而更新视图。关系图如下:
Model-View-Presenter (MVP). 该模式将领域模型,视图和基于用户输入产生的行为(控制器)分成3个不同的组成部分。视图部分提供给用户进行交互,交互信息通过视图传到控制器,控制器更新模型,模式触发事件到控制器,同时,控制器负责更新视图。关系图如下:
相关编程模型
Component. 应用程序的可视或非可视组成部件,比如SmartParts, services, 和控件.
Container. 包含了组件或服务的类。
event broker. 支持松散耦合的发布订阅事件机制的系统。
State. 存在于WorkItem中,以键-值的字典的方式来存放共享信息。
Service.以松散耦合的方式为其他的组件提供功能的组件。比如:ModuleLoaderService
WorkItem. 运行时的组件和服务的容器,该容器能协作其中封装的用例代码的执行。一般WorkItem和用例对应。
相关角色
infrastructure developer. 负责进行应用程序的基础服务的开发的开发人员。
module developer. 负责进行应用程序的业务组件的开发的开发人员。
shell developer. 负责建立应用程序外壳的开发人员。
SmartPart developer. 负责开发应用程序的SmartParts的开发人员。
CAB提供了一个非常灵活的编程框架,利用这一框架,可以很好的将一个应用分离成不同的模块进行开发。
我们来看看一个基于CAB的典型应用都有哪几部分组成:
首先一个CAB应用需要通过继承自FormShellApplication的应用程序类来进行启动。FormShellApplication需要传入两个类型参数,继承自WorkItem的类,和继承自Form的类(应用程序主界面FormShell)。主界面上可以放置供所有界面视图使用的公共的UI Element (如:Toolbar)。和显示用户界面的WorkSpace。WorkItem是封装了用例实现的容器,容器中的对象可以共享信息。WorkItem也可以包含下级WorkItem.
由于CAB的优点之一是能够很好的支持模块化开发,业务开发人员可以专注于某一方面业务模块的开发,如:仓库管理系统中,入库和出库就是同一个系统中的两个业务点,在CAB的支持下,完全可以将这两个业务点交由不同的开发人员进行开发,只要按照同样的既定的规范开发(界面规范,接口规范等),就能很好地进行集成。CAB中通过Module很好的实现了这一点。
一般我们将module实现于dll文件中,每个Dll文件可能包含系统某一方面的功能,当我们在独立开发好各个Module之后,我们可以利用CAB有选择的加载这些功能模块,从而支持系统运行。要实现Module被加载很简单,只需要在ProfileCatalog.xml中将需要被加载的module配置进去就可以了。当然前提是被加载的模块需要符合CAB的Module设计的标准。如图中所示,这个dll中需要有一个继承自ModuleInit的类,Module被加载的时候,该类的Load方法将被调用,所以大家也可以在继承类中通过重载Load方法来扩展其加载时的行为。
一般情况下,在独立的模块中,我们还需要实现相应的继承WorkItem的类来实现其业务用例的封装,这个WorkItem我们需要将其添加到RootWorkItem的WorkItems集合中,以便和其建立联系。
在WorkItem中可以使用MVP的模式来实现我们的系统。上图中的View是在主窗体的WorkSpace中显示的和用户进行交互的界面,Presenter则是响应界面操作,处理业务逻辑的地方,Model则是我们的数据。
虽然我们的系统可以模块化的独立开发,各个模块之间实现了松耦合,但是系统运行时,CAB还是需要通过某种方式将各个模块糅合在一起,以便形成一个有机的整体。首先CAB是一个IOC的容器,它可以在运行时根据需要实例化各种对象,并将其注入到对其有需要的对象中,达到对象的组装,然后可以通过发布订阅事件系统及共享State实现了对象间的通讯。
CAB涉及的Dll有:
常用的命名空间如下:
附两张CAB中命名空间Microsoft.Practices.CompositeUI和Microsoft.Practices.CompositeUI.Winforms涉及的类和接口:
个人认为,CAB是的不错的WinForm应用框架,目前主要还是体现在对界面层和业务逻辑层的支持上。如果配合其他的技术框架如Nhibernate对数据库层进行支持,将会更好。
现在我们来看看基于CAB的应用程序中非常重要的一个类。这个类可以认为是一个CAB应用的启动点。他就是FormShellApplication。
FormShellApplication的继承关系如下:
该类需要传入两个类型参数,一个是继承自WorkItem的类(如果不需要通过重载WorkItem的OnRunStarted方法来实现更多处理,这里可以直接使用WorkItem),一个是继承自Form的窗体。
public class Program:FormShellApplication<MyWorkItem,ShellForm>
通常我们在Main方法中调用其Run方法,执行Run方法会初始化许多应用程序信息。可以通过override来重写或增加FormShellApplication的方法处理。
FormShellApplication的初始化包括(可以通过子类重载FormShellApplication的相关方法来判断其执行顺序)以下步骤:
1. RegisterUnhandledExceptionHandler |
2. 创建 Build strategies |
3. 创建顶级 WorkItem |
4. 创建和初始化 Visualizer |
5. 添加 services |
6.创建Shell |
CAB提供的标准服务如下,开发人员可以在此基础上进行扩展:
ü SimpleWorkItemActivationService
ü FileCatalogModuleEnumerator
ü WindowsPrincipalAuthenticationService
ü ModuleLoaderService
ü DataProtectionCryptographyService
ü TraceSourceCatalogService
ü CommandAdapterMapService
ü WorkItemExtensionService
ü WorkItemTypeCatalogService
ü ControlActivationService
执行了FormShellApplication的子类的Run方法,一个CAB应用程序就算是启动了。随后就是根据自己的需要来执行WorkItem了。
通过前面的介绍我们可以知道在静态Main方法中执行继承自FormShellApplication的对象实例可以进行很多框架的初始化工作,如加载模块,加载服务等。从FormShellApplication的类型定义中:
public abstract class FormShellApplication<TWorkItem,TShell> : WindowsFormsApplication<TWorkItem,TShell> where TWorkItem : WorkItem where TShell : Form
可以看到,需要两个类型参数,一个是WorkItem,一个是Form,FormShellApplication在进行初始化工作的时候会对这两个类型进行实例化。并且会将Form类型的实例 Show出来。这里的Form类型的实例即是我们应用程序的主界面。
主界面将是用户与系统的主要交互区域。他将做为一个容器,承载其他的业务界面。
CAB中提供了WorkSpace组件,他作为用户控件和SmartPart控件的显示容器,可以以各种各样的统一的显示方式呈现业务界面。也就是说WorkSpace可以以不同的风格呈现其中的用户控件。WorkSpace支持显示,隐藏,激活和关闭其中的用户控件。当然,CAB目前提供了几种默认的显示风格,接下来我们将一个一个介绍,如果开发人员觉得这些显示风格还不够用,当然也可以自己进行扩展。
所有类型的WorkSpace都是实现了接口IWorkSpace的。
目前CAB提供的WorkSpace有:
WindowWorkspace
MdiWorkspace
TabWorkspace
DeckWorkspace
ZoneWorkspace
下面就来说说每种WorkSpace的特点:
1, WindowWorkSpace
该WorkSpace能将你需要在其中显示的用户控件在一个WinForm中显示出来,我们可以通过和该WorkSpace对应的WindowSmartPartInfo来设置用户控件的显示属性,如标题信息,是否是模式窗口等。
2, MdiWorkspace
该WorkSpace是在WindowWorkSpace的基础上发展而来的,他同样将一个用户控件在单独的Form中进行显示,并且,他还将以MDI的形式显示和管理其子窗体。对应用户控件的显示信息的设置是通过设置smartPartInfo,然后作为参数传入WorkSpace的Show方法。
3, TabWorkspace
该WorkSpace能将你需要在其中显示的用户控件以Tab页的形式显示出来,我们可以通过和该WorkSpace对应的TabSmartPartInfo来设置用户控件的显示属性,如标题信息。
4, DeckWorkspace
该WorkSpace将以类似于重叠的卡片的形式来显示用户控件,当前激活的界面将在卡片的最上方。卡片的数序是由Workspace进行管理。没有和该WorkSpace对应的SmartPartinfo.
5, ZoneWorkspace
该WorkSpace将以平铺的方式显示用户控件,比如类似OutLook的界面,可以将一个界面划分成多个Zone,每个Zone都是作为呈现用户控件的一个容器。同样的,我们可以通过设置ZoneSmartPartInfo来设置用户控件在WorkSpace中的显示属性。
从WorkItem的属性列表中我们可以看出,WorkItem中支持命令和事件,同时WorkItem可以嵌套,通过Parent进行关联,通过RootWorkItem可以获取顶层WorkItem,开发人员可以利用这一特性来组织自己业务用例和划分业务用例的粒度。WorkItem中使用State来共享信息,在同一个WorkItem容器中的对象可以共享访问这个信息。 WorkItem容器中的对象对其中的Service都可以访问。
WorkItem通过调用Run方法进行启动,调用这个方法的时候会调用他的OnRunStarted方法,一般我们自定义了一个WorkItem,如果有需要可以重载其OnRunStarted方法来自定义其启动逻辑。Run方法调用后会触发RunStarted事件。
一般我们会借助WorkItem提供的特性,采用MVC的模式进行业务用例的封装。使用SmartPart作为用户交互的UI显示部分(View),创建一个控制类来进行业务逻辑的封装(Controller),然后将业务数据存放于内存实体中(Model)。用户界面和内存实体采用绑定的方式关联起来。
MVC模式的应用,我们来看看他的初始化和用户交互过程中的操作是如何进行的?
初始化:
1,应用系统加载WorkItem,WorkItem将用户界面在WorkSpace中进行显示
2,用户界面加载,调用控制逻辑进行初始数据的处理和获取
3,控制逻辑获取初始数据
4,控制逻辑将数据存放于内存实体中
5,控制逻辑将数据与用户界面进行绑定,界面会自动根据绑定的数据源进行初始化显示
用户交互:
1,用户操作用户界面,调用相应控制逻辑
2,控制逻辑进行业务处理,进行相关服务或数据库访问
3,控制逻辑修改内存实体中的数据
4,由于数据与界面绑定,数据的变化直接反应到界面显示。
一、简单概念介绍
CAB提供一个开发环境能很好的隐藏复杂度和提高生产力,通过高度抽象和关注点的分离,开发人员能够关注于业务逻辑提高基础框架代码的复用。Smart Part是整个CAB体系中重要的一部分,它可以将界面独立于业务逻辑,让界面和业务逻辑松散的耦合起来。Smart Part的应用中有几个重要概念:
WorkSpace:作为一个容器,它可以统一的添加和显示视图。CAB中提供了一组控件作为视图的容器,包括DeckWorkSpace,MDIWorkSpace,TabWorkSpace,WindowWorkSpace,ZoneWorkSpace。它们的作用有点类似普通Windows 控件Panel,MDI窗口,TAB控件,Window窗体。在程序中根据名称可以通过WorkItem的WorkSpace集合索引到它们。
Smart Part: 也可以被称作View,实际上是一个个自定义的控件。
其他像WorkItem, State之类的概念我在前两次的学习笔记中曾经介绍过了,这里就不再罗嗦了。
我们利用CAB中WorkSpace和Smart Part到底可以做些什么呢?WorkSpace可以一致的显示多个控件,Shell 开发人员创建定制的 workspaces 以提供控件周围相似的框架和修饰,共享的布局和定位,眩目的界面切换。下面我们还是通过一个实例来逐步理解CAB之Smart Part应用给我们带来的好处,特别是在多个数据来源,将多个界面块组合成一个统一美观的整体时的应用方便性。
二、实例研究
1.应用场景
我相信研究过CAB的朋友肯定知道微软提及的一个应用案例,Dell的客服桌面应用。该应用就是为了提高克服人员的办公效率,将在提供客户服务过程中需要从多个软件系统获取的信息,统一的集成到一个界面上来。也许大家会为该应用卓越的表现力,而感到亲切、人性化,其实类似这样的应用我们通过CAB基础件的应用,也可以达到类似的效果,而不需要在界面化太多的精力去处理业务和界面之间的交互。为了能够说明问题,我在学习过程中也做了一个简单的例子。
大家可能都开发过网站或者用过Share point portal,那大家肯定都应该知道Web Part这个概念,在CAB中类似的有了Smart Part的概念。Web Part能将一个个的信息块集成到一个统一的Portal,Smart Part也能将不同的信息块集成到一个界面。我们不妨假设这样一个应用场景:
假设您是某百货店的店长,希望每天能看到店里的销售业绩,能看到自己每天的邮件,能看到自己喜欢的Blog上的资源,能有软件提示自己每天的行程安排,最重要的是这些信息需要在一个统一的界面上显示出来。面对这样的需求,CAB来开发界面大有用武之地,如果采用SOA的架构,更能使遗产系统的价值利用得更好。
2.需求分析
具体来说该软件有以下需求:
a.销售业绩的信息从店面现有的进销存系统中获得。
b.Blog信息通过RSS获得
c.每天的日程安排情况从秘书给自己设定的OA系统中获得。
d.电子邮件通过集团统一的电子邮件系统中获得,该邮件系统支持Web Service。
e.软件界面简单统一,不能有过多的界面切换,最好能将所有概要信息显示在一个界面之上。
f.软件开发周期要短,尽量利用现有系统的数据和逻辑。
g.界面可能会扩展,以后可能需要集成更多的系统。
面对这些需求,我们不难得出结论:用CAB基础件开发界面,用Web Service集成遗产系统信息都是不错的选择。
3.开始建立应用
由于本例我们主要是为了学习CAB,至于Web Service的应用不做阐述。为了模拟类似的效果,和SOA系统开发过程中共享契约的原则,我们通过数据集来代表契约(XSD文件),用实际的XML文件代表Web Service的返回结果。
第一步:建立解决方案
a.启动VS2005,新建windows application,命名为TestSmartPart在项目中引用以下组件:
- Microsoft.Practices.CompositeUI;
- Microsoft.Practices.CompositeUI.WinForms;
- Microsoft.Practices.ObjectBuilder
第二步:绘制主窗口
a.将系统默认产生的Form1窗体,命名为TestSmartPartForm。
b.通过菜单-->工具-->自定义工具箱,选择浏览Microsoft.Practices.CompositeUI.WinForms.dll,这是工具箱中能出现DeckWorkSpace,MDIWorkSpace等控件。
c.打开TestSmartPartForm的设计视图,调整窗体的高度和宽度,绘制5个DeckWorkSpace控件,分别命名为deckWS_Calendar,deckWS_Blogs,deckWS_Plan,deckWS_Task,deckWS_MainEmail。最终的界面效果如下图:
第三步:定义数据实体(或者定义服务契约)
a.新建DataSources文件夹。
b.在DataSources文件夹中添加myBlogs,myEmail,myTasks,myPlans四个数据集。具体结构在文中不作描述,大家可以参考文后的源代码。
c.根据数据集的结构,建立数据源。这里我们用Xml文件来代替数据库中读取的数据,分别添加myBlogs.xml,myEmail.xml,myTasks.xml,myPlans.xml。数据的内容我在此略过。将各个Xml文件内容录入完成后,拷贝到运行目录。我不知道为什么设置文件的Copy To OutPut Dierctory属性为always,VS.Net编译项目的时候无法复制到输出目录,难道该属性是在建立安装程序才起作用?后来我发现是生成的时候连同DataSource文件夹,一起输出到了Bin目录,因此读取的时候也需要加上DataSource目录。
第四步:建立视图
a.建立BlogView、DailyPlan、DailyTask、Emails四个文件夹,这样做的目的只是为了让项目的结构更加清晰,同时以后要是对这些视图进行扩展,也可以把子视图都放到对应的文件夹里。
b.在项目根目录下添加UserControl,命名为TitlePart.cs。给该用户控件加上一个lable控件,并且加入以下代码:
c.建立BlogView视图。在文件夹BlogView新建UserControl,命名为myBlogView.cs。将该类从TitlePart继承,将标题设置为“我的博客".加入Grid控件和数据集myBlogs进行绑定。最终效果如下图:
d.建立dailyPlanView视图。在文件夹DailyPlan中新建用户控件,命名为myPlan.cs。将该类从TitlePart继承,将标题设置为“我的日程"。加入Grid控件和数据myPlans进行绑定。
e.建立myTaskView视图。在文件夹DailyTask中新建一个UserControl,命名为myTask.cs,将该类从TitlePart继承,将标题设置为“销售报表"。进入myTask.cs的设计时加入控件reportView1,同时根据myTask数据集建立柱状图。最终界面如下图:
f.建立EmailView视图。为了表现WorkSpace能够层层显示子视图,我们在EmailView视图上,添加了两个DeckworkSpace用来显示邮件的列表视图和详细信息视图。EmailView本身又是通过上一级WorkSpace显示的。这里略去邮件列表视图和邮件详细信息视图的建立过程,请参前面视图建立过程和源代码。
到此我们基本上完成各个视图的建立,大家不难发现,他们之间是相对独立的,和WorkSpace无关的。
第五步:编写WorkItem和Controller,连接所有视图
a.建立应用入口类。新建SmartPartApplication.cs,将类代码修改成以下形式:
b.建立根级别的WorkItem。在本例中WorkItem的主要作用是提供显示试图的方法,将业务实体数据填充到数据集,并且让数据集和控件绑定。
以显示BlogView为例,我们来说明WorkItem是如何将BlogView展示到特定的WorkSpace的。首先我们来看以下代码:
系统通过入口程序会自动调用SmartRootWorkItem的Run方法,然后调用ShowBlogView方法来显示Blog视图。我们需要重点关注的是ShowBlogView本身。在显示视图之前我们需要为视图中用到的数据,创建实例并且将其载入WorkItem的State列表,以便在视图和Controller中共享使用。如代码:
最后我们需要将视图,在住窗体的deckWS_Blogs控件中显示。在WorkItem中我们直接可以通过this.Workspace[]来索引住窗体上的Workspace,CAB已经自动将在设计时添加的Workspace控件添加到了Workspaces列表。索引导特定的Workspace后,我们需要将要显示的试图添加到WorkItem的Items集合,最后通过Workspace.Show(view)来显示视图。代码如下:
其他视图的添加方法类似,我就不再做说明。
c.正对根级别的WorkItem,建立对应的Controller。新建RootController.cs,将RootController类从Controller继承,加入以下代码,将其与WorkItem关联。
同时为了将业务实体和WorkItem、View结合起来,我们将数据集通过State列表共享。如以下代码:
RootController类中当然还共享了其他如DailyPlan、DailyTask等业务数据,由于篇幅不再列出,在源代码中可以查看。
第六步:修改视图代码,在视图中显示数据。
我们还是以BlogView为例来说明,视图是如何将数据显示出来的,其他视图都相似,请参考源代码。在视图中主要是通过私有变量和加有[State]修饰符的属性和业务数据、Controller相关联的。代码如下:
第七步:特殊处理,视图的嵌套。
EmailView视图和其他视图有些不一样,EmailView视图有DeckWorkspace,在DeckWorkspace中还必须显示两个子视图。这样就使数据的共享,WorkItem的访问存在少许差异。我们不妨首先来看SamrtRootWorkItem中显示EmailView的方法:
我们发现TestSmartPart.Emails.EmailWorkItem被加入SamrtRootWorkItem的Items集合,deckWS_MainEmail这个Workspace也被加入SamrtRootWorkItem的Items集合。因此访问这些Workspace时,必须通过WorkItem.Parent.Workspaces[ ]来访问。代码如下:
第八步:运行程序
经过一些修改后,我们可以运行我们的例程,大家看看下面这个界面是不是像一个Portal啊?