程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WF從入門到精通(第八章):調用外部方法及工作流(二)

WF從入門到精通(第八章):調用外部方法及工作流(二)

編輯:關於.NET

創建外部數據服務

我們現在來到了更加復雜的一節,我們的任務是為外部數據服務創建橋接代碼。宿主必須有這些代碼,它才能訪問到工作流實例試圖傳遞過來的數據。我們將使用工具來為工作流創建活動(這在下一節介紹),但對於宿主這邊的通信連接來說,卻沒有現成的工具。

在這裡,我們將創建一個稍微簡化的連接橋版本(這是對於完整的連接橋架構來說)。該版本僅僅支持工作流到宿主的通信。(當我們學到17章時,我們將會創建一個可重用的通用雙向連接橋。)我們在此將創建的連接橋被分成了兩個部分:一是connector,它實現了我們前面已經開發好了的接口;二是service,除了別的事情外,它有一個職責是激發“data available”事件以及提供一個“read”方法,使用該方法來把數據從工作流中取出。

提示:該代碼應由你而不是WF來提供。我在寫本地數據交換服務時提供了該代碼,但你要寫的代碼可以有所不同。唯一要求是本地數據交換服務實現了通信接口並提供一種機制,用於檢索需要交換的數據。

為什麼如此復雜?和傳統的.NET對象不同,工作流實例在工作流運行時的范圍內執行。因此進出工作流實例的事件都由工作流運行時進行代理。工作流運行時必須做這些工作,因為你的宿主應用程序不能把數據發送給已經被持久化或不處在執行狀態下的工作流實例。

回到我們的連接橋上,該連接類包含一個字段,工作流將使用要被傳回的數據來填充該字段。對於我們正在創建的本示例應用程序來說,我們不允許並發執行工作流實例,但這僅僅是出於方便。通常情況下,並沒有阻止我們執行並發執行的工作流實例,這些我們將在第17章看到。

當然,每一個工作流實例可能會返回不同的數據,至少它傳遞的駕駛員會和另一個工作流實例不同。連接類的職責是實現我們開發的在宿主這邊接口,以及不間斷地保持這些數據。當宿主請求該數據時,連接類根據工作流實例ID來確定應正確返回的DataSet是否已經到達。

該服務類為你處理一些任務。首先,它使用工作流運行時注冊該ExternalDataService,以便我們可在宿主和工作流實例間進行通信。它維護一個連接類的單例副本,並把它自己作為服務提供者綁定到該連接類。該服務類也充當了工廠(設計模式)的角色,確保我們有一個且僅有一個連接類(實例)。(假如我們實現了雙向的接口,該服務類也會提供一個“write”方法。)我們現在就來創建這些類。

創建橋接器(bridge connector)類

1.在Visual Studio中打開MVDataService項目,定位到MVDataCnnector.cs文件,最後打開該文件。

2.在所定義的名稱空間中添加下面的代碼:

public sealed class MVDataConnector : IMVDataService
{
private DataSet _dataValue = null;
private static WorkflowMVDataService _service = null;
private static object _syncLock = new object();
}

字段_dataValue用來容納工作流實例產生的數據。字段_service用來容納數據服務對象的單一實例。_syncLock對象僅僅用來進行線程的同步。

3.下面,我們添加一個static屬性來訪問該服務對象的單一實例。代碼如下:

WorkflowMVDataService
public static WorkflowMVDataService MVDataService
{
  get { return _service; }
  set
  {
    if (value != null)
    {
      lock (_syncLock)
      {
        // Re-verify the service isn't null
        // now that we're locked
        if (value != null)
        {
          _service = value;
        } // if
        else
        {
          throw new InvalidOperationException("You must provide a service instance.");
        } // else
      } // lock
    } // if
    else
    {
      throw new InvalidOperationException("You must provide a service instance.");
    } // else
  }
}

4.我們需要添加一個屬性來訪問該DataSet,代碼如下:

public DataSet MVData
{
 get { return _dataValue; }
}

5.因為連接器類從IMVDataService派生,因此我們必須實現MVDataUpdate方法:

public void MVDataUpdate(DataSet mvData)
{
 // Assign the field for later recall
 _dataValue = mvData;
 // Raise the event to trigger host read
 _service.RaiseMVDataUpdateEvent();
}

