程序=数据结构+算法,而企业级的软件=数据+流程,流程往往千差万别,客户自身有时都搞不清楚,随时变化的情况更是家常便饭,抛开功能等不谈,需求变化很大程度上就是流程的变化,流程的变化会给开发工作造成很大麻烦。而本审批流程具有较强的通用性,同时也有很大的灵活性,虽然无法100%的解决各种非常个性化的审批流程,但至少也能解决其中80%以上的较为通用的流程了。本文将就分享部分心得!

本审批流程从实现上来说由流程设计器、流程控制组件和表单设计器三大部分组成。下面将分别进行描述:

1. 流程设计器

流程设计器基于 Web,使用了JQuery UI、EasyUI、Bootstrape、Knockout.js等等前端框架,可随意设计符合自己的流程,首先看看效果图:

目前的流程库支持5种类型的活动,其中并行活动表示其前面的活动需进行并行审批,其入口事件都执行通过后才允许执行当前活动;分支活动主要用来判定后续到底哪些活动需要进一步执行,分支活动由系统自动根据所设条件自动执行,条件的声明采用 C# 的标准语法;每种活动均设有抵达入口和出口的外部方法接口,可在后台编写好代码后再在界面上进行配置绑定,目的是提供诸如发送邮件通知等类似的接口功能;同时还能随意自定义任何活动上的审批事件,例如保存、上报、回退、驳回、通过、结束等等,如下是事件的参数设置界面:

在此不得不提一下knockout.js,进行属性的绑定和界面的更新实在是太方便了,但就是声明 ViewModel 的定义太麻烦。因此本人此处还用到了其 knockout.mapping 插件,通过 ko.mapping.fromJS(data, mapping, this) 一句代码就可直接把 Json 数据转换为 ViewModel,也能一句代码就能把 ViewModel 转换回要保存的 Json 数据,如果你使用knockout.js 的话,简直没有不使用该插件的道理,可让你从声明 ViewModel 的繁杂体力活中解脱出来。如下是工作流定义从 WebApi 加载后绑定至界面所需的 ViewModel 的创建代码:

var ViewModel = function(data) {

    var self = this;

    ko.mapping.fromJS(data, mapping, this);

    self.StartCount = ko.computed(function() {

        var total = 0;

        $.each(self.ActivityDefinitions(), function() {

            if (this.ActivityType() == 0) total++;

        });

        return total;

    });

    self.FinishCount = ko.computed(function() {

        var total = 0;

        $.each(self.ActivityDefinitions(), function() {

            if (this.ActivityType() == 4) total++;

        });

        return total;

    });

    self.refresh = function(item) {

        ko.mapping.fromJS(item, self);

    };}

2. 流程控制组件

流程控制组件主要完成流程的跳转控制,数据的加载和保存等。流程控制组件和前面文章中提到过的底层组件一样,使用了 Provider 模式。因为需要在界面上直接配置自定义跳转执行条件,而在C#中目前还没有类似于 Javascript 的 eval 方法。为实现该功能,需要对字符串脚本进行动态编译,目前.NET 下支持C# 脚本的工具还是很多的,在此先后用过CS-SCRIPT,Javascript.NET和Roslyn,但都没成功。

CS-SCRIPT 很好用,但.NET 4.0 下的版本有问题,虽然是 .NET 4.0 的,但实际上还得安装.NET 4.5,当时在本机测试没任何问题,一旦部署至服务器上就会报错,提示不能加载 System.Runtime.CompilerServices 类型之类的错误,最后发现是其依赖的 Mono.CSharp.dll 的问题;最终的原因也搞明白了个大概,是.NET 4.0 下安装了 .NET 4.5 后会修改默认的 .NET 4.0 底层框架,也就是说,这是升级至 .NET 4.5 带来的兼容性问题,导致在安装了 .NET 4.5 的环境下所生成的针对.NET 4.0 的 Mono.CSharp.dll 组件无法在没安装.NET 4.5 的环境下运行。因为目前很多的服务器环境还是 Windows Server 2003,还没法安装.NET 4.5,很是蛋疼,只有放弃。

Javascript.NET 的最大的特点是使用非常简单,使用JS兼容的语法,但无法传递Dynamic类型的参数。

