WF 4.0 活動採樹狀結構,開發自訂活動可繼承四大基類,各有適用場景與能力邊界。本文系統化解析四類別差異,並以 HttpGet (葉子活動) 與 FlowChartActivity、MyWhile (複合/控制流活動) 為例完整示範。
| 基類 | 適用場景 | 核心方法 | 非同步支援 | 存取運行時服務 | 排程子活動 | 建立書籤 | 自訂追蹤/持久化 |
|---|---|---|---|---|---|---|---|
| CodeActivity | 簡單同步葉子活動 | Execute | ❌ | ✅ (Context) | ❌ | ❌ | ❌ |
| AsyncCodeActivity | 非同步葉子活動 (APM 模式) | BeginExecute/EndExecute | ✅ (APM) | ✅ | ❌ | ❌ | ❌ |
| Activity | 組合現有活動為新活動 (宣告式) | Implementation (Func<Activity>) | ✅ (透過子活動) | ✅ | ✅ (宣告式) | ❌ | ❌ |
| NativeActivity | 完整控制流、書籤、持久化、追蹤 | Execute/CacheMetadata | ✅ (完整) | ✅ (NativeActivityContext) | ✅ (ScheduleActivity) | ✅ (CreateBookmark) | ✅ |
public sealed class HttpGet : CodeActivity
{
public InArgument Uri { get; set; }
protected override string Execute(CodeActivityContext context)
{
var request = HttpWebRequest.Create(Uri.Get(context));
using (var response = request.GetResponse())
using (var stream = response.GetResponseStream())
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd(); // 同步阻塞直到完成
}
}
}
var fetch = new HttpGet { Uri = "https://www.msn.com" };
string content = WorkflowInvoker.Invoke(fetch);
Console.WriteLine(content);
💡InArgument<T>取值需透過.Get(context)或context.GetValue(arg),CodeActivityContext提供運行時服務存取 (追蹤、變數、屬性)。
public sealed class HttpGet : AsyncCodeActivity
{
public InArgument Uri { get; set; }
protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state)
{
var request = HttpWebRequest.Create(Uri.Get(context));
context.UserState = request; // 傳遞狀態至 EndExecute
return request.BeginGetResponse(callback, state);
}
protected override string EndExecute(AsyncCodeActivityContext context, IAsyncResult result)
{
var request = (WebRequest)context.UserState;
using (var response = request.EndGetResponse(result))
using (var stream = response.GetResponseStream())
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
💡BeginExecute發起非同步操作立即返回IAsyncResult,**不阻塞工作流執行緒**;操作完成觸發EndExecute取得結果。適用於 I/O 密集型操作 (Web Request、DB、檔案)。
以 FlowChart 為例,建構促銷折扣計算邏輯:
public sealed class FlowChartActivity : Activity
{
public string PromoCode { get; set; }
public int NumKids { get; set; }
public FlowChartActivity(string promoCode, int numKids)
{
PromoCode = promoCode; NumKids = numKids;
Implementation = new Func(CreateBody); // 關鍵:指定建構活動樹的工廠方法
}
Activity CreateBody()
{
// 變數宣告
var promo = new Variable { Default = PromoCode };
var numberOfKids = new Variable { Default = NumKids };
var discount = new Variable();
// FlowStep: 折扣未套用
var discountNotApplied = new FlowStep
{
Action = new WriteLine { DisplayName = "Discount not applied", Text = "Discount not applied" },
Next = null
};
// FlowStep: 折扣已套用
var discountApplied = new FlowStep
{
Action = new WriteLine { DisplayName = "Discount applied", Text = "Discount applied" },
Next = null
};
// FlowDecision: 判斷折扣 > 0
var flowDecision = new FlowDecision
{
Condition = ExpressionServices.Convert(ctx => discount.Get(ctx) > 0),
True = discountApplied,
False = discountNotApplied
};
// 各促銷代碼對應步驟 (Single, MNK, MWK, Default)
// ... 此處省略單步驟建構代碼 (Assign, TryCatch, Sequence 等)
// FlowSwitch 依 PromoCode 分派
var promoCodeSwitch = new FlowSwitch
{
Expression = promo,
Cases = { { "Single", singleStep }, { "MNK", mnkStep }, { "MWK", mwkStep } },
Default = discountDefault
};
// 組裝 Flowchart
return new Flowchart
{
DisplayName = "Promotional Discount Calculation",
Variables = { discount, promo, numberOfKids },
StartNode = promoCodeSwitch,
Nodes = { promoCodeSwitch, singleStep, mnkStep, mwkStep, discountDefault, flowDecision, discountApplied, discountNotApplied }
};
}
}
WorkflowInvoker.Invoke(new FlowChartActivity("MWK", 2));
💡Activity透過Implementation = new Func<Activity>(CreateBody)**宣告式組合** 內建活動,**不撰寫命令式 Execute 邏輯**,WF 運行時負責排程執行活動樹。
內建 Sequence、While、If、Parallel 皆繼承此類。以自訂 MyWhile 示範:
public sealed class MyWhile : NativeActivity
{
public Collection Variables { get; } = new Collection();
public Activity Condition { get; set; }
public Activity Body { get; set; }
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
base.CacheMetadata(metadata); // 註冊 Variables、Condition、Body 至中繼資料
if (Condition == null)
metadata.AddValidationError($"While {DisplayName} requires a Condition");
}
protected override void Execute(NativeActivityContext context)
{
InternalExecute(context, null);
}
void InternalExecute(NativeActivityContext context, ActivityInstance instance)
{
// 排程 Condition 評估,完成後回呼 OnEvaluateConditionCompleted
context.ScheduleActivity(Condition, new CompletionCallback(OnEvaluateConditionCompleted));
}
void OnEvaluateConditionCompleted(NativeActivityContext context, ActivityInstance instance, bool result)
{
if (result && Body != null)
{
// Condition 為 true 且有 Body → 排程 Body 執行,完成後再次回呼 InternalExecute (形成迴圈)
context.ScheduleActivity(Body, InternalExecute);
}
// result == false 或 Body == null → 迴圈結束,不再排程
}
}
CacheMetadata:驗證必要引數、註冊變數/子活動/書籤至運行時中繼資料,**驗證失敗加入 ValidationError 可阻止工作流啟動**。ScheduleActivity:**排程子活動執行**,支援完成回呼 (CompletionCallback),實現複雜控制流 (迴圈、分支、平行、錯誤處理)。CreateBookmark:建立書籤,支援 **長時間等待外部事件** (人工審核、訊息到達) 並持久化工作流實例。NativeActivityContext:提供完整運行時服務存取 (變數、屬性、追蹤、持久化、計時器、書籤管理)。CodeActivity<TResult>AsyncCodeActivity<TResult>ActivityNativeActivityWF 4.0 四大活動基類形成 **能力階梯**:CodeActivity ⊂ AsyncCodeActivity ⊂ Activity ⊂ NativeActivity。**原則:選擇滿足需求的最簡單基類**,避免過度使用 NativeActivity 導致複雜度暴增。掌握 CacheMetadata 驗證、ScheduleActivity 排程、Bookmark 等核心概念,即可開發出健壯、可重用、效能優異的自訂活動庫。