工作流使用這個方法來把DataSet保存到_dataValue字段中。它激發事件以通知宿主數據已經准備好了。該橋接器類的完整代碼參見清單8-3。注意我們這時並沒准備去編譯整個應用程序,我們還有更多的代碼需要添加。

清單8-3 完整的MVDataconnector.cs源文件 MVDataConnector

using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;
namespace MVDataService
{
  public sealed class MVDataConnector : IMVDataService
  {
    private DataSet _dataValue = null;
    private static WorkflowMVDataService _service = null;
    private static object _syncLock = new object();
    public static WorkflowMVDataService MVDataService
    {
      get { return _service; }
      set
      {
        if (value != null)
        {
          lock (_syncLock)
          {
            // Re-verify the service isn't null
            // now that we're locked
            if (value != null)
            {
              _service = value;
            } // if
            else
            {
              throw new InvalidOperationException("You must provide a service instance.");
            } // else
          } // lock
        } // if
        else
        {
          throw new InvalidOperationException("You must provide a service instance.");
        } // else
      }
    }
    public DataSet MVData
    {
      get { return _dataValue; }
    }
    // Workflow to host communication method
    public void MVDataUpdate(DataSet mvData)
    {
      // Assign the field for later recall
      _dataValue = mvData;
      // Raise the event to trigger host read
      _service.RaiseMVDataUpdateEvent();
    }
  }
}

創建橋接服務(bridge service)類

1.再次在Visual Studio中打開MVDataService項目,定位到WorkflowMVDataService.cs文件,打開該文件准備進行編輯。

2.我們創建好了MVDataConnector類,我們還要把下面的代碼復制到WorkflowMVDataService.cs文件中:

public class WorkflowMVDataService
{
 static WorkflowRuntime _workflowRuntime = null;
 static ExternalDataExchangeService _dataExchangeService = null;
 static MVDataConnector _dataConnector = null;
 static object _syncLock = new object();
 public event EventHandler<MVDataAvailableArgs> MVDataUpdate;
 private Guid _instanceID = Guid.Empty;
}

3.我們需要具有從類的外部訪問_instanceID的能力,因此添加下面的屬性:

public Guid InstanceID
{
 get { return _instanceID; }
 set { _instanceID = value; }
}

4.我們現在要添加一個靜態的工廠方法,我們將用它去創建本類的實例。我們這樣做是為了確保在我們創建本橋接服務的時候,所有重要的事情都已完成。例如,我們需要確保ExternalDataService服務已被插入到了工作流運行時中。我們也將添加剛才已經創建好了的橋接器類,並把它作為一個可插拔服務以便工作流能訪問到該數據連接器類。因此,我們在上面一步所添加的屬性下面還要添加下面的方法:

CreateDataService
public static WorkflowMVDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime)
{
  lock (_syncLock)
  {
    // If we're just starting, save a copy of the workflow runtime reference
    if (_workflowRuntime == null)
    {
      // Save instance of the workflow runtime.
      _workflowRuntime = workflowRuntime;
    } // if
    // If we're just starting, plug in ExternalDataExchange service
    if (_dataExchangeService == null)
    {
      // Data exchange service not registered, so create an
      // instance and register.
      _dataExchangeService = new ExternalDataExchangeService();
      _workflowRuntime.AddService(_dataExchangeService);
    } // if
    // Check to see if we have already added this data exchange service
    MVDataConnector dataConnector = (MVDataConnector)workflowRuntime.
      GetService(typeof(MVDataConnector));
    if (dataConnector == null)
    {
      // First time through, so create the connector and
      // register as a service with the workflow runtime.
      _dataConnector = new MVDataConnector();
      _dataExchangeService.AddService(_dataConnector);
    } // if
    else
    {
      // Use the retrieved data connector.
      _dataConnector = dataConnector;
    } // else
    // Pull the service instance we registered with the connection object
    WorkflowMVDataService workflowDataService = MVDataConnector.MVDataService;
    if (workflowDataService == null)
    {
      // First time through, so create the data service and
      // hand it to the connector.
      workflowDataService = new WorkflowMVDataService(instanceID);
      MVDataConnector.MVDataService = workflowDataService;
    } // if
    else
    {
      // The data service is static and already registered with
      // the workflow runtime. The instance ID present when it
      // was registered is invalid for this iteration and must be
      // updated.
      workflowDataService.InstanceID = instanceID;
    } // else
    return workflowDataService;
  } // lock
}

