閱讀776 返回首頁    go 阿裏雲 go 技術社區[雲棲]


WCF技術剖析之三十一: WCF事務編程[中篇]

[續《上篇》]通過將TransactionFlowAttribute特性應用在服務契約的某個操作之上,並指定相應的TransactionFlowOption枚舉直,僅僅定義了事務流轉的策略而已。或者說,通過這種方式確定對事物流轉的一種意願,客戶端是否願意將當前事務流出,服務端是否願意接受流入的事務,可以通過TransactionFlowAttribute特性進行控製。所以說,服務操作上定義個TransactionFlowAttribute特性是是否進行事務流轉的總開關,真正的事務傳播是建立在TransactionFlowOption.Allowed或者TransactionFlowOption.Mandatory之上的。

至於WCF框架是否有能力對事物進行流轉,按照怎樣的協議進行流轉,則是通過綁定實現的,現在我們首先看看怎樣的綁定具有事務流轉的能力。

一、綁定對事務流轉的支持

WCF技術剖析(卷1)》中的第3章對綁定的本質進行了深層次的剖析,閱讀過本章的讀者應該知道:綁定是一係列綁定元素(BindingElement)的有序組合,相應的綁定元素對消息進行相應的處理以實現特定的目標,比如MessageEncodingBindingElement實現對消息的編碼和解碼,TransportBindingElement實現對消息的傳輸。

消息交換是WCF進行通信的唯一手段,任何需要傳輸的數據最終都需要最為消息的一部分。對象事務流轉來說,客戶端需要將當前事務進行序列化並嵌入到消息中;服務端則需要從接收到的消息中提取事務相關信息,反序列化以重建事務。這樣的操作同樣實現在一個綁定元素中,即TransactionFlowBindingElement

既然TransactionFlowBindingElement實現了對事物的流轉,那麼我們就可以根據某個綁定對象的綁定元素集合中是否包含該元素判斷綁定是否支持事務流轉。為此,我寫了如下一個簡單的方法,傳入相應的Binding對象,打印出相應的綁定類型是否支持事務流轉:

   1: static void PrintTransactionFlowSupport(Binding binding)
   2: {
   3:     TransactionFlowBindingElement transactionFlowElement = binding.CreateBindingElements().Find< TransactionFlowBindingElement>();
   4:     Console.WriteLine("{0,-30} {1}",binding.GetType().Name,transactionFlowElement!=null?"Yes":"No");
   5: }

現在,我們通過調用PrintTransactionFlowSupport方法,判斷所有的係統綁定是否為事務流轉提供支持。從輸出結果來看,除了BasicHttpBindingNetMsmqBindingMsmqIntegrationBinding三種,其餘的係統綁定均包含TransactionFlowBindingElement綁定元素,也就是說它們均具有對事務就是傳播的能力。

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         Console.WriteLine("{0,-30} {1}", "Binding", "Transaction Flow");
   6:         Console.WriteLine("--------------------------------------------");
   7:         //BasicHttpBinding
   8:         PrintTransactionFlowSupport(new BasicHttpBinding());
   9:  
  10:         //WS Binding
  11:         PrintTransactionFlowSupport(new WSHttpBinding());
  12:         PrintTransactionFlowSupport(new WS2007HttpBinding());
  13:         PrintTransactionFlowSupport(new WSDualHttpBinding());
  14:         PrintTransactionFlowSupport(new WSFederationHttpBinding());
  15:         PrintTransactionFlowSupport(new WS2007FederationHttpBinding());
  16:  
  17:         //TCP and IPC Binding
  18:         PrintTransactionFlowSupport(new NetTcpBinding());
  19:         PrintTransactionFlowSupport(new NetNamedPipeBinding());
  20:         //MSMQ Binding
  21:         PrintTransactionFlowSupport(new NetMsmqBinding());
  22:         PrintTransactionFlowSupport(new MsmqIntegrationBinding());
  23:     }
  24: }

輸出結果:

Binding                            Transaction Flow
-----------------------------------------------
BasicHttpBinding                   No
WSHttpBinding                      Yes
WS2007HttpBinding                  Yes
WSDualHttpBinding                  Yes
WSFederationHttpBinding            Yes
WS2007FederationHttpBinding        Yes
NetTcpBinding                      Yes
NetNamedPipeBinding                Yes
NetMsmqBinding                     No
MsmqIntegrationBinding             No

