阅读提示 今天,事务不再仅适用于数据库应用程序。通过使用Windows Communication Foundation(Windows通讯基础,简写为WCF)中的统一事务系统,你不仅可以为数据库应用程序创建事务性服务,也可以为发送消息、工作流以及其它类型的应用程序创建事务性服务。
WCF的核心目的是帮助你构建基于.NET的安全可靠的和事务性的服务。在本文中,你将会在前两篇代码基础上进行构建以创建你的第一个事务性服务。
一、WCF中的事务
事务能够确保一组相关的操作以一个原子单元的方式发生。换句话说,在该单元中的每个操作必须都成功或都失败。WCF提供了一个集中的事务系统-你可以用来治理你的事务操作。在过去,事务逻辑通常用作一个标准方法来治理数据库事务(还记得VB中的BeginTrans和CommitTrans吗?),但是不存在标准方法来执行非数据库事务。WCF的目标就是用一个统一事务系统来解决这个问题-你可以应用于数据库、通讯或其它的事务性行为中。
WCF编程模型使得事务易于使用。为此,你要把一组操作组织成一个事务范围。这个范围定义了一个事务的原子。下列伪代码说明了这一点:
Using(TransactionScope theScope = new
TransactionScope())
{
Service1.submitRequest(myRequest);
Service2.submitRequest(myOtherRequest);
Service3.submitRequest(myFinalRequest);
theScope.Complete();
}
二、构建你的第一个可事务化的服务
一开始,你首先需要一个事务性的服务。这就意味着你愿意让你的服务参予一个在你的契约操作上的由客户初始化的事务,由你来指定(使用属性和配置文件)该事务的行为。在构建该服务后,你将逐步分析构建一个客户-在调用该服务时它使用一事务范围。假如你已经安装了Visual Studio.NET,那么你应该很快就能够运行起来。
让我们首先用Visual Studio.NET创建一新的Web站点。然后,选择IndigoService工程类型并且把它命名为Tservice。你的屏幕应该看上去如图1所示。
图1.创建一新的Indigo服务:在选择Indigo服务工程类型并命名其为Tservice后,你的屏幕看上去的样子。
Visual Studio将用一个称为IMyService的接口为你创建一个缺省的服务和一个称为MyService的服务类。你将在App_Code子文件夹下的Service.cs文件中找到该服务类的代码。你可以用显示在列表1中的代码来代替在这个文件中的代码。你还将需要添加一个到System.Transactions命名空间的参考引用-以便在你编译它时,代码能够正确工作。
列表1.可事务化的Web服务:
//这个Web服务与在前一篇文章中所描述的TemperatureService相同,
//但是这个版本包含了使该服务成为事务性的属性.
using System;
using System.ServiceModel;
using System.Transactions;
[ServiceContract(Namespace = "http://Devx.Indigo.Transactions")]
public interface ITransactableTemperatures
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Required)]
double ftoc(double f);
[OperationContract]
[TransactionFlow(TransactionFlowOption.Required)]
double ctof(double c);
}
[ServiceBehavior(TransactionIsolationLevel =
IsolationLevel.ReadCommitted)]
public class TransactableTemperatures : ITransactableTemperatures
{
[OperationBehavior(TransactionScopeRequired = true,
TransactionAutoComplete = true)]
public double ftoc(double f)
{
double dReturn = 0.0;
dReturn = ((f - 32) * 5) / 9;
return dReturn;
}
[OperationBehavior(TransactionScopeRequired = true,
TransactionAutoComplete = true)]
public double ctof(double c)
{
double dReturn = 0.0;
dReturn = ((c + 32) * 9) / 5;
return dReturn;
}
}
注重,该事务性Web服务的代码等同于以前文章中的"温度"Web服务-用一些新的属性把它描述为事务性的。
当在接口级上定义方法时,OperationContract的属性被用[TransactionFlow]属性加以修饰。这可以通知运行时刻在OperationContract上的操作存在疑问时如何在一个事务条件下作出响应。其有效值为Allowed-当操作可以或不可以用于一个事务中时;而当永不会用于一个事务中时它取值为NotAllowed;当必须在一个事务范围内使用时,它取值为Required。
在服务级上,[ServiceBehavior]属性通过使用TransactionIsolationLevel属性来指定事务的属性。理想情况下,事务应该展示四个要害属性-atomic,consistent,isolated和durable,缩略词为ACID。然而,保持它们之间彼此完全相互隔离可以使得持有资源锁的时间比必需的时间更长些-这可能导致锁竞争、降低性能甚至导致死锁条件-此时,不同的事务彼此都需要锁来完成自身而该锁却由另外的事务持有。设置隔离级别让你选择最匹配于其它应用程序的隔离程度。你可以通过把IsolationLevel属性值设置为ServiceBehavior的TransactionIsolationLevel属性值来设置隔离级别。在列表1中,IsolationLevel被设置为ReadCommited-这意味着在该事务中不能读取volatile型数据,但是能被修改。要全面地了解不同的隔离级别属性值,你可以参考帮助文档中的System..Transactions.IsolationLevel枚举。注重,还有一个枚举System.Data.IsolationLevel;因此,假如你在代码中参考System.Data命名空间,那么你必须以完全限定方式来使用这个IsolationLevel以避免冲突问题。
最后,列表1使用[OperationBehavior]属性定义了在服务中的Web方法中的事务行为-它把自己的TransactionScope属性设置为true。这个TransactionScope设置指明必须在某一范围内调用该操作(这将在客户内部展示出来)。TransactionAutoComplete属性也被设置为true来指示当方法完成执行时,将把该事务标志为complete。假如TransactionAutoComplete属性被设置为false,那么你将调用OperationContext.Current.SetTransactionComplete()方法来手工设置该事务的正确完成;否则,该事务将标志其为失败的。
三、运用服务
为了运行这个服务,你还需要做一些细节工作。首先,你必须更改Service.svc文件以指向新的类和接口。在该文件中,找到下面一行:
<span class="pf">Class="MyService"
把它改变为:
<span class="pf">Class="TransactableTemperatures</span>"
下一步,配置运行时刻以答应事务;否则,运行该服务将引起一个错误。为此,从一个命令行提示符上发出如下命令:
Xws_reg --wsat+
该命令配置MSDTC以处理WS-Atomic事务(WSAT)。
当然在最后,你需要编辑web.config文件以为你的服务配置serviceModel设置。完整的web.config文件看上去如下样子:
<?xml version="1.0"?>
<configuration xmlns=
"http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.serviceModel>
<services>
<service type="TransactableTemperatures">
<endpoint contract="ITransactableTemperatures" binding="wsHttpBinding" bindingConfiguration="Binding1"/>
</service >
</services >
<bindings>
图2.运行服务:该图显示出从一个浏览器中运行示例事务性服务的结果。
<wsHttpBinding>
<binding configurationName="Binding1"
transactionFlow="true" />
</wsHttpBinding>
</bindings>
</system.serviceModel>
<system.web>
<compilation debug="true">
<assemblies>
<add assembly="System.Transactions,
Version=2.0.0.0,
Culture=neutral,
PublicKeyToken=B77A5C561934E089"/>
</assemblies>
</compilation>
</system.web>
</configuration>
修改完web.config后,运行该服务将产生如图2所示的屏幕快照。
图2.运行服务-该图显示出示例事务性服务从一个浏览器中运行的结果。
四、为该服务构建一客户端
当服务可用时,你可以把一个Windows表单客户应用程序添加到工程上。表单布局如图3所示;当然你也可以只使用下载版本。
图3.设计客户-该图显示出Visual Studio表单设计器中的Windows表单客户应用程序。
用户把一个值输入到该文本框中,并使用单选按钮选择一个转换方法,然后点击Convert按钮来调用该服务。但是在创建相应的代码之前,你需要创建到该服务的一个代理。为此,使用下面在服务页面上的指令(见图2)-使用svcutil.exe工具来生成一个代理文件和一个客户配置文件。从我的试验看出,不使用生成的配置文件(名叫output.config)更为轻易些;而是,只创建你自己的版本-它仅使用当前应用程序要求的部分,并命名它为App.config。
为运行svcutil.exe,可以从一个命令行提示符上运行下列命令:
Scvutil <url of service>?wsdl
前面的命令创建了两个文件-它们可能的命名为tempuri.org.cs和output.config。其中tempura.org.cs文件包含你需要用于调用你的服务的代理,因此现在把它添加到你的Windows客户工程。
接下来,把一个命为App.config的新文件添加到该工程并为其创建如下内容:(为简化起见,这些是output.config中的过滤结果)
<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
<system.serviceModel>
<client>
<endpoint configurationName="default" address= "http://atlas/TransactableService/Service.svc/"
binding="wsHttpBinding" bindingConfiguration="Binding1" contract="ITransactableTemperatures"/>
</client>
<bindings>
<wsHttpBinding>
<binding configurationName="Binding1" transactionFlow="true" />
</wsHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
现在,你可以设置BTn_Click事件处理器来调用该事务性服务-使用如下的一个事务性范围:
PRivate void btnConvert_Click(object sender, EventArgs e){
using(TransactableTemperaturesProxy theProxy =
new TransactableTemperaturesProxy("default"))
{
TransactionOptions transactionOptions = new TransactionOptions();
transactionOptions.IsolationLevel =IsolationLevel.ReadCommitted;
using (
TransactionScope tx = new TransactionScope(
TransactionScopeOption.RequiresNew,transactionOptions))
{
double d = Convert.ToDouble(txtIn.Text);
double dResult = 0.0D;
if (rbCToF.Checked)
dResult = theProxy.ctof(d);
else
dResult = theProxy.ftoc(d);
lblOut.Text = dResult.ToString();
tx.Complete();
}
}
}
前面的代码首先使用缺省的配置文件名建立代理。你可以看到这是在App.config内的配置文件名。
接下来,该代码创建一个新的为IsolationLevel.ReadCommitted(匹配服务IsolationLevel设置,如前面所讨论的)而配置的transactionOptions集合。
最后,该代码在{}块中创建一个新的名叫tx的TransactionScope,这样以来发生在该块中的每一件事情都在这个tx范围内被事务化。该块中的最后一行代码把tx标志设置为Complete,并且该事务成功结束。
事务在任何相连接的系统中都是极其重要的。直到现在,开发者往往把事务与数据库方法相提并论,但是如今事务也能够良好地应用于其它类型的操作。在任何相连接的系统中,在一个工作流中传递的消息能够大大地受益于事务性。当把一些健壮的,企业级的基础结构放置到一起时,在你的代码中提供可事务性的服务至关重要。所以,事务性是WCF中最重要的支柱之一。
在本文中,你看到了怎样构建一个简单的事务性服务以及怎样从一个Windows表单客户程序中消费它。尽管这个例子只涉及到可能性的一点皮毛,但是它应该能够让你理解如何为你的客户构建和提供事务性软件服务!