Microsoft Windows Workflow Foundation 入門:開發人員演練

Microsoft Windows Workflow Foundation 入門:開發人員演練

 

發佈日期: 11/30/2005 | 更新日期: 11/30/2005

Dino Esposito
Solid Quality Learning

適用於:
Microsoft Windows Workflow Foundation
Microsoft Windows Vista

摘要:對於需要爲 Microsoft .NET 平臺創建工作流驅動應用程序的開發人員而言,本文將介紹他們感興趣的 Microsoft Windows Workflow Foundation 技術和功能。

本文撰寫的對象爲 Windows Workflow Foundation beta 1。請注意,在該技術的最終版本問世之前,內容上很可能會發生更改。

*
本頁內容
有關向 Windows 平臺添加工作流支持的初步知識 有關向 Windows 平臺添加工作流支持的初步知識
創建第一個工作流 創建第一個工作流
接收和使用數據 接收和使用數據
工作流運行庫 工作流運行庫
工作流和活動 工作流和活動
開發自定義活動 開發自定義活動
計劃更現實的工作流 計劃更現實的工作流
小結 小結

有關向 Windows 平臺添加工作流支持的初步知識

Microsoft Windows Workflow Foundation (WWF) 是一個可擴展框架,用於在 Windows 平臺上開發工作流解決方案。作爲即將問世的 Microsoft WinFX 的組成部分,Windows Workflow Foundation 同時提供了 API 和一些工具,用於開發和執行基於工作流的應用程序。Windows Workflow Foundation 提供單個統一的模型,以便創建跨越多個類別應用程序的端到端解決方案,包括人力工作流和系統工作流。

Windows Workflow Foundation 是一個廣泛且通用的工作流框架,並且從下到上、在每個級別都針對可擴展性進行了設計。基於 Windows Workflow Foundation 的解決方案,由得到 Microsoft .NET 代碼支持且在宿主應用程序中運行的互連組件組成。就像在定製的環境中以可視方式創建 Web 頁一樣,您需要在可視設計器中制訂特定工作流的步驟,並且添加代碼隱藏工作流組件以實現規則並定義業務過程。

Windows Workflow Foundation 提供一個工作流引擎、一個 .NET 託管 API、運行庫服務以及與 Microsoft Visual Studio 2005 集成的可視化設計器和調試器。可使用 Windows Workflow Foundation 來生成並執行同時跨越客戶端和服務器的工作流,以及可在所有類型的 .NET 應用程序內部執行的工作流。

本文通過幾個循序漸進的示例對 Windows Workflow Foundation 的進行了流暢的簡介,並且說明它的工作方式。

工作流 是以活動示意圖形式定義的人力或系統過程模型。活動 是工作流中的一個步驟,並且是工作流的執行、重用和創作單位。活動示意圖表達規則、操作、狀態以及它們的關係。Windows Workflow Foundation 工作流通過安排活動而設計,然後它編譯爲 .NET 程序集,且在工作流運行庫和公共語言運行庫 (CLR) 中執行。

創建第一個工作流

Windows Workflow Foundation 主要由 .NET 驅動的運行庫環境組成,該環境處理在 Visual Studio 設計器中設計和實現的特殊對象。Microsoft .NET Framework 2.0 是支持 Windows Workflow Foundation 所必需的。單獨的安裝程序包爲 Visual Studio 2005 添加了 Windows Workflow Foundation 設計器和項目模板支持。一旦安裝,就會向 Visual Studio 2005 中的標準項目列表中添加一個全新的節點,如圖 1 所示。


1. Visual Studio 2005 中的工作流項目模板

您可以在各種選項中進行選擇,其中每個選項都標識了特定類型的工作流應用程序。表 1 顯示工作流項目模板的不完全列表。

表 1. Visual Studio 2005 中的工作流項目類型
類型 說明

順序工作流控制檯應用程序 (Sequential Workflow Console Application)

創建用於生成工作流的項目,該工作流包含一個默認的順序工作流和一個控制檯測試宿主應用程序。

順序工作流庫 (Sequential Workflow Library)

創建用於以庫的形式生成順序工作流的項目。

工作流活動庫 (Workflow Activity Library)

創建一個用來創建活動的庫的項目,以後可以將其作爲工作流應用程序中的構造塊重用。

狀態機控制檯應用程序 (State Machine Console Application)

創建用於生成狀態機工作流和控制檯宿主應用程序的項目。

狀態機工作流庫 (State Machine Workflow Library)

創建用於以庫的形式生成狀態機工作流的項目。

空工作流 (Empty Workflow)

創建可以包含工作流和活動的空項目。

Windows Workflow Foundation 支持兩種基本工作流樣式:順序工作流和狀態機工作流。

順序工作流 非常適合以下類型的操作,即該操作由依次執行直至最後一個活動完成的步驟的管線表示。但是,順序工作流的執行並非完全是順序的。它們仍然可以接收外部事件或者啓動並行任務,在這種情況下,確切的執行順序可能有所不同。

狀態機工作流 由一組狀態、轉換和操作組成。首先,將一個狀態表示爲起始狀態,然後,基於事件執行向另一個狀態的轉換。狀態機工作流可以具有確定工作流結束的最終狀態。

讓我們假設您選擇並創建了一個新的順序工作流控制檯應用程序項目。Visual Studio 2005 解決方案資源管理器將包含兩個文件 — workflow1.cs 以及最初從視圖中隱藏的 workflow1.designer.cs。這兩個文件表示創建的工作流。Windows Workflow Foundation 工作流包含工作流模型文件和一個代碼文件類。workflow1.cs 類是可在其中寫入您自己的工作流業務邏輯的代碼文件類。workflow1.designer.cs 類表示活動示意圖的說明。該文件由 Visual Studio 2005 按照與 Microsoft Windows Forms 項目中的窗體非常類似的方式自動管理。當向工作流中添加活動時,Visual Studio 2005 會用以編程方式生成活動示意圖的 Microsoft C# 代碼更新設計器類。要繼續使用 Windows 窗體的類比,那麼工作流類似於窗體,而活動類似於控件。

我們可以爲活動佈局選擇另一種形式的持久性 — XML 工作流標記格式。要嘗試這一方法,可從項目中刪除 workflow1.cs 文件並添加一個新的工作流項,如圖 2 所示。


2. 用代碼分隔添加順序工作流項

現在,項目包含兩個文件:workflow1.xoml 和 workflow1.xoml.cs。前一個文件包含表示工作流模型的 XML 工作流標記;後一個文件是一個代碼文件類,並且包含工作流的源代碼和事件處理程序。如果雙擊 .xoml 文件,會看到處於運行狀態的可視工作流設計器(參見圖 3)。

不存在爲工作流模型的序列化選擇標記或代碼的運行時暗示 — 一旦該工作流編譯爲程序集,它們就是等效的。

工作流應用程序是完成工作(例如,發送或接收數據)的活動和管理一組子活動的執行的複合活動(例如,IfElseWhile)的混合體。工作流可實現複雜的端到端方案,例如文檔審閱、PO 批准、IT 用戶管理、合作伙伴之間的信息交換、任何種類的嚮導或業務線應用程序。

圖 3 顯示一個極爲簡單的示例工作流,它只包含一個活動 — code1 塊。


3. Visual Studio 2005 工作流設計器

Code 塊對應於 Code 類的一個實例,並且表示工作流中的一個活動,其行爲以用戶定義的代碼表示。後端代碼是通過 Visual Studio 2005 輸入的(只需雙擊設計器中的所選元素),這是 ASP.NET 應用程序和其他 Visual Studio 2005 項目爲人熟悉的編程風格。

當雙擊該活動時,代碼文件會打開且提供代碼處理程序的存根。

private void code1_ExecuteCode(object sender, EventArgs e)
{
   // Some code here
}

當工作流運行庫在處理工作流的過程中開始處理指定的活動塊時,在代碼處理程序中鍵入的任何語句都將執行。以下代碼只是輸出一條歡迎消息。

private void code1_ExecuteCode(object sender, EventArgs e)
{
    Code c = (Code) sender;
    Console.WriteLine("Hello, from '{0}'./nI'm an instance of the {1} class.", 
       c.ID, c.ToString());
}

除了可視化佈局以外,工作流還包含 workflow1.xoml.cs 文件中保存的以下代碼。

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;

namespace HelloWorldWorkflow
{
    public partial class Workflow1 : SequentialWorkflow
    {
        private void code1_ExecuteCode(object sender, EventArgs e)
        {
           Code c = (Code) sender;
           Console.WriteLine("Hello, from '{0}'./nI'm an instance of the {1} class.", 
                              c.ID, c.ToString());
        }
    }
}

partial 屬性涉及不完全類 — 這是 .NET Framework 2.0 中的一個新概念。不完全類 是這樣的一種類:它的定義可能覆蓋不同的源文件。每個源文件都包含一個有頭有尾的普通類定義,只不過該定義是不完整的,並且沒有窮盡該類所需的邏輯。編譯器將把不完全的類定義合併到該類可以編譯的完整定義中。不完全類與面向對象沒有任何關係;它們是用來擴展項目中類行爲的源代碼級別和受到程序集限制的方式。在 .NET Framework 2.0 中,不完全類是用於防止 Visual Studio 2005 在代碼文件內部注入自動生成代碼的手段。原始類中缺少的任何綁定代碼都將由運行庫通過添加不完全類進行添加。

工作流只能由 Windows Workflow Foundation 工作流運行庫執行,並且工作流運行庫需要外部應用程序按照若干個規則來承載它。出於測試目的,Visual Studio 2005 還向項目中添加了一個 program.cs 文件。該文件是一個簡單的控制檯應用程序,如下所示。

class Program
{
    static AutoResetEvent waitHandle = new AutoResetEvent(false);

    static void Main(string[] args)
    {
        WorkflowRuntime workflowRuntime = new WorkflowRuntime();
        workflowRuntime.StartRuntime();

        workflowRuntime.WorkflowCompleted += OnWorkflowCompleted;

        Type type = typeof(HelloWorldWorkflow.Workflow1);
        workflowRuntime.StartWorkflow(type);

        waitHandle.WaitOne();
        workflowRuntime.StopRuntime();

        // A bit of feedback to the user    
         Console.WriteLine("");
         Console.WriteLine("");
         Console.WriteLine("==========================");
         Console.WriteLine("Press any key to exit.");
         Console.WriteLine("==========================");
         Console.ReadLine();
    }

    static void OnWorkflowCompleted(object sender, WorkflowCompletedEventArgs e)
    {
       waitHandle.Set();
    }
}

正如您在上述代碼中的粗體行中看到的那樣,出於簡單性的目的,Visual Studio 2005 在該控制檯應用程序中硬編碼工作流類的名稱。要防止控制檯應用程序在完成之後立即退出,您可能需要在 Main 方法的結尾添加 Console.ReadLine 調用。此刻,您已經做好生成和測試該工作流的所有準備:請按 F5 並執行。如果一切順利,那麼會看到圖 4 中顯示的輸出。


4. 控制檯宿主應用程序執行的示例工作流

