程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 基礎:構建排隊WCF響應服務

基礎:構建排隊WCF響應服務

編輯:關於.NET

原文地址:http://msdn.microsoft.com/zh- cn/magazine/cc163482.aspx

目錄

排隊調用

響應服務

設計響應服務合約

使用消息 頭

ResponseContext 類

客戶端編程

服務端編程

響 應服務編程

結束語

Windows Communication Foundation (WCF) 使 客戶端與服務之間能夠以非連接方式進行通信。客戶端將消息發布給隊列,服務 稍後再對這些消息進行處理。這種交互方式造就了一種不同於默認的請求/響應模 式的編程模型,從而有望更好地平衡負載、提高可用性、進行補償工作,為用戶 帶來諸多好處。本專欄首先簡要介紹 Windows® Communication Foundation 的排隊調用功能,然後提出“如何從排隊的調用獲取結果”這樣一個 有趣的問題,接著通過一些超酷的 Windows Communication Foundation 編程技 術以及我為此所編寫的助手類來找到解決辦法。

排隊調用

Windows Communication Foundation 使用 NetMsmqBinding 來支持排隊調用。Windows Communication Foundation 在傳輸消息時不是通過 TCP 或 HTTP,而是通過 Microsoft® 消息隊列 (MSMQ)。客戶端也不是將 Windows Communication Foundation 消息發送到某個在線服務,而是發送到 MSMQ 隊列。所有客戶端所面 向和交互的對象是隊列,而非服務端點。因此,調用在本質上是異步的、是不連 接的。直到服務在將來某一時刻處理消息時,這些調用才得以執行。

請注 意,Windows Communication Foundation 消息並不直接映射到 MSMQ 消息。一個 MSMQ 消息可以包含一個或多個 Windows Communication Foundation 消息,具體 個數視合約會話模式而定。對於必需會話模式,多個 Windows Communication Foundation 調用可共存於一個 MSMQ 消息中;而對於允許或不允許會話模式(由 單調用和單例式服務使用),每個 Windows Communication Foundation 調用將 位於單獨的 MSMQ 消息中。

如同各 Windows Communication Foundation 服務一樣,客戶端會與代理進行交互,如圖 1 所示。由於已將代理配置為使用 MSMQ 綁定,因而該代理不會向任何特定服務發送 Windows Communication Foundation 消息,而是將調用轉換為 MSMQ 消息,然後將這些消息發布到端點地 址所指定的隊列中。

圖 1WCF 排隊調用體系結構

在服務端,當具有排隊端點的服務主機啟動後, 主機會安裝隊列偵聽程序。隊列偵聽程序會檢測到隊列中的消息並使其出隊,然 後創建主機端以調度程序為終點的偵聽器鏈。調度程序會照例調用服務實例。如 果客戶端向隊列發布了多個消息,偵聽程序會隨著消息的出隊創建新的實例,最 終以異步、非連接的並發調用結束。

如果主機處於離線狀態,消息將在隊 列中保持待處理狀態。待下次主機上線時,消息會被轉發給服務。

面向隊 列進行的、可能處於非連接狀態的調用不可能返回任何值,因為在將消息調度到 隊列時並未調用任何服務邏輯。此外,調用可能會在客戶端應用程序停止運行後 被調度給服務進行處理,而這時客戶端根本無法處理返回的值。同樣,調用也無 法將任何服務端異常返回給客戶端,而且也沒有客戶端用來捕獲和處理異常。由 於客戶端不會因為調用操作而被封鎖,更確切地說,客戶端只有在將消息送去排 隊的片刻才才被封鎖,因而從客戶端的角度來看,排隊調用在本質上屬於異步調 用。這些是單向調用的典型特征。因此,由使用 NetMsmqBinding 的端點所提供 的任何合約都只能具有單向操作。Windows Communication Foundation 會在加載 服務和代理時對此進行驗證:

//只能對排隊合約執行單向調用
[ServiceContract]
interface IMyContract
{
  [OperationContract(IsOneWay = true)]
  void MyMethod();
}