由於BasicHttpBinding基於WS-I Basic Profile標準的綁定,而兩個基於MSQM的綁定(NetMsmqBindingMsmqIntegrationBinding)隻能采用單向(One-Way)的消息交換模式,所以它們不具有事務流轉的能力。但是,即使對於契約的支持事務的綁定類型,事務流轉默認也是被關閉的,在真正需要事先事務流轉的場景中,需要通過配置或者編成的方式開啟該選項。此外,事務流轉涉及事務在消息中的格式化問題,而事務的格式化決定於采用的協議。通過《談談分布式事務之四: 兩種事務處理協議OleTx與WS-AT》我們知道,WCF支持三種不同的事務處理協議:OleTx,WS-AT 1.0和WS-AT 1.0。事務處理協議通過類型TransactionProtocol類型表示,TransactionProtocol定義如下:

   1: public abstract class TransactionProtocol
   2: {    
   3:     public static TransactionProtocol Default { get; }
   4:  
   5:     public static TransactionProtocol OleTransactions { get; }
   6:     public static TransactionProtocol WSAtomicTransactionOctober2004 { get; }
   7:     public static TransactionProtocol WSAtomicTransaction11 { get; }
   8: }

TransactionProtocol是一個抽象類,定義了三種靜態隻讀屬性OleTransactions、WSAtomicTransactionOctober2004和WSAtomicTransaction11,用於獲取分別代表OleTx,WS-AT 1.0和WS-AT 1.0三種協議的具體TransactionProtocol對象。這三種具體的TransactionProtocol類型以內部(Internal)類型的方式定義。Default製度屬性返回默認的事務處理協議,和OleTransactions屬性值一致。

對於NetTcpBindingNetNamedPipeBinding來說,我們可以通過屬性TransactionFlow設置或者獲取綁定是否支持事務流轉的開關,並通過TransactionProtocol屬性設置或者獲取綁定支持的事務處理協議。

   1: public class NetTcpBinding : Binding, IBindingRuntimePreferences
   2: {
   3:     //其他成員
   4: public bool TransactionFlow { get; set; }
   5: public TransactionProtocol TransactionProtocol { get; set; }
   6: }
   7:  
   8: public class NetNamedPipeBinding : Binding, IBindingRuntimePreferences
   9: {
  10:     //其他成員
  11:     public bool TransactionFlow { get; set; }
  12:     public TransactionProtocol TransactionProtocol { get; set; }
  13: }

而對於基於WS的綁定來說,由於綁定本身就是為跨平台和互操作涉及的,所以僅僅支持基於WS-AT的事務處理協議,其中WSHttpBindingWSDualHttpBindingWSFederationHttpBinding支持的協議是WS-AT 1.0,而WS2007HttpBindingWS2007FederationHttpBinding支持的是WS-AT 1.1。所以,它們僅僅具有TransactionFlow屬性,並沒有TransactionProtocol屬性,該屬性定義在它們的基類WSHttpBindingBase上麵:

   1: public abstract class WSHttpBindingBase : Binding, IBindingRuntimePreferences
   2: {
   3:     //其他成員
   4:     public bool TransactionFlow { get; set; }
   5: }

係統綁定的TransactionFlow和TransactionProtocol屬性(僅限於NetTcpBindingNetNamedPipeBinding)可以通過配置的方式指定。下麵的配置中定義了開啟了transactionFlow開關的兩個綁定(NetTcpBinding和WS2007HttpBinding),並將其中的NetTcpBinding的TransactionProtocol設置成基於WS-AT 1.0的協議(transactionProtocol="WSAtomicTransactionOctober2004")。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <bindings>
   5:             <netTcpBinding>
   6:                 <binding name="transactionalTcpBinding" transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004" />
   7:                 </netTcpBinding>
   8:           <ws2007HttpBinding>
   9:             <binding name="transactionalHttpBinding" transactionFlow="true" />
  10:           </ws2007HttpBinding>
  11:         </bindings>
  12:         <services>
  13:             <service name="Artech.TransactionalServices.BankingService">
  14:                 <endpoint address="net.tcp://127.0.0.1/bankingservice" binding="netTcpBinding"
  15:                     bindingConfiguration="transactionalTcpBinding" contract="Artech.TransactionalServices.IBankingService" />
  16:               <endpoint address="https://127.0.0.1/bankingservice" binding="ws2007HttpBinding"
  17:       bindingConfiguration="transactionalHttpBinding" contract="Artech.TransactionalServices.IBankingService" />
  18:             </service>
  19:         </services>
  20:     </system.serviceModel>
  21: </configuration>