最后又使用了Roslyn,和Javascript.NET一样,.NET 4.0 版本的貌似不支持 dynamic 类型参数,反正我是没测试成功,估计.NET 4.5 的版本倒是支持,但因很多部署环境还是Windows Server 2003,因此最后还是放弃了。最后没折,只有自己实现了,通过 CSharpCodeProvider 动态编译技术实现了一个Eval方法,一番折腾后发现其实是很简单的,效果也还不错,该 Eval 的代码如下,在此粘出代码共享下劳动成果:

 1 /// <summary>

 2         /// 动态编译,获取条件表达式的执行结果

 3         /// </summary>

 4         /// <param name="expression">判断条件表达式</param>

 5         /// <param name="objectInstance">对象实例,各个属性和Form表单对应</param>

 6         /// <param name="activityInstance">当前执行的活动实例对象</param>

 7         /// <returns>编译并执行条件表达式后返回的结果</returns>

 8         private object Eval(string expression, dynamic objectInstance, ActivityInstance activityInstance)

 9         {

             var codeProvider = new CSharpCodeProvider();

             var cpt = new CompilerParameters();

 

             //引用程序集

             cpt.ReferencedAssemblies.Add("system.core.dll");

             cpt.ReferencedAssemblies.Add("system.dll");

             cpt.ReferencedAssemblies.Add("Microsoft.CSharp.dll");

             //获取对象实例的类型

             var type = (Type)objectInstance.GetType();

             //获取活动实例对应的程序集路径

             var path = activityInstance.GetType().Assembly.Location;

             cpt.ReferencedAssemblies.Add(path);

             if (type.Assembly.Location != path)

             {

                 cpt.ReferencedAssemblies.Add(type.Assembly.Location);

             }

             cpt.CompilerOptions = "/t:library";

             cpt.GenerateInMemory = true;

 

             var builder = new StringBuilder("");

             builder.Append("using System;\n");

             builder.Append("using System.Dynamic;\n");

             builder.Append("using Yb.Workflow.Provider;\n");

 

             var ns = type.Namespace;

             if (ns != "Yb.Workflow.Provider")

             {

                 builder.Append(string.Format("using {0};\n", ns));

                 cpt.ReferencedAssemblies.Add(activityInstance.GetType().Assembly.Location);

             }

 

             builder.Append("namespace CSCodeEvaler{ \n");

             builder.Append("public class CSCodeEvaler{ \n");

             builder.Append("public bool EvalCode(dynamic objectInstance,ActivityInstance activityInstance){\n");

             builder.Append("return " + expression + "; \n");

             builder.Append("} \n");

             builder.Append("} \n");

             builder.Append("}\n");

             //在内存中编译

             var cr = codeProvider.CompileAssemblyFromSource(cpt, builder.ToString());

             if (cr.Errors.Count > )

             {

                 throw new InvalidExpressionException(

                     string.Format("Error ({0}) evaluating: {1}",

                     cr.Errors[].ErrorText, expression));

             }

 

             var a = cr.CompiledAssembly;

             object instance = a.CreateInstance("CSCodeEvaler.CSCodeEvaler");

 

             Type t = instance.GetType();

             var mi = t.GetMethod("EvalCode");

             //反射调用方法的执行结果

             object result = mi.Invoke(instance, new object[] { objectInstance, activityInstance });

             return result;

         }

3. 表单设计器

表单设计器将在下个版本中集成,当前还只能在流程定义中手动敲入已编辑好的表单内容。表单内容的持久化和加载使用了前面提到过的 ExtensionDataApi,因为支持 .NET 4.0 下的 dynamic 属性,同时提供了一个名为 Properties 的字典来管理所有的属性名和属性值,和 MVC 下的 Request.Form.AllKeys 简直就是绝配,因此可非常方便地和表单界面进行集成。换句话说,你只管设计好表单界面即可,具体表单数据的保存和加载完全可以交由 ExtensionDataApi 来完成。

如需进一步了解,可点击:http://pjdemo.yellbuy.com/