由於與 MSMQ 的交互封裝在綁定中,因而在服務調用代碼或客戶 端調用代碼中沒有任何與調用排隊相關的內容。服務代碼和客戶端代碼看起來與 任何其他 Windows Communication Foundation 客戶端代碼和服務代碼都是一樣 的,如圖 2 所示。

Figure2實現和使用排隊服務

Service Side
[ServiceContract]
interface IMyContract
{
  [OperationContract(IsOneWay = true)]
  void MyMethod();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract
{
  [OperationBehavior (TransactionScopeRequired = true)]
  public void MyMethod() {...}
}
Client Side
MyContractClient proxy = new MyContractClient();
proxy.MyMethod();
proxy.Close ();

針對排隊服務定義端點時,端點地址中必須包含隊列名稱和隊 列類型(公有或私有):

<endpoint
   address = "net.msmq://localhost/private/
     MyServiceQueue"
  binding = "netMsmqBinding"
  ...
/>

最後,MSMQ 是 Windows Communication Foundation 的事務性資源管理器。如果隊列是事務性的,則當客戶端的事務中止 時,客戶端所發布的消息將會回滾。在服務端,從隊列中讀取消息時會啟動新的 事務。如果服務參與並中止該事務(可能因異常而導致),消息會回滾到隊列中 等待下一次重試。Windows Communication Foundation 提供了完善的故障檢測和 病毒消息處理支持功能,本專欄對此不作介紹。

響應服務

到目前 為止,我們所介紹的排隊調用的編程模型是單側的:客戶端向隊列發布單向消息 ,再由服務處理該消息。如果排隊的操作真的就是單向調用,那麼這種模型足以 滿足要求。然而,排隊服務有時候需要反過來向其客戶端報告調用的結果、返回 的結果,甚至是錯誤。但在默認情況下,這是無法實現的。Windows Communication Foundation 將排隊調用與單向調用等同起來,而單向調用在本質 上是禁止任何此類響應的。此外,排隊服務(及其客戶端)可能未處於連接狀態 。如果客戶端發布對未連接服務的排隊調用,則當服務最終獲得並處理這些消息 時,可能不會有客戶端來接收值,因為客戶端可能早已離線了。這一問題的解決 方案是讓服務將報告返回給客戶端所提供的排隊服務。我將此類服務稱作響應服 務。圖 3 顯示了此類解決方案的體系結構。

圖 3響應服務

響應服務就是系統中的另一個排隊服務。它同樣可能與客戶端 斷開連接並由單獨的進程或單獨的計算機進行托管,或者它也可能共享客戶端的 進程。如果響應服務共享客戶端的進程,則當客戶端啟動時,響應服務即開始處 理排隊的響應。將響應服務由獨立於客戶端的進程(甚至是計算機)托管有助於 進一步將響應服務的生存期與使用該響應服務的客戶端相分離。

設計響應 服務合約

就如使用任何 Windows Communication Foundation 服務一樣, 客戶端和服務需要預先商定響應合約及其適用對象(例如返回的值和錯誤信息, 或僅僅是返回的值)。請注意,您也可將響應服務拆分為兩個服務,一個用於響 應結果,另一個用於響應錯誤。例如,假定有如下由排隊 MyCalculator 服務所 實現的 ICalculator 合約:

  [ServiceContract]
  interface ICalculator
  {
   [OperationContract(IsOneWay = true)]
   void Add(int number1,int number2);
   ... //更多操作
  }
  [ServiceBehavior(InstanceContextMode =
   InstanceContextMode.PerCall)]
  class MyCalculator : ICalculator {...}

要求 MyCalculator 服務以計算結果來響應客 戶端並報告所有錯誤。計算結果為整數形式,錯誤以 Windows Communication Foundation ExceptionDetail 數據合約形式表示。對於響應服務,可按下列方式 定義 ICalculatorResponse 合約:

[ServiceContract]
interface ICalculatorResponse
{
  [OperationContract (IsOneWay = true)]
  void OnAddCompleted(int result,ExceptionDetail error);
}
支持 ICalculatorResponse 的 響應服務需要檢查返回的錯誤信息,在方法結束時通知客戶端應用程序、用戶或 應用程序管理員,並將結果提供給相關方。下面是一個支持 IcalculatorResponse 的簡單響應服務:

[ServiceBehavior (InstanceContextMode = InstanceContextMode.PerCall)]
class MyCalculatorResponse : ICalculatorResponse
{  
  [OperationBehavior(TransactionScopeRequired = true)]
  public void OnAddCompleted(int result,ExceptionDetail error)
  {
    MessageBox.Show("結果 = " + result,"MyCalculatorResponse");
   if(error != null)
   {
     //處理錯誤
   }
  }
}

實現 MyCalculator 和 MyCalculatorResponse 會直接引出兩個 問題。第一個問題是同一響應服務可能會被用於處理多個排隊服務上多個調用的 響應(或完成),而 MyCalculatorResponse(更重要的是其所服務的客戶端)無 法區分這些響應。這一問題的解決方案是讓發出原始排隊調用的客戶端向該調用 分配某個唯一的 ID 作為標記。排隊服務 MyCalculator 需要將該 ID 傳遞給 MyCalculatorResponse,使其能夠應用與該 ID 相關的某種自定義邏輯。

第二個問題是排隊服務如何發現響應服務的地址。與雙向回調不同的是,Windows Communication Foundation 內部並不支持將響應服務引用傳遞給服務。而將該地 址放入服務主機配置文件中(客戶端一節中)並不是明智之舉,因為同一排隊服 務可能會被多個客戶端調用,而每個客戶端都有其自身專用的響應服務和地址。

一種可能的解決方案是將客戶端所管理的 ID 和所需的響應服務地址作為 參數基於排隊服務合約明確傳遞給每個操作:

[ServiceContract]
interface ICalculator
{
  [OperationContract(IsOneWay = true)]
  void Add(int number1,int number2,
       string responseAddress,string methodID);
}

同樣,排隊 服務也可以將響應服務的方法 ID 作為參數基於排隊響應合約明確傳遞給每個操 作:

[ServiceContract]
interface ICalculatorResponse
{
  [OperationContract(IsOneWay = true)]
  void OnAddCompleted(int result,ExceptionDetail error,
             string methodID);
}

使用消息頭

盡管將地 址和 ID 作為顯式參數進行傳遞能夠解決以上問題,但這種做法會改變原始的合 約,並且在業務級參數的基礎上又在同一操作中引入了管道級參數。因而更好的 解決方案是讓客戶端將響應地址和操作 ID 存儲在調用的傳出消息頭中。這種方 式是在將帶外信息(只有通過此方式才會出現在服務合約中的信息)傳遞給服務 時通常所采用的技術。

操作上下文會提供傳入和傳出標頭的集合,這些集 合可以通過 IncomingMessageHeaders 和 OutgoingMessageHeaders 屬性來獲取 :

public sealed class OperationContext : ...
{
  public MessageHeaders IncomingMessageHeaders {get;}
  public MessageHeaders OutgoingMessageHeaders {get;}
  ... //更多成員
}

各集合均為 MessageHeaders 類型,代表 MessageHeader 對 象的集合:

public sealed class MessageHeaders : IEnumerable<...>, ...
{
  public void Add (MessageHeader header);
  public T GetHeader<T>(int index);
  public T GetHeader<T>(string name,string ns);
  ... //更多成員
}

不能使用 MessageHeader 類來與應 用程序開發人員直接進行交互,而應使用可提供類型安全性並輕松將公共語言運 行時 (CLR) 類型轉換為消息頭的 MessageHeader<T> 類:

public abstract class MessageHeader : ...
{...}
public class MessageHeader<T>
{
  public MessageHeader();
  public MessageHeader(T content);
  public T Content {get;set;}
  public MessageHeader GetUntypedHeader(string name,string ns);
  ... //更多成員
}

對於 MessageHeader<T> 的類型參數,可以使用任何可序 列化類型或數據合約類型。可以圍繞 CLR 類型構造 MessageHeader<T>, 然後使用 GetUntypedHeader 方法將其轉換為 MessageHeader 並存儲在傳出消息 頭中。GetUntypedHeader 要求您提供通用類型參數名稱和命名空間。名稱和命名 空間將用於從標頭集合中查找標頭。查找操作可通過 MessageHeaders 的 GetHeader<T> 方法來執行。調用 GetHeader<T> 可獲取所用 MessageHeader<T> 類型參數的值。

ResponseContext 類

由 於客戶端需要在消息頭中傳遞地址和方法 ID,因而僅僅一個基元類型參數並不能 滿足要求,而應使用 ResponseContext 類(如圖 4 所示,其中省略了一些錯誤 處理代碼)。

Figure4ResponseContext 類

Service Side
[ServiceContract]
interface IMyContract
{
  [OperationContract(IsOneWay = true)]
  void MyMethod();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract
{
  [OperationBehavior (TransactionScopeRequired = true)]
  public void MyMethod() {...}
}
Client Side
MyContractClient proxy = new MyContractClient();
proxy.MyMethod();
proxy.Close ();

ResponseContext 提供了一個位置來存儲響應地址和 ID。此 外,如果客戶端要使用單獨的錯誤響應服務,ResponseContext 還提供了一個字 段用來存儲錯誤響應服務地址。(圖 4 並未體現出這一點,完整代碼請參閱本期 的下載內容。)

客戶端負責構造具有唯一 ID 的 ResponseContext 實例 。盡管客戶端可以將此 ID 作為構造參數來提供,但它也可以使用 ResponseContext 的構造函數(僅接受響應地址參數),並讓該構造函數為此 ID 生成 GUID。ResponseContext 的重要屬性是靜態 Current 屬性。它完全封裝了 與消息頭的交互。通過訪問 Current 屬性,您便獲得了一個理想的編程模型: get 訪問器從傳入消息頭中讀取 ResponseContext 實例,而 set 訪問器將 ResponseContext 實例存儲在傳出標頭中。

客戶端編程

客戶端可 通過對每次調用使用不同的 ResponseContext 實例來為每個方法調用提供一個 ID。客戶端需要將 ResponseContext 實例存儲在傳出消息頭中。此外,客戶端還 必須在新的操作上下文中執行該操作,而不能使用其現有操作上下文。這一要求 對於服務和非服務客戶端都同樣適用。Windows Communication Foundation 使客 戶端能夠通過如下定義的 OperationContextScope 類來對當前線程采用新的操作 上下文:

public sealed class OperationContextScope : IDisposable
{
  public OperationContextScope(IContextChannel channel);
  public OperationContextScope(OperationContext context);
  public void Dispose();
}

OperationContextScope 是在現有上下文不適宜的情況下轉換新 上下文的常規技術。OperationContextScope 的構造函數用新上下文替換當前線 程的操作上下文。在 OperationContextScope 實例上調用 Dispose 即可恢復原 來的上下文(即使它為空)。如果不調用 Dispose,可能會破壞同一線程中需要 使用先前上下文的其他對象。因此,OperationContextScope 專用於 using 語句 中,僅僅為某一代碼作用域提供新的操作上下文(即使遇到異常)。

當構 造新的 OperationContextScope 實例時,應向其構造函數提供調用所用代理的內 部通道。客戶端需要創建新的 OperationContextScope,並在作用域內分配給 ResponseContext.Current。具體步驟如圖 5 所示。

Figure5針對響應服務的客戶端編程

string methodId = GenerateMethodId(); // 使用輔助方法
string responseQueue =
  "net.msmq://localhost/private/MyCalculatorResponseQueue";
CalculatorClient proxy = new CalculatorClient();
using (OperationContextScope contextScope =
  new OperationContextScope(proxy.InnerChannel))
{
  ResponseContext.Current = new ResponseContext(
    responseQueue,methodId);
  proxy.Add(2,3);
}
proxy.Close();

為使客戶端的工作簡單化、自動化,需要使用 封裝圖 5 所示響應服務設置步驟的代理基類。與雙向回調不同的是,Windows Communication Foundation 並不提供這樣的代理類,因此必須手動創建一個代理 類。為簡化該任務,我編寫了如圖 6 所定義的 ResponseClientBase<T>。

Figure6ResponseClientBase<T>

public class ResponseClientBase<T> : ClientBase<T> where T : class
{
  public readonly string ResponseAddress;
  public ResponseClientBase(string responseAddress)
  {
    ResponseAddress = responseAddress;
  }
  public ResponseClientBase(string responseAddress,string endpointName)
    : base(endpointName)
  {
   ResponseAddress = responseAddress;
  }
  public ResponseClientBase(string responseAddress,
   NetMsmqBinding binding,EndpointAddress remoteAddress) :
     base(binding,remoteAddress)
  {
   ResponseAddress = responseAddress;
  }
  protected string Enqueue(string operation,params object[] args)
  {
    using(OperationContextScope contextScope =
     new OperationContextScope(InnerChannel))
   {
     string methodId = GenerateMethodId();

     ResponseContext.Current =
      new ResponseContext (ResponseAddress,methodId);
     Type contract = typeof(T);
     MethodInfo methodInfo = contract.GetMethod(operation);
     methodInfo.Invoke(Channel,args);
     return responseContext.MethodId;
   }
  }
  protected virtual string GenerateMethodId()
  {
   return Guid.NewGuid().ToString();
  }
}

要使用 ResponseClientBase<T>,應從其中派生一個具體類,並為類型參數提供排 隊合約類型。與普通代理的處理方式不同的是,不要再從合約派生子類,而應提 供一組類似的方法,這些方法都將返回方法 ID 的字符串,這些字符串不能為 void。(這就是您不能從合約派生子類的原因,因為基於合約的操作不會返回任 何內容,所有操作都是單向的)。例如,圖 7 顯示了使用此排隊服務合約的、可 識別響應服務的對應代理過程:

[ServiceContract]
interface ICalculator
{
  [OperationContract(IsOneWay = true)]
  void Add(int number1,int number2);
  ... //更多操作
}

Figure7從 ResponseClientBase<T> 派生

class CalculatorClient : ResponseClientBase<ICalculator>
{
  public CalculatorClient(string responseAddress) :
   base (responseAddress) {}
  public CalculatorClient(string responseAddress,string endpointName) :
   base (responseAddress,endpointName) {}
  public CalculatorClient (string responseAddress,
   NetMsmqBinding binding,EndpointAddress remoteAddress) :
   base (responseAddress,binding,remoteAddress) {}
  ... //更多構造函數
  public string Add(int number1,int number2)
  {
    return Enqueue("Add",number1,number2);
  }
}

使用 ResponseClientBase<T> 後,圖 5 所示的代碼將簡 化為:

string responseAddress =
  "net.msmq://localhost/private/MyCalculatorResponseQueue";
CalculatorClient proxy = new CalculatorClient(responseAddress);
string methodId = proxy.Add(2,3);
proxy.Close ();

ResponseClientBase<T> 的虛擬方法 GenerateMethodId 使用方法 ID 的 GUID。ResponseClientBase<T> 的子 類可將其覆蓋,並提供任何其他唯一的字符串,如遞增的整數。

ResponseClientBase<T> 的構造函數接受響應地址和常規代理參數 ,如端點名稱、地址和綁定。構造函數將響應地址存儲在只讀的公共字段中。 ResponseClientBase<T> 從常規 ClientBase<T> 中派生而來,因此 所有構造函數將委托給其各自的基本構造函數。

ResponseClientBase<T> 的核心是 Enqueue 方法。Enqueue 將接 受要調用(實際上排列消息)的操作名稱和操作參數,創建新的操作上下文作用 域,生成新的方法 ID,並將該 ID 和響應地址存儲在 ResponseContext 中。它 通過設置 ResponseContext.Current 將 ResponseContext 分配給傳出消息頭, 然後使用反射來調用所提供的操作名稱。由於使用了反射和後期綁定,因而 ResponseClientBase<T> 不支持合約層次結構或重載操作。對於這些情況 ,需要進行手動編碼,如圖 5 所示。

服務端編程

排隊服務通過 ResponseContext.Current 訪問其傳入消息頭,並從中讀取響應地址和方法 ID。 服務需要使用該地址來構造響應服務的代理,需要將該 ID 提供給響應服務。服 務通常並不會直接使用 ID。

服務可以使用與客戶端相同的技術來將方法 ID 傳遞給響應服務:使用傳出消息頭將 ID 向外傳遞給響應服務,而不使用顯式 參數。與客戶端一樣,服務也必須通過 OperationContextScope 來采用新的操作 上下文,以便能夠修改傳出標頭集合。服務可以編程方式構造響應服務的代理, 並將響應地址和 NetMsmqBinding 實例提供給該代理。服務甚至還可以從配置文 件中讀取綁定設置。具體步驟如圖 8 所示。

Figure8針對響應服務的服務端編程

[ServiceBehavior (InstanceContextMode = InstanceContextMode.PerCall)]
class MyCalculator : ICalculator
{
  [OperationBehavior (TransactionScopeRequired = true)]
  public void Add(int number1,int number2)
  {
   int result = 0;
    ExceptionDetail error = null;
   try
   {
     result = number1 + number2;
   }
   catch(Exception exception) //不要再次引發
   {
     error = new ExceptionDetail(exception);
   }
   finally
    {
     ResponseContext responseContext = ResponseContext.Current;

     EndpointAddress responseAddress =
      new EndpointAddress (responseContext.ResponseAddress);

     NetMsmqBinding binding = new NetMsmqBinding();
     CalculatorResponseClient proxy =
      new CalculatorResponseClient (binding,responseAddress);

     using (OperationContextScope scope =
      new OperationContextScope(proxy.InnerChannel))
     {
       ResponseContext.Current = responseContext;
       proxy.OnAddCompleted(result,error);
     }

     proxy.Close();
   }
  }
}

服務會捕獲由 業務邏輯操作所引發的所有異常,並使用 ExceptionDetail 對象將各個異常進行 包裝。服務不會再次引發異常,因為異常會中止用於將響應消息排入響應服務隊 列中的事務,所以再次引發異常會取消響應。

在 finally 語句中,無論 是否出現異常,服務都會作出響應。它使用來自響應上下文的地址和 NetMsmqBinding 的新實例來構造響應服務的代理。服務使用代理的內部通道來播 種新的 OperationContextScope。在新的作用域中,服務會將通過設置 Response.Current 而收到的相同響應上下文添加到新環境的傳出標頭中,然後調 用響應服務代理,實際上就是使響應排入隊列中。之後,服務會釋放上下文作用 域並關閉代理。

為使服務從標頭中提取響應參數時的工作簡單化、自動化 ,我創建了 ResponseScope<T> 類:

public class ResponseScope<T> : IDisposable where T : class
{
  public readonly T Response;
  public ResponseScope();
  public ResponseScope(string bindingConfiguration);
  public ResponseScope(NetMsmqBinding binding);
  public void Dispose ();
}

ResponseScope<T> 是可釋放的對象,它會安裝 新的操作上下文,當它被釋放後,作用域將恢復為原操作上下文。為實現自動化 (即使遇到異常),應在 using 語句中使用 ResponseScope<T>。

ResponseScope<T> 接受代表響應合約的類型參數,並為 Response 提供相同類型的只讀公共字段。Response 是響應服務的代理, ResponseScope<T> 的客戶端使用 Response 來調用響應服務上的操作。 Response 無需釋放,因為 ResponseScope<T> 會在 Dispose 中這樣做。 ResponseScope<T> 將根據配置文件中的默認端點或使用提供給構造函數的 綁定信息(配置節名或實際綁定實例)來將 Response 實例化。圖 9 顯示了 ResponseScope<T> 的實現。

Figure9實現 ResponseScope<T>

public class ResponseScope<T> : IDisposable where T : class
{
  private OperationContextScope m_Scope;
  public readonly T Response;
  public ResponseScope() : this(new NetMsmqBinding()) {}
  public ResponseScope(string bindingConfiguration) :
    this(new NetMsmqBinding(bindingConfiguration) {}
  public ResponseScope(NetMsmqBinding binding)
  {
    ResponseContext responseContext = ResponseContext.Current;
    EndpointAddress address =
     new EndpointAddress (responseContext.ResponseAddress);
   ChannelFactory<T> factory = new ChannelFactory<T>(binding, address);
    Response = factory.CreateChannel();
   //立刻切換上下文
    m_Scope = new OperationContextScope(Response as IContextChannel);
   ResponseContext.Current = responseContext;
  }
  public void Dispose()
  {
   IDisposable disposable = Response as IDisposable;
    disposable.Dispose();
   m_Scope.Dispose();
  }
}

使用 ResponseScope<T> 後,圖 8 所示的 finally 語句 將簡化為:

finally
{
  using (ResponseScope<ICalculatorResponse> scope =
   new ResponseScope<ICalculatorResponse>())
  {
    scope.Response.OnAddCompleted(result,error);
  }
}

ResponseScope<T> 的構造函數使用 ResponseContext.Current 來提取傳入的響應向下文。然後使用通道工廠通過響 應服務代理來將 Response 初始化。實現 ResponseScope<T> 的關鍵在於 不要在 using 語句中使用 OperationContextScope。ResponseScope<T> 通過構造新的 OperationContextScope 來建立新的操作上下文。 ResponseScope<T> 會封裝 OperationContextScope 並保存新創建的 OperationContextScope。在 using 語句中釋放 ResponseScope<T> 後, 即會恢復原有上下文。然後,ResponseScope<T> 會使用 ResponseContext.Current 將響應上下文添加到新操作上下文的傳出標頭中,然 後返回。

響應服務編程

響應服務會訪問其傳入消息頭集合,從中 讀取方法 ID 並相應作出響應。圖 10 演示了此類響應服務一種可能的實現方式 。

Figure10實現響應服務

[ServiceBehavior (InstanceContextMode = InstanceContextMode.PerCall)]
class MyCalculatorResponse : ICalculatorResponse
{
  [OperationBehavior(TransactionScopeRequired = true)]
  public void OnAddCompleted(int result,ExceptionDetail error)
  {   
   string methodID = ResponseContext.Current.MethodId;

   if(error == null)
   {
     //通知客戶端 、觸發事件等等
   }
   else
   {
     //處理錯誤
   }
  }
}

響應服務通過 ResponseContext.Current 訪問其操作上下文傳入標頭,像排隊服務那樣提取方 法 ID 並處理操作的完成或錯誤。

結束語

排隊服務適用於多種應 用環境。如果您的應用環境需要服務向客戶端報告操作的完成、錯誤和結果,通 過本專欄所提供的助手類可以擴展 Windows Communication Foundation 的基本 功能,它們通過一兩行代碼便補充了這一原本沒有的支持功能。

本專欄所 介紹的 Windows Communication Foundation 技術(例如與消息頭進行交互、替 換操作上下文、編寫自定義代理基類及自定義上下文)在許多其他情況下也都非 常有用,對於自定義框架尤為如此。

將您想向 Juval 詢問的問題和提出 的意見發送至[email protected].

Juval Lowy是 IDesign 公司的軟 件架構師,致力於提供 WCF 培訓和 WCF 體系結構咨詢服務。他最近編寫了一本 名為《Programming WCF Services》(WCF 服務編程技術)(O'Reilly, 2007) 的新書。另外,他還是 Microsoft 硅谷地區的區域總監。可以通過 www.idesign.net 與 Juval 取得聯系。

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