1. 程式人生 > >WCF4.0進階系列 使用工作流實現服務

WCF4.0進階系列 使用工作流實現服務

轉載原地址:https://www.cnblogs.com/zzw1986/p/4709842.html

【前言】

企業使用WCF服務的一個主要原因是通過包裝現有的元件和程式構建面向服務的應用,這些應用通過不僅簡單而且適應力很強地方式重用。這種策略為企業帶來了非常大的靈活性,因為它可以簡單地響應快速變化的業務需求、並迅速地建立或更改系統以適應這些需求。
 
許多企業採用的業務過程都由一系列明確的、並按照特定順序執行的步驟組成。其中一些步驟可能涉及呼叫服務的操作,這需要確保WCF服務的操作順序應當與基本業務過程相匹配。你已瞭解到在服務的方法上通過操作行為特性,可以指定某個操作發起或終止一個會話;除此之外,服務幾乎再不能控制客戶端呼叫服務操作的順序。這增加了強制客戶端程式按照順序呼叫服務操作的困難,此外這種方式還可能導致難以發現(並糾正)的錯誤。使用工作流來定義服務可以幫助解決這個問題,並且強制客戶端按照一定的順序呼叫服務的操作。
 
另外一個潛在的問題是誰負責真正地定義和實現業務過程的邏輯。 沒有誰比業務分析師能更好地掌握企業所採用業務過程。你不應該期望一個業務分析師同時還精通WCF、或者掌握如何實現WCF服務的操作;很明顯這些任務應當是開發人員負責的。另外一個方面,開發人員可能非常擅長構建重用的元件和服務,但是不能充分理解企業所採用的業務過程。 WF(工作流)也可以幫助解決這個問題。業務分析師可以和開發人員一起工作,使用WF定義各種業務過程的圖形模型,開發人員實現執行該模型描述的各種任務所要求的程式碼。

還有另外一個需要考慮的問題是服務的擴充套件性。 WF提供完美的框架實現長時間執行的業務過程。使用WF工作流構建的WCF服務可以將會話狀態資訊持久化。此外,在企業中,你可以使用Windows Server AppFabric寄宿和管理基於WF工作流的WCF服務。

在本章,你將看到使用WF工作流為業務過程建立模型,然後如何根據此模型構建WCF服務;此外,你還可以看到如何構建客戶端程式與工作流服務互操作。

【正文】

構建一個簡單的工作流服務和客戶端程式

首先,我們將使用WF構建一個簡單的服務,該服務暴露給外部呼叫的操作是無狀態的。然後你將學習如何調從客戶端呼叫該操作。

實現工作流服務

在本章的第一組練習中,讓我們回顧ProductService服務,客戶端程式訪問該服務獲取AdventureWorks銷售的產品資訊。作為工作流服務的一部分,你將實現GetProduction操作,該操作接受產品編號引數並返回產品的詳細資訊。

建立ProductsWorkflowService服務

1. 使用Visual Studio建立一個新的WCF工作流服務程式專案: 在建立新專案對話方塊中,選擇Visual C#,然後選擇Workflow,再選擇WCF Workflow Service Application模板。在專案名稱處輸入ProductsWorkflowService; 然後選擇一個你本地計算機的資料夾儲存該專案;最後輸入解決方案的名稱ProductsWorkflow。

Visual Studio將在ProductsWorkflowService專案中建立一個的名為Service1.xamlx的工作流服務。下圖顯示了該服務:

這是一個簡單的順序化服務,它由WorkflowService活動構成,該活動包括一系列順序活動定義的步驟。 ReceiveRequest活動等待由客戶端指定的名為GetData操作的請求訊息;SendResponse活動傳送響應訊息至客戶端程式。目前,GetOperation操作不會執行其他流程;但是你將在ReceiveRequest活動和SendResponse活動之間新增必要的邏輯。

ReceiveRequest活動和SendResponse活動定義服務合約。在後面的步驟中,你將看到如果不想使用預設值該如何改變操作和服務合約的名字。

2. 在解決方案瀏覽器中,重新命名Service1.xamlx為ProductsService.xamlx

3.在工作流的設計檢視視窗中,點選順序化服務活動邊界外的背景。然後在屬性視窗中,設定ConfigurationName和Name屬性為ProductsService

工作流的ConfigurationName屬性在服務配置檔案web.config檔案中用於指定該服務的名稱。Name屬性在服務元資料的WSDL描述中指定該服務的名字。

4. 在設計檢視中點選ReceiveRequest活動。 在OperationName文字框中,輸入GetProduct。這個名字就是在服務合約中該操作的名字。

