CAB

 

CAB简单了说就是一套程序集,或者说是一套代码框架。使用了很多设计模式以优化软件开发,主要是实现winForm桌面程序开发的。

CAB 的优点:

1:允许构建由各个具有协作关系的独立模块组合成的复杂应用。
2:分离关注点,能将各个模块的开发的关注点和Shell的开发分离开来。
3:提供了一个能支持高质量的桌面应用开发的框架
4:提高了生产力和节约了开发时间,进一步巩固了架构师和开发人员的劳动成果。

CAB 的功能:

1.动态加载独立模块

2.模块之间或模块内部的事件机制(event broker)

3.command pattern(映射UI的各类活动)

4.基于MVC开发模式

5.提供了一整套的服务authentication services, authorization services, module location, and module loading services ,当然也允许自定义服务

一些重要的概念

workItem可以这样说,一个用例就映射一个workitem,主要用于处理业务逻辑,可调用 controller,presenter,view控制界面呈现

,service,服务;module模块,软件模块划分时用到;priflecatalog软件配置;moduleLoader模块加载器;moduleinit构造模块的类;

shellapplication,相当于winform的Application类,为启动主窗口,实现加载各个模块module

shell,主程序入口command映射页面各个控件活动,workspace想到于view的加载区域,

uiextensionSite界面静态加载的控件

eventSubcription/eventPublication 事件发布和订阅,可以使用代码编辑器生成

view 一个用户控件

smartPart  同view,但具有SmartPart特性,支持对接口ISmartPartInfoProvider的实现

controller对应于MVC的controller,逻辑对象,控制View中呈现的各种业务实体Model

objectBuilder CAB的基础,提供依赖框架,构建框架和组件工厂等

Dependency Injection 依赖注入,支持创建对象时同时创建该对象依赖的对象


接下来描述的内容包括:

如何使用Smart Client Software Factory 创建CAB程序,CAB程序代码结构,界面设计和组织方式,UIExtensionSite与SmartPart

创建和注册服务

新建业务模块:创建workItem和子workItem,管理state,添加SmartPart到workItem

event Broker机制

依赖注入

action,command,configure,application


文章出处:http://www.diybl.com/course/4_webprogram/asp.net/asp_netshl/200843/108321.html

 

组合界面程序块介绍(Composite UI Application Block)

 看来微软要全力推SmartClient了,Application Block推出了一个又一个。我们这个项目既然要用SmartClient,自然会关注这些好东

东。目前我们确定要使用的Application Block 包括, Offline 和 CompositeUI 。有的同学就会说,这些个Block实在是太复杂,不好用。确

实他们的代码都很复杂,满是什么模式XXX一大堆。但是用起来却不是很难。这里就介绍一下CAB(Composite UI Application Block)的基本使

用方法。注意是使用方法和一些使用原则的东西,因为还没有深入去分析具体的代码。
  
首先介绍一下CAB是个什么东西,CAB最大的特征是她就是一个IOC界面容器,它可以根据容器内组件的依赖关系自行组装,以解除耦合,CAB中

的View,Controler,以及Service组件都是通过该容器自行组装,以完成业务操作。CAB中的 Event attribute 和 command attribute 又为解

耦提供了许多方便。 
  下面中就几个主要和我们打交道的类做一下介绍。
(1)  SmartParts
     就是我们最小的界面组件,并且和WorkItem中的View一一对应。声明SmartPart的方式也非常简单。比如

  
  
[SmartPart] public partial class DropRegistration : UserControl { }


在类DropRegistration上面加上[SmartPart]这个属性就好。容器就会认为这个是一个SmartPart,在需要的时候会将其注入到需要的类中。一般

在View上设计展示给用户的最小元素,比如一个登陆框。你可以用SmartPartHolder或是WorkSpace来装载显示它。
(2)  WorkSpace
    是显示组件的容器,WorkSpace中的WorkItem都是通过传入的WorkSpace来显示自己。CAB中提供了几种WorkSpace,不同的展示方式。
  1)MdiWorkspace:把WorkItem 作为一个子窗体进行显示,与平常的MDI窗体相似。
  2)TabWorkspace: 以tag的形式显示WorkItem。
  3)DeckWorkspace: 以平铺的形势,一次只显示一个WorkItem。
  4)ZoneWorkspace:这个还没试过,找个时间补齐 ^_^。

(3)WorkItems
    这个家伙是一个用例中不同组件的运行时容器。所以在项目中决定以用例来划分WorkItem,来作为我们的一个原则之一。
 在一个WorkItem中包含了重要的方法、属性还有成员.
 1) 首先当然是初始化的方法,每个WorkItem都必须实现Run(IWorkspace workspace)方法.
        2) 每个WorkItem都有State,你可以把它想象成Asp.net中的Session一样,可以把一些在本WorkItem中传递的信息存到State中。
 这样在这个WorkItem中的都可以访问他。要实现这并不难。在你类属性上面加上[State]的属性比如:

  
  
