本文基於 .NET Framework 3.0 的預發布版本撰寫而成。文中包含的所有信息均有變更可能。
本文討論:
構建基本活動和復合活動
異步和基於事件的活動
自定義設計體驗
驗證和錯誤處理
本文使用了以下技術:
.NET Framework 3.0
自定義工作流 活動是 Windows® Workflow Foundation 的其中一個最重要的方面,在構建它們時需要考慮許多功能 。Don Box 和 Dharma Shukla 在他們於 2006 年 1 月撰寫的文章“使用 Windows Workflow Foundation 的聲明性模型簡化開發”中,探討了構建工作流的核心觀點以及工作流運行時與活動之 間的交互(請參閱 msdn.microsoft.com/msdnmag/issues/06/01/WindowsWorkflowFoundation)。在本文 中,我會介紹為您的業務領域構建自定義活動時所需的核心組件,包括運行時職責、設計時體驗和異步活 動開發。
構建自定義活動時,您必須做的第一個選擇是構建簡單活動還是復合活動。簡單(或基本)活動是邏 輯和執行都封裝在活動代碼內的那些活動。簡單活動示例有:Windows SDK 中的 SendEmail 活動或從數 據庫查詢數據的活動。
與此相反,復合活動要依賴於子活動的執行才能實現其目標。您可以按照 自己的需求以兩種不同的方法創建復合活動。一種方法是構建這樣的復合活動:它允許用戶在設計時添加 子活動,並控制這些子活動的執行。例如,Windows Workflow Foundation 隨附的 While 活動允許您添 加子活動,然後配置 While 活動的循環功能。While 復合活動主要用於管理其子活動的執行。構建復合 活動的另外一種方法是創建從現有復合活動(例如,Sequence 活動)派生的活動並添加一組活動作為子 活動以定義可重用組件。
可重用活動(如經理批准活動)有幾個階段最容易使用現有活動集合加 以模擬。經理批准活動可以使用諸如 IfElse 之類的活動來判斷經理是否在線。如果在線,可以發送一條 即時消息;否則,發送一封電子郵件就可以了。此活動封裝了請求批准的所有復雜邏輯,請求批准在工作 流中並且其本身就是一個工作流。此活動的用戶只需在各自的工作流中使用這個復合活動就可以擺脫實際 與經理聯系時所必須處理的復雜事務與細節。
要開始理解構建活動的概念,最好從一個非常基本 的活動著手,然後逐步將其構建為更為復雜的活動。為了說明這些概念,本文使用了兩個主要活動:一個 是用於在 ASP.NET 成員身份庫中創建用戶的活動,一個是自定義 Switch 活動,用於模擬與同名代碼概 念相類似的執行。這兩個活動及其他活動的完整源代碼可在本文提供的代碼下載中找到。
構建基 本活動
構建活動在許多方面上都與構建組件相似。畢竟,活動是剛好利用 Windows Workflow Foundation 的功能的可重用組件,該活動在 Windows Workflow Foundation 框架中執行。所以,構建活 動的第一步是確定其功能和接口。以 CreateUser 活動為例,其功能是通過 ASP.NET 的成員身份提供程 序模型創建用戶。接口會需要創建用戶所必需的屬性:即電子郵件地址、用戶名和密碼之類的內容。
使用 Activity 基類中的已替換 Execute 方法來編寫用於處理基本活動的功能的代碼。除了編寫 特定於活動功能的代碼外,作為活動開發人員,您還要負責返回活動的狀態。對於基本活動,這幾乎總是 Closed 狀態。返回 Closed 是向工作流運行時指明,您的活動已完成其執行過程,運行時可以自由安排 工作流中的下一個活動。圖 1 顯示了一個用於在 ASP.NET 2.0 的成員身份系統中創建用戶的基本活動。 有關活動生命周期以及運行時與活動之間的約定的更詳細信息,請參閱上文提到的由 Don Box 和 Dharma Shukla 撰寫的文章。
Figure 1 創建用戶基本活動
public string Password { get { return _password; } set {_password = value; } } public string Email { get { return _email; } set{ _email = value; } } public string UserName { get { return _username; } set{ _username = value; } } public string MembershipProviderName { get { return _provider; } set{ _provider = value; } } protected override ActivityExecutionStatus Execute( ActivityExecutionContext executionContext) { MembershipProvider mp = String.IsNullOrEmpty(MembershipProviderName) ? Membership.Provider : Membership.Providers[MembershipProviderName]; MembershipCreateStatus status; mp.CreateUser(UserName, Password, Email, null, null, true, Guid.NewGuid(), out status); return ActivityExecutionStatus.Closed; }
相關屬性
為了使活動的使用者能夠配置活動的執行,提供活動的屬性是非常重要的。有了標准 Microsoft® .NET 屬性或字段(如前例所示),活動的使用者必須在工作流中編寫代碼以設置這些屬 性的值。但要考慮存在這樣一種情況:您想要為技術水平較低的人員提供一種構建工作流的機制。您不希 望所有用戶只是為了將數據從一個活動連接到另一個活動就不得不編寫代碼。聲明性 Windows Workflow Foundation 模型使您能夠更加輕松地處理這些情況,支持這一模型的其中一個功能便是相關屬性。
相關屬性是存儲活動值或工作流狀態的一種方式。與將值存儲在類本身中的標准屬性不同,相關 屬性的值存儲在由 DependencyObject 基類維護的字典中。
相關屬性支持多個關鍵功能,但其中 最重要的可能就是稱為活動綁定的功能。如同名稱所暗示的那樣,活動綁定就是將一個活動的屬性綁定到 另一個活動或工作流本身的屬性的操作。將相關屬性綁定到另一個屬性時,其值會自動傳播。例如,如果 工作流為即將創建的用戶名定義了一個屬性,而您已將 UserName 屬性定義為相關屬性,則您可以將 UserName 屬性綁定到工作流的該屬性。當使用參數啟動工作流時,傳遞到工作流的值將通過綁定自動傳 遞到您的活動。圖 2 描述了這種關系。
圖 2 使用相關屬性進行綁定
這種綁定為組件帶來了功能強大的模型,而相當多的人只有依賴於 UI 開發才能實現該同一模型。活 動綁定與 ASP.NET 和 Windows Forms 中的數據綁定非常類似,它使得連接組件變得更為簡單。它還會使 非程序員在構建工作流時變得更加愉快。在正確設計活動之後,用戶應當能夠將它們拖放到設計圖面上並 連接活動之間的值,而不必編寫任何代碼。
在活動中定義相關屬性分為兩步。第一步包括指定和 注冊相關屬性定義,方法是創建一個靜態變量,然後調用 DependencyProperty 類的 Register 方法來設 置該變量的值。第二步是定義一個包含 get 和 set 方法的標准 .NET 屬性,其中您依賴基類來存儲或檢 索相關屬性的值。請注意這裡有一段 Visual Studio® 2005 代碼,它將這一過程變得更為簡單。圖 3 中的代碼顯示重定義為相關屬性的 UserName 屬性。
Figure 3 作為相關屬性的 UserName 屬性
public static DependencyProperty UserNameProperty = System.Workflow.ComponentModel.DependencyProperty.Register( "UserName", typeof(string), typeof(CreateUserActivity)); [Description("此用戶的新用戶名")] [Category("User")] [Browsable(true)] [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible)] public string UserName { get { return (string)base.GetValue( CreateUserActivity.UserNameProperty); } set { base.SetValue(CreateUserActivity.UserNameProperty, value); } }
還請注意,在本例中,該 .NET 屬性本身具有標記,用以指示其在屬性浏覽器中的顯示方式。 這是 Visual Studio 設計器中最引人注目的地方,當突出顯示活動時,其類別和說明將自動顯示出來。 這些屬性都是標准的 .NET 設計器屬性,在 MSDN® 庫中進行了詳細的說明。
活動綁定是通過 使用相關屬性啟用的一項功能。其他功能包括屬性提升、更改通知和狀態管理。由於屬性值存儲在中央字 典中,因此活動狀態更容易被序列化。同樣,利用這個中央存儲庫能夠更加方便地監控這些屬性的更改, 從而允許其他活動或工作流代碼注冊更改並在修改特定值時采取行動。
一個常見問題是,開發人 員應在何時使用相關屬性來代替標准 .NET 屬性?答案非常簡單,那就是對於任何會因為綁定到另一個屬 性或字段而獲得好處的屬性,都應該使用相關屬性。事實是,沒有不使用相關屬性的更好托辭,另外,有 了代碼段的支持後,很容易將它們編寫出來。
構建復合活動
雖然基本活動具有很大價值, 但您常常會希望提供更為復雜的可重用組件。在可重用組件中,其中一種是可重用活動,它包括幾個代表 可重用交互或邏輯流的基本活動。例如,在創建用戶之前首先請求經理批准的活動就是一個復合活動。包 含在這一個活動中的內容有請求批准、接收響應,以及創建用戶。構建此復合活動的目標是允許用戶將其 添加到工作流中而不用擔心批准過程的復雜性和它的工作方式。
這種簡單的復合活動重用了一個現有基類(通常是 SequenceActivity 類)的執行控制。這種情況很 常見,以至於 Visual Studio Extension for Windows Workflow Foundation 附帶了一個針對此類活動 的可視設計器。只需將新活動添加到您的項目便可以開始從 Sequence 活動派生的復合活動,並且能夠在 可重用組件中定義各個階段,就像定義工作流一樣。使用屬性浏覽器,您可以將基類更改為其他任何復合 活動,從而可更改執行語義,同時卻可繼續使用設計器構建活動邏輯。圖 4 顯示了用於基於 Sequence 活動創建新復合活動的活動設計器。
圖 4
封裝活 動時,需要將所含活動的屬性提供給復合活動的使用者。在上一示例中,復合活動的用戶需要能夠為用戶 名、電子郵件地址、角色、正確配置所含活動而需要的其他詳細信息等提供值。Visual Studio Extensions 提供了一種稱為屬性提升的機制,使得能夠更加輕松地做到這一點。使用復合活動設計器時 ,如果右鍵單擊某項活動,會出現上下文相關菜單,其中便包含用以提升該活動可綁定屬性的選項。如果 選擇此選項,則相關屬性便會在復合活動中聲明,同時所含活動的屬性會綁定到這些值。這樣就能向您提 供需要設置的屬性,而不用強制您編寫代碼連接這些值。您可決定將提供的哪個屬性調用給您的復合活動 使用者,並可繼續隱藏活動的實現細節。
如果希望對子活動的執行加以更多控制,請編寫一個派 生自 CompositeActivity 的活動,然後在 Execute 方法中處理子活動的執行安排。例如,可以考慮 Switch 活動。.NET Framework 3.0 中提供的 IfElse 活動為有條件執行活動的單一分支提供了模型。 Parallel 活動為執行所有分支提供了模型。只有 ConditionedActivityGroup (CAG) 活動為有條件執行 多個分支提供了模型。Switch 比 CAG 更簡單,它提供了一個良好的機制來顯示如何控制子活動的執行, 從而能夠根據規則或代碼決策有條件地執行活動的多個分支。圖 5 顯示了用於工作流中的 Switch 活動 。請注意,該活動的每個分支都有一個名為 Condition 的屬性,可將其設置為聲明性規則條件或代碼條 件,如同 IfElse 活動的某個分支的條件一樣。僅當該條件存在且判斷其值為 True 時,該活動分支才會 執行。
圖 5 Switch 活動的有條件執行
作為復合活動,Switch 活動由許多可有條件執行的序列組成。因此,第一步就是創建另一個活動,即 SwitchBranch 活動,它派生自 Sequence 活動並定義了一個名為 Condition 的 ActivityCondition 屬 性。就是這個屬性允許用戶設置用以指示相應序列是否應執行的代碼或聲明性規則條件。SwitchBranch 充分利用了它的基本活動,同時沒有提供其自身的執行邏輯。
在 Switch 活動的 Execute 方法中 ,將檢查每個已啟用的子活動並判斷相應的條件。如果條件已設置並判斷其值為 True,則必須安排執行 該子分支。通過調用 ActivityExecutionContext 中的 ExecuteActivity 方法來實現這一點。這是復合 活動之間用以區別管理子活動的方式時最常使用的方法。無論您是希望以並行方式執行、按順序執行還是 以相反順序執行,您都能通過 Execute 方法來識別執行計劃以及應該執行哪些子活動而不應該執行哪些 子活動。
Switch 活動的 Execute 方法會向運行時返回 Executing 狀態。這表示該活動已安排執 行其子活動,但尚未完成。這會使運行時不再執行下一個活動,除非它得到此活動已完成的通知。當執行 完所有子活動時,Switch 活動必須能夠通知運行時,其已執行完此活動。因此,在安排執行子分支之前 ,Switch 活動需要注冊活動的更改通知,以便能夠知曉子活動的結束時間。圖 6 顯示了用於注冊此通知 的代碼。請注意,此更改通知利用了先前提到的可監控相關屬性的更改的能力。活動只有實現 IActivityEventListener 接口才能獲得這些更改的通知。
Figure 6 注冊更改通知
foreach(Activity child in EnabledActivities) { SwitchBranchActivity childBranch = child as SwitchBranchActivity; if (childBranch != null && childBranch.Condition.Evaluate(childBranch, executionContext)) { childBranch.Closed += OnChildClosed; executionContext.ExecuteActivity(childBranch); } } return ActivityExecutionStatus.Executing;
在 IActivityEventListener 接口的 OnEvent 方法中,當活動得知某個子分支已結束時,它會查看是否還有需要執行的任何其他子活動。如果沒有,則 活動可以請求 ActivityExecutionContext 將自身停止。這與從 Execute 方法返回 Closed 並讓運行時 知道該活動已完成其所有任務相同。這是在每個活動必須確定其完成自身工作所依據的具體條件時各活動 之間加以區別的另一種常見方法。
對於應如何執行子活動以滿足創建者和業務案例的需求,每個復合活動可能都會有不同的語義。此處 所示的示例只是為了在活動開發者創建復合活動時能夠更好地了解所關注的主要問題。在您構建活動時一 定要牢記這些內容,並判斷該示例是否適用於您的特定情況。
異步和基於事件的活動
雖然 有些活動能夠以簡單的同步方式來完成各自的工作,但在許多情況下,活動會先開始一部分工作,然後等 待響應。同樣,您可能需要創建一個活動,它可以偵聽外部事件,如即將創建的新文件或即將觸發的 Windows Management Instrumentation (WMI) 事件。鑒於在運行時中管理工作流的方式,您不能直接在 您的活動中為外部資源創建事件處理程序。例如,如果您在活動中針對對文件系統的更改注冊了一個事件 處理程序,則當工作流需要進入空閒序列化狀態時,您的處理程序會與其失去聯系。從而造成所引發的任 何事件都無法到達您的活動。
Windows Workflow Foundation 提供了基礎結構,可幫助解決與當 前可能保持的工作流之間的通信這一問題。該通信模型構建於一個隊列系統之上:活動通常注冊以接收關 於隊列的消息,而宿主應用程序中的服務則發送關於隊列的消息。您的自定義活動可以使用此模型來處理 外部事件,也可以傳遞異步活動執行的完成。這樣,您的活動可以先執行到某一點,然後等待激發因素的 到來以便繼續執行。請看一下圖 7,它描述了宿主應用程序中的代碼與工作流中的代碼(或活動)之間的 通信模型。
圖 7 與活動異步通信
為了您的活動能夠偵聽消息是否到達某個隊列,您需要首先確保該隊列存在。如果它不存在,則必須 進行創建。這通常在活動的 Initialize 或 Execute 方法中實現,還要取決於隊列希望在何時能夠接收 到消息。WorkflowQueuingService 提供了創建、查找或刪除工作流隊列所需的方法。最後,您的活動必 須注冊才能接收到這些通知,方法是在工作流隊列自身中注冊 QueueItemAvailable 事件。在確保隊列存 在並注冊事件後,當隊列中有可用項目時,您的活動會得到通知,之後,您可以從隊列中取出該項目並對 其進行處理。
創建異步活動時,請使用我剛剛介紹的步驟來准備您的活動,使其能夠接收到關於 隊列的消息,當您的異步活動完成時,請將一條消息送入隊列以通知給您的活動,還可以選擇向它發送結 果數據。由於 OnEvent 方法通過 Sender 參數來接收對 ActivityExecutionContext 的引用,所以可通 過調用 CloseActivity 方法來結束該活動。
例如,可以考慮一個簡單的 ReadLine 活動。在本例 中,ReadLine 活動將在其執行方法中創建一個隊列並注冊數據是否到達隊列的通知(通過實現 IActivityEventListener 接口來接收所說的通知)。宿主應用程序或自定義運行時服務會通過控制台檢 索用戶輸入並針對活動將其加入隊列。ReadLine 活動示例隨附在本文的代碼中,它使用此模式作為其實 現。
要創建一個能夠充當事件接收器的活動(如 HandleExternalEvent 活動),您還需要實現 IEventActivity 接口。如果您的活動要偵聽事件,則此接口用於定義該活動的主要職責:
public interface IEventActivity { void Subscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler); void Unsubscribe(ActivityExecutionContext parentContext, IActivityEventListener<QueueEventArgs> parentEventHandler); IComparable QueueName { get; } }
QueueName 屬性必須返回 IComparable 值,消息加入隊列時,它可以唯一地標識您的活動。 對於用於將消息加入隊列以通知工作流運行時的代碼,也需要使用這同一個隊列名。
通過此接口 ,能夠命令活動在其執行前訂閱事件並讓活動知道何時取消訂閱。在訂閱和取消訂閱方法中,該活動負責 確保使用 QueueName 來創建隊列並在處理結束時刪除隊列。此外,這也為您的活動能夠向任何本地服務 注冊信息提供了機會,這些本地服務將代表活動來執行邏輯並通過將消息加入隊列予以響應。
本 地服務是您定義並從主機添加到工作流運行時的一個類,它可以被您的宿主代碼、工作流或您的活動所利 用。只要宿主應用程序處於運行狀態,本地服務就能夠維護事件處理程序或其他偵聽程序,從而可通過將 消息加入隊列來確保相應的數據到達工作流。您傳遞給本地服務的信息應包括隊列名和工作流實例 ID 的 相關信息,以及該服務發起工作或向您的活動返回結果時所需的任何信息。
本文的示例代碼包括一個自定義事件活動,它用於創建隊列,以及注冊處理程序以在事件到達隊列時 獲得通知。宿主程序會在一個短暫的延遲之後簡單的將項目加入隊列,以此來展示如何將項目加入隊列; 在實際情況中,您的本地服務大多負責發送數據,但這表明宿主也可以為活動將數據加入隊列。
使用基於事件的活動的通用模式是在 Execute 方法中檢查隊列中是否有項目,如果沒有數據,則訂閱 適用於該隊列的項目並返回執行狀態。然後,在項目到達的處理程序中,從隊列讀出數據,也可以選擇在 結束您的活動之前引發該活動中的任何事件。
通過實現這些接口,現在,您的活動可以按照不同的方式參與到工作流中。例如,在狀態機工作流中 ,您的活動現在可以是在事件驅動的序列中作為第一個活動出現的事件,從而允許您的事件觸發一系列活 動,並可能轉變為新狀態。有關這種類型活動的完整示例,請看 Windows SDK 中的 FileWatcher 活動。
處理錯誤
活動開發人員的另一項職責是處理錯誤條件。活動中的錯誤管理有許多特性與任何其他代碼一樣,但 對於管理活動執行,還要考慮一些特殊注意事項。當活動執行邏輯中發生錯誤時,您應引發一個異常,就 像對待任何其他組件那樣盡可能提供所需的一切詳細信息。這會向運行時發送一個信號:已發生一個錯誤 ,它可以與工作流中的活動交互以使這些活動徹底關閉,運行時也可能會調用特殊處理程序來處理這些錯 誤。
只要您的活動執行代碼中引發異常,就會調用活動的 HandleFault 方法。在 HandleFault 方法 中,您應編寫釋放任何資源或取消您的活動可能已經發起的任何異步操作所需的代碼。請注意,該異常仍 將觸發到運行時;此方法只是允許您的活動在出現該方法後加以整理。復合活動還支持 FaultHandlers, 它是一個特殊的附加子活動,在復合活動從 faulting 狀態轉變為 closed 狀態之後作為便於後處理的手 段而執行。您可以使用 FaultHandlers 視圖來模擬如何處理子活動所引發的錯誤,從而能夠在工作流中 使用更好的錯誤處理代碼來處理特定於該問題的異常。
Activity 類中定義的 HandleFault 方法的默認行為是調用活動的 Cancel 方法。這樣能夠集中整理 資源,但這可能並不適用於所有情況,因為根據所引發異常的類型,您的活動可能有特定的資源需要整理 。如果您沒有處理資源的特定需要,請執行其他記錄操作或整理操作,隨後,在大多數情況下,默認行為 會替您工作。
請注意,通常,取消操作用於實現這樣的語義:一個復合活動已衍生出許多子活動,為了實現其所需 的語義,它需要取消一部分子活動正在進行的執行操作。取消是積極的操作,它用於支持向前執行,而錯 誤是消極的,它用於實現異常情況。取消和錯誤處理是兩個完全不同的操作,HandleFault 的基類實現調 用 Cancel 方法只是為了支持非常基本的情況。
設計體驗
許多活動設計用於基於模型的用戶界面(如 Visual Studio Extensions for Windows Workflow Foundation 隨附的設計器)中。在許多方面上,活動的設計體驗比執行要素更重要,因為對於其他開發 人員和活動的使用者而言,它是最為直觀的。在許多不同的情況下(而不僅僅是在開發時),Windows Workflow Foundation 都會允許工作流可視化,因此您的活動不僅要傳達其職責的語義,而且也要表明它 看起來對用戶是合乎邏輯且有用的,這非常重要。
設計時體驗有幾個組成部分,包括外觀、交互性和驗證。這些設計組成部分本身便是 .NET 類,並通 過應用於活動類的自定義屬性與活動相關聯。第一個這樣的類是活動設計器,它為核心開發人員創建豐富 的設計時體驗提供入口點。該設計器類允許您通過與屬性浏覽器、上下文菜單、鼠標點擊與移動,以及活 動在設計圖面上的實際繪圖之間的交互來參與設計體驗。
通過創建從 ActivityDesigner 基類、或 Windows Workflow Foundation 庫中可用的任何數量的復合 活動設計器派生的類,可以實現創建設計器。以下是應用於 Switch 活動的 SwitchActivityDesigner:
[Designer(typeof(SwitchActivityDesigner),
typeof(IDesigner))]
public partial class SwitchActivity :
System.Workflow.ComponentModel.CompositeActivity,
IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
...
我將重點介紹在自定義設計器中最常使用的幾個關鍵功能。其中的第一個功能是能夠 提供針對您的活動的自定義上下文菜單項,使用戶能夠發起某項操作。通過覆蓋設計器中的 Verbs 屬性 ,可以指定應添加到活動的上下文菜單中的菜單項集合。這樣就允許用戶通過右鍵單擊或其他方式來打開 上下文菜單,然後發起操作。對於添加到集合中的每個動詞,都要提供一個事件處理程序,當用戶在菜單 中單擊相應的項時會調用該事件處理程序。此外,需要傳入 DesignerVerbGroup 枚舉的一個成員,以指 示您希望在上下文菜單中如何對特定動詞進行分組。這樣便為您提供了根據目的來分組相關活動的能力, 如編輯或設置選項。在 Switch 活動的示例代碼中,添加了一個動詞以允許用戶重置子分支上的所有條件 。以下是將該動詞添加到上下文菜單中的代碼:
protected override ActivityDesignerVerbCollection Verbs { get { ActivityDesignerVerbCollection newVerbs = new ActivityDesignerVerbCollection(); newVerbs.AddRange(base.Verbs); newVerbs.Add(new ActivityDesignerVerb(this, DesignerVerbGroup.Edit, "清除所有條件", new EventHandler(ClearConditions))); return newVerbs; } }
除 Verbs 屬性外,您還可以覆蓋 DesignerVerbs 屬性,它允許您將動詞添加到錯誤上下文菜單中以 指示當前配置中存在的問題。最後,您還可以覆蓋 SmartTagVerbs 屬性,以將項目添加到智能標記,您 會在大多數復合活動的名稱下找到智能標記。通常僅當您需要提供設計圖面的替代視圖(類似於取消和錯 誤處理程序視圖)時才使用此項目。
也許,活動開發人員最常見的設計時需要就是能夠在設計圖面上自定義活動的外觀。Windows Workflow Foundation 為創建您所需的外觀提供了多種機制:從僅僅設置幾個屬性的非常簡單的方法到允 許您使用 System.Drawing.Graphics 對象直接繪制活動的最強大的方法。我將首先介紹最簡單的方法, 在許多情況下,它們足以滿足您的需要。
就工作流活動的外觀而言,需要掌握的第一點是它參與到設計器主題當中。主題只是用於定義每個活 動在設計器中的外觀。如果您安裝了 Visual Studio Extension for Windows Workflow Foundation 並 在“Tools”(工具)菜單中選擇了“Options”(選項),您將看到工作流設計器的一個類別,其中顯示 了兩個已安裝主題。單擊“New”(新建)按鈕,您會了解每個活動的可用屬性。您可以保存主題並將其 應用在 Visual Studio 設計器中,但如果您要在您自己的應用程序中重新托管工作流設計器控件,您還 可以使用它。通過使用主題,您可以完全更改工作流設計圖面及設計器中托管的每個活動。
設計器的默認主題用於檢查活動並允許它提供主題信息。在您的活動開發過程中,您通過使用一個屬 性將自定義 DesignerTheme 與您的自定義設計器相關聯來實現這一點。然後,在您的設計器主題的構造 函數中,您可以為 BackColorStart、BackColorEnd、ForeColor 和 BackgroundStyle 等屬性設置值,以 更改活動及其文本的著色。CreateUserActivityDesignerTheme 提供了一個更新這些值的示例:
public class CreateUserActivityDesignerTheme : ActivityDesignerTheme { public CreateUserActivityDesignerTheme(WorkflowTheme theme) : base(theme) { this.BackColorEnd = Color.BurlyWood; this.BackColorStart = Color.Bisque; this.BackgroundStyle = LinearGradientMode.ForwardDiagonal; this.ForeColor = Color.Black; } }
除了顏色和設計,您還可以指定在活動的默認位置畫一個圖標。通過使用 ToolboxBitmap 屬性及指定 作為嵌入式資源構建到程序集中的一個圖像文件(通常為 16×16 位圖或 PNG 文件)將圖標與活動連接 起來。除了在設計器中使用該圖像外,當手動或通過安裝程序將活動添加到工具箱時也顯示這個圖像,這 使得給定的屬性名有意義。圖 8 顯示了顯示有主題和圖標支持的工作流內部的 CreateUser 活動。
圖 8 活動的自定義主題和圖標
創建復合活動的設計器時,除了要處理主題和圖標的問題外,您可能還要必須在您的子活動設計器之 間繪制連接器,否則請更改如何包含這些子活動設計器的外觀。但是,有幾個基類可能會提供編寫代碼時 所需的功能,以便減少您必須編寫的代碼量。例如,對於 Switch 活動,設計器從 ParallelActivityDesigner 派生,ParallelActivityDesigner 用於將每個子活動繪制為分支並創建設計 器動詞以添加新分支。Switch 活動設計器只是必須覆蓋一個方法並返回一個新的 SwitchBranchActivity 。對於您擁有的帶有分支的任何復合活動,此設計器會被證明很有用並被 IfElse、Parallel 和 Listen 活動用於內部。
ParallelActivityDesigner 類的基類是 StructuredCompositeActivityDesigner 類,當您的復合活 動包含其他設計器時,StructuredCompositeActivityDesigner 類為連接器提供大量的呈現邏輯。
StructuredCompositeActivityDesigner 的一個同級類是 FreeFormActivityDesigner。此設計器是 StateMachine 工作流設計器的基礎,當可在復合活動中隨意放置子活動(與結構化放置相反)時使用。 將這兩種設計器中的一種用作您自己的設計器的基礎。
最後一個注意事項:當您使用活動設計器創建復合活動時,該活動會在設計圖面上出現並呈現出它的 所有子活動。因此,您的復合活動的使用者會在您的實現中看到所有內部活動。這可能是您想要的,但如 果不是這樣,您可以通過為您的復合活動創建一個從 ActivityDesigner 基類派生的設計器來解決這一問 題。這樣便實現了簡單呈現,即僅呈現一個活動。您可以附加設計器主題和 ToolboxBitmap 屬性以進一 步自定義外觀,從而提供真正的封裝。
驗證
在創建活動或任何相關組件時,驗證用戶輸入並確保組件配置正確是一種很好的做法。事實上,如果 您能在一開始就阻止用戶,使其不會錯誤地配置您的組件,那樣會更好。您可以在以下兩個主要開發方面 確保活動能夠得到正確配置:在設計器中以及針對您的活動使用自定義驗證程序。
在為您的活動創建自定義設計器時,請控制可以添加到復合活動中的那些活動或者可將您的活動添加 到其中的那些活動。請注意,這一行為僅適用於設計環境,如果您的活動是通過代碼添加的,則不起作用 ,這一點很重要。
通過覆蓋 ActivityDesigner 基類中的 CanBeParentedTo 方法,您可以過濾能夠將您的活動添加到其 中的那些活動的類型。如果您從該方法返回 false,則用戶會看到一個紅色的斜槓,這是代表 No 的國際 符號,表示不能添加您的活動。例如,SwitchBranch 活動僅允許它自身成為 SwitchActivity 的父項, 從而確保它只在正確的上下文中運行。通過覆蓋 CompositeActivityDesigner 類中的 CanInsertActivities 方法,您能夠控制是否可以將某個特定活動或某一組活動添加到您的復合活動中。 利用這兩個方法,您能夠控制與您的活動相關的活動結構。當您開始審視活動的執行需求時,這一點變得 非常重要,因為它能確保您進行設置以便成功執行。
驗證您的活動的另外一個方法是創建自定義驗證程序。Validator 類負責檢查您的活動並確保根據您 的要求進行了配置,此驗證始終會發生,而不管您是否是使用設計器將活動添加到工作流中的。現在您很 可能猜測出應該如何創建驗證程序:創建一個從 ActivityValidator 派生的類,然後覆蓋 Validate 方 法。然後,使用 ActivityValidatorAttribute 將您的驗證程序連接到您的活動。
CreateUser 活動有一個屬性用於保存密碼。但是,在工作流中靜態配置密碼並不是一個好主意。相反 ,驗證程序組件會確保將密碼屬性綁定到另外一個值,以便能夠在運行時設置該屬性的值。圖 9 顯示了 用於測試該屬性並確保它已進行了綁定的 CreateUserValidator 類。
Figure 9 創建用戶驗證程序
public override ValidationErrorCollection Validate( ValidationManager manager, object obj) { ValidationErrorCollection baseErrors = base.Validate(manager, obj); CreateUserActivity user = obj as CreateUserActivity; if (user == null) throw new ArgumentException( "活動必須為 CreateUser 活動"); // 確保我們正處於有效的設計器體驗 if (user.Parent != null && !user.IsBindingSet(CreateUserActivity.PasswordProperty)) { baseErrors.Add(new ValidationError( "必須使用活動綁定設置密碼", 1, false, "Password")); baseErrors.Add(new ValidationError("哎呀", 2, true)); } return baseErrors; }
代碼首先會驗證正在被驗證的活動的類型是否正確,以及它是否是在上下文中而非自身的編譯環境中 進行驗證。如果您不檢查父屬性是否為 null,則您將不能在一開始就構建您的活動,因為在編譯您的活 動時驗證程序會運行。ValidationErrorCollection 用於將錯誤集合傳回設計器,以便將相應的錯誤消息 添加到用戶界面中。
結論
正如您所看到的,Windows Workflow Foundation 為您構建可在工作流中使用的可重用的、易於設計 的活動提供了豐富的環境,而無論這些活動是葉活動(是實現域特定邏輯的關鍵)還是復合活動(是創建 反映實體間實際交互的控制流模式的關鍵)。事實上,無論是任何現成的活動還是您編寫的自定義活動, Windows Workflow Foundation 都同樣對待。
從活動執行到設計體驗,您能夠訪問的資源與構建基活動庫的 Microsoft 小組所能訪問的資源是一樣 的。構建健壯的活動使您能夠將您的應用程序和服務提供給 Windows Workflow Foundation 的處理模型 並能夠利用該模型必須提供的所有服務。我鼓勵您利用社區網站 (wf.netfx3.com) 來查找活動以及在構 建您自己的活動後提交這些活動。
Matt Milner 是自由的軟件顧問,他的專長是 Microsoft 技術,包括 .NET、Web 服務、Windows Workflow Foundation、Windows Communication Foundation 和 BizTalk Server。作為 Pluralsight 的 一名講師,Matt 講授 Workflow、BizTalk Server 和 Windows Communication Foundation 方面的課程 。Matt 與他的妻子 Kristen 及兩個兒子住在明尼蘇達州。請通過 Matt 的博客與他聯系。
本文配套源碼:http://www.bianceng.net/dotnet/201212/791.htm