5.在前面一節(“創建橋接器類”)我們創建的連接器對象中保存有我們在第4步中創建的該橋接器對象。我們現在將添加一個靜態方法,使用該方法可返回該橋接服務實例。盡管這些現在看來沒有太大必要,但稍後會講講我們這樣做的理由。代碼如下:

GetRegisteredWorkflowDataService
public static WorkflowMVDataService GetRegisteredWorkflowDataService(Guid instanceID)
{
  lock (_syncLock)
  {
    WorkflowMVDataService workflowDataService = MVDataConnector.MVDataService;
    if (workflowDataService == null)
    {
      throw new Exception("Error configuring data serviceservice cannot be null.");
    } // if
    return workflowDataService;
  } // lock
}

6.下面我們將添加我們(私有屬性)的構造器和析構器。有了橋接器類後,我們需要確保在橋接器對象和橋接服務對象間不會造成循環的引用。你需要添加下面的代碼:

WorkflowMVDataService和~WorkflowMVDataService
private WorkflowMVDataService(Guid instanceID)
{
  _instanceID = instanceID;
  MVDataConnector.MVDataService = this;
}
~WorkflowMVDataService()
{
  // Clean up
  _workflowRuntime = null;
  _dataExchangeService = null;
  _dataConnector = null;
}

7.盡管我們為橋接服務類添加了一些重要的東西,但還沒有把ExternalDataService引入工作流運行時中,我們仍然要添加一些代碼,以使工作流運行時具有讀取數據並返回給宿主應用程序的能力。橋接器對象實際上是維持該連接狀態,但宿主使用這個服務來獲得要訪問的數據。下面是我們要添加的read方法:

public DataSet Read()
{
  return _dataConnector.MVData;
}

8.要為我們的橋接服務添加的最後的功能塊是一個方法,它激發“機動車數據更新(motor vehicle data update)”事件。工作流使用這個方法來為宿主發送一個通知,告知要挑選的數據已經獲取完了。代碼如下:

public void RaiseMVDataUpdateEvent()
{
  if (_workflowRuntime == null)
    _workflowRuntime = new WorkflowRuntime();
  _workflowRuntime.GetWorkflow(_instanceID); // loads persisted workflow instances
  if (MVDataUpdate != null)
  {
    MVDataUpdate(this, new MVDataAvailableArgs(_instanceID));
  } // if
}

完整的橋接服務代碼參見清單8-4:

清單8-4 完整的WorkflowMVDataService.cs源文件 WorkflowMVDataService

using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;
namespace MVDataService
{
  public class WorkflowMVDataService
  {
    static WorkflowRuntime _workflowRuntime = null;
    static ExternalDataExchangeService _dataExchangeService = null;
    static MVDataConnector _dataConnector = null;
    static object _syncLock = new object();
    public event EventHandler<MVDataAvailableArgs> MVDataUpdate;
    private Guid _instanceID = Guid.Empty;
    public Guid InstanceID
    {
      get { return _instanceID; }
      set { _instanceID = value; }
    }
    public static WorkflowMVDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime)
    {
      lock (_syncLock)
      {
        // If we're just starting, save a copy of the workflow runtime reference
        if (_workflowRuntime == null)
        {
          // Save instance of the workflow runtime.
          _workflowRuntime = workflowRuntime;
        } // if
        // If we're just starting, plug in ExternalDataExchange service
        if (_dataExchangeService == null)
        {
          // Data exchange service not registered, so create an
          // instance and register.
          _dataExchangeService = new ExternalDataExchangeService();
          _workflowRuntime.AddService(_dataExchangeService);
        } // if
        // Check to see if we have already added this data exchange service
        MVDataConnector dataConnector = (MVDataConnector)workflowRuntime.
          GetService(typeof(MVDataConnector));
        if (dataConnector == null)
        {
          // First time through, so create the connector and
          // register as a service with the workflow runtime.
          _dataConnector = new MVDataConnector();
          _dataExchangeService.AddService(_dataConnector);
        } // if
        else
        {
          // Use the retrieved data connector.
          _dataConnector = dataConnector;
        } // else
        // Pull the service instance we registered with the connection object
        WorkflowMVDataService workflowDataService = MVDataConnector.MVDataService;
        if (workflowDataService == null)
        {
          // First time through, so create the data service and
          // hand it to the connector.
          workflowDataService = new WorkflowMVDataService(instanceID);
          MVDataConnector.MVDataService = workflowDataService;
        } // if
        else
        {
          // The data service is static and already registered with
          // the workflow runtime. The instance ID present when it
          // was registered is invalid for this iteration and must be
          // updated.
          workflowDataService.InstanceID = instanceID;
        } // else
        return workflowDataService;
      } // lock
    }
    public static WorkflowMVDataService GetRegisteredWorkflowDataService(Guid instanceID)
    {
      lock (_syncLock)
      {
        WorkflowMVDataService workflowDataService = MVDataConnector.MVDataService;
        if (workflowDataService == null)
        {
          throw new Exception("Error configuring data serviceservice cannot be null.");
        } // if
        return workflowDataService;
      } // lock
    }
    private WorkflowMVDataService(Guid instanceID)
    {
      _instanceID = instanceID;
      MVDataConnector.MVDataService = this;
    }
    ~WorkflowMVDataService()
    {
      // Clean up
      _workflowRuntime = null;
      _dataExchangeService = null;
      _dataConnector = null;
    }
    public DataSet Read()
    {
      return _dataConnector.MVData;
    }
    public void RaiseMVDataUpdateEvent()
    {
      if (_workflowRuntime == null)
        _workflowRuntime = new WorkflowRuntime();
      _workflowRuntime.GetWorkflow(_instanceID); // loads persisted workflow instances
      if (MVDataUpdate != null)
      {
        MVDataUpdate(this, new MVDataAvailableArgs(_instanceID));
      } // if
    }
  }
}