5. 在屬性視窗, 改變ServiceContractName屬性值為{http://adventure-works.com}/IProductsService. 花括號內的內容為該服務合約的名稱空間。

6. 確認ReceiveRequest活動仍然處於選中狀態,然後點選設計檢視視窗左下角的"變數"標籤。修改變數data的名字為localProductNumber,並修改該變數的型別為String。GetProduct操作接受的訊息包含由客戶端程式提供的產品編號;並且該操作返回一個包含產品詳細資訊的訊息。你將使用該變數儲存客戶端請求訊息中的產品編號。

請不要修改或者刪除handle變數。工作流使用該變數關聯SendResponse活動和ReceiveRequest活動;當你完成上述操作後,再次點選"變數"標籤,以隱藏變數定義。

注意:重新命名data變數將導致該工作流的幾個驗證錯誤。這是因為ReceiveRequest活動和SendResponse活動仍舊引用data變數。你將在下一步中修改ReceiveRequest活動,並在下一個練習中更新SendResponse活動,使它們引用正確的變數並修正工作流的驗證錯誤。

7. 在ReceiveRequest活動中,在"Content"屬性中,點選"檢視訊息"。將顯示內容定義對話視窗。你使用該視窗指定傳遞到該操作的引數。也可以通過訊息物件或者引數列表指定該資訊。訊息物件是實現IMessage介面的型別。在第十一章你將瞭解到更多關於訊息物件和IMessage介面的詳細內容。

8. 在內容定義對話方塊中,選擇引數選項,然後點選點選建立新引數。並使用下面的資訊設定該引數:

屬性
名稱 ProductNumber
型別 String
分配至 localProductNumber


該操作的SOAP訊息包含在此處定義的任何引數。 "分配至"欄位的定義使工作流執行時通過特定的變數獲取該引數的值,從而你可以在該工作流的其他活動中訪問該引數。

注意:如果你不熟悉Windows workflow foundation,你可能會非常驚訝地發現"分配至"的值通過Visual Basic表示式來設定,儘管現在是你使用的是Visual C#。這不是一個錯誤。 當你設計一個工作流並且必須指定一個表示式作為活動定義的一部分, 你將始終使用Visual Basic語法。但是,當你編寫程式碼支援活動和工作流時,你必須使用你建立工作流專案時指定的語言。

9. 在設計檢視視窗中,點選ReceiveRequest活動,然後選擇屬性視窗,確認CanCreateInstance複選項被選中。該屬性用以指定客戶端程式呼叫該操作去創一個新的服務例項並開始新的會話。服務中只要有一個操作必須允許該屬性。

現在,你已經為GetPorduct操作定義了請求訊息。回憶之前的章節,該操作的目的是從AdventureWorks資料庫中獲取產品的詳細資訊,建立一個ProductData物件併為該物件賦值,最後返回ProductData物件至客戶端。在本章中,我們同樣需要實現上述功能,但是具體程式碼發生了變化。在接下里的練習中,你將看到這些具體的程式碼。

通過Visual Studio提供的標準工作流活動,可以實現GetProduct操作的基本邏輯。但是,WF並沒有提供任何一個活動幫助你與資料庫互動。因此,在下面的練習中,你將建立自定義的活動以查詢AdventureWorks資料庫,然後你將這些活動放加入定義GetProduct操作的工作流。自定義的活動包括

  • ProductExists    該活動將測試特定產品編號的產品是否在AdventureWorks資料庫中存在,並返回一個Boolean值。 你將使用實體框架連線到資料庫,與之前章節一樣仍舊需要使用ProductsEntityModel元件。 你將傳遞物件AdventureWorksEntities用以連線資料庫,並傳遞產品編號作為該活動的輸入引數。
  • FindProduct    該活動將從AdventureWorks資料庫中獲取指定產品的詳細資訊,然後填充ProductData物件。在這之前,對於該活動的輸入引數,你將傳遞AdventureWorksEntities物件連線資料庫,同使還傳入產品編號引數。該活動將返回填充資料後的ProductData物件。

 

建立ProductData型別,並實現ProductExists和FindProduct活動

1. 在Visual Studio中,新增如下檔案到ProductsWorkflowService專案。

屬性
模板 Code Activity
名稱 ProductsService.Activities.cs


2. 新增引用ProductsEntityModel元件到該專案中

3. 新增引用System.Data.Entity元件和System.Runtime.Serialization元件

4. 開啟檔案ProductsService.Activities.cs,然後在該檔案的頭部新增下列using語句
using System.Runtime.Serialization;
using System.ServiceModel;
using ProductsEntityModel;

5. 新增ProductData類到該檔案

[DataContract]

public class ProductData

{

[DataMember]

public string Name;

 

[DataMember]

public string ProductNumber;

 

[DataMember]

public string Color;

 

[DataMember]

public decimal ListPrice;

}

6. 建立ProductsExists活動。所有程式碼活動都繼承CodeActivity類。該類提供一個方法Execute,當代碼活動被工作流呼叫時執行該方法。你使用活動執行時期望執行的程式碼來重寫該方法。

public sealed class ProductExists : CodeActivity<Boolean>

{

// Define an activity input argument of type string

public InArgument<AdventureWorksEntities> Database { get; set; }

public InArgument<string> ProductNumber { get; set; }

 

// If your activity returns a value, derive from CodeActivity<TResult>

// and return the value from the Execute method.

protected override bool Execute(CodeActivityContext context)

{

string productNumber = ProductNumber.Get(context);

AdventureWorksEntities database = Database.Get(context);

 

int numProducts = (from p in database.Products

where string.Equals(p.ProductNumber, productNumber)

select p).Count();

 

return numProducts > 0;

}

}

 

7. 建立FindProduct活動

public sealed class FindProduct : CodeActivity<ProductData>

{

public InArgument<AdventureWorksEntities> Database { get; set; }

public InArgument<string> ProductNumber { get; set; }

 

protected override ProductData Execute(CodeActivityContext context)

{

string productNumber = ProductNumber.Get(context);

AdventureWorksEntities database = Database.Get(context);

 

Product matchedProduct = database.Products.First(

p => string.Compare(p.ProductNumber, productNumber) == 0);

 

ProductData productData = new ProductData()

{

Name = matchedProduct.Name,

ProductNumber = matchedProduct.ProductNumber,

Color = matchedProduct.Color,

ListPrice = matchedProduct.ListPrice

};

 

return productData;

}

}

 

8. 生成方案;此時你將得到錯誤訊息"Compiler error(s) encountered processing expression data.ToString(). ToString is not a member of 'Data'". 這由SendResponse活動未通過驗證的錯誤引起。此刻,先忽略該錯誤。我們將在後面的練習中糾正該錯誤。檢視是否還有其他錯誤訊息,我們必須確保在進行下一步之前沒有其他的錯誤。

 

現在,你可以返回到定義GetProduct操作的工作流上, 實現其業務邏輯。 假設你的程式碼編譯成功,你將在工具欄內發現已經自動新增FindProduct活動和ProductExists活動。

 

實現GetProduct操作的業務邏輯

1. 返回到ProductsService.xamlx的設計檢視視窗,並在工具欄內確認FindProduct活動和ProductsExists活動已經新增。

2. 在設計檢視視窗中,點選"順序服務活動"以隱藏工具欄,然後點選設計檢視視窗左下角的"變數"標籤。新增下面的引數:

引數名 引數型別 範圍 預設值
database ProductsEntityModel.AdventureWorksEntities 順序服務 New AdventureWorksEntities()
productData ProductsWorkflowService.ProductData 順序服務 Nothing
Exists  Boolean  順序服務 False


當指定database變數的型別時,在變數型別下拉列表中,點選"瀏覽型別"。在"瀏覽和選擇.NET型別"對話方塊中,在<Referenced assemblies>下,展開ProductsEntityModel[1.0.0.0],然後展開ProductsEntityModel,點選AdventureWorksEntities後,點選"確認"按鈕。使用同樣的方式,新增並設定變數productData的型別。

3. 在工具欄中,拖動ProductsExists活動到順序服務中的ReceiveRequest活動與SendResponse活動之間:

4. 選中ProductsExists活動,在屬性視窗中,設定其Database屬性值為database,ProductNumber屬性值為localProductNumber,並設定Result值為exists。
Database和ProductNumber屬性是在ProductExists活動中定義的輸入引數。localProductNumber變數包含來自客戶端傳送至ReceiveRequest活動的訊息中的產品編號。database變數包含一個新的AdventureWorksEntities型別的新例項,該例項用以連線至資料庫。result屬性是Execute方法的執行的結果,並且該步驟將變數的值賦給exists變數。

5. 在工具欄中,展開"Control Flow"區域,拖動If活動到順序服務的ProductExists活動和SendResponse活動之間。在條件文字框內輸入"exists"。

6. 在工具欄中,拖動FindProduct活動到If活動的Then框內;那麼此時的工作流將如下如所示:

7.選中findProduct活動,在屬性視窗中,設定Database屬性值為database,ProductNumber屬性值為localProductNumber,並設定Result屬性為productData.

當exists變數值為true時,將呼叫FindProductData活動。Database和ProductNumber屬性是FindProduct活動中定義的輸入引數。Result屬性是Productdata物件,其包含從AdventureWorks資料庫中獲取的產品的詳細資訊。如果exists變數值為false,那麼變數productData將會被設定為Nothing(C#中為null)

8. 在設計檢視視窗中,點選SendResponse活動,然後點選"檢視訊息"。在內容定義對話方塊中,選擇引數選項。點選"瀏覽型別"以指定該引數的型別。點選"確認"按鈕完成引數型別的指定。請按照下列規範設定該引數:

屬性
名稱 Product
型別 ProductsWorkflowService.ProductData
productData

上述設定指定SendResponse活動回傳至客戶端的訊息。在本例中,該訊息包含呼叫FindProduct活動獲取的產品詳細資訊。

現在,你已經完成了GetProduct操作的定義。如果需要額外的功能,你還可以為該服務新增其他的操作,但是你需要記住該服務是一個工作流服務;它蘊含了客戶端程式在什麼時候,以什麼方式呼叫該服務的操作(具體資訊見本章後續內容)

到目前為止,該ProductsWorkflowService包含一個可以部署的、可之前訪問其他服務的客戶端訪問的WCF服務。如果你檢查web.config檔案,你將發現該檔案包含了所需的最基本的、且最少的配置資訊,但通過這些資訊,你可以獲取該服務的元資料以構建相應的客戶端。下圖展示了web.config檔案中ServiceModel片段:

如果你不希望使用預設的配置,你可以修改該配置檔案,並定義服務端點。

下一步將是測試該服務是否能正常工作,並確認GetProduct操作能否返回正確的資訊。最簡單的方式是使用WCF預設的測試客戶端程式。

 

測試ProductsWorkflowService服務

1. 開啟ProductsWorkflowService專案下的web.config檔案,新增下面的連線字串到該檔案:

<connectionStrings>

<add name="AdventureWorksEntities"connectionString="metadata=res://*/ProductsModel.csdl|res://*/ProductsModel.ssdl|res://*/ProductsModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=localhost;initial catalog=AdventureWorks;integrated security=False; user id=ap_wcf; password=ap_wcf; multipleactiveresultsets=True;App=EntityFramework&quot;"providerName="System.Data.EntityClient" />

</connectionStrings>

 

該連線字串供ProductsEntityModel元件使用以連線至AdventureWorks資料庫

2. 儲存web.config;然後回到ProductsService.xamlx工作流的設計檢視視窗

3. 選擇Visual studio的"除錯"選單,然後點選"開始除錯"。

ASP.NET開發伺服器啟動,並作為該服務的宿主程式。WCF測試客戶端程式同時啟動並連線至該服務。WCF測試客戶端程式查詢該服務的元資料,並在左邊面板中顯示服務所提供的操作:

4. 在WCF測試客戶端程式的左邊面板中,雙擊GetProduct()

5. 在GetProduct面板中,面板的上部分為請求區域,在"值"欄位下方的文字框中輸入"WB-H098",然後點選"呼叫"。如果出現安全警告資訊,點選"確認"按鈕—因為你僅僅是在傳送一個測試訊息至執行在你計算機上服務,因此不存在安全問題。

6. 確認服務傳送了包含WB-H098產品詳細資訊的響應。

7. 在WCF測試客戶端工具中,點選右邊面板下部的XML標籤。該面板顯示請求訊息和響應訊息的XML內容。你可以看到ProductData物件如何被序列化為響應訊息的一部分。

8. 關閉WCF測試客戶端工具。

 

實現工作流服務的客戶端程式

客戶端程式不需要知道服務是如何實現地,也不關心建立和寄宿服務使用的技術。重要的是開發人員構建客戶端程式能訪問服務元資料,知道使用哪個協議連線至服務。因此,你可以採用建立客戶端程式訪問其他型別WCF服務相同的方式,建立客戶端程式以訪問工作流服務。

建立工作流客戶端程式

1. 在Visual Studio中,新增一個新專案到ProductsWorkflow解決方案中。 該專案使用的模板為工作流控制檯程式模板。

2. 建立完專案後,重新命名Workflow1.xaml檔案為ClientWorkflow.xaml

3. 在設計檢視視窗下開啟ClientWorkflow.xaml;該工作流目前為空。你將新增活動到該工作流,以連線至ProductsWorkflowService並呼叫GetProduct操作。

4. 在屬性視窗中,更改Name屬性為ProductsWorkflowClient.ClientWorkflow

5. 開啟program.cs檔案;修改Main方法。將WorkflowInvoker.Invoke(new Workflow1());修改為WorkflowInvoker.Invoke(new ClientWorkflow ());

6. 新增服務引用ProductsWorkflowService到ProductsWorkflowClient專案。(具體操作參見第二章);新增服務引用後,編譯該專案。

7. 返回到ClientWorkflow工作流的設計檢視視窗,並調出Visual Studio工具箱。

注意在工具欄頂部出現一個新的名為"ProductsWorkflowClient.ProductsWorkflowService.Activities"區域。該區域包含GetProduct程式碼活動。當你新增服務引用到工作流客戶端程式時,服務引用新增嚮導為該服務的每一個對外的操作生成一個程式碼活動。你可以使用這些程式碼活動呼叫服務的操作。每個活動都作為一個代理,通過代理連線至服務,然後傳送請求訊息,並等待服務響應。

8. 在工具欄者中,展開"控制工作流"區域,拖動順序活動到空的ClientWorkflow工作流上。

9. 拖動GetProduct活動至ClientWorkflow工作流的順序活動中

10. 在屬性視窗中,修改GetProduct活動的DisplayName屬性值為Get Water Bottle

11. 檢查該活動的其他屬性。

EndpointConfiguration屬性指定配置檔案app.config中客戶端端點的名字;工作流將使用該端點連線至服務。App.config檔案是由新增服務引用嚮導自動生成,你可以編輯該檔案,比如當服務更改了繫結地址,那麼你需要重新配置客戶端程式使用的繫結。

ProductNUmber屬性對應ReceiveRequest活動為GetProduct操作所提供的引數。你應該使用呼叫操作前使用一個產品編碼填充該屬性。服務找到該產品的詳細資訊後,該資訊被當作引數傳輸至SendResponse活動,然後SendResponse活動將ProductData物件回傳至客戶端

12. 在設計檢視視窗中,點選Get Water Bottle活動,新增下面的引數:

引數名 引數型別 範圍 預設值
productReturned ProductsWorkflowClient.ProductsWorkflowService.ProductData 順序服務 Nothing


13. 在設計檢視模式中,選擇Get Water Bottle活動,設定Name屬性的值為productReturned;設定ProductNumber屬性值為"WB-H098" (包括引號)

14. 在工具欄中,展開Primitives區域,拖動WriteLine活動之順序活動中,並且職位處於Get Water Bottle活動之下。在Text文字框中,輸入productReturned.Name:

15. 在WriteLine活動下面,再新增另外一個GetProduct活動到該工作流中,該活動包含下列屬性:

屬性
DisplayName Get Mountain Seat Assembly
EndpointConfiguration BasicHttpBinding_IProductsService
Product productReturned
ProductNumber "SA-M198" 


16. 在設計檢視中,複製WriteLine活動,然後在Get Mountain Seat Assembly活動下面貼上該活動。下圖顯示了完成後的工作流:

17. 生成專案。

 

測試工作流客戶端程式

1. 在專案瀏覽器中,在方案ProductsWorkflow上點選右鍵,然後點選"屬性"

2. 設定多重啟動專案。在屬性頁中,在左邊面板中選擇常用屬性,然後選擇專案啟動,然後在右邊面板中選擇多個啟動專案,然後設定兩個專案的動作為start

3. 在非調適模式下,啟動方案。ASP.NET開發伺服器將啟動,該伺服器用以寄宿ProductsWorkflowService服務;客戶端程式同時也將執行,並且在控制檯視窗中顯示產品WB-H098和產品SA-M198的全名。

4. 按ENTER鍵,關閉客戶端程式並返回到Visual Studio

 

處理工作流服務的異常

在第三章"構建健壯的程式和服務"中,你已經看到如何在服務中捕獲異常,並將這些異常報告給客戶端程式。這是構建健壯系統重要的組成部分。讓我們回顧一下基本步驟

1. 定義強型別的fault類,並標記DataContrat特性

2. 在每個服務操作的邏輯中,捕獲可能發生的任何異常

3. 在異常處理器中,確定異常的原因,構建對應的fault類的例項,然後填充響應的資訊到該例項上

4. 丟擲型別安全的FaultException<>異常,該異常能接收fault物件。

你可以在工作流服務中應用同樣的邏輯。在工作流服務中捕獲異常,並將這些異常作為型別安全的faults報告給客戶端的方式與常規的WCF服務的實現方式非常相似。使用工作流服務的問題是如何傳送FaultException<>至客戶端程式。

在使用C#構建的常規WCF服務中,你可以在一個操作上標記FaultContract特性以指定該操作可能生成的faults。在該操作定義的內部,你可以簡單地丟擲一個FaultException<>異常,WCF執行時將完成剩下的一切任務:WCF執行時建立一個fault訊息,併為該訊息填充FaultException<>指定的資料,然後將該訊息作為服務響應回傳至客戶端程式。當你使用工作流服務,該服務的操作從為Receive活動指定的屬性中派生。你可以指定這些專案,比如操作名、服務合約名、操作能接收的請求訊息模型,但是你不能採用FaultContract特性。相應地,為了能在工作流服務的操作中丟擲FaultException<>異常,你需要執行顯示地執行一些任務(這些任務構成工作流的一部分)。特別地,你必須在工作流中合適的位置新增額外的SendReply活動,然後配置它們,以使其能傳送可能出現的各種fault型別。

在下面的練習中,你將重新實現第三章中的SystemFault和DatabaseFault類。你還將修改GetProduct操作以捕獲異常。如果異常的原因是資料庫問題,該操作將丟擲一個DatabaseFault;否則,將丟擲一個SystemFault。

為ProductsWorkflowService服務新增錯誤處理

準備工作:在*\WCF\Step.by.Step\Chapter8資料夾下建立ProductsWorkflowFaultHandling資料夾,然後 複製ProductsWorkflow資料夾內的ProductsWorkflowService到新建資料夾。

1. 開啟ProductsWorkflowFaultHandling資料夾中ProductsWorkflowService下的ProductsWorkflowService.csproj

2. 開啟ProductsService.Activitieis.cs檔案,並新增兩個資料合約DatabaseFault和SystemFault

[DataContract]

public class SystemFault

{

[DataMember]

public string SystemOperation { get; set; }

 

[DataMember]

public string SystemReason { get; set; }

 

[DataMember]

public string SystemMessage { get; set; }

}

 

[DataContract]

public class DatabaseFault

{

[DataMember]

public string DbOperation { get; set; }

 

[DataMember]

public string DbReason { get; set; }

 

[DataMember]

public string DbMessage { get; set; }

}

 

3. 生成專案;該步驟很重要,因為如果不執行該步驟,上述兩個新的型別將不能在工作流中被識別。

4. 在設計檢視視窗下開啟ProductsService.xamlx,然後在工具欄中,展開錯誤處理區域,拖動TryCatch活動到順序服務中,並且其位置處於ReceiveRequest活動與ProductsExists活動之間。

5. 在工具欄中,從控制流區域中拖動一個順序活動到TryCatch活動的Try框內

6. 在設計檢視視窗中,拖動ProductExist、if、和SendResponse活動及其它們的內容到TryCatch活動try框內的順序服務中。完成後,工作流將如下圖所示:

7. 在TryCatch活動的Catches區域內,點選"新增新異常"。將出現異常型別的下拉列表,選擇System.Exeception,然後按Enter鍵。Catches區域將展開,並顯示為一個區域,在該區域內你可以新增處理異常的活動。

8. 從工具欄的控制流區域中選擇新增一個if活動,並將該活動拖動至上述步驟建立異常活動區域中。在條件框內輸入TypeOf exeception.InnerException Is System.Data.SqlClient.SqlException. 該表示式用以檢查丟擲的異常是否為SqlException

9. 點選"變數"標籤,然後新增下面的引數:

引數名 引數型別 範圍 預設值
dbf ProductsWorkflowService.DatabaseFault TryCatch Nothing


10. 新增一個順序活動到異常處理器的If活動下Then區域內。

11. 新增一個Assign活動到上述新建立的順序活動中。在該活動的To文字框內,輸入dbf。然後開啟屬性視窗,並點選Value屬性後的省略符按鈕。在表示式編輯對話方塊中,輸入下列表達式
New DatabaseFault() With { .DbOperation = "Connect to database", .DbReason="Exception accessing database", .DbMessage=exception.InnerException.Message }。該表示式使用VB程式碼建立一個新的DatabaseFault物件,並且將引發錯誤對應的異常填充到該物件中。然後將該物件傳送至客戶端。你將使用SendReply活動傳送響應,但在工具箱中不能發現該活動。你必須在Visual Studio中通過Receie活動自動生成一個配置好的SendReply活動。

12. 在設計檢視視窗中,找到ReceiveRequest活動,然後在該活動上點選右鍵,然後點選建立SendReply。設計器將新增一個名為SendReplyToReceiveRequest的SendReply活動,該活動位置ReceiveRequest活動的下方。拖動SendReplyToReceiveRequest活動到Catches活動下àIf活動中à順序服務àAssign活動的下方。然後更改其DisplayName為Send DatabaseFault

13. 在Send DatabaseFault活動中,點選內容定義框。 內容定義對話方塊將出現。選擇引數選項,然後新增下面的引數:

引數名 引數型別
databaseFaultException System.ServiceModel.FaultException<ProductsWorkflowService.DatabaseFault> New FaultException(Of DatabaseFault)(dbf)


14. 點選"變數"標籤,然後新增下面的引數

引數名 引數型別 範圍 預設值
sf ProductsWorkflowService.SystemFault TryCatch Nothing


15. 新增一個順序活動到異常處理器的Else區域中,然後新增一個Assign活動到新新增的順序活動中。 在Assign活動的To文字框內,輸入sf。然後開啟屬性視窗,點選Value屬性後的省略符按鈕。在出現的表示式編輯對話方塊中,輸入表示式:New SystemFault() With {    .SystemOperation = "Cet Product", .SystemReason="Exception finding product details", .SystemMessage=exception.Message }。該表示式使用VB程式碼建立一個新的SystemFault物件,並且將引發錯誤對應異常的值填充到該物件中,然後傳遞給客戶端。

16. 在設計檢視視窗中,找到ReceiveRequest活動,然後點選右鍵,再次點選"建立SendReply"。 SendReply建立之後,拖動SendReplyToReceiveRequest活動至Catches活動下àelse活動中à順序服務àAssign活動的下方。然後在屬性視窗中,更改該活動的DisplayName為Send SystemFault

17. 在Send SystemFault活動中,點選內容定義框。內容定義對話方塊將出現。選擇引數選項,然後新增下面的引數:

引數名 引數型別
systemFaultException System.ServiceModel.FaultException<ProductsWorkflowService.SystemFault> New FaultException(Of SystemFault)(sf)


18. 重新生成ProductsWorkflowService專案。

你可以快速的檢查服務的元資料以確認服務是否正確的實現錯誤處理。若要這麼做,那麼你在ProductsWorkflowService專案上點選右鍵,然後選擇"在遊覽器中檢視"。IE將自動啟動,並顯示ProductsService服務頁。在該頁中,你點選http://localhost:42969/ProductsService.xamlx?wsdl (注意:埠號可能不同)。然後你將看到該服務的WSDL描述,從該檔案中,我們可以發現DatabaseFault和SystemFault訊息已經在GetProduct方法中生成。

現在,你可以開始測試ProductsWorkflowService服務的錯誤處理能力。要測試錯誤處理,你需要在客戶端程式中新增捕獲FaultException<DatabaseFault>和FaultException<SystemFault>異常。因為一些人的個人偏好,也為了證明工作流服務可以和非工作流客戶端程式一起工作,我們將使用第三章所建立的客戶端專案。

 

測試ProductsWorkflowService服務的的錯誤處理

準備工作:複製第三章的ProductsClient專案到*\WCF\Step.by.Step\Chapter8\ProductsWorkflowFaultHandling資料夾下

1. 在Visual Studio中,新增ProductsClient專案;

2. 為ProductsClient專案新增服務引用;在新增服務引用對話方塊中,點選"識別服務";然後在Namespace文字框處,輸入ProductsService,然後點選確定

3. 修改Programm.cs

static void Main(string[] args)

{

Console.WriteLine("Press ENTER when the servcie has started");

Console.ReadLine();

 

ProductsServiceClient proxy = newProductsServiceClient("BasicHttpBinding_IProductsService");

 

try

{

Console.WriteLine("Display the details of a product");

string procutNumber = "WB-H098";

ProductData product = proxy.GetProduct(procutNumber);

Console.WriteLine("Name:" + product.Name);

Console.WriteLine("Color:" + product.Color);

Console.WriteLine("Price:" + product.ListPrice.ToString());

}

 

catch (FaultException<DatabaseFault> dbf)

{

Console.WriteLine("DatabaseFault {0}: {1}\n{2}",

dbf.Detail.DbOperation,

dbf.Detail.DbMessage,

dbf.Detail.DbReason);

}

catch (FaultException<SystemFault> sf)

{

Console.WriteLine("SystemFault {0}: {1}\n{2}",

sf.Detail.SystemOperation,

sf.Detail.SystemMessage,

sf.Detail.SystemReason);

}

catch (FaultException ex)

{

Console.WriteLine("{0}: {1}", ex.Code.Name, ex.Reason);

}

catch (Exception ex)

{

Console.WriteLine(ex.Message);

}

 

 

Console.ReadLine();

}

 

4. 在方案屬性中,設定ProductsWorkflowService和ProductsClient均為啟動專案

5. 在非調適模式下啟動方案。在客戶端控制檯應用程式中,按ENTER鍵以連線至服務,然後確認Water Bottle產品的詳細資訊顯示在控制檯視窗中。再次按下ENTER鍵,以關閉客戶端控制檯視窗。

6. 在ProductsWorkflowService,將連結字元中的user id=ap_wcf修改為user id=ap_wcf1

7. 生成方案然後再次在非調適模式下執行方案。在客戶端控制檯程式視窗中,按下ENTER鍵以連線池服務。但是這次,服務將返回一個FaultException<DatabaseFault>訊息;其具體資訊如下圖所示:

8. 關閉客戶端控制檯視窗,並返回到Visual Studio

9. 在ProductsWorkflowService中,恢復連線字串中的user id.

 

 

寄宿工作流服務

到現在為止,都是使用Visual Studio自帶的ASP.NET開發伺服器寄宿工作流服務。此外,你還可以在其他環境中 寄宿WCF服務,比如IIS/WAS或者自定義一個宿主程式。

在IIS中寄宿工作流服務

在IIS中寄宿工作流服務與寄宿常規服務非常相似。但有一點不一樣;在第一章的使用IIS寄宿WCF服務時,你使用釋出站點嚮導;而寄宿工作流服務,你需要使用建立部署包嚮導。

部署ProductsWorkflowService到IIS

準備工作:在*\WCF\Step.by.Step\Chapter8資料夾下建立HostProductsWorkflowService資料夾;然後進入該資料夾建立IIS和Custom資料夾;

1. 在Visual Studio中,在ProductsWorkflowFaultHandling方案下的ProductsWorkflowService專案上點選右鍵。然後點選"打包/釋出設定"

2. 設定包的位置和IIS中application的名字:

3. 儲存設定後,再次在專案上點選右鍵,然後點選"生成部署包",等待"釋出成功"訊息出現在Visual Studio的狀態列中

4. 在資源管理器中,定位到*\WCF\Step.by.Step\Chapter8\HostProductsWorkflowService\IIS目錄,確認ProductsWorkflowService.zip檔案已經建立。

該檔案包含服務部署包。你可以複製該檔案到執行IIS的伺服器上,然後在伺服器上安裝該包。在本章的練習中,IIS執行在同一臺計算機上,因為你不需要複製檔案的過程。

5. 使用管理員身份執行IIS

6. 在IIS左邊的連接面板中,展開計算機節點,然後展開站點,最後點選預設的web站點

7. 在IIS右邊的行為面板中,點選匯入程式。將啟動匯入程式包嚮導。

如果你沒有看到匯入程式連結,請先安裝Web部署工具。http://www.iis.net/download/WebDeploy

8. 當匯入程式包嚮導啟動後,點選瀏覽按鈕,選擇第三步生成的檔案ProductsWorkflowService.zip。然後點選下一步按鈕

9. 在安裝包內容頁面,確認所有內容都被選中。然後點選"下一步"

10. 在輸入應用程式安裝包資訊頁。確認應用程式路徑和連結字串。然後點選"下一步"

11. 等待服務被安裝完畢。在安裝過程和結果頁面,確認結果的提示資訊"兩個目錄和五個檔案已經新增"。然後點選"結束"按鈕

12. 在IIS管理中,展開預設web站點,你將看到ProductsWorkflowService程式已經列出。

13. 點選ProductsWorkflowService程式,然後點選內容試圖標籤。你將發現三個專案:bin資料夾,ProductsService.xamlx檔案和web.config檔案

14. 在IIS左邊的連接面板,選擇ProductsWorkflowService程式,然後點選右鍵,選擇管理應用程式,並選擇高階設定。在高階設定對話方塊中,確認任應用程式池使用ASP.NET v4.0,然後點選確認按鈕。

15. 在內容試圖面板中,選擇ProductsService.xamlx檔案,然後點選右鍵,選擇在瀏覽器中檢視。IE將自動啟動,並開啟ProductsWorkflowService服務頁。點選連結http://localhost/ProductsWorkflowService/ProductsService.xamlx?wsdl可以檢視該服務的元資料。

16. 關閉IE瀏覽器,並返回到Visual Studio

 

通過配置客戶端程式連線到寄宿在IIS的服務,你可以測試該服務的功能是否正確

測試寄宿在IIS中的ProductsWorkflowService服務

準備工作:複製ProductsWorkflowFaultHandling資料夾下的ProductsClient專案到HostProductsWorkflowService資料夾下。

1. 使用Vsisual Studio開啟ProductsClient專案,然後開啟該專案下的web.config檔案

2. 修改端點的地址為 http://localhost/ProductsWorkflowService/ProductsService.xamlx

3. 在Visual Studio的方案瀏覽器中,在ProductsClient上點選右鍵,然後點選"設定為啟動專案";然後在非調適模式下啟動方案

4. 在客戶端控制檯程式中,點選ENTER鍵連線到服務。確認客戶端程式成功地連線至服務,並且返回water bottole的詳細資訊

5. 關閉客戶端控制檯視窗,然後返回到Visual Studio

 

在自定義程式中寄宿工作流服務

寄宿工作流服務與寄宿非工作流服務很相似,但並不完全一樣。最主要的區別在與寄宿程式必須提供建立和管理工作流的執行時。幸運的是,.net framework提供了WorkflowServiceHost類以支援上訴的需求。WorkflowServiceHost 位於WorkflowServiceHost.Actitivities名稱空間下,因此如果你使用該類,必須引用WorkflowServiceHost.Activities名稱空間。

注意:相當令人困惑的是,在.NET類庫中,有兩個WorkflowServiceHost類:

它們一個位於System.ServiceModel.Activities;另一個位於System.ServiceModel。後者是為NET Framework3.0構建,它實現了不同的工作流模型。如果你使用.net framework4.0構建寄宿工作流服務的應用充許,你需要使用前者名稱空間中的WorkflowServiceHost類

WorkflowServiceHost類一個與ServiceHost類似,其提供類似的方法,屬性,和事件;當寄宿服務時的設定也僅僅只有一兩處的不同。最值得注意的不同點是WorkflowServiceHost類提供了一個構造器,該構造器可以接受一個定義工作流服務根節點活動並當宿主程式接收到請求時啟動該活動。下面兩張圖也說明了這點。

另外一個有用的構造器,接受WorkflowService物件。如下圖所示,在後面的練習中我們將使用該構造器。

 

建立一個自定義程式寄宿ProductsWorkflowService服務

1. 在*\WCF\Step.by.Step\Chapter8\HostProductsWorkflowService\Custom資料夾下建立一個專案名為ProductsWorkflowHost的工作流控制檯應用程式。建立完專案後,刪除Workflow1.xaml

2. 將ProductsWorkflowFaultHandling資料夾下的ProductsWorkflowService專案新增添至方案ProductsWorkflowHost。

3. 新增引用ProductsWorkflowService到專案ProductsWorkflowHost

4. 開啟ProductsWorkflowHost專案下的Program.cs檔案,在檔案頭部,新增下面的using語句
using System.ServiceModel.Activities;
using System.Xaml;

5. 刪除Main方法中現有的程式碼;並新增下面的程式碼

static void Main(string[] args)

{

WorkflowService service =XamlServices.Load(@"..\..\..\..\..\ProductsWorkflowFaultHandling\ProductsWorkflowService\ProductsService.xamlx")as WorkflowService;

 

WorkflowServiceHost host = new WorkflowServiceHost(service);

host.Open();

Console.WriteLine("Service is running. Press ENTER to stop");

Console.ReadLine();

host.Close();

}

第一行基於ProductsWorkflowService專案下的ProductsService.xamlx檔案建立WorkflowService物件。 該檔案包含了ProductsWorkflowService服務的定義。 XamlService類的靜態Load方法可以讀取任何包含描述工作流的檔案;並將它轉化成圖形物件。ProductsService.xamlx檔案包含一個工作流服務,因為它可以安全地轉換成一個WorkflowService物件。

第二行宣告建立一個WorkflowServiceHost物件;該物件用於寄宿工作流服務。和ServiceHost物件一樣,WorkflowServiceHost類允許你使用程式碼配置服務的端點、或者從配置檔案中讀取配置資訊。在此練習中,你將使用配置檔案中的配置。

剩下的程式碼你應該很熟悉。WorkflowServiceHost的Open方法使宿主程式開始偵聽請求;close方法用以停止服務。

6. 使用WCF服務配置工具開啟ProductsWorkflowHost配置檔案app.config

7. 在配置面板中,點選服務端點,然後點選建立一個新服務連結啟動建立服務元素嚮導,並建立一個新的服務端點。使用下表中的值指定向導中對應的屬性。

頁面 提示框
指定服務型別 服務型別 ProductsService
指定服務合約 服務合約 IProductsService
指定通訊模式   TCP
指定端點地址 地址 Net.tcp://localhost:8080/ProductsService.xamlx


8. 儲存配置檔案,並退出WCF服務配置工具

9. 使用Visual Studio開啟配置檔案,然後新增連線至AdventureWorks資料庫的連線字串
<connectionStrings>

<add name="AdventureWorksEntities"connectionString="metadata=res://*/ProductsModel.csdl|res://*/ProductsModel.ssdl|res://*/ProductsModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=localhost;initial catalog=AdventureWorks;integrated security=False; user id=ap_wcf; password=ap_wcf; multipleactiveresultsets=True;App=EntityFramework&quot;"providerName="System.Data.EntityClient" />

</connectionStrings>

 

10.儲存app.config

 

測試寄宿的工作流服務

1. 新增ProductsClient專案到方案ProductsWorkflowHost中;然後開啟ProductsClient的app.config檔案。並新增一個新的客戶端端點
<endpoint address="net.tcp://localhost:8080/ProductsService.xamlx" binding="netTcpBinding"contract="ProductsService.IProductsService" name="NetTcpBinding_IProductsService" />

2. 修改Program.cs檔案,使客戶端代理通過新建立的客戶端端點連線至服務

3. 在方案ProductsWorkflowHost的屬性中,設定ProductsClient專案與ProductsWorkflowHost專案為啟動專案;並確認ProductsWorkflowService未非啟動專案。

4. 在非調適模式下,執行方案。 如果Windows安全警告出現,點選"允許"按鈕以使ProductsServiceHost程式開啟TCP的8080埠

5. 在客戶端程式控制臺視窗中,按ENTER鍵連線至ProductsWorkflowService服務。確認客戶端程式正常工作,併成功連線至服務而且獲取到產品的詳細資訊

6. 關閉客戶端控制檯視窗,關閉寄宿服務的控制視窗。並返回到Visual Studio

 

在工作流服務中實現常用的訊息模式

你已經看到你可以建立工作流服務,而且該工作流服務迴圈地執行"等待請求,傳送響應"的訊息處理過程。但是,這僅僅是客戶端程式和服務通常使用的訊息模式之一。該模式下服務的操作,客戶端關注的是,是否存在一個同步執行緒控制從客戶端傳遞至服務,並從服務端返回;當客戶端程式傳送請求至服務,看起來好像是在呼叫本地一個方法,並且直到服務的響應達到客戶端之前,都不會消耗任何處理過程。 在第十二章,你可以看到使用其他模式(非訊息模式)建立並實現WCF客戶端程式和服務。比如,一個客戶端程式能傳送一個one-way訊息至服務,然後立即執行處理過程。

非同步訊息模式下,一個客戶端程式傳送請求後,當服務開始處理請求時,客戶端執行執行過程。如果服務需要傳送迴應,客戶端程式需要在另外一個單獨的執行緒中配置偵聽服務的響應;並在接受到響應後開始處理該響應。

另外一種常見的訊息模式是回撥。在該模式下,服務能呼叫一個客戶端程式,並且儘可能警告客戶端程式服務的狀態發生改變。在該模式下,來自客戶端程式的一個單獨的請求訊息可以開啟到服務的一個通道,服務使用該通道把訊息回傳至客戶端,甚至傳送請求訊息以獲取客戶端的響應。在第16章你將學習到具體的細節。

WCF提供了特性和屬性,你可以使用他們配置服務合約,操作合約,服務,和客戶端程式,當然你也可以使用程式碼實現去實現這些配置。 如果你使用工作流實現服務和客戶端程式,你可以使用訊息活動實現上述及其他的訊息模式。

訊息活動

你應該已經注意到工作流工具箱有一個區域名為Messaging。該區域內的活動為工作流服務傳送,接受,和關聯訊息。下表總結了其中一些行為的目的。如果你需要更詳細的資訊,請查閱MSDN或者Visual Studio幫助文件

屬性
Receive 該活動封裝了在端點偵聽訊息和等待訊息。你可以通過設定Content屬性來指定訊息資訊形式;並將該資訊賦予給工作流中的變數。你還可以指定操作的名字及服務合約的名字,以便工作流執行時使用獲取服務合約。
另外一個重要的屬性是CanCreateInstance。如果該屬性設定為true,該活動指定型別的訊息可以啟動一個新的服務例項並對該客戶端建立一個新的會話(如果當前沒有執行的客戶端)。 如果該屬性值設定為false,在服務接受並處理訊息之前該客戶但的會話將一直存在。
如果你希望實現訊息級別的安全,你可以使用ProtectionLevel屬性為簽名並加密訊息
SendReply 該活動不會出現在Visual Studio的工具箱中,儘管你經常使用該活動。該活動的目的是向客戶端傳送服務的響應。工作流中的每個傳送響應活動應該有一個對應的接收活動, 該接收活動上點選右鍵並選擇建立傳送響應活動,可以生成一個配置好的傳送響應活動
ReceiveAndSendReply 這是一個複合活動,它由接收活動和傳送響應活動構成。Visual Studio使用WCF工作流服應用程式模板生成單個接收併發送響應活動,你可以使用它定義一個操作的起始點
Send 一個客戶端 可以使用傳送活動向服務傳送一個請求訊息。和接收活動一樣,通過Content屬性指定傳送訊息的形式。你通過設定Endpoint、EndpointAddress和EndpointConfiguration屬性設定傳送訊息目的端點(服務端點)和傳送訊息端點(客戶端端點)的詳細資訊。此外,你還應指定操作名和服務合約名以識別在服務中呼叫的操作。
如果服務實現了訊息級別的安全,你需要在傳送活動中設定ProtectionLevel屬性使其與服務端的接收活動該屬性的值匹配。
ReceiveReply 與傳送響應相似,你不能在工具欄中發現該活動。當客戶端程式使用傳送活動傳送請求訊息,客戶端會提供一個對應的接收回應活動以獲取服務的響應訊息。在傳送活動上點選右鍵並選擇建立接收響應活動,可以生成一個配置好的接收響應活動
SendAndReceiveReply 與接收併發送響應一樣,該活動也是一個複合活動,它由傳送活動和對應的接收響應活動構成。


請注意,在本章前面的練習中你所建立的工作流客戶端程式,你並未顯示地使用傳送或者傳送並接收響應活動。相反,你使用了自定義的GetProduct活動,該活動是由新增服務引用嚮導自動生成。實際上,GetProduct活動是一個簡單的複合活動,該活動包含了一個傳送活動、接收響應活動及賦值活動巢狀組成的順序互動(如下圖所示)。

活動向服務傳送GetProduct訊息並等待服務響應。由服務通過響應訊息的回傳值被賦值到一個臨時變數, 該變數就是自定義的GetProduct活動的返回值。

在工作流服務例項中關聯請求和迴應訊息

之前章節展示了服務的宿主程式可以支援服務的多個例項。當一個客戶端程式連線到服務例項,客戶端建立一個通道並使用該通道向服務例項傳送請求訊息,服務端的WCF執行時確保響應通過正確的通道傳送至正確的客戶端。當你向工作流服務傳送訊息時,接收活動為請求建立一個識別符號,並確保傳送響應的活動使用該識別符號將對應的響應通過正確的通道傳送至客戶端程式。該識別符號被命名為correlation handle。你可以在接收活動的CorrelationInitializers屬性中使用這個correlation handle。當你為接收活動建立一個傳送響應活動,WCF設計器為你自動建立一個CorrelationHandle變數,並使用接收活動的Correlation Initializer屬性的值填充該變數。這些變數一般都命名為_handle1, _handler2,等等。你不需要理解correlation handler在內部是如何工作或者包含的資料,只需要知道它們用以區分請求並確保響應被正確的回傳至對應的客戶端。

當你在工作流中添加發送活動並建立一個接收響應活動時,上述邏輯同樣適用。傳送活動的Correlation Initializer屬性自動填充一個correlation handle,然後接收響應活動也關聯了相同的correlation handle

使用訊息行為實現訊息模式

一個服務最常見的訊息模式是接收請求/傳送響應訊息迴圈,你可以使用接收併發送響應複合行為實現該模式。你可以使用操作的名字、服務合約和訊息型別簡單地配置接收活動。當一個工作流處於接收活動狀態時,工作流將被鎖定直到接收到匹配的訊息。你可以提供必要的邏輯以處理接收到的訊息,並建立響應,該響應通過傳送響應活動回傳至客戶端。請注意,傳送響應活動使用與接收訊息相同的通道。如果服務發現異常,你可以配置額外的傳送響應活動以傳送FaultException訊息至客戶端。

在這種模式下,客戶端程式使用傳送並接收響應複合動作,該活動通常由新增服務引用嚮導生成的代理活動中,並且該活動已經被預先配置好。傳送並接受響應活動減少服務的行為;傳送活動向服務傳送訊息,然後執行接收響應活動,在這期間處於鎖定狀態,直到從傳送訊息的通道中接收到服務端的響應。與接收併發送響應活動利用服務一樣,傳送和接受響應活動包含在順序活動中,因此在傳送請求後並在接收到響應之前插入額外的邏輯。然而,你應該儘量保持任何處理過程儘量短暫,因為當服務傳送響應時如果一個客戶端沒有執行接收響應,服務將被鎖定。

你可以在客戶端程式中使用傳送活動實現一個簡單形式的one-way訊息,並且不用等待服務響應。該服務應使用接收活動以接受訊息,但必須不可以試圖傳送響應;否則,該服務將被鎖定,也可能不確定—這包含傳送任何錯誤訊息。

基本的非同步訊息模式要求一點額外的配置。客戶端程式能使用傳送活動以向服務提交一個請求,然後開始執行其要求的任何過程。但是,客戶端程式必須準備接收該服務的響應,並且不能不適當地鎖定服務。 一個可以實現該目標的較好方式是在客戶端使用Parallel活動;當客戶端處理服務的一個響應訊息時,該活動中其他的請求訊息可以順序地等待服務的響應。

在非同步模型中,服務在接收活動上等待以偵聽請求,然後生成一個響應訊息。但是,沒有什麼能阻止服務生成多個迴應。在客戶端程式向經紀服務傳送訊息詢問產品報價的場景下。服務可能向多個提供產品報價服務傳送請求。當服務接收到從各個供應商處得到響應後,該服務向客戶端傳送對應的響應。如果多個服務提供商向服務傳送了響應,那麼服務可能向客戶端傳送多個響應。在該模式下,客戶端必須準備好接收多個響應。你可以在客戶端工作流中使用While活動以達到該目的:偵聽服務的響應,依次地接收並處理每個響應訊息。

 

管理會話和維護工作流服務的狀態

到目前為止你在本章中研究的工作流服務都只有比較簡單的特性,暴露給客戶端簡單的都是無狀態的操作。在現實世界中,工作流服務是相當複雜的。它們經常需要維護會話狀態,並提供多個操作。當你使用程式實現WCF服務時,你可以指定服務的會話的模式、使用方法去實現服務的操作。WCF執行時管理服務例項,操作可以按照任何順序實現,因為方法的順序在C#類中並不重要。

但是,這對於工作流服務來講發生了變化。工作流的重點在於為各種不同的任務定義它們的操作順序。你可以通過在每個操作上使用接收和傳送響應活動實現工作流服務,但是,當你需要實現多個操作,並偵聽不同的請求訊息,你該如何為設定這些活動順序? 此外,你應該如何維持服務例項及其它的狀態資訊? 比如,回想第七章中的購物車服務。 該購物車服務提供了四個操作:新增商品、移除商品、獲取購物車的資訊和執行結算。該服務的業務規則指定一個客戶端程式必須呼叫新增商品為第一個請求並初始化一個會話,建立服務例項,然後例項化會話狀態;結算操作終止該會話,並銷燬狀態資訊。除此之外,客戶端還可以按照任何順序呼叫新增商品、移除商品和獲取購物車操作。如果你考慮WCF執行時如何處理這種情況,你可以看到WCF執行時的本質:執行一些迴圈,等待請求訊息,分配這些請求至適當的方法。為了在工作流服務中提供相同的功能,你可以