如果現有的係統綁定不能滿足你的需要(比如你需要同時采用HTTP傳輸協議和OleTx事務處理協議),可以通過編程或者配置的方式創建自定的綁定(CustomBinding)。創建支持事務流轉的自定義綁定的時候,你需要做的僅僅是將TransactionFlowBindingElement添加到綁定元素集合中,並設置TransactionFlow和TransactionProtocol屬性即可。下麵的配置就定義了這樣一個基於OleTx的HTTP綁定。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <bindings>
   5:             <customBinding>
   6:                 <binding name="transactionalBinding">
   7:                     <textMessageEncoding />
   8:                     <transactionFlow transactionProtocol="OleTransactions"/>
   9:                     <httpTransport />
  10:                 </binding>
  11:             </customBinding>
  12:         </bindings>
  13:         <services>
  14:             <service name="Artech.TransactionalServices.BankingService">
  15:                 <endpoint address="https://127.0.0.1/bankingservice" binding="customBinding"
  16:                     bindingConfiguration="transactionalBinding" contract="Artech.TransactionalServices.IBankingService" />
  17:             </service>
  18:         </services>
  19:     </system.serviceModel>
  20: </configuration>

二、 綁定與TransactionFlow設置

通過應用TransactionFlowAttribute特性為某個操作設置相應的事務流轉策略,綁定決定了實現事務流轉的能力和方式,兩個的不同組合表現出不同的事務流轉行為。在這裏,事務的流轉包含兩個層麵的意思,即事務的流出或者發送,以及事務的流入或者接收。對於WCF的客戶端框架來說,對於通過TransactionFlowAttribute特性設置的三個選項來說,NotAllowed和Allowed對綁定的事務流轉能力沒有任何要求,而Madantory則強製要求終結點的綁定能夠實現事務的流轉(綁定本身能夠支持事務流轉並且TransactionFlow開關必須開啟)。結合上麵所介紹的,事務流轉選項和綁定類型兩兩組合所表現出的行為如下麵的表格所示(這裏的事務綁定表示TransactionFlow開關開啟的支持事務流轉的綁定)。
 

事務綁定

非事務綁定

NotAllowed

當前事務不需要存在,存在的當前事務不會被流出

當前事務不需要存在,存在的當前事務不會被流出

Allowed

當前事務不需要存在,存在的當前事務會被流出

當前事務不需要存在,存在的當前事務不會被流出

Mandatory

當前事務必須存在,存在的當前事務會被流出

不合法的組合

對於一個服務契約來說,如果任何一個操作的TransactionFlow選項被定義成Mandatory,相應終結點所采用的綁定必須是事務綁定(接下來我們將本身支持事務流轉,並開啟了TransactionFlow開關的綁定稱為事務綁定)。下麵的代碼和配置中,通過TransactionFlowAttribute將唯一的Transfer操作的事務流轉選項設置為Mandatory,並選用不支持事務流轉的BasicHttpBinding。當使用創建的ChannelFactory<TChannel>創建服務代理的時候,拋出如圖1所示的InvalidOperationException異常。

   1: [ServiceContract(Namespace="https://www.artech.com/")]
   2: public interface IBankingService
   3: {
   4:     [OperationContract]
   5:     [TransactionFlow(TransactionFlowOption.Mandatory)]
   6:     void Transfer(string accountFrom, string accountTo, double amount);
   7: }
   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <system.serviceModel>    
   4:     <client>
   5:       <endpoint address="https://127.0.0.1:3721/bankingservice" binding="basicHttpBinding" contract="Artech.TransactionalServices.IBankingService" name="bankingservice" />
   6:     </client>    
   7:   </system.serviceModel>
   8: </configuration>

image

圖1 客戶端在Mandatory事務流轉選項情況下采用非事務綁定拋出的異常

上麵所說的是不同的事務流轉選項和綁定類型在客戶端的表現行為,現在我們將目光轉移到服務端。較之客戶端,服務的情況要稍微複雜一些,處理考慮事務流轉選項和綁定對事務流轉的支持之外,還需要考慮以下三個因素:

  • 接收的消息中是否具有包含流入事務的SOAP報頭;
  • 如果包括需要考慮流入事務在SOAP報頭中的XML格式是否與綁定采用的事務處理協議一致;
  • 如果不一致需要考慮事務報頭的MustUnderstand屬性是True(或1)還是False(或者0)。

WCF服務端服務流轉表現出來的最終行為決定於上述的五個要素,下麵的表格流出了它們之間不同的組合最終表現出來的事務處理行為。

image

首先,如果一個服務契約的任何一個操作的TransactionFlow選項定義成Mandatory,那麼強製要求相應的終結點采用事務綁定。比如說,同樣對於上麵定義的IBankingService服務契約(TransactionFlow),但是使用默認的WS2007HttpBinding(默認情況下TransactionFlow是關閉的),在進行服務寄宿的時候,會拋出如圖2所示的InvalidOperationException異常。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>      
   4:         <services>
   5:             <service name="Artech.TransactionalServices.BankingService">
   6:                 <endpoint address="https://127.0.0.1:3721/bankingservice" binding="ws2007HttpBinding" contract="Artech.TransactionalServices.IBankingService" />
   7:             </service>
   8:         </services>
   9:     </system.serviceModel>
  10: </configuration>

image

圖2 客戶端在Mandatory事務流轉選項情況下采用非事務綁定拋出的異常

其次,同樣對於TransactionFlow選項為Mandatory的操作,如果接收的消息並不包含流入事務的SOAP報頭,或者說流入的事務在SOAP報頭中的表示並不符合綁定采用的事務處理協議,由於Mandatory選項在服務端的含義就是強製需要流入一個可以理解的事務,在這種情況下服務端會返回一個Fault消息,並導致客戶端拋出異常。同樣是對於前麵給定義的IBankingService服務契約,如果我們將客戶端和服務端終結點的綁定配置成不同的事務處理協議,比如客戶端采用默認的OleTx,服務端則采用WS-AT 1.1。客戶端在進行服務調用的時候,會拋出如圖3所示的ProtocolException異常。

客戶端配置:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <bindings>
   5:       <netTcpBinding>
   6:         <binding name="nonTransactionalBinding" transactionFlow="true"/>
   7:       </netTcpBinding>
   8:     </bindings>
   9:     <client>
  10:       <endpoint address="net.tcp://127.0.0.1:3721/bankingservice" binding="netTcpBinding" bindingConfiguration="nonTransactionalBinding" contract="Artech.TransactionalServices.IBankingService" name="bankingservice" />
  11:     </client>    
  12:   </system.serviceModel>
  13: </configuration>

服務端配置:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>      
   4:         <bindings>
   5:             <netTcpBinding>
   6:                 <binding name="transactionalBinding" transactionFlow="true" transactionProtocol="WSAtomicTransaction11" />
   7:             </netTcpBinding>
   8:         </bindings>
   9:         <services>
  10:             <service name="Artech.TransactionalServices.BankingService">
  11:                 <endpoint address="net.tcp://127.0.0.1:3721/bankingservice" binding="netTcpBinding"
  12:                     bindingConfiguration="transactionalBinding" contract="Artech.TransactionalServices.IBankingService" />
  13:             </service>
  14:         </services>
  15:     </system.serviceModel>
  16: </configuration>

image

圖3 客戶端端和服務端采用不同的事務處理協議導致的異常(Mandatory)

倘若接收到的消息中存在事務報頭,並且報頭的MustUnderstand屬性為True或者1,對於Allowed選項來說,如果采用非事務綁定,或者說雖然采用事務綁定,但是事務報頭與綁定采用的事務處理協議不符。在這種情況下,服務端不能有效地理解事務報頭,也會向客戶端返回一個Fault消息,並導致客戶端拋出異常。比如說,我們采用上麵提供的配置(客戶端和服務端綁定采用不同的事務處理協議),如果我們將服務契約IBankingService的Transfer操作的TransactionFlow選項設置為Allowed,客戶端在進行服務調用的時候會拋出如圖4所示的ProtocolException異常。但是,如果MustUnderstand屬性為False或者0,事務報頭會被忽略。

   1: [ServiceContract(Namespace = "https://www.artech.com/")]
   2: public interface IBankingService
   3: {
   4:     [OperationContract]
   5:     [TransactionFlow(TransactionFlowOption.Allowed)]
   6:     void Transfer(string accountFrom, string accountTo, double amount);
   7: }

image 圖4 客戶端端和服務端采用不同的事務處理協議導致的異常(Allowed)

相似情況同樣發生在TransactionFlow選項為NotAllowed的時候,所不同的是:即使接收到的事務報頭與綁定采用的事務處理協議相匹配,仍然會導致事務報頭不能理解的異常。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-27 16:04:38

  上一篇:go  WCF技術剖析之三十一:WCF事務編程[上篇]
  下一篇:go  WCF技術剖析之三十一: WCF事務編程[下篇]