CallExternalMethod活動

你目前在本章看到過的所有代碼都已支持一個特殊的WF活動:CallExternalMethod活動。CallExternalMethod活動的作用是可以接受一個接口及該接口所支持的方法,並來調用這個方法。現在的問題是,由誰來實現這個方法?

你可能會考慮由你的宿主應用程序來完成,但這不太正確。假如你向前看看前面的一節“創建橋接器類”,你實際上在那裡會找到這個方法。數據連接器由ExternalDataService捆住實現了該方法。該數據服務依次把該方法的調用轉換成一個宿主應用程序能識別的事件。

直接使用CallExternalMethod活動是允許的,你甚至可以繞開一些服務代碼就可把它插入到你的應用程序中。但是繞開這些服務代碼對你來說還有一組難題。你的宿主應用程序和你的工作流實例是一對一地聯系在一起的。在這裡使用數據服務來完成這件事要更適合一些,當你把該數據服務結合起來後,你就能使許多的應用程序實例從許多的工作流實例中進行數據訪問,而繞過你創建好的那些數據服務後則不能做到這些。

對於直接使用CallExternalMethod活動,它通常更適合於創建自定義活動來為你調用外部方法。你可使用一個工具來自定義你的數據交換接口和創建派生自CallExternalMethod的活動,更恰當地對其命名,對它們的屬性(接口和方法名稱)進行配置。接下來我們就來看看該工具的使用方法。

創建和使用自定義外部數據服務活動

回頭看看,我們剛剛寫下的代碼比目前整本書已寫過的代碼還要多。原因是WF事先不知道我們的工作流將和我們的宿主應用程序之間交換些什麼信息。因此在二者之間毫無疑問必須做一些工作,以便對它們之間的差距進行填充。

但是,WF知悉所有的工作流活動,我們可愉快地使用一個工具來對我們的數據傳送接口進行解釋,使用ExternalDataExchange特性(attribute)來進行標記,自動地生成WF活動。

我們本章正生成的應用程序把數據從工作流中發送到宿主應用程序中,也就是說數據傳送是單向的。我故意這樣做是因為,我們只有積累了足夠的知識,才能更好地學習並充分理解雙向數據傳送。我們將使用的Workflow Communication Activity生成器完全有能力創建那些發送和接受宿主數據的活動。對於本應用程序的特殊性,我們將“扔掉”它的輸出部分,因為我們不需要它。(其實,將生成的活動是畸形的,因為我們的接口沒有指定宿主到工作流的通信,這些我們將保留到第10章講解。)

為此,我們就來執行wca.exe並創建一個可用來發送數據到我們的宿主應用程序的活動。