YbSoftwareFactory 代码生成插件【十六】:Web 下灵活、强大的审批流程实现(含流程控制组件、流程设计器和表单设计器)的更多相关文章

  1. YbSoftwareFactory 代码生成插件【十四】:通过 DynamicLinq 简单实现 N-Tier 部署下的服务端数据库通用分页

    YbSoftwareFactory 的 YbRapidSolution for WinForm 插件使用CSLA.NET作为业务层,CSLA.NET的一个强大的特性是支持 N-Tiers 部署.只需非 ...

  2. YbSoftwareFactory 代码生成插件【十五】:Show 一下最新的动态属性扩展功能与键值生成器功能

    YbSoftwareFactory 各种插件的基础类库中又新增了两个方便易用的功能:动态属性扩展与键值生成器,本章将分别介绍这两个非常方便的组件. 一.动态属性扩展 在实际的开发过程中,你肯定会遇到数 ...

  3. 基于Extjs的web表单设计器 第六节——界面框架设计

    基于Extjs的web表单设计器 基于Extjs的web表单设计器 第一节 基于Extjs的web表单设计器 第二节——表单控件设计 基于Extjs的web表单设计器 第三节——控件拖放 基于Extj ...

  4. 基于Extjs的web表单设计器 第七节——取数公式设计之取数公式的使用

    基于Extjs的web表单设计器 基于Extjs的web表单设计器 第一节 基于Extjs的web表单设计器 第二节——表单控件设计 基于Extjs的web表单设计器 第三节——控件拖放 基于Extj ...

  5. 基于Extjs的web表单设计器 第五节——数据库设计

    这里列出表单设计器系列的内容,6.7.8节的内容应该在春节后才有时间出了.因为这周末就请假回老家了,准备我的结婚大事.在此提前祝大家春节快乐! 基于Extjs的web表单设计器 基于Extjs的web ...

  6. 基于Extjs的web表单设计器 第三节——控件拖放

    看过之前设计器截图的朋友应该有印象,可能会发觉我们的设计器UI设计布局其实类似Visual studio 的设计界面,采用的是左.中.右三个区域布局.左侧为控件区域.中间为表单的画布设区域.右侧为属性 ...

  7. Unit01: Web概述 、 HTML概述 、 文本处理 、 图像和超链接 、 表格 、 表单

    Unit01: Web概述 . HTML概述 . 文本处理 . 图像和超链接 . 表格 . 表单 demo1.html <!-- 声明网页的版本(文档类型) --> <!doctyp ...

  8. .net web 开发平台- 表单设计器 一(web版)

    如今为了适应需求的不断变化,动态表单设计器应运而生.它主要是为了满足界面的不断变化和提高开发速度.比如:一些页面客户可能也无法确定页面的终于布局,控件的位置,在哪种情况下显示或不显示等可能须要随时改动 ...

  9. 基于Extjs的web表单设计器 第一节

    前面一节介绍了表单设计器的背景和最终的大概样式,本节主要介绍表单设计器的需求及功能设计. 在讲需求之前先明确几个常用的概念: 主表或者卡片表——具有多行多列的一个区域的控件块,如下图所示. 明细表—— ...

随机推荐

  1. *HDU3038 并查集

    How Many Answers Are Wrong Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Ja ...

  2. javascript平时例子⑩(表情发送)

    <!DOCTYPE html><html> <head> <meta charset="utf-8" /> <title> ...

  3. hibernate 异常:Unexpected Exception caught setting

    异常信息:Unexpected Exception caught setting 'outHeight' on 'class com.srpm.core.project.seismicFortific ...

  4. JQuery中on()函数详解

    JQuery API中定义的on方法,专业名词很多,读起来并不是那么容易,而对于开发人员知道函数怎么使用就可以了.本文将JQuery的说明翻译如下: on(events,[selector],[dat ...

  5. 本周psp

      本周PSP 类别 内容 开始时间 中止时间 终止时间 总用时 产品计划会议 定义产品的用户需求,以及从这个产品中得到什么.解决啥问题 18:00 0 20:00 120分钟 撰写博客 会议记录与个 ...

  6. osip2 代码分析

    主要类型定义: 1.osip_t /** * Structure for osip handling. * In order to use osip, you have to manage at le ...

  7. Ubuntu使用阿里云软件源

    如果在安装Ubuntu时,选择的地区为美国,建议更新为阿里云或国内 软件源 sudo sed -i s/archive.ubuntu.com/mirrors.aliyun.com/g /etc/apt ...

  8. 利用xtrabackup备份mysql数据库

    利用xtrabackup备份mysql数据库 一.安装1.直接下载二进制文件wget http://www.percona.com/downloads/XtraBackup/XtraBackup-2. ...

  9. php 读取文件

    <?php /** *@param string $ip *@return string ip对应的地区 */ function getLocation($ip) { $ip_file_path ...

  10. sql小技巧

    --实际只会更新一条.可有效防止误操作.特别是操作线上正式数据时. UPDATE TOP(1) Table2 SET Culumn1='value'WHERE id IN(269102,269104) ...