[SmartPart] public partial class DropRegistration : UserControl { [State] private Customer customer = null ; public CustomerDetailView() { InitializeComponent(); } private void CustomerSummaryView_Load( object sender, EventArgs e) { if ( ! DesignMode) { customerBindingSource.Add(customer); } }



 就表示用的是State中的该类型的信息,在窗体加载的时候就可以直接绑定customer信息到窗体中,而没有在此之间发现实例  

化的代码。
    3) WorkItem 中运用到了MVC模式。所以包括了View和Controller,一般情况下一个View和一个Controller.


(4)ApplicationHost
 Application host 是应用程序中的各种组件的组织体。程序的WorkItem均运行于Application Host之上。他是CAB容器的入口。
   
(5) Services
     CAB 中提供了一系列的基础服务类以供在程序中使用。当然你也可以实现自己特殊功能的服务。这里介绍CAB中的几种重要的服务:
   1) Catalog Reader Service: 用于读取配置目录的服务。
   2) Event Broker Service : 事件代理服务,符合CAB中事件声明规则的事件可以通过它来传递,很多时候解耦就靠它哦。
   3) Module Loader Service : 模块加载服务,目前用的比较少,或是用了没感觉.
   4) Authentication Service : 估计我们会借鉴一下然后,自己实现一套.
  5) State Persistence Service : WorkItem中的状态持久化服务,用以保存WorkItem的State中的信息到持久层。


(6) Modules
    模块由以上说到的 Services, WorkItems, SmartParts, Controllers, Business Entities 组成. 从最直观的形象上说就是一个Dll,一 

 个子系统。他有自己的初始化方法Start(),用于初始化本模块和运行它的WorkItems.

基础的东西就先介绍到这里,希望对组员有所帮助。以后会随着研究的深入,继续在Blog上探讨CAB的各个特性以及使用的方式。

给出一个地址  http://www.gotdotnet.com/codegallery/codegallery.aspx?id=22f72167-af95-44ce-a6ca-f2eafbf2653c  大家可以去下载程序包。

 

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对数据库层进行支持,将会更好。

 

 

Composite UI Application Block 文章收集

最近 Reiview Smart Client 架構, 與偉大的 CAB (Composite UI Application Block).

發現 Szymon Kobalczyk's Blog  收集了一些相當好的 心得

MSDN 上也有偏很讚的文章, 針對 Smart Client Software Factory 做了 Overview

CABPedia.. 我真的不知道該怎樣形容...  Wiki for CAB 相關資訊... 太酷了

發現我有段時間 沒有寫些 像樣 的

 

 

其实从名字就可以看出来,MVC(Model-View-Controller)和MVP(Model-View-Presenter)的区别就在于一个使用了C,一个使用了P,呵呵。
MVC中,View进行用户交互,输入用户请求给Controller,Controller处理业务,更新Model,Model通过触发事件,从而更新View。
MVP 中,View依然进行用户交互,输入用户请求给Presenter,Presenter处理业务,更新Model,Model通过触发事件,通知 Presenter,然后Presenter再更新View,在这个交互过程中, Model没有和View进行交互,完全依赖于中间人Presenter。
具体的优缺点,我想在使用中才会具体体会到。

 

        前段时间在弄CAB (Composite UI Application Block), 按着书上写的方法写了一个模块workitem extension 扩展类。当被扩展的workitem的run方法被调用的是时候,扩展类所重写的方法老是没有被调用。书上说可以我测试却不可以,搞得那天好心烦。设断点 调试,明明看到workitemExtensionService 里面注册了我的那个extension。 extension 里面的OnRunStarted就是没有被调用到,我崩溃了。 后来没有办法去看CAB的源码,检查了一个晚上终于发现了一个天大的秘密, 这是一个BUG!!!!!!!!
        我们来看一下CAB的默认moduleloader :
         private   void  InnerLoad(WorkItem workItem, IModuleInfo[] modules)
        