創建通信活動

1.為使wca.exe能生成符合我們要求的代碼,我們需要確保有一個接口。因此,確保MVDataService項目生成時無錯誤(如生成時有錯誤,請糾正所有的錯誤)並已生成了MVDataService程序集。

2.點擊操作系統的開始按鈕,然後點擊運行菜單項打開運行對話框。

3.在打開的組合框控件中輸入“cmd”,然後點擊確定進入命令提示符窗口。

4.更改起始目錄以便我們能直接訪問到“MVDataService”程序集。通常使用的是“cd”命令。

5.wca.exe文件默認情況下被安裝到Program Files目錄下的Windows SDK子目錄中。(當然,假如你沒有使用默認目錄進行安裝,你在此需要使用你安裝Windws SDK的目錄。)在命令行提示符下輸入下面的命令來執行該工具(包含雙引號):

“C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\Wca.exe” MVDataService.dll

按下回車鍵,該工具的輸出結果和下面的類似:

6.在命令提示符中鍵入dir你可看到wca.exe創建的文件。

7.IMVDataService.Sinks.cs文件不是必須要的,可忽略它甚至是刪除它,因為該文件只是包含了一些指示,沒有代碼。(在我們的通信接口中沒有定義事件。)我們在第十章將再使用這個文件。對於另一個生成的文件:IMVDataService.Invokes.cs文件,是我們要保留的文件。它包含我們能使用的一個新活動的源代碼,該活動可把數據從工作流中發送給宿主應用程序。因此,我們將重命名該文件以便更加好用。在命令提示符下輸入“ren IMVDataService.Invokes.cs MVDataUpdate.cs”,然後按下回車鍵重命名該文件。

8.因為我們剛剛重命名的這個文件是一個工作流活動,因此我們需要把它從當前目錄下移動到MVWorkflow目錄下以便編譯和使用。在命令提示符下輸入“move MVDataUpdate.cs ..\..\..\MVWorkflow”,然後按下回車鍵。

9.回到Visual Studio,我們需要把這個新創建的MVDataUpdate.cs文件添加我們的工作流項目中。

10.編譯並生成MVWorkflow項目,假如出現編譯錯誤的話,修正所有的錯誤。在你成功編譯後,在視圖設計器界面模式下打開Workflow1.cs文件將會在Visual Studio的工具箱中看到這個MVDataUpdate活動。

備注:假如MVDataUpdate因為某些原因沒有添加進Visual Studio工具箱中,請關閉該解決方案,然後再重新打開它。

我們現在就准備好了一個活動,我們可使用它來把數據發送到我們的宿主應用程序中。該活動的基類是CallExternalMethod,它的作用是觸發對工作流執行環境的外部調用。

添加並配置該工作流通信活動

1.在Visual Studio中以視圖設計器的模式打開MVWorkflow項目中的Workflow1.cs文件。該工作流預先加載了兩個活動:一個是Delay活動,用來模擬處理數據的等待時間;一個是Code活動,它創建並填充一個基於駕駛員姓名的DataSet。

2.打開Visual Studio工具箱,定位到MVDataUpdate活動。

3.把該活動拖拽到工作流視圖設計器界面上,放到Code活動的下面使它在Code活動執行後執行。

4.我們的工作流在視圖設計器上的設計工作這就完成了。現在該寫少量代碼來進行銜接。因此以代碼視圖模式打開Workflow1.cs文件。在Workflow1類中找到GenerateMVDData方法。這個方法是codeActivity1所執行的方法,在裡面你會看到對GenerateVehicleTable和GenerateViolationTable兩個方法的調用,它們創建並填充所要返回的DataSet。(其實,你可用一些外部服務來為駕駛員的信息進行一個調用,但我們在此對這些進行了模擬)。在生成了DataSet後,我們需要添加下面的代碼以把DataSet返回給宿主:

// Assign the DataSet we just created as the host data
mvDataUpdate1.mvData = ds;

指定了我們所返回的DataSet後,我們就完成了工作流的開發,並且使用了工具來把該DataSet傳給宿主應用程序。但我們的宿主應用程序需要做些什麼才能接收到該數據了?

在宿主應用程序中檢索工作流數據

現在讓我們返回到我們的主應用程序中。我們現在要做的是修改應用程序,以使用我們在本章的“創建外部數據訪問”這一節中創建的橋接類。

