程序=數據結構+算法,而企業級的軟件=數據+流程,流程每每千差萬別,客戶自身有時都搞不清楚,隨時變化的狀況更是屢見不鮮,拋開功能等不談,需求變化很大程度上就是流程的變化,流程的變化會給開發工做形成很大麻煩。而本審批流程具備較強的通用性,同時也有很大的靈活性,雖然沒法100%的解決各類很是個性化的審批流程,但至少也能解決其中80%以上的較爲通用的流程了。本文將就分享部分心得!前端
本審批流程從實現上來講由流程設計器、流程控制組件和表單設計器三大部分組成。下面將分別進行描述:算法
1. 流程設計器 express
流程設計器基於 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. 流程控制組件app
流程控制組件主要完成流程的跳轉控制,數據的加載和保存等。流程控制組件和前面文章中提到過的底層組件同樣,使用了 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,非常蛋疼,只有放棄。ide
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 {
10
var codeProvider =
new CSharpCodeProvider();
11
var cpt =
new CompilerParameters();
12
13
//
引用程序集
14 cpt.ReferencedAssemblies.Add(
"
system.core.dll
");
15 cpt.ReferencedAssemblies.Add(
"
system.dll
");
16 cpt.ReferencedAssemblies.Add(
"
Microsoft.CSharp.dll
");
17
//
獲取對象實例的類型
18
var type = (Type)objectInstance.GetType();
19
//
獲取活動實例對應的程序集路徑
20
var path = activityInstance.GetType().Assembly.Location;
21 cpt.ReferencedAssemblies.Add(path);
22
if (type.Assembly.Location != path)
23 {
24 cpt.ReferencedAssemblies.Add(type.Assembly.Location);
25 }
26 cpt.CompilerOptions =
"
/t:library
";
27 cpt.GenerateInMemory =
true;
28
29
var builder =
new StringBuilder(
"");
30 builder.Append(
"
using System;\n
");
31 builder.Append(
"
using System.Dynamic;\n
");
32 builder.Append(
"
using Yb.Workflow.Provider;\n
");
33
34
var ns = type.Namespace;
35
if (ns !=
"
Yb.Workflow.Provider
")
36 {
37 builder.Append(
string.Format(
"
using {0};\n
", ns));
38 cpt.ReferencedAssemblies.Add(activityInstance.GetType().Assembly.Location);
39 }
40
41 builder.Append(
"
namespace CSCodeEvaler{ \n
");
42 builder.Append(
"
public class CSCodeEvaler{ \n
");
43 builder.Append(
"
public bool EvalCode(dynamic objectInstance,ActivityInstance activityInstance){\n
");
44 builder.Append(
"
return
" + expression +
"
; \n
");
45 builder.Append(
"
} \n
");
46 builder.Append(
"
} \n
");
47 builder.Append(
"
}\n
");
48
//
在內存中編譯
49
var cr = codeProvider.CompileAssemblyFromSource(cpt, builder.ToString());
50
if (cr.Errors.Count >
0)
51 {
52
throw
new InvalidExpressionException(
53
string.Format(
"
Error ({0}) evaluating: {1}
",
54 cr.Errors[
0].ErrorText, expression));
55 }
56
57
var a = cr.CompiledAssembly;
58
object instance = a.CreateInstance(
"
CSCodeEvaler.CSCodeEvaler
");
59
60 Type t = instance.GetType();
61
var mi = t.GetMethod(
"
EvalCode
");
62
//
反射調用方法的執行結果
63
object result = mi.Invoke(instance,
new
object[] { objectInstance, activityInstance });
64
return result;
65 }
3. 表單設計器
表單設計器將在下個版本中集成,當前還只能在流程定義中手動敲入已編輯好的表單內容。表單內容的持久化和加載使用了前面提到過的 ExtensionDataApi,由於支持 .NET 4.0 下的 dynamic 屬性,同時提供了一個名爲 Properties 的字典來管理全部的屬性名和屬性值,和 MVC 下的 Request.Form.AllKeys 簡直就是絕配,所以可很是方便地和表單界面進行集成。換句話說,你只管設計好表單界面便可,具體表單數據的保存和加載徹底能夠交由 ExtensionDataApi 來完成。
如需進一步瞭解,可點擊:http://pjdemo.yellbuy.com/