如何创建一个简单的基于OleTx协议的WCF事务实例

    由于工作需要,最近研究了一下WCF分布式事务方面的技术,自己做了一个基于OleTx协议的WCF事务实例,供大家参考。

    一、实例背景

    本实例以大家熟悉的银行转账业务为背景,在几台不同的机器上部署一套基于OleTx协议的WCF分布式服务。

    本例需要A、B、C、D四台计算机,其中A表示SqlServer服务器,B部署取款服务,C部署存款服务,D部署客户端程序(调用存款和取款服务),D同时直接A进行日志写入。一次转账操作分为三个部分:存款、取款、写日志,这三部分组成一个转账事务,要么全部成功,要么全部失败。

    二、WCF服务端代码

    创建一个WCF服务项目OleTxService。

    1、契约

    取款服务契约如下:

 1 using System.Net.Security;
 2 using System.ServiceModel;
 3 using OleTxService.Models;
 4 
 5 namespace OleTxService
 6 {
 7     /// <summary>
 8     /// 取款服务接口
 9     /// </summary>
10     [ServiceContract(SessionMode = SessionMode.Required)]
11     public interface IWithDrawService
12     {
13         /// <summary>
14         /// 取款操作
15         /// </summary>
16         /// <param name="accountId">取款账号</param>
17         /// <param name="amount">取款金额</param>
18         [OperationContract]
19         [TransactionFlow(TransactionFlowOption.Mandatory)]
20         [FaultContract(typeof(MyFault), ProtectionLevel = ProtectionLevel.None)]
21         void Withdraw(string accountId, double amount);
22     }
23 }
View Code

    存款服务契约如下:

 1 using System.Net.Security;
 2 using System.ServiceModel;
 3 using OleTxService.Models;
 4 
 5 namespace OleTxService
 6 {
 7     /// <summary>
 8     /// 存款服务接口
 9     /// </summary>
10     [ServiceContract(SessionMode = SessionMode.Required)]
11     public interface IDepositService
12     {
13         /// <summary>
14         /// 存款操作
15         /// </summary>
16         /// <param name="accountId">存款账号</param>
17         /// <param name="amount">存款金额</param>
18         [OperationContract]
19         [FaultContract(typeof(MyFault), ProtectionLevel = ProtectionLevel.None)]
20         [TransactionFlow(TransactionFlowOption.Mandatory)]
21         void Deposit(string accountId, double amount);
22     }
23 }
View Code

    由于后面要设置事务操作行为的TransactionAutoComplete属性为false,所以要求其中契约的Session要设置为SessionMode.Required。

    在操作上要增加一个TransactionFlow特性,允许事务在客户端到服务端之间流转。

    2、服务实现

    取款服务如下:

 1 using System;
 2 using System.Configuration;
 3 using System.Data;
 4 using System.Data.SqlClient;
 5 using System.ServiceModel;
 6 using OleTxService.Helper;
 7 using OleTxService.Models;
 8 
 9 namespace OleTxService