{
            
if  (modules.Length  ==   0 )
                
return ;

            LoadAssemblies(modules);
            List
< ModuleMetadata >  loadOrder  =  GetLoadOrder();

            
foreach  (ModuleMetadata module  in  loadOrder)
                module.LoadServices(workItem);

            
foreach  (ModuleMetadata module  in  loadOrder)
                module.InitializeModuleClasses(workItem);

            
foreach  (ModuleMetadata module  in  loadOrder)
                module.InitializeWorkItemExtensions(workItem);

            
foreach  (ModuleMetadata module  in  loadOrder)
                module.NotifyOfLoadedModule(OnModuleLoaded);
        }
       
        模块装载的时候Load方法会先检查一下传进来的参数有没有null值这些问题, 然后转而调用到上面的这个 InnerLoad方法来装载模块。这个方法依次的 装载服务、 初始化模块、 初始化workitem extension、 然后触发模块装载完毕事件....

        起初我怀疑我的workitemextension 挂钩到workitem里面的时机晚了, 果然不出我所料。 我们看一下 ModuleMetaData 类的 InitializeModuleClasses 方法
             public   void  InitializeModuleClasses(WorkItem workItem)
            
{
                
if  (modulesInitialzed)
                    
return ;

                modulesInitialzed 
=   true ;
                EnsureModuleClassesExist(workItem);

                
try
                
{
                    
foreach  (IModule module  in  moduleClasses)
                    
{
                        module.Load();

                        
if  (traceSource  !=   null )
                            traceSource.TraceInformation(Properties.Resources.ModuleStartCalled, module.GetType());
                    }

                }

                
catch  (FileNotFoundException ex)  { ThrowModuleReferenceException(ex); }
                
catch  (Exception ex)  { ThrowModuleLoadException(ex); }
            }
       而模块的InitializeModuleClasses 方法竟然调用了 IModule.Load() 方法!! 也就是说如果我在所 重写的 ModuleInit.Load() 方法里面开始我的逻辑的话, 此时workitemextension 还没有被初始化,还没有挂钩到所扩展的workitem里面!!!      而CAB附带的几个quick start 却恰恰在重写的ModuleInit.Load()方法里面开始逻辑,也就是说在模块装载还没有完全完成的时候就已经开始逻辑了!! 严重误导,严重浪费了我的时间......

       看来要自己找方法解决了。那个NotifyOfLoadedModule 是最后的步骤, 这个步骤会触发 ModuleLoader.Loaded 事件。 这才是真正的装载完毕! 于是我改为在 ModuleLoaded 的loaded 事件发生的时候开始我写的逻辑。
       贴一下我写的代码:
     public   class  PressureExplorerModuleInit : ModuleInit
    
{
        
private  WorkItem shellRootWorkItem;

        
//  the root work item of the shell will be given by constructor injection
        [InjectionConstructor]
        
public  PressureExplorerModuleInit(
            [ServiceDependency] 
            WorkItem shellRootWorkItem)
        
{
            
this .shellRootWorkItem  =  shellRootWorkItem;
            shellRootWorkItem.Services.Get
< IModuleLoaderService > ().ModuleLoaded  +=   new  EventHandler < Microsoft.Practices.CompositeUI.Utility.DataEventArgs < LoadedModuleInfo >> (PressureExplorerModuleInit_ModuleLoaded);
        }


        
private   void  PressureExplorerModuleInit_ModuleLoaded( object  sender, Microsoft.Practices.CompositeUI.Utility.DataEventArgs < LoadedModuleInfo >  e)
        
{
            
if  (e.Data.Assembly  ==  System.Reflection.Assembly.GetExecutingAssembly())
                Loaded();
        }


        
private   void  Loaded()
        
{
            
//  start the peWorkItem after the module is fully loaded by calling run method
            PEWorkItem peWorkItem  =  shellRootWorkItem.WorkItems.AddNew < PEWorkItem > ( " PEWorkItem " );
            peWorkItem.Run();
        }

    }

       至此还没有结束,虽然那我的问题解决了, 但是我还是想到网上别人有什么更好的解决方法。 然后我找到了一篇文章有关新的 new module loader for CAB , 此模块装载器修正了我所说的这个bug, 还提供了一些很有用的新功能:

Towards the end of the Smart Client Software Factory project, we wanted to be able to support a more comprehensive module loader system for CAB applications. Our goals were:

    1. Support grouping modules together into logical "sections" which represented groups of functionality. These groups might be things like "layout", "services", "applications", etc.
    2. Allow dependencies to be expressed between these sections (i.e., make sure you load all the layout modules before you load the application modules).
    3. Allow dependencies to be expressed in an external place instead of forcing them to be expressed in attributes on the assembly.
    4. Make it easier to provide transports for the profile catalog XML without having to write an entirely new enumerator.
    5. If possible, preserve backward compatibility with the new loader and old enumerators, so that we could preserve effort people had putting into writing their own enumerators.
    6. Fix a bug in the module loader that causes WorkItem extensions to be registered too late to see WorkItems that were being created in the module's Load method.


    1.  支持把模块按功能分组, 每一组模块(模组section)  代表一系列有关的功能 。 比如 “排版” 模组,“服务” 模组, “应用” 模组等等。
    2.  允许表达 section 之间的关系依赖性(耦合), 比如你可以确定 排版section 会在 应用section 装载完毕之后再装载。
    3.  允许在外部定义 关系依赖性,突破原先的只能在assembly attribute里面定义的局限。
    4.  不需要写整个 模块枚举器来实现 profile catalog XML 的传输。
    5.  向后兼容, 可以使用之前写好的 模块枚举器。
    6.  修正了我上面所描述的那个bug, 这个bug 使得 WorkItem extension 注册晚了 ( 注册聆听workitem的RunStarted, Initialized等事件晚了 )。
 
 
WorkItem/SmartPart 描述和实例

       这里先了解 WorkItem SmartPart 是什么东西? WorkItem 是一个容器,在它里边可以放 UI 控件, SmartPart ,各种服务和其他的组件,也可以嵌套 WorkItem ,所以放在父容器中的各种对象都可以在嵌套中的 WorkItem 获取以达到共享的目的。 SmartPart 就是每个功能模块的最小视图,如图 2.3.1 所看到的每个小块的视图就是一个 SmartPart

 

2.3.1

1.          利用 SCSF 导航功能创建一个 Module 项目,如图 2.3.2

 

2.3.2

 

2.3.3

2.          Views 目录中利用 SCSF 生成三个 VIEW 视图分别为 TestLeftView TestMainView UiElementView ,(这里要说明一下,通过 SCSF 产生的 VIEW 都是按照 MVP 模式生产的)并在类 WorkspaceNames 增加相关代码:

    public class WorkspaceNames

    {

        public const string LayoutWorkspace = "LayoutWorkspace" ;

        public const string ModalWindows = "ModalWindows" ;

        public const string LeftWorkspace = "LeftWorkspace" ;

        public const string RightWorkspace = "RightWorkspace" ;

        public const string LayoutUIElementView = "LayoutUIElementView" ;

    }

    ModuleController (继承于WorkItem )中增加下列代码:

 

public class ModuleController : WorkItemController

    {

        public override void Run()

        {

            AddViews();

        }

        private void AddServices(){}

        private void ExtendMenu(){}

        private void ExtendToolStrip(){ }

 

        private void AddViews()

        {

 

            TestLeftView left = WorkItem.Items.AddNew<TestLeftView >();

            WorkItem.Workspaces[WorkspaceNames .LeftWorkspace].Show(left);

 

            TestMainView mainV = WorkItem.Items.AddNew<TestMainView >();

            WorkItem.Workspaces[WorkspaceNames .RightWorkspace].Show(mainV);

 

            WorkItem.Items.AddNew<ModuleActions >();

            ActionCatalogService.Execute(ActionNames .ViewCustomerUIElement, WorkItem, this , null );

            ActionCatalogService.Execute(ActionNames .ShowCustomerUIElement, WorkItem, this , null );

 

        }

    }

其中的代码 WorkItem.Workspaces[WorkspaceNames .RightWorkspace].Show(mainV) 的作用就是把视图放在对应的WorkSpace 中显示。

3.          运行并看结果

 

2.3.4

发表于 @ 2008年05月04日 22:35:41 |评论(2 ) |编辑

新一篇: SmartClient Software factory中的Composite UI Application Block(Cab)技术了解(三):UIElement  | 旧一篇: SmartClient Software factory中的Composite UI Application Block(Cab)技术了解(一):Shell&amp;Layout

评论

# hugygr 发表于2008-09-27 22:52:27  IP: 221.6.3.*
请教你,SCSF自动生成好框架后,没有运行一次时View目录上单击右键出现smart Client Factory子项,但在运行一次后即不再出现,不知如何解决?谢谢
已经反复试过多次,

我用的是VS2008,SCSF-april 2008

我的邮箱:hugygr@163.com
# simonllf 发表于2008-10-02 22:20:58  IP: 220.112.58.*
不清楚你指的是在IDE中看不到SCSF的项目,还是说在生成的SCSF项目中运行时出现问题.如果是前者.正常来说你可以在SCSF自动生成的 项目中用右键创建SCSF其他的项目,如果没有.你可以在IDE菜单中选Guidance Package Manager项.在其中选中你需要显示的SCSF子项目就可以了.如果是后者请再详细说明状况.还有我用的是2007,可能会有些许不同.

 

 

前面已经说到, CAB框架中除了提供了强大的支持功能,如依赖注入,事件机制,界面组合显示,还有一个专门封装业务逻辑的 WorkItem。企业应用系统其实主要就是企业业务的自动化实现,一个应用框架提供的支持功能都只是为了更好地为业务服务。下面就来看看 CAB中的 WorkItem是如何封装业务用例的。

首先看看 WorkItem都包含哪些元素?

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,由于数据与界面绑定,数据的变化直接反应到界面显示。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值