我在随笔《Winform开发框架之框架演化》和《Winform开发框架之混合型框架的实现》都对Winform框架的变种,混合型框架进行了比较详细的介绍,本文继续上篇对混合型框架进行进一步的说明。
混合型框架为了支持WCF方式和传统访问数据库方式两种对数据操作的方式,有两个地方有扇出操作,一个是在界面上调用接口对象获取数据的时候有扇出操作,为了实现WCF方式和传统访问数据库方式的处理,如下所示。当然,如果必要,也可以扩展成支持更多的类型,如可能存在旧系统的WebService调用方式等。
另外,整个框架支持Oracle、SqlServer、MySql、Sqlite、Access等数据库的访问操作,因此在业务层调用具体数据访问类的时候,根据配置的不同,具体构造的数据库访问对象也不同,因此,这里也有一个扇出操作,而且扇出数量和支持的数据库一致,如下所示。
混合型框架可以看成是Winform框架高级版本,除了它本身是一个完整的业务系统外,它外围的所有辅助性模块均(如通用权限、通用字典、通用附件管理、通用人员管理。。。。)都实现了这种混合型的框架,因此使用非常方便,整个框架如果简化来看,就是在原有的Winform界面层,用接口调用方式,避免和业务逻辑类的紧耦合关系。由于他是通过接口方式的调用方式,它本身又可以通过配置指定指向WCF的实现,因此也囊括了WCF框架的一切特点。
说到WCF的访问方式,混合型框架把业务系统的WCF服务和辅助性公用模块的WCF服务分开,首先是服务分开,然后是客户端配置文件分开。
客户端配置文件分开,是通过把他们的服务配置信息分别用不同的文件表示,如辅助性模块的WCF配置文件为BaseWcfConfig.config,业务系统的WCF配置文件为WcfConfig.config,通过这样的分离设置,我们在主配置文件app.Config文件里面,就清爽很多了,如下所示。
<appSettings> <!--软件名称--> <add key="ApplicationName" value="深田之星仓库管理系统"/> <!--开发商名称--> <add key="Manufacturer" value=""/> <!--组件的数据库类型:access、sqlserver、sqlite、oracle等,默认为sqlserver可不写--> <add key="ComponentDbType" value="sqlserver"/> <add key="AttachmentLicense" value="397cV0hDLlNlY3VybXR5fOS8jeWNjuiBqnzlua-lt57niLHlkK-o_6rmioDmnK-mnInpmZDlhbzlj7h8RmFsc2Uv" /> <!--组件模块调用方式,采用WCF方式Value为wcf,采用Winform方式Value为win--> <add key="CallerType" value="wcf"/> <!--WCF服务的配置文件地址--> <add key="BaseWcfConfig" value="BaseWcfConfig.config"/> <add key="WcfConfig" value="WcfConfig.config"/> </appSettings>
这样我们通过修改CallerType的内容(WCF或者WIN),就可以实现两种不同方式的访问了。
整个项目工程的布局,除了刚才介绍的WCF服务模块,其实还有很多其他模块的,如下面图所示,包括业务逻辑模块,服务接口调用模块,Winform界面模块、WCF服务逻辑模块,整个系统的模块就包含这些,当然外围的辅助性模块,如字典、权限、人员等等,他们各自按照这个方式进行组织处理,系统调用的时候,不会具体关心它们的调用逻辑,因为它们的调用方式已经通过约定的CallerType的内容进行了指定。
对于混合型框架,不管它的数据调用逻辑是按照传统的Winform方式,还是分布式的WCF服务调用方式,实现代码都是一样的,因为它是基于接口层调用,如下所示是在框架系统中调用数据字典模块获取数据的代码实现。
/// <summary> /// 根据字典类型获取对应的CListItem集合 /// </summary> /// <param name="dictTypeName"></param> /// <returns></returns> public static CListItem[] GetDictByDictType(string dictTypeName) { List<CListItem> itemList = new List<CListItem>(); Dictionary<string, string> dict = WHC.Dictionary.UI.CallerFactory<WHC.Dictionary.Facade.IDictDataService>.Instance.GetDictByDictType(dictTypeName); foreach (string key in dict.Keys) { itemList.Add(new CListItem(key, dict[key])); } return itemList.ToArray(); }
系统框架调用自己的接口获取数据,模式也和上面一样,与传统的Winform框架调用代码相比,并没有增加任何工作流,只是调用对象有点变化而已。
private void winGridViewPager1_OnDeleteSelected(object sender, EventArgs e) { if (MessageDxUtil.ShowYesNoAndTips("您确定删除选定的记录么?") == DialogResult.No) { return; } int[] rowSelected = this.winGridViewPager1.GridView1.GetSelectedRows(); foreach (int iRow in rowSelected) { string ID = this.winGridViewPager1.GridView1.GetRowCellDisplayText(iRow, "ID"); CallerFactory<IItemDetailService>.Instance.Delete(ID); } BindData(); }
如果是传统的Winform框架,它的删除操作的核心调用代码是如下所示,是不是很相似的呢?
BLLFactory<ItemDetail>.Instance.Delete(ID);
当然,虽然混合型框架比传统的Winform框架和WCF开发框架更为通用,不过由于它引入了多一层,而且为了实现更多模块的分离,增加了一些设计上的复杂性,整个项目工程看起来显得复杂了一点,如下面就是一个以字典模块为例的混合型框架的内部结构。
为了实现更简单化的开发,更快更高效的完成混合型框架的开发工作,我扩展了我的代码生成工具Database2Sharp,使其支持这种混合型框架的代码生成工作,这样开发混合型框架就和开发其他两种Winform开发框架、WCF开发框架一样,非常方便了。
生成混合型框架项目的步骤就是在【EnterpriseLibrary代码生成】的最后一步进行勾选设置即可。
代码生成工具,生成整体性的混合型框架项目如下所示,只是没有下图的界面部分,这部分在实际开发过程中,结合我的混合型框架案例进行整合即可,另外也可以界使用Database2Sharp进行Winform界面的开发,这样整体性就非常方便操作了:
虽然整体性的混合型框架比其他两种框架模块,总体增加了一些难度及复杂性,不过,为了使得整个混合型框架开发和使用更加方便,我已经在设计上做了很多相关的工作,力求更好、更高效的使用好这种混合型框架,下面是我对整体性的框架做了的优化改进工作。
1)把所有通用的模块开发好,方便更好的集成使用,更加高效利用通用模块,重复利用度更高;
2)把WCF服务发布和服务逻辑分开,更好管理和发布WCF服务,服务发布只需要svc文件,不含任何后台代码;
3)统一的业务调用规则和命名规则,所有模块的接口调用统一为CallerFactory<I***Service>方式,通用模块和框架的命名规则和机制完全一样。
4)WCF服务配置文件分离,通用性的辅助模块的配置文件为BaseWcfConfig.config,业务系统的WCF配置文件为WcfConfig.config,配置文件分离更方便管理和维护,减少主配置文件app.Config的复杂性。
5)最后一条,也是最重要的一条,就是代码生成工具Database2Sharp的同步支持。通过代码生成工具,更好、更快的生成整个混合性框架的代码和项目工程,一键解决所有的烦恼。Winform界面,利用代码生成工具Database2Sharp进行生成,然后在项目中整合即可。
1、工作流系统有什么用?
可以简化您关于 业务对象状态的判断的流程。举例:如果一个对象每个阶段有2个状态(通过或不通过),一共有3个价段,分别可能有4个人参与到流程中,并且状态处理的方向可能与当前操作员相关,有些操作员权限大,可直接跳转到下一阶段,如是没有工作流系统,我们的代码可能会是什么样子呢?
if( 阶段1.Statu1 == obj.Status) { //您的代码
if( operatorID == obj张三。ID ) { ... } } else if( 阶段1.Statu2 == obj.Status ) { //您的代码
.... } else if( ....) { ..... }
您能想象会出现多少 if else ,更糟糕的是如果哪天客户告诉你,要在某阶段增加一个 特殊状态,或特殊逻辑,你肯定有想自杀的想法,或者你想把客户杀掉。为什么?因为难度成指数级别在增长。
另外,这只是处理逻辑上代码的增长,如果您根据每个状态都在界面上对应有个交互界面,比如:等您审核的XXX,等待您审批的XXX。。。,您知道会出现多少用户交互界面嘛?况且,用户哪天突发奇想说要增加审批环节的时候,就是入地狱的时候了。
所有变更都是侵入性的,必须重新编写代码,编译、发布、重新布置,啊,我的天哪,“天尽头,何处有香丘?”。
2、关于工作流学习的文章或成功应用太少,不免让很多同学感到大海茫茫,如何开始?
首先,感谢WXWinter ,关于WF4.0,我的基础学习基本上是参考他的例子理解基本概念的,但是当你理解了基本概念后,想建立一个审批工作流系统您还是无从下手,如何办?
困难澄清:
2.1.工作流如果要良好运行,必须有外围用户系统的支撑,比如,关于如何进行权限检查及认证哪是业务对象的事,不应该侵入到工作流中。
2.2.工作流并不是孤立的运行,有时他需外部审批人的动作,因此必须有渠道为流程添加审批节点,并指定审批人,因此您必须提供相应的流程设计器供用户或实施人员使用。
2.3.有时流程运行需要与人做交互,由操作员决策节点应如何向下走,并且要向运行中的流程提供运行参数,外部支撑系统应如何提供流程的运行参数呢,这和流程运行上下文有关,可能还与当前用户有关。
2.4.关于节点自身的审批结果,有多种多样的组合,比如:一票通过、多票通过、一票否定、多票否定、按比例通过、按比例否定等。或者他们的组合。
看到这里,也许您的大脑已被许多概念交织在一起,一片混乱了。别急,总有解决方法的,下面我们先预览一下我心目中的工作审批系统是什么样的?和您的期望是否一致,我们之间能产生些许共鸣嘛?
3.以公司常见的“借款申请”为例,我们一步一步的演示:
环境交待:张三 是公司员工,因要出差处理某事,出差前向公司申请借款 5000元,借款申请必须先征得部门经理 李四的同意,后经财务经理 赵五 审核同意,才能用署名后的借款单到出纲换取 现金。
流程:发起借款申请->李四审批-> 赵五审批-> 出纳付款
3.1 发起审请
3.2 启动流程
3.3 流程启动后的结果: 等待部门经理审批
3.4 部门经理审批:
首先,查看待办事项:
双击查看:
点击同意流转到下一节点:财务审批
3.4审请人查看流程进度:
3.5 财务审批
双击打开申请单:
点击同意,弹出付款账户供财务选择,点击确认流程结束。
4.流程设计器预览:
Oh yeah….!!!!完成,感觉如何呢?
另外,我想表达一点,如何您的解决方案连自己都觉得在哪一点有不自然或觉得繁琐的地方,说明设计与实现肯定有问题,应反思,因为用户很少给自己第二次机会,提供用户使用的软件应是能解决实际问题,有价值且优秀的产品,这样您就有了市场需求了。
期待下文吧,OK,整个系统如何动作?审批工作流系统的设计要点是什么?
预知后事,敬请关注!!!!