備注:盡管這是一個簡化的例子,但這個應用程序仍然是一個完全意義上的Windows Form應用程序,它演示了怎樣處理工作流及其怎樣進行多線程操作(比如updating控制的時候)。

為了讓我們的接口可使用工作流返回的數據集,我們需要使用橋接代碼中的connector類來對我們的工作流實例進行注冊(為了使我們能正確的接收DataSet)。我們也需要勾住(hook)MVDataUpdate事件,以便我們的應用程序知道接收數據的時間。為方便做這些事。我們將為“Retrieve MV Data”按鈕的event handler添加一點代碼,並為MVDataUpdate添加一個新的event handler。

備注:假如你不熟悉匿名方法(anonymous methods)的話,現在就是簡要學習它的一個好機會!

為我們的宿主應用程序添加工作流外部數據服務

1.在Visual Studio解決方案資源管理器中打開Form1.cs文件,並切換到代碼視圖界面。

2.找到cmdRetrieve_Click方法。在該響應按鈕點擊的事件方法中已經存在了初始化工作流實例的代碼,但我們還需要在創建工作流實例和啟動該實例之間的地方插入一些代碼,也就是在調用“_workflowRuntime.CreateWorkflow”的下面添加如下的代碼(為讓Visual Studio為我們自動生成事件處理程序的代碼,請盡量不要使用復制粘貼的方式,應在=號後使用連續兩個Tab鍵):

// Hook returned data event.
MVDataService.WorkflowMVDataService dataService =
 MVDataService.WorkflowMVDataService.CreateDataService(
        _workflowInstance.InstanceId,
        _workflowRuntime);
dataService.MVDataUpdate +=
 new EventHandler<MVDataService.MVDataAvailableArgs>(
         dataService_MVDataUpdate);

3.在Form1類中,為Visual Studio剛剛創建的dataService_MVDataUpdate事件處理程序添加下面的事件處理代碼,並去掉存在的“not implemented”異常。

dataService_MVDataUpdate
IAsyncResult result = this.BeginInvoke(
  new EventHandler(
      delegate
      {
        // Retrieve connection. Note we could simply cast the sender as
        // our data service, but we'll instead be sure to retrieve
        // the data meant for this particular workflow instance.
        MVDataService.WorkflowMVDataService dataService =
          MVDataService.WorkflowMVDataService.
          GetRegisteredWorkflowDataService(e.InstanceId);
        // Read the motor vehicle data
        DataSet ds = dataService.Read();
        // Bind the vehicles list to the vehicles table
        ListViewItem lvi = null;
        foreach (DataRow row in ds.Tables["Vehicles"].Rows)
        {
          // Create the string array
          string[] items = new string[4];
          items[0] = (string)row["Plate"];
          items[1] = (string)row["Make"];
          items[2] = (string)row["Model"];
          items[3] = (string)row["Color"];
          // Create the list item
          lvi = new ListViewItem(items);
          // Add to the list
          lvVehicles.Items.Add(lvi);
        } // foreach
        // Bind the violations list to the violations table
        foreach (DataRow row in ds.Tables["Violations"].Rows)
        {
          // Create the string array
          string[] items = new string[4];
          items[0] = (string)row["ID"];
          items[1] = (string)row["Plate"];
          items[2] = (string)row["Violation"];
          items[3] = ((DateTime)row["Date"]).ToString("MM/dd/yyyy");
          // Create the list item
          lvi = new ListViewItem(items);
          // Add to the list
          lvViolations.Items.Add(lvi);
        } // foreach
      } // delegate
    ), null, null
); // BeginInvoke
this.EndInvoke(result);
// Reset for next request
WorkflowCompleted();

就這樣!我們的應用程序就完成了,編譯並執行該應用程序。當你點擊“Retrieve MV Data”按鈕時,選中的駕駛員姓名就會被傳給工作流實例。當DataSet創建好後,該工作流實例就會激發MVDataUpdate事件。宿主應用程序代碼會截獲該事件進行數據的接收,然後把它綁定到ListView控件。

在最後一步我們需注意一個關鍵的地方,就是在我們調用WorkflowMVDataService的靜態方法GetRegisteredWorkflowDataService來檢索數據服務包含的DataSet後,我們使用數據服務的Read方法來把該DataSet拉進我們的宿主應用程序執行環境中以便我們進行數據綁定。

用InvokeWorkflow調用外部工作流

這兒要問你一個問題:假如你有一個正在執行的工作流,該工作流能執行第二個工作流嗎?

答案是Yes!有這樣一個活動,InvokeWorkflow活動,可用它來啟動第二個工作流。我們通過一個例子來簡要地看看這個活動。我們將創建一個新的控制台應用程序示例來啟動一個工作流,該工作流只是向控制台輸出一條信息。在輸出該信息後,該工作流實例啟動第二個工作流實例,被啟動的工作流實例也輸出一條信息,這樣就生動地為我們展示了兩個工作流都執行了。

調用第二個工作流

1.和前面一樣,本章的源代碼中包含了完整版和練習版兩種版本的WorkflowInvoker應用程序。我們現在打開練習版的WorkflowInvoker解決方案。

2.在Visual Studio加載WorkflowInvoker解決方案後,在WorkflowInvoker解決方案中添加一個新的基於順序工作流庫的項目,工作流的名稱命名為:Workflow1,保存該項目。

3.下一步,從工具箱中拖拽一個Code活動到工作流視圖設計器界面上。在該活動的ExecuteCode屬性中鍵入“SayHello”,然後按下回車鍵。

4.Visual Studio會自動切換到代碼編輯界面。定位到Visual Studio剛剛添加的SayHello方法,在該方法內輸入下面的代碼:

// Output text to the console.
Console.WriteLine("Hello from Workflow1!");

5.我們現在需要添加第二個要執行的工作流,因此重復步驟2,但工作流的名稱命名為:Workflow2。重復步驟3和4,但把信息“Hello from Workflow1!”替換為“Hello from Workflow2!”,當然工作流源文件的名稱也要重命名為workflow2.cs,以避免沖突。

6.我們想在第一個工作流中調用第二個工作流,但這樣做,我們還需要添加對第二個工作流的引用。在這之前,我們需要編譯並生成Workflow1。

7.回到Visual Studio解決方案資源管理器,為Workflow1項目添加對項目Workflow2的項目級引用。

8.回到Workflow1的工作流視圖設計器界面上。這次,拖拽一個InvokeWorkflow活動到你的順序工作流視圖設計器界面上。

9.看看這個新活動的屬性,我們會看到它有一個“TargetWorkflow”屬性需要我們去設置。點擊以激活它的TargetWorkflow屬性,然後點擊它的浏覽(...)按鈕(該按鈕帶三個點)。

10.這將打開一個“浏覽和選擇一個.NET類型”對話框。在左邊面板中選擇Workflow2,這將在右邊的面板中顯示Workflow2類型。在右邊的面板中選擇Workflow2類型(Workflow2.Workflow2是它的完全限定名稱),然後點擊確定。

11.然後Visual Studio會檢查該Workflow2工作流,並在工作流視圖設計器的InvokeWorkflow活動內部展示它的圖形界面。

12.工作流現在就完整地實現了,我們現在需要為WorkflowInvoker項目添加對Workflow1和Workflow2的項目引用。

13.接下來在Program.cs文件中定位到下面的代碼上:

Console.WriteLine("Waiting for workflow completion.");

14.在上面的代碼下添加如下代碼:

// Create the workflow instance.
WorkflowInstance instance =
 workflowRuntime.CreateWorkflow(typeof(Workflow1.Workflow1));
// Start the workflow instance.
instance.Start();

15.我們現在將為宿主應用程序添加少量的代碼,以便每個工作流完成後通知我們。在WorkflowCompleted的事件處理程序中插入下面的代碼:

if (e.WorkflowDefinition is Workflow1.Workflow1)
 Console.WriteLine("Workflow 1 completed.");
else
 Console.WriteLine("Workflow 2 completed.");
waitHandle.Set();

第一個完成的工作流設置AutoResetEvent,以便強制應用程序等待工作流完成。我們可添加代碼以使應用程序等待所有的工作流,但出於展示的目的這已足夠。假如你編譯並執行該WorkflowInvoker應用程序,你將在控制台中看到下面圖8-4中所展示的輸出結果。假如輸出信息的順序有些許的不同,不用吃驚,這是多線程程序的特征。

圖8-4 WorkflowInvoker應用程序的控制台輸出

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved