工作流系统中的语法标记系统
以微软的WF为基础,为达到ERP中的批核与通知目的,参考现有系统中的技术,在此分享其中的技巧。
先来看最终的结果,也就是下图中的工作流宿主。
在上面的工作流定义中,除工作流宿主(rehost)技术外,还需要解决规则表达式的解析难题。
.NET WF提供的规则编辑器是基于代码的,但对于一个无代码的开发环境,需要找到一种表达式的设计,解析规范。
比如,在运行时,我们需要根据当前的运行参数解析出以上表达式的分支,最终流向哪个批核结点。
经过摸索,我提供现有系统中已经应用的三种方案,供读者参考。
1 查询表达式
比如一个费用报销流程,当报销金额小于1000元时,可由部门主管直接批核。用SQL语句表达如下:
SELECT AMOUNT FROM dbo.ICMOVH WHERE AMOUNT <1000AND REF_NO='<%InventoryMovementEntity.RefNo%>‘
<% 标记表示当前正在运行的业务对象,<%InventoryMovementEntity.RefNo%> 这一句可取到当前业务对象的标识值。再与金额条件Amount(小于等于1000)组合,当存在这样的单据时,即为满足分支条件,由部门主管批核。
2 查询语句
查询语句的作用是为了发送消息通知,将SQL命令通过参数化的方式封装,达到运行时获取数据的目的。
比如一个安全库存报警,当物料安全库存变动时,物料的库存余额小于当前物料设定的安全库存。
为了查询数据,设计出一种新语法标记,例子如下:
<%RepeatEachRecord_Begin.SqlQuery1%>Job No: <%SqlQuery1.job_no%><%RepeatEachRecord_End.SqlQuery1%>
通过上图的界面设计中可看出,一个消息通知可包含很多个SQL命名查询,每个SQL命名查询可返回一个或多个数据行,我们在消息文本中用特定的语法,解析出命名查询中字段,即可达到获取数据的目的。
3 常量
工作流中的活动可理解为一个封装的独立的代码片段,为了与外界通信,必须向活动传递参数。有时候参数是固定的值,比如当前的报销单据,在前面我们用到过<%InventoryMovementEntity.RefNo%>表示报销单据编号。
若是要取到当前登录系统的用户,可用<%SessionEntity.UserId%>来获取。
比如给仓库单据增加一个验证,取出当前用户创建的引用(Reference)为空的参考编号。可通过下面的SQL语句实现:
SELECT RefNo FROM dbo.ICMOVH WHERE REFERENCE IS NULLAND CREATED_BY='<%SessionEntity.UserId%>‘
4 调用程序代码
在一套业务工作流系统中调用代码实在是无奈之举。如果发生的业务很有规律,可考虑封装为活动以方便重用。
代码活动的表达方式如下:
我用了三行标记为的是表示一个.NET方法:
assembly=Microsoft.Workflowclass=Microsoft.Workflow.Extensionmethod=Microsoft.Workflow.Extension.VoucherPost
如果你熟悉.NET反射,很快能写出调用这个方法的代码。
Assembly assembly = null;
Type type = null;
string method = string.Empty;List<string> format = new List<string>() { "assembly", "class", "method" };string[] codes = Regex.Split(this.Code, ";");foreach (string code in codes){if (string.IsNullOrWhiteSpace(code))continue;
string[] segments = Regex.Split(code, "=");if (Microsoft.Common.Shared.StringCompare(segments[0].ToUpper(), "assembly".ToUpper()) == 0)assembly = Assembly.Load(segments[1]);if (Microsoft.Common.Shared.StringCompare(segments[0].ToUpper(), "class".ToUpper()) == 0)type = assembly.GetType(segments[1]);if (Microsoft.Common.Shared.StringCompare(segments[0].ToUpper(), "method".ToUpper()) == 0)method = segments[1];}type.GetMethod(method, BindingFlags.Public | BindingFlags.Static).Invoke(null, null);
从最后一段代码中可以看出,上面标记的方法必须是静态(static,非实例)方法。
最后,我将前三种情况附加到程序中,以帮助对话框的形式提供给工作流开发人员。
我这里所提到的工作流是以微软的WF为基础开发的一套工作流系统,如果你需要自定义一套工作流设计,业务开发,表达式解析等功能,文中的标记技术可供您参考。