調試工作流應用程序也很容易。實際上,需要做的所有工作就是放置斷點。您可以在工作流代碼文件類中的任何位置放置斷點(就像通常對 C# 代碼所做的那樣),或者直接在設計器視圖中放置斷點(這確實非常有趣)。需要選擇希望調試器介入的活動,按 F9 設置斷點,如圖 5 所示。


5. 工作流設計器視圖中放置的斷點

一旦代碼流到達設置了斷點的活動,Visual Studio 2005 就會將控制權移交給工作流調試器(參見圖 6)。從這裏開始,您可以按照預期的那樣,在可視化設計器中按 F11 單步執行代碼和活動。


6. 處於調試會話狀態的工作流應用程序

接收和使用數據

讓我們繼續分析並修改該工作流,以使其在實例化以後接收和使用數據。有兩種在實例化工作流以後使其接收數據的常規方法:參數和事件。如果選擇使用參數,則需要在可視化設計器中手動定義參數名稱和類型的列表。如果選擇使用事件,則需要創建並添加一個自定義活動(該活動充當在工作流模型中的某個位置介入的外部源),並且傳入一些數據。我們將在下文中說明基於事件的方法;現在,讓我們重點說明一下參數。

如圖 7 所示,Workflow1 的 Properties 窗格顯示一個 Parameters 集合,您將在設計時用名稱/值對進行填充。


7. 向工作流中添加參數

圖 8 顯示正在工作的參數編輯器。您可以爲所需的每個參數創建一個新項並指明它的名稱、類型和方向。


8. 添加的 FirstName LastName 字符串參數

參數的類型可手動鍵入,也可從定製的對象瀏覽器中選擇。在關閉 Workflow Parameters Editor 對話框後,立即修改代碼文件以合併剛剛定義的參數。您通常添加兩個公共屬性,並且使它們公開 Parameters 集合的內容,如下面的代碼所示。

public partial class Workflow1 : SequentialWorkflow
{
   public string UserFirstName
   {
      get { return (string) Parameters["FirstName"].Value; }
      set { Parameters["FirstName"].Value = value; }
   }

   public string UserLastName
   {
      get { return (string) Parameters["LastName"].Value; }
      set { Parameters["LastName"].Value = value; }
   }   

   :
}

使用公共屬性確實是一種良好的編程做法,可使代碼變得更加簡潔明瞭。它絕不是使用參數數據的要求。直接對工作流的 Parameters 集合進行調用也是完全可行的。如果用公共屬性包裝參數,則請隨意選擇屬性的名稱。然而,請記住,C# 中的參數名是區分大小寫的。

那麼是誰實際上通過這些參數提供輸入數據呢?完成這一任務的是宿主應用程序。然而,需要注意的是,宿主在初始化時(此時,工作流被加載以便執行到運行庫容器中)設置任何參數。爲了稍微詳細地說明這一點,讓我們編寫一個基於 Windows 窗體的示例宿主應用程序。該示例應用程序將提供幾個文本框,以便用戶輸入名字和姓氏(參見圖 9),並且將它們傳遞給工作流代碼處理程序。爲了使用這些參數,讓我們重新編寫代碼處理程序,如下所示。

private void code1_ExecuteCode(object sender, EventArgs e)
{
    MessageBox.Show("Welcome, " + UserFirstName + " " + UserLastName);
}

該示例 Windows 窗體宿主應用程序中的關鍵內容是附加到 Start Workflow 按鈕的單擊處理程序。


9. 工作流宿主 Windows 窗體應用程序

窗體的代碼隱藏類的全部源代碼如下所示。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Reflection;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;


namespace WinFormHost
{
    public partial class Form1 : Form
    {
        private WorkflowRuntime _wr = null;
        private string _workflowAssembly = "";
        private string _workflowTypeName = "";

        public Form1()
        {
            InitializeComponent();
            _workflowAssembly = "WorkflowWithParams";
            _workflowTypeName = "WorkflowWithParams.Workflow1";
            _wr = new WorkflowRuntime();
            _wr.StartRuntime();
        }


        private void btnStartWorkflow_Click(object sender, EventArgs e)
        {
            string assemblyName = _workflowAssembly;
            string typeName = _workflowTypeName;

            // Attempt to get type by fully-qualified name
            Assembly assembly = Assembly.Load(assemblyName);
            Type workflowType = assembly.GetType(typeName);

            Dictionary parameters = new Dictionary();
            parameters.Add("FirstName", txtFirstName.Text);
            parameters.Add("LastName", txtLastName.Text);

            // Start the workflow
            Guid instanceID = Guid.NewGuid();
            _wr.StartWorkflow(workflowType, instanceID, parameters);
        }
    }
}

要填充參數集合,需要使用由字符串和對象組成的基於泛型的字典。項的名稱是字符串,而所包含的值被配置爲對象。需要向該字典中添加像工作流模型中的靜態參數一樣多的項 — 在此情況下,請添加 FirstNameLastName。這兩個參數獲得在 UI 文本框中鍵入的內容。

最後,在創建指定模型的實例的同時將執行工作流。運行庫對象上的 StartWorkflow 方法具有多個重載。代碼中使用的版本接受工作流類型、輸入參數的集合以及系統生成的全局唯一標識符 (GUID)。

對於每個進程,只需要一個工作流運行庫實例;對於每個 AppDomain,不能有一個以上的實例。這裏,最需要做的事就是直接在窗體的構造函數中創建所需的實例。同一個運行庫對象可以照看多種工作流實例。運行庫基於實例的 GUID 來區分它們,並且爲每個特定實例接收私有數據。


10. 正在工作的參數化工作流(由 Windows 窗體應用程序承載)

出於純粹的教學目的,讓我們在這一開發階段迅速觀察一下設計器和工作流標記代碼。下面是 workflow1.designer.cs 源代碼文件。

public sealed partial class Workflow1 : SequentialWorkflow
{
   private void InitializeComponent()
   {
      ParameterDeclaration FirstName = new ParameterDeclaration();
      ParameterDeclaration LastName = new ParameterDeclaration();
      this.code1 = new System.Workflow.Activities.Code();
      // 
      // code1
      // 
      this.code1.ID = "code1";
      this.code1.ExecuteCode += new System.EventHandler(this.code1_ExecuteCode);
      // 
      // Workflow1
      // 
      this.Activities.Add(this.code1);
      this.DynamicUpdateCondition = null;
      this.ID = "Workflow1";
      FirstName.Direction = System.Workflow.ComponentModel.ParameterDirection.In;
      FirstName.Name = "FirstName";
      FirstName.Type = typeof(string);
      FirstName.Value = null;
      LastName.Direction = System.Workflow.ComponentModel.ParameterDirection.In;
      LastName.Name = "LastName";
      LastName.Type = typeof(string);
      LastName.Value = null;
      this.Parameters.Add(FirstName);
      this.Parameters.Add(LastName);
   }

   private Code code1;
}

下面是相應的工作流標記內容。

<?Mapping XmlNamespace="ComponentModel" 
ClrNamespace="System.Workflow.ComponentModel" 
          Assembly="System.Workflow.ComponentModel" ?>
<?Mapping XmlNamespace="Compiler" ClrNamespace="System.Workflow.ComponentModel.Compiler" 
          Assembly="System.Workflow.ComponentModel" ?>
<?Mapping XmlNamespace="Activities" ClrNamespace="System.Workflow.Activities" 
          Assembly="System.Workflow.Activities" ?>
<?Mapping XmlNamespace="RuleConditions" ClrNamespace="System.Workflow.Activities.Rules" 
          Assembly="System.Workflow.Activities.Rules" ?>
<SequentialWorkflow x:Class="WorkflowWithParams.Workflow1" 
                    x:CompileWith="Workflow1.xoml.cs" 
                    ID="Workflow1" 
                    xmlns:x="Definition" xmlns="Activities">
    <SequentialWorkflow.Parameters>
        <wcm:ParameterDeclaration Name="FirstName" Type="System.String" Direction="In" 
                                  xmlns:wcm="ComponentModel" />
        <wcm:ParameterDeclaration Name="LastName" Type="System.String" Direction="In" 
                                  xmlns:wcm="ComponentModel" />
    </SequentialWorkflow.Parameters>
    <Code ExecuteCode="code1_ExecuteCode" ID="code1" />
</SequentialWorkflow> 

請注意,在創建該工作流的實例以便執行時,必須顯式初始化並傳入 Parameters 集合中靜態定義的所有參數。

工作流運行庫

宿主通過 WorkflowRuntime 類與 Windows Workflow Foundation 交互。請不要被如上所示示例宿主表面上的簡單性所矇騙,以至於您注意不到這一要點。可以讓宿主負責處理很多附加、關鍵的方面,例如:創建一個或多個進程以及一個或多個 AppDomain;按照需要封送 AppDomain 之間的調用;設置隔離機制。出於可伸縮性的原因,宿主可能需要創建多個進程來利用一臺計算機中的多個 CPU,或者在一個計算機場中運行大量工作流實例。

宿主還可以做其他事情。例如,它可以控制在工作流需要長久等待時應用的策略,偵聽特定事件並將它們傳達給用戶或管理員,設置超時並重試每個工作流,公開性能計數器,以及寫入日誌信息以用於調試和診斷。

主機通過在啓動時向容器註冊的預定義和自定義服務來完成大多數附加任務。示例宿主沒有做上述任何一件事情,並且僅限於啓動工作流實例。這在很多常見情況下是可以接受的。

工作流和活動

讓我們後退一步,並且在工作流項目處於活動狀態時分析一下 Visual Studio 2005 工具箱。圖 11 中顯示的工具箱列出了可用來設計步驟的順序及其相互關係,以便形成工作流模型的活動。


圖 11. Windows Workflow Foundation 工作流的構造塊

表 2 提供每個活動的簡短說明,以及這些活動適用於哪些方案。

表 2. Windows Workflow Foundation 構造塊
活動 說明

Code

使您能夠向工作流中添加 Microsoft Visual Basic .NET 或 C# 代碼以執行自定義操作。但是,這些代碼不應該用對 Web 服務等外部資源的依賴性來阻塞工作流。

Compensate

使您能夠在發生錯誤時調用代碼來撤消或者補償已經由工作流執行的操作。通常,對於現在已被取消的操作,您可能希望向先前已經獲得成功通知的用戶發送電子郵件。

ConditionedActivityGroup (CAG)

使您的工作流能夠基於特定於每個活動的準則有條件地執行一組子活動,直到針對 CAG 整體滿足完成條件。子活動相互獨立並可能並行執行。

Delay

使您能夠控制工作流的定時以及將延遲內置到工作流。您可以在 Delay 活動上提供超時,以便工作流在恢復執行之前暫停。

EventDriven

代表一系列其執行由事件觸發的活動。第一個子活動必須能夠等待外部事件。可行的首要子活動是 EventSink 和 Delay。在這種情況下,Delay 用作超時。

EventSink

在向 WorkflowRuntime 註冊的數據交換服務引發指定事件時,使工作流能夠從該服務接收數據。

ExceptionHandler

使您能夠處理指定類型的異常。ExceptionHandler 活動是其他活動的包裝,在指定的異常發生時,這些活動實際執行所需的任何工作。可根據情況指定一個用於存儲異常的本地變量,並且使其可以在代碼隱藏中使用。

IfElse

使您的工作流能夠有條件地執行多個可供選擇的分支之一。可在每個分支上放置一個條件,而條件爲真的第一個分支將執行。無需在最後一個分支上放置條件,因爲它被視爲“else”分支。

InvokeMethod

使您的工作流能夠調用接口上的方法,以便將消息從工作流發送到向 WorkflowRuntime 註冊的數據交換服務。

InvokeWebService

使您的工作流能夠調用 Web 服務方法。您需要指定要使用的代理類(使用 WSDL),以及您想要調用的方法的名稱。同步和異步調用都受到支持。

InvokeWorkflow

使您的工作流能夠調用或啓動另一個工作流(可達到任意深度)。例如,被調用的工作流可以調用第三個工作流,該工作流又可以調用第四個工作流,等等。遞歸調用不受支持。受支持的調用模型是發後不理。

Listen

使工作流能夠等待(可能存在的)多個事件之一,或者在指定的超時間隔之後停止等待,並且基於結果分支。可向每個分支中添加一個或多個由事件驅動的活動。只有第一個滿足條件的分支被執行;其他分支都不會運行。

Parallel

使您的工作流能夠相互獨立地執行兩個或更多個操作。該活動在繼續執行之前會等待這些操作終止。

Policy

使您能夠表示或執行規則集合。該活動不在工具箱中;要訪問它的功能,必須創建自定義活動並使用派生。

Replicator

使您的工作流能夠創建給定活動的任意多個實例,並且順序或同時執行它們。

SelectData

使您的工作流能夠通過在外部數據源對象上定義的方法查詢外部數據。當觸發 SelectData 活動時,關聯的方法將在宿主線程內部執行。該方法返回的值被傳遞給工作流。

Sequence

使您能夠協調一組子活動的連續執行。該序列在最後一個子活動完成之後完成。

SetState

使您的狀態機工作流能夠指定向新狀態的轉換。

State

表示狀態機工作流中的狀態。

StateInitialization

在 State 活動中,用作在狀態轉換時執行的子活動的容器。

Suspend

掛起工作流的操作,以便能夠在發生某個錯誤條件時進行干預。當工作流實例掛起時,將記錄錯誤。可指定一個消息字符串來幫助管理員診斷髮生了什麼事情。與當前實例關聯的所有狀態信息都被保存,並且這些信息會在管理員繼續執行時恢復。

Terminate

使您能夠在發生任何異常情況時立即結束工作流的操作。如果是在 Parallel 活動內部調用,則所有分支都被突然終止,而無論它們的當前狀態如何。當工作流終止時,會記錄錯誤,並提供一個消息以幫助管理員弄清楚發生了什麼事情。

Throw

使您能夠引發指定類型的異常。使用該活動等效於在用戶代碼中引發異常的代碼處理程序。該活動是引發 .NET 異常的聲明性方式。

TransactionalContext

事務上下文 是用於對活動進行分組的塊。該活動主要用於事務性執行、補償和異常處理,可以根據情況進行同步。通過同步事務性上下文,可確保對活動中共享數據的任何訪問都將正確地序列化。

UpdateData

使您的工作流能夠通過在外部數據源對象上定義的方法更新數據存儲區。當 UpdateData 活動被觸發時,關聯的方法將在宿主線程內部執行。

WaitForData

使您的工作流能夠從外部數據源對象接收信息。當傳入的數據修改綁定數據源的狀態時,該活動被觸發。傳入的數據是通過綁定數據源服務接收的。

WaitForQuery

使外部應用程序能夠在您的工作流中查詢數據。該活動將在從宿主收到查詢之前一直等待。來自外部應用程序的查詢使用綁定數據源服務上的方法提交給工作流。

WebServiceReceive

使作爲 Web 服務本身公開的工作流能夠接收 Web 服務請求。

WebServiceResponse

使作爲 Web 服務本身公開的工作流能夠響應 Web 服務請求。

While

使您的工作流能夠在一個條件被滿足時執行一個或多個活動。在每次迭代之前,都評估該條件。如果爲真,則所有子活動都會執行;否則,該活動完成。可指定聲明性條件或代碼條件。

活動表示使用 Windows Workflow Foundation 進行工作流編程的聲明性方法。使用活動,可在設計時創作工作流模型並將值分配給每個活動的屬性。如果選擇帶有代碼分隔功能的工作流項,則最後的結果會作爲 XML 標記保存到具有 .xoml 擴展名的工作流標記文件中。否則,創作的模型將作爲對工作流對象模型的一系列調用持久保存在設計器生成的 C# 或 Visual Basic .NET 類文件中。前一種方法類似於 ASP.NET 頁,而後一種方法類似於 Windows 窗體應用程序所採用的方法。

Visual Studio 2005 隱藏了這兩種方法之間的大多數差異。您總是以可視方式設計工作流,並且 Visual Studio 2005 透明地將您的工作持久保存爲兩種不同格式中的一種。如果您選擇採用“僅代碼”解決方案(沒有 XOML 和代碼分隔),則可調整設計器代碼以使其變得更加靈活一些。例如,可讓它從配置文件或數據庫中讀取參數的默認值。如果選擇採用工作流標記和代碼分隔,則在工作流的代碼及其模型之間產生巧妙的分隔。

是否可以用編程方式修改工作流模型?在設計時,可在 Visual Studio 中以編程方式對工作流做您可以做的所有事情。在運行時,對活動集合進行動態更新也是可以的,而這爲您提供了對正在運行的工作流實例進行更改的能力。動態更改由在設計時未知的業務更改激發,或由首先修改然後完成業務過程的業務邏輯需要激發。在任何情況下,它都應該只涉及有限的更改 — 完善而不是重新設計。

動態更新適用於應用程序上下文中的單個工作流實例。同一工作流類型的將來實例將不會受到更改的影響。對工作流實例的動態更新可以從工作流實例本身中進行,也可以從您的應用程序代碼外部進行。

Windows Workflow Foundation 框架支持 Web 服務互操作性,這包括能夠將工作流作爲 Web 服務向 ASP.NET 客戶端和其他工作流公開。Windows Workflow Foundation 支持將工作流作爲在 Microsoft IIS 6.0 上運行 ASP.NET 的 Web 服務器或服務器場上的 ASP.NET Web 服務公開。

Windows Workflow Foundation 框架活動集包含 WebServiceReceiveWebServiceResponse 活動,這使工作流能用作 Web 服務終結點。

要想作爲 Web 服務公開,工作流必須包含 WebServiceReceive 活動,以便從客戶端獲得傳入的調用。快捷菜單命令將工作流作爲 Web 服務發佈,如圖 12 所示。


圖 12. 將工作流作爲 Web 服務發佈

開發自定義活動

Windows Workflow Foundation 中可擴展性的要點是創建自定義活動,因爲這使您可以擴展用於生成工作流模型的構造塊集。

讓我們研究一下活動的內部體系結構,方法是開發一個自定義活動來發送電子郵件。Windows Workflow Foundation 爲自定義活動提供一個現成的 Visual Studio 2005 模板。它的名稱爲 Workflow Activity Library。該模板創建一個可任意重命名的 C# 文件 — 例如,可將其重命名爲 SendMailActivity。活動是從父類繼承的普通類。可從任何現有活動(無論它是內置的活動,還是您自己創建或從第三方供應商購買的活動)派生您的活動。顯然,父類向新的組件中添加了預定義的行爲。要完全從頭開始生成活動,請讓其從 Activity 派生。下面的代碼示例顯示新類的主幹。

public partial class SendMailActivity : System.Workflow.ComponentModel.Activity
{
   public SendMailActivity()
   {
      InitializeComponent();
   }

   protected override Status Execute(ActivityExecutionContext context)
   {
       : 
   }
}

正如您可以猜到的那樣,Execute 方法是該組件的核心 — 即完成該組件的核心任務的位置。

在開發之後,活動就被放到工具箱中,以供拖放操作將其拖放到新的工作流應用程序中。儘管屬性列表不是必需的,但不帶屬性的活動幾乎沒有任何用處。要添加屬性,您需要在設計器中選擇正在開發的活動,然後單擊 Properties 窗格上的 Activity Properties 項(參見圖 13)。


圖 13. 向自定義活動中添加屬性

向活動中添加屬性與向工作流中添加參數並無太大的不同。必須做的工作就是爲每個需要的屬性配置名稱和屬性。圖 14 顯示如何向 SendMail 活動中添加 To 屬性。


圖 14. 添加到 SendMail 活動中的 To 屬性

爲了完成所有工作,我們添加了其他屬性(如 FromSubjectBodyHost),以便用戶可以完整地配置要發送的電子郵件。當您添加屬性時,嚮導會修改包含活動的邏輯在內的 C# 代碼隱藏文件。

最後一個步驟是使 Execute 方法變得充實一些,以指示它在執行該活動時發送電子郵件。

protected override Status Execute(ActivityExecutionContext context)
{
    MailAddress toAddress = new MailAddress(To);
    MailAddress fromAddress = new MailAddress(From);

    MailAddressCollection addresses = new MailAddressCollection();
    addresses.Add(toAddress);

    MailMessage msg = new MailMessage(fromAddress, toAddress);
    msg.Subject = Subject;
    msg.Body = Body;

    SmtpClient mail = new SmtpClient(Host);
    mail.Send(msg);
    return Status.Closed;
}

如果在工作流解決方案的內部開發活動項目,則工作流文檔將自動查找工具箱中列出的新活動,如圖 15 所示。否則,必須通過右鍵單擊工具箱來添加它。


圖 15. SendMail 活動顯示在工具箱中

圖 16 說明 SendMail 活動確實有效。


圖 16. 正在工作的 SendMail 活動

計劃更現實的工作流

讓我們看一下如何組合表 2 中列出的一些活動,從而解決一項更爲現實的任務。假設有這樣一個業務應用程序,其中,訂單在完成之前可能要經歷多個狀態。在典型的方案中,有一些根據當前狀態指示訂單中可能發生某些事件的規則。例如,可以處理或更新未完成的訂單,但不能將其取消或發送。

當事件發生時,狀態機工作流將轉換訂單的狀態。例如,當訂單未完成並且 BeingProcessed 事件發生時,狀態機工作流會將訂單轉換到正確的狀態。圖 17 顯示示例訂單狀態機工作流的關係圖。


圖 17. 管理訂單的狀態機的示例架構

讓我們首先創建一個狀態機工作流。您將使用 State 活動對訂單的可能狀態進行建模。然後,通過使用 EventDriven 活動指定可以從每個狀態發生的事件。通過自定義服務產生的外部事件將轉換訂單的狀態。要執行轉換,需要使用 SetState 活動。創作工作流後,需要使用 Windows 窗體宿主應用程序來檢驗它。

工作流通過專門爲此目的建立的服務與外部世界通信。該服務會引發工作流內的事件驅動活動將掛鉤到的事件。同樣,該服務公開了供該工作流調用的公共方法並向主機發送數據。方法和事件在接口中定義。該接口也稱爲數據交換服務。每當工作流與外部組件交互(進行輸入和輸出)時,您都需要該服務。

數據交換服務是常規的 .NET 類庫,它最起碼包含一個接口定義以及一個實現該接口的類。該接口是爲您希望表示的任務定製的。在此情況下,狀態機表示訂單的生存期,該接口包含五個事件。

[DataExchangeService]
public interface IOrderService
{
    event EventHandler OrderCreated;
    event EventHandler OrderShipped;
    event EventHandler OrderUpdated;
    event EventHandler OrderProcessed;
    event EventHandler OrderCanceled;
}

[DataExchangeService] 屬性將 IOrderService 標記爲數據交換服務接口,以便工作流運行庫知道它將用來與工作流實例交換數據。在此情況下,宿主將向一串 EventDriven 活動引發事件,從而向工作流實例發送數據。如果需要,可通過 InvokeMethod 活動從工作流實例內部調用 IOrderService 接口中的方法。

該接口中的事件聲明使用泛型,這是 .NET Framework 2.0 中的一項非常熱門的新功能。EventHandler 類是一個委託,它表示用於處理事件的函數的原型。在 .NET Framework 1.x 中,EventHandler 按如下方式定義。

void EventHandler(object sender, EventArgs e)

要使該事件傳遞自定義數據結構(例如,OrderEventArgs),必須創建一個新的委託並使用它來替代 EventHandler。下面是一個示例。

delegate void OrderEventHandler(object sender, OrderEventArgs e)

該模式在 .NET Framework 2.0 中仍然有效。然而,.NET Framework 2.0 中泛型的出現使您無需顯式定義(和實例化)新的委託類即可獲得相同的結果。您將使用 EventHandler 委託的泛型版本,其中,事件數據的類型是參數。

事件傳遞 OrderEventArgs 類型的客戶端數據,該類型是一個從 Windows Workflow Foundation WorkflowMessageEventArgs 類派生的自定義類,後者在同一個程序集中按如下方式定義。

[Serializable]
public class OrderEventArgs : WorkflowMessageEventArgs
{
    private string _orderId;

    public OrderEventArgs(Guid instanceId, string orderId) : base(instanceId)
    {
        _orderId = orderId;
    }

    public string OrderId
    {
       get { return _orderId; }
       set { _orderId = value; }
    }
}

下一步,需要定義一個實現該接口的類。該類所引發的公共方法與接口中引發的事件一樣多。

public class OrderService : IOrderService
{
    public OrderService()
    {
    }

    public void RaiseOrderCreatedEvent(string orderId, Guid instanceId)
    {
        if (OrderCreated != null)
            OrderCreated(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderShippedEvent(string orderId, Guid instanceId)
    {
        if (OrderShipped != null)
            OrderShipped(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderUpdatedEvent(string orderId, Guid instanceId)
    {
        if (OrderUpdated != null)
            OrderUpdated(null, new OrderEventArgs(instanceId, orderId));
    }

    public void RaiseOrderProcessedEvent(string orderId, Guid instanceId)
    {
        if (OrderProcessed != null)
            OrderProcessed(null, new OrderEventArgs(instanceId, orderId));
    }
        
    public void RaiseOrderCanceledEvent(string orderId, Guid instanceId)
    {
        if (OrderCanceled != null)
            OrderCanceled(null, new OrderEventArgs(instanceId, orderId));
    }

    public event EventHandler OrderCreated;
    public event EventHandler OrderShipped;
    public event EventHandler OrderUpdated;
    public event EventHandler OrderProcessed;
    public event EventHandler OrderCanceled;
}

現在,需要用訂單服務編譯該程序集,並重新切換到狀態機工作流項目。在該工作流項目中,首先需要添加對新創建程序集的引用。接下來,需要添加四個 State 活動並且按如下方式命名它們: WaitingForOrderStateOrderOpenStateOrderProcessedStateOrderCompletedState

表 3 表示工作流的狀態關係圖。每個狀態都有一些能夠導致向另一個狀態進行轉換的事件。

表 3. 訂單的示例狀態機
狀態 受支持的事件 轉換到

WaitingForOrderState

OrderCreated

OrderOpenState

OrderOpenState

OrderUpdated

OrderOpenState

 

OrderProcessed

OrderProcessedState

OrderProcessedState

OrderUpdated

OrderOpenState

 

OrderCanceled

Terminate 活動

 

OrderShipped

OrderCompletedState

OrderCompletedState

   

要實現該關係圖,需要向每個 State 活動中添加與該表中受支持事件相同數目的 EventDriven 塊。例如,名爲 WaitingForOrderStateState 活動將包含單個 EventDriven 活動(該活動的名稱可以是任意的,例如 OrderCreatedEvent)。如圖 18 所示,EventDriven 活動嵌入一個 EventSink 活動和一個 SetState 活動,以便捕獲外部事件並轉換到新的狀態。


圖 18. OrderCreatedEvent EventDriven 活動的內部視圖

EventSink 活動的 Properties 窗格上,可選擇自己喜歡的數據交換服務(在此情況下爲 IOrderService 接口)以及要預訂的事件名稱。如果單擊 EventSink 活動 Properties 窗格上的 InterfaceType 項,則 Visual Studio 2005 將提供該項目可用的數據交換服務列表。選擇服務後,EventName 屬性反映由該服務所公開事件的列表。可選擇自己感興趣的事件並繼續。對於 OrderCreatedEvent 活動,可選擇 OrderCreated 事件。

SetState 活動將狀態機轉換到由其 TargetState 屬性指示的新狀態。圖 18 中的 SetState 活動設置爲 OrderOpenState

可對錶 3 中的所有狀態和事件接收器重複執行上述操作。最後,您的工作流應該如圖 19 所示。


圖 19. 最終完成的訂單狀態機

最後一個步驟涉及到生成 Windows 窗體應用程序以測試該工作流。用戶界面包含一個用於跟蹤所有未完成訂單的列表視圖,以及用於創建新訂單的文本框和按鈕。其他按鈕將用來更新、處理和終止該訂單。

狀態機工作流在 Form_Load 事件中初始化。狀態機工作流的初始化要比順序工作流複雜一些,尤其是當您希望能夠跟蹤狀態更改的時候。下面的代碼示例顯示如何初始化工作流運行庫。

private void StartWorkflowRuntime()
{
   // Create a new Workflow Runtime for this application
   _runtime = new WorkflowRuntime();

   // Register event handlers for the WorkflowRuntime object
   _runtime.WorkflowTerminated += new 
          EventHandler(WorkflowRuntime_WorkflowTerminated);
   _runtime.WorkflowCompleted += new 
          EventHandler(WorkflowRuntime_WorkflowCompleted);

    // Create a new instance of the StateMachineTrackingService class  
    _stateMachineTrackingService = new StateMachineTrackingService(_runtime);

    // Start the workflow runtime 
    _runtime.StartRuntime();

    // Add a new instance of the OrderService to the runtime
    _orderService = new OrderService();
    _runtime.AddService(_orderService);
}

StateMachineTrackingService 在運行庫之上工作,並且用跟蹤工作流中狀態更改的功能來擴展它。數據交換服務的實例還被添加到運行庫中。

當用戶單擊以創建新訂單時,將執行下面的代碼。

private Guid StartOrderWorkflow(string orderID)
{
   // Create a new GUID for the WorkflowInstanceId
   Guid instanceID = Guid.NewGuid();

   // Load the OrderWorkflows assembly
   Assembly asm = Assembly.Load("OrderWorkflows");

   // Get a type reference to the OrderWorkflows.Workflow1 class
   Type workflowType = asm.GetType("OrderWorkflows.Workflow1");

   // Start a new instance of the state machine with state tracking support
   StateMachineInstance stateMachine = 
          _stateMachineTrackingService.RegisterInstance(workflowType, instanceID);
   stateMachine.StateChanged += new 
          EventHandler(StateMachine_StateChanged);
   stateMachine.StartWorkflow();
   _stateMachineInstances.Add(instanceID.ToString(), stateMachine);

   // Return the workflow GUID 
   return instanceID;
}

首先,代碼實例化工作流實例並註冊狀態更改的事件處理程序。請注意,使用 .NET Reflection 來獲得類型信息並不是絕對需要的,但這可以大大提高靈活性。普通的舊運算符 typeof 也可以很好地將工作流實例的類型傳遞給工作流運行庫。

圖 20 顯示正在工作的示例應用程序。按鈕是基於所選工作流實例的狀態而啓用的。


圖 20. Windows 窗體應用程序中承載的狀態機工作流

當用戶單擊給定按鈕時,通信接口上的相應事件將被引發並被工作流的事件接收器捕獲。例如,對處於掛起狀態的工作流實例而言,其 Order Processed 按鈕的單擊事件將按如下方式處理。

private void btnOrderEvent_Click(object sender, EventArgs e)
{
   // Get the name of the clicked button 
   string buttonName = ((Button)sender).Name;

   // Get the GUID of the selected order
   Guid instanceID = GetSelectedWorkflowInstanceID();

   // Get the ID of the selected order
   string orderID = GetSelectedOrderID();

   // Disable buttons before proceeding
   DisableButtons();

   // Determines what to do based on the name of the clicked button
   switch(buttonName)
   {
      // Raise an OrderShipped event using the Order Local Service
      case "btnOrderShipped":
         _orderService.RaiseOrderShippedEvent(orderID, instanceID);
     break;

      // Raise an OrderUpdated event using the Order Local Service
      case "btnOrderUpdated":
         _orderService.RaiseOrderUpdatedEvent(orderID, instanceID);
         break;

      // Raise an OrderCanceled event using the Order Local Service
      case "btnOrderCanceled":
         _orderService.RaiseOrderCanceledEvent(orderID, instanceID);
         break;

      // Raise an OrderProcessed event using the Order Local Service
      case "btnOrderProcessed":
         _orderService.RaiseOrderProcessedEvent(orderID, instanceID);
         break;
     }
}

該工作流中引發的事件由圖 21 中所示的 EventDriven 活動捕獲。


圖 21. 狀態機用於處理 Order Processed 事件的 EventDriven 塊

EventSink 活動捕獲該事件,並且通過轉換到 SetState 活動所設置的狀態來處理它。工作流中的狀態更改由附加的狀態跟蹤服務檢測,並通過 StateChanged 事件報告給宿主,如上述代碼清單所示。

您可以在 http://msdn.microsoft.com/workflow 找到本文中討論的全部示例的完整源代碼,以及更多的工作流內容。

小結

Windows Workflow Foundation 旨在成爲新的和現有的 Microsoft 產品的工作流框架,它向所有需要爲 .NET 平臺創建工作流驅動應用程序的開發人員提供了 WinFX 的強大功能和 Visual Studio 2005 的易用性。

Windows Workflow Foundation 爲工作臺帶來的主要好處是統一的工作流模型和一組能夠取代很多專用庫的工具。在這方面,Windows Workflow Foundation 對於目前工作流產品的供應商也具有重要意義,因爲採用 Windows Workflow Foundation 則意味着他們不必再維護其低級別的代碼,並且可以集中力量去完成更高級別的任務。

Windows Workflow Foundation 是一種面向多種特定應用程序和需要的工作流技術。Windows Workflow Foundation 因而成爲一種廣泛的框架,它是爲提高每個級別的可擴展性而設計的。這種形式的可擴展性的最佳示例是自定義活動和可插接的運行庫服務。自定義活動使您可擴展可用來創作工作流的構造塊集。可更改持久性存儲和跟蹤等運行庫服務以適應應用程序的環境,並可使應用程序將數據持久存儲到 Microsoft SQL Server 或其他供應商的數據庫中。

Windows Workflow Foundation 的 Visual Studio 2005 擴展將允許對工作流進行可視化建模和直接代碼訪問。

還可以在其他設計環境中承載可視化設計器,從而使設計器提供商可以將可視化建模功能嵌入到其自己的環境中,並且提供應用程序用戶所熟悉的用戶體驗。

本文僅僅討論了所有 Windows Workflow Foundation 技術和功能中的一些粗淺知識,提供了有關其工作方式、內部原理的概述和一些有代表性的示例代碼。

關於作者

Dino Esposito 是一位居住於意大利羅馬的培訓師和顧問。作爲 Wintellect 團隊的成員,Dino 專門研究 ASP.NET 和 ADO.NET,並且花費大部分時間在歐洲和美國進行教學和諮詢活動。值得一提的是,Dino 爲 Wintellect 管理 ADO.NET 課件,同時爲 MSDN Magazine 撰寫 Cutting Edge 專欄。

轉到原英文頁面

原文出處:Microsoft Windows Workflow Foundation
發佈了33 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章