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方法,判斷所有的係統綁定是否為事務流轉提供支持。從輸出結果來看,除了BasicHttpBinding、NetMsmqBinding和MsmqIntegrationBinding三種,其餘的係統綁定均包含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的綁定(NetMsmqBinding和MsmqIntegrationBinding)隻能采用單向(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屬性值一致。
對於NetTcpBinding和NetNamedPipeBinding來說,我們可以通過屬性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的事務處理協議,其中WSHttpBinding、WSDualHttpBinding、WSFederationHttpBinding支持的協議是WS-AT 1.0,而WS2007HttpBinding和WS2007FederationHttpBinding支持的是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屬性(僅限於NetTcpBinding和NetNamedPipeBinding)可以通過配置的方式指定。下麵的配置中定義了開啟了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設置
事務綁定 |
非事務綁定 |
|
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>
圖1 客戶端在Mandatory事務流轉選項情況下采用非事務綁定拋出的異常
上麵所說的是不同的事務流轉選項和綁定類型在客戶端的表現行為,現在我們將目光轉移到服務端。較之客戶端,服務的情況要稍微複雜一些,處理考慮事務流轉選項和綁定對事務流轉的支持之外,還需要考慮以下三個因素:
- 接收的消息中是否具有包含流入事務的SOAP報頭;
- 如果包括需要考慮流入事務在SOAP報頭中的XML格式是否與綁定采用的事務處理協議一致;
- 如果不一致需要考慮事務報頭的MustUnderstand屬性是True(或1)還是False(或者0)。
WCF服務端服務流轉表現出來的最終行為決定於上述的五個要素,下麵的表格流出了它們之間不同的組合最終表現出來的事務處理行為。
首先,如果一個服務契約的任何一個操作的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>
圖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>
圖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: }
圖4 客戶端端和服務端采用不同的事務處理協議導致的異常(Allowed)
相似情況同樣發生在TransactionFlow選項為NotAllowed的時候,所不同的是:即使接收到的事務報頭與綁定采用的事務處理協議相匹配,仍然會導致事務報頭不能理解的異常。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 16:04:38