10 {
11     /// <summary>
12     /// 取款服务
13     /// </summary>
14     public class WithDrawService : IWithDrawService
15     {
16         /// <summary>
17         /// 取款操作
18         /// </summary>
19         /// <param name="accountId">取款账号</param>
20         /// <param name="amount">取款金额</param>
21         [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
22         public void Withdraw(string accountId, double amount)
23         {
24             string connString = ConfigurationManager.ConnectionStrings["MyBank"].ConnectionString;
25             try
26             {
27                 var _parms = new SqlParameter[]
28                 {
29                     SQLServerHelper.BuildInParameter("id", SqlDbType.VarChar, accountId),
30                     SQLServerHelper.BuildInParameter("amount", SqlDbType.Float, amount)
31                 };
32                 SQLServerHelper.ExecuteNonQuery(connString, CommandType.StoredProcedure, "P_WITHDRAW", _parms);
33                 OperationContext.Current.SetTransactionComplete();
34             }
35             catch (Exception ex)
36             {
37                 throw new FaultException<MyFault>(new MyFault(string.Format("取款异常:{0}", ex.Message)), "取款出错");
38             }
39         }
40     }
41 }
View Code

    存款服务如下:

 1 using System;
 2 using System.Configuration;
 3 using System.Data;
 4 using System.Data.SqlClient;
 5 using System.ServiceModel;
 6 using OleTxService.Helper;
 7 using OleTxService.Models;
 8 
 9 namespace OleTxService
10 {
11     /// <summary>
12     /// 存款服务
13     /// </summary>
14     public class DepositService : IDepositService
15     {
16         /// <summary>
17         /// 存款操作
18         /// </summary>
19         /// <param name="accountId">存款账号</param>
20         /// <param name="amount">存款金额</param>
21         [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
22         public void Deposit(string accountId, double amount)
23         {
24             string connString = ConfigurationManager.ConnectionStrings["MyBank"].ConnectionString;
25             try
26             {
27                 var _parms = new SqlParameter[]
28                 {
29                     SQLServerHelper.BuildInParameter("id", SqlDbType.VarChar, accountId),
30                     SQLServerHelper.BuildInParameter("amount", SqlDbType.Float, amount)
31                 };
32                 SQLServerHelper.ExecuteNonQuery(connString, CommandType.StoredProcedure, "P_DEPOSIT", _parms);
33                 OperationContext.Current.SetTransactionComplete();
34             }
35             catch (Exception ex)
36             {
37                 throw new FaultException<MyFault>(new MyFault(string.Format("存款异常:{0}", ex.Message)), "存款出错");
38             }
39         }
40     }
41 }
View Code

    其中,取款和存款的具体代码未贴出,读者可以自己实现。具体操作:从银行账户表的一个账号(一行数据)减少金额,另一个账号增加金额。

    注意,在服务操作上增加了一个操作行为:[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]。TransactionScopeRequired=true表示该服务操作需要在一个显示的TransactionScope中。TransactionAutoComplete=false表示必须手工完成该事务,其方法是在要完成事务的地方写上:OperationContext.Current.SetTransactionComplete();

    3、配置WCF服务

    在配置文件中配置这两个服务,有关WCF服务的配置代码如下:

  <system.serviceModel>
    <bindings>
      <netTcpBinding>
        <binding name="transactionBinding" transactionFlow="true" transactionProtocol="OleTransactions" />
      </netTcpBinding>
    </bindings>
    <services>
      <service name="OleTxService.DepositService">
        <endpoint address="deposit" binding="netTcpBinding" bindingConfiguration="transactionBinding"
          contract="OleTxService.IDepositService" />
      </service>
      <service name="OleTxService.WithDrawService">
        <endpoint address="withdraw" binding="netTcpBinding" bindingConfiguration="transactionBinding"
          contract="OleTxService.IWithDrawService" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
        <add binding="basicHttpsBinding" scheme="https" />
    </protocolMapping>    
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>
View Code

    我们将服务配置成netTcpBinding,并在绑定配置中加上允许事务流转、指定事务协议的配置项:

    <bindings>
      <netTcpBinding>
        <binding name="transactionBinding" transactionFlow="true" transactionProtocol="OleTransactions" />
      </netTcpBinding>
    </bindings>
View Code

    三、在IIS中部署netTcpBinding的WCF服务

    由于我们的服务是采用netTcpBinding,在IIS中寄宿时需要有些注意的地方。

    1、部署网站

    在IIS中新增一个网站oletransaction,用来部署本例中的服务。在本例中,服务终结点中的地址配置的是相对地址,也可以为空。

    2、编辑绑定

    在IIS中寄宿非HTTP协议的WCF服务时,需要手动为网站编辑相应的绑定。在本例中,需要为服务增加一个net.tcp绑定,并在其中指定绑定端口。

    在IIS中选中网站,右击“编辑绑定(Edit Bindings...)”,在弹出的“站点绑定(Site Bindings)”对话框中添加一个net.tcp绑定。

       

    其中,绑定信息“8060:*”表示设置TCP的监听端口为8060。

    3、添加net.tcp协议支持

    需要手动在网站中添加对net.tcp协议的支持。

    选中网站,点击“高级设置(Advanced Setting...)”。

    在弹出的高级设置窗口中找到“启用的协议(Enabled Protocols)”,添加net.tcp,协议之间以逗号分隔。

    4、浏览服务

    在完成编辑绑定、添加协议支持后,即可浏览服务:

    如何不能访问,请检查防火墙是否关闭,Net Tcp监听服务是否打开。

    照此方式在B、C服务器中分别部署取款服务、存款服务。

    四、WCF客户端代码

    1、引用服务

    根据服务的WSDL地址分别引用取款服务、取款服务。

    2、编写转账代码

    转账的总体代码如下:

                using (var scope = new TransactionScope())
                {
                    try
                    {
                        Transfer(fromAccountId, toAccountId, amount);
                        var logContent = string.Format("完成一条转账操作,转出账号:{0},转入账号:{1},转账金额:{2}", fromAccountId, toAccountId,
                            amount);
                        WriteLog(logContent, remark);
                        scope.Complete();
                        Console.WriteLine("转账成功!");
                    }
                    catch (FaultException<withdraw.MyFault> ex)
                    {
                       Console.WriteLine("转账失败:{0},错误详情:{1}", ex.Message, ex.Detail.ErrorMessage); 
                    }
                    catch (FaultException<deposit.MyFault> ex)
                    {
                        Console.WriteLine("转账失败:{0},错误详情:{1}", ex.Message, ex.Detail.ErrorMessage); 
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("转账失败:{0}", ex.Message);
                    }
                }
View Code

    其中转账方法Transfer定义如下:

        /// <summary>
        /// 转账操作
        /// </summary>
        /// <param name="fromAccountId">转出账号</param>
        /// <param name="toAccountId">转入账号</param>
        /// <param name="amount">转账金额</param>
        static void Transfer(string fromAccountId, string toAccountId, double amount)
        {
            withdraw.WithDrawServiceClient clientWithdraw = new WithDrawServiceClient("NetTcpBinding_IWithDrawService");
            clientWithdraw.Withdraw(fromAccountId, amount);
            deposit.DepositServiceClient clientDeposit = new DepositServiceClient("NetTcpBinding_IDepositService");
            clientDeposit.Deposit(toAccountId, amount);

        }
View Code

    写日志的代码如下:

        /// <summary>
        /// 记录日志
        /// </summary>
        /// <param name="content">日志内容</param>
        /// <param name="remark">备注</param>
        static void WriteLog(string content, string remark)
        {
            var sql = ConfigurationManager.AppSettings["InsertBankRecord"];
            var _params = new SqlParameter[]
            {
                SQLServerHelper.BuildInParameter("ID", SqlDbType.UniqueIdentifier, Guid.NewGuid()),
                SQLServerHelper.BuildInParameter("Content", SqlDbType.NVarChar, content),
                SQLServerHelper.BuildInParameter("Remark", SqlDbType.NVarChar, remark ?? string.Empty),
                SQLServerHelper.BuildInParameter("UpdateTime", SqlDbType.DateTime, DateTime.Now)
            };
            SQLServerHelper.ExecuteNonQuery(_myConnString, CommandType.Text, sql, _params);
        }
View Code

    在转账的总体代码中,将调用取款服务、存款服务、写日志这三个操作放到一个TransactionScope变量scope中。只有当三个操作本身正常完成,并且调用scope.Complete()后,事务成功,否则事务失败。

    3、部署客户端

    将客户端部署到D计算机,并配置好服务地址、数据库连接串。

    五、配置MSDTC

    由于本例采用的是基于OleTx协议的WCF事务,用到了微软的MSDTC服务,因此,需要配置MSDTC服务。

    1、配置MSDTC

    在“管理工具(Administrative Tools)”中打开“组件服务(Component Services)”。

    找到我的计算机节点:“组件服务(Component Services)”=>“计算机(Computers)”=>“我的计算机(My Computer)”,右键点击“属性(Properties)”。

    在“MSDTC”选项卡中,勾选“使用本地协调器(Use local coordinator)”选项。

    2、配置Local DTC

    还需要配置“本地DTC(Local DTC)”。

    在组件服务中找到本地DTC节点:组件服务(Component Services)=>计算机(Computers)=>我的计算机(My Computer)=>分布式事务协调器(Distributed Transaction Coordinator),右击“属性(Properties)”。

    在弹出的本地DTC属性窗口中,点击“安全(Security)”选项卡,配置成下面的值。

    六、验证事务

    部署好整个WCF分布式事务系统后,运行程序,验证事务正确性:

  • 正常输入情况下应该完成整个事务
  • 调用服务错误情况下应该回滚整个事务
  • 本地写日志出错情况下应该回滚整个事务

    七、总结

    本文完成了一个基于OleTx协议的WCF分布式事务的代码编写和系统部署的过程。总共有三大要点:

  • WCF事务编程,主要注意契约和操作行为中的一些关于事务的特性。
  • 在IIS中部署基于netTcpBinding的WCF服务,主要关注编辑站点绑定、添加net.tcp协议支持。
  • 配置MSDTC和本地DTC,这是部署WCF分布式事务的必要条件。
posted @ 2015-07-20 17:37  捡贝壳的小哥  阅读(897)  评论(4编辑  收藏  举报