系列文章導航:
WCF從理論到實踐(1):揭開神秘面紗
WCF從理論到實踐(2):決戰紫禁之巔
WCF從理論到實踐(3):八號當鋪之黑色契約
WCF從理論到實踐(4):路在何方
WCF從理論到實踐(5):Binding細解
WCF從理論到實踐(6):WCF架構
WCF從理論到實踐(7):消息交換模式
WCF從理論到實踐(8):事件廣播
WCF從理論到實踐(9):實例模式和對象生命周期
WCF從理論到實踐(10):異常處理
WCF從理論到實踐(11)-異步
WCF從理論到實踐(12):事務
WCF從理論到實踐(13):事務投票
WCF從理論到實踐(14):WCF解決方案模板
WCF從理論到實踐(15):響應變化
WCF從理論到實踐(16):操作重載(帶視頻+ppt+源碼)
WCF從理論到實踐(17):OO大背離(帶視頻+ppt+源碼)
通過上文WCF從理論到實踐:事務的學習,我們了解了WCF中實現事務的一些基本常識,但WCF中的事務并不止那么簡單,上文中我們欠缺了一個最重要的功能:事務投票,所謂事務投票就是一種靈活控制事務提交的方式,在上文中我們設置服務方法的TransactionAutoComplete為true,其實意味著方法在沒有異常的情況下自動投贊成票,但有時我們希望當操作中只有某個數據滿足具體條件的時候,才能贊同事務提交,這樣上文的實現明顯就不滿足需求了,此時我們可以用OperationContext.Current.SetTransactionComplete();顯示的進行投票。注意,WCF的事務必須在全票通過的時候才能得以提交。本文還是結合銀行的例子 來演示一下事務投票,并且搭配一個漂亮的WPF客戶端,可謂買一送一了,:)。
本文目的
- 進一步學習WCF事務
- 順便體驗一下WPF
本文適合讀者
本文適合WCF中級用戶,至少需要了解事務的基本常識和簡單實現,初學者可以先閱讀WCF從理論到實踐:事務
進一步學習WCF事務
本文中,我們要模擬的現實情境如下,搭建一個聯盟銀行服務自助系統,這個系統提供在各個銀行之間進行自由轉帳的功能,按照慣例,系統分為四個層次,分別如下:
層次
|
項目
|
服務契約
|
Jillzhang.Wcf.Transactions.Contracts
|
服務端
|
Jillzhang.Wcf.Transactions
|
宿主程序
|
Jillzhang.Wcf.Transactions.ICBC-用于模擬工商銀行
Jillzhang.Wcf.Transactions.CCB-用于模擬建設銀行
|
客戶端
|
Jillzhang.Wcf.BankClient – 包括一個漂亮的WPF窗體
|
服務契約
我們在此處定義一個IBank的服務契約,它包括四個操作契約,分別為:
操作契約
|
契約說明
|
[OperationContract(IsTerminating=false)]
[TransactionFlow(TransactionFlowOption.Allowed)]
decimal Receive(decimal money); |
接受其他銀行的匯款,增加本行帳戶余額
|
[OperationContract(IsTerminating = false)] [TransactionFlow(TransactionFlowOption.Allowed)]
decimal Send(decimal money);
|
給其他銀行帳戶匯款,減少本行帳戶余額
|
[OperationContract(IsTerminating = false)]
decimal GetBalance();
|
獲取帳戶余額
|
[OperationContract(IsTerminating=false)]
decimal SendOnServer(decimal money,string toBank);
|
通過一個銀行接口,完成匯款操作,其中事務是在服務端進行
|
服務端
服務端,由于我本地沒有數據庫,偷下懶,就用一個靜態變量表示帳戶余額了。大家在驗證事務的效果的時候,應該將這里的數據存儲到數據庫中。整個服務端的代碼為:

服務端
//======================================================================

//

// Copyright (C) 2007-2008 Jillzhang

// All rights reserved

// guid1: 4990573c-d834-47f4-a592-3543a9dca047

// guid2: 3c1ffbfe-40e7-48b5-9f83-572dbdcb3bdd

// guid3: 8dfde0dc-07a6-41a9-8fc1-89a11593aa04

// guid4: 61ab2778-e017-4552-b70f-dc772f5e56f5

// guid5: e8c89ed4-360a-417b-a5b2-8e3a459733e3

// CLR版本: 2.0.50727.1433

// 新建項輸入的名稱: Class1

// 機器名稱: JILLZHANG-PC

// 注冊組織名:

// 命名空間名稱: $rootnamespace$

// 文件名: Class1

// 當前系統時間: 3/31/2008 10:32:01 PM

// 用戶所在的域: jillzhang-PC

// 當前登錄用戶名: jillzhang

// 創建年份: 2008

//

// created by Jillzhang at 3/31/2008 10:32:01 PM

// http://jillzhang.cnblogs.com

//

//======================================================================


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using Jillzhang.Wcf.Transactions.Contracts;


namespace Jillzhang.Wcf.Transactions



{

[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete=false,InstanceContextMode=InstanceContextMode.PerSession)]

public class Bank:Contracts.IBank



{

static decimal _balance = 10M;


public Bank()



{


}

[OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)]

public decimal Receive(decimal money)



{

_balance += money;

Console.WriteLine("收到資金:"+money);

OperationContext.Current.SetTransactionComplete();

return _balance;

}

[OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)]

public decimal Send(decimal money)



{

if (money > _balance)



{

throw new FaultException("余額不足!");

}

_balance -= money;

Console.WriteLine("發送資金:" + money);

OperationContext.Current.SetTransactionComplete();

return _balance;

}

public decimal GetBalance()



{

return _balance;

}


public decimal SendOnServer(decimal money,string toBank)



{

WSDualHttpBinding bind = new WSDualHttpBinding();

bind.TransactionFlow = true;

BankProxy bank = new BankProxy(bind, new EndpointAddress(toBank));

using (System.Transactions.TransactionScope tx = new System.Transactions.TransactionScope())



{

if (money > _balance)



{

throw new FaultException("余額不足!");

}

_balance -= money;

Console.WriteLine("發送資金:" + money);

bank.Receive(money);

tx.Complete();

}

return _balance;

}

}

}


注意,本文的Receive和Send方法的TransactionAutoComplete設置的為false,這樣就需要我們在事務包含的每個方法中均顯示的進行事務投票,即調用OperationContext.Current.SetTransactionComplete();如果忽略此處,系統會出現如下的異常:
而且如果TransactionAutoComplete為false的時候,必須將InstanceContextMode設置為PerSession,并且服務契約(ServiceContract)也必須設置SessionMode為Required,否則分別 會出現如下異常:
而如果TransactionAutoComplete為True,則要求TransactionScopeRequired必須同時為True,否則會出現異常:
宿主程序
宿主程序非常簡單,我們只是將服務寄宿到兩個不同的進程中并且指定不同的服務地址便可,唯一值得注意的是因為服務契約需要事務支持,所以Binding的TransactionFlow也必須為True.
他們的代碼分別為:
Jillzhang.Wcf.Transactions.ICBC

ICBC
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using Jillzhang.Wcf.Transactions.Contracts;


namespace Jillzhang.Wcf.Transactions.ICBC



{

class Program



{

static void Main(string[] args)



{

Uri baseAddress = new Uri("http://127.0.0.1:8654/ICBC");

using (ServiceHost host = new ServiceHost(typeof(Bank), baseAddress))



{

WSDualHttpBinding bind = new WSDualHttpBinding();

bind.TransactionFlow = true;

host.AddServiceEndpoint(typeof(IBank), bind, "");

host.Open();

Console.WriteLine("中國工商銀行開始營業!");

Console.Read();

}

}

}

}


Jillzhang.Wcf.Transactions.CCB

CCB
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using Jillzhang.Wcf.Transactions.Contracts;


namespace Jillzhang.Wcf.Transactions.CCB



{

class Program



{

static void Main(string[] args)



{

Uri baseAddress = new Uri("http://127.0.0.1:8655/CCB");

using (ServiceHost host = new ServiceHost(typeof(Bank), baseAddress))



{

WSDualHttpBinding bind = new WSDualHttpBinding();

bind.TransactionFlow = true;

host.AddServiceEndpoint(typeof(IBank), bind, "");

host.Open();

Console.WriteLine("中國建設銀行開始營業!");

Console.Read();

}

}

}

}



客戶端
一個WPF應用程序,可以調用服務來模擬銀行轉帳功能,因為本文著重介紹WCF,對此不作詳細贅述,以后在一起學WPF系列中會逐步加以學習,本文只給出主要的xaml文件和.cs文件
Window1.xaml

Windows.xaml
<Window x:Class="Jillzhang.Wcf.BankClient.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

WindowStyle="ToolWindow" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen"

Title="銀行聯盟自助服務系統" ResizeMode="NoResize" Initialized="Window_Initialized">

<Window.Resources>

<Style x:Key="styleBackground">

<Setter Property="Control.Background">

<Setter.Value>

<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">

<GradientStop Color="#50000000" Offset="0.5"/>

<GradientStop Color="#ff999999" Offset="1"/>

LinearGradientBrush>

Setter.Value>

Setter>

<Setter Property="Control.Width" Value="500">

Setter>

Style>


<Style x:Key="styleBanner">

<Setter Property="StackPanel.Background">

<Setter.Value>

<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">

<GradientStop Color="DarkGray" Offset="0.1" />

<GradientStop Color="Black" Offset="1" />

LinearGradientBrush>

Setter.Value>

Setter>

<Setter Property="TextBlock.Foreground" Value="White" />

<Setter Property="TextBlock.FontFamily" Value="Tahoma" />

Style>


<Style x:Key="styleContentArea_Base">

<Setter Property="Border.BorderThickness" Value="1" />

<Setter Property="Border.CornerRadius" Value="10" />

<Setter Property="Border.Margin" Value="12" />

Style>


<Style x:Key="styleContentArea" BasedOn="{StaticResource styleContentArea_Base}">

<Setter Property="Border.Background" Value="White" />

<Setter Property="Border.BorderBrush" Value="Gray" />

<Setter Property="TextBlock.FontFamily" Value="Sans Serif" />

Style>

Window.Resources>

<Grid Style="{DynamicResource styleBackground}">

<Grid.RowDefinitions>

<RowDefinition Height="Auto"/>

<RowDefinition Height="*"/>

Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="*"/>

<ColumnDefinition Width="2.5*"/>

Grid.ColumnDefinitions>


<Grid Grid.ColumnSpan="2" Grid.Column="0" Grid.Row="0" Height="70" Style="{DynamicResource styleBanner}">

<TextBlock

FontSize="26"

Padding="10,0,10,0"

Text="銀行聯盟自助服務系統"

VerticalAlignment="Center"

/>

Grid>

<Grid Grid.Column="0" Grid.Row="1" Height="150">

<GroupBox Header="銀行列表" FontSize="15" Margin="5,5,5,10">

<StackPanel Margin="5,20,5,15">

<RadioButton Name="radioButton1" FontSize="13" Height="25" IsChecked="True" Checked="radioButton1_Checked">中國工商銀行RadioButton>

<Separator Height="10">Separator>

<RadioButton FontSize="13" Name="radioButton2" Height="25" Checked="radioButton2_Checked">中國建設銀行RadioButton>

StackPanel>

GroupBox>

Grid>

<Grid Grid.Column="1" Grid.Row="1" Grid.RowSpan="2">

<Border Style="{DynamicResource styleContentArea}">

<Grid>

<Grid.RowDefinitions>

<RowDefinition Height="Auto" MinHeight="36" />

<RowDefinition Height="*"/>

<RowDefinition Height="*"/>

Grid.RowDefinitions>

<Grid.ColumnDefinitions>

<ColumnDefinition Width="Auto" MinWidth="36" />

<ColumnDefinition Width="*"/>

Grid.ColumnDefinitions>

<Image Margin="4,4,0,0" Stretch="None" Source="Resources\Icons\agent.ico" />

<StackPanel Grid.Column="1" Orientation="Horizontal">

<TextBlock FontSize="15" Padding="8" Text="您好,您操作的是:" VerticalAlignment="Center" />

<TextBlock FontSize="13" Padding="8" Name="AccountName" VerticalAlignment="Center" /> StackPanel>

<StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2">

<TextBlock Text="帳戶余額:" FontSize="13" Padding="15">TextBlock>

<TextBlock FontSize="13" Padding="8" Name="Balance" VerticalAlignment="Center" />

StackPanel>

<StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Margin="0,0,0,10">

<TextBlock Text="轉帳金額:" FontSize="13" Padding="15" >TextBlock>

<TextBox Text="" Width="100" Height="25" Name="textBox1">TextBox>

<Separator Width="10" Height="0">Separator>

<Button Content="轉帳" Padding="25,2,25,2" Height="25" Click="Button_Click" Name="button1">Button>

StackPanel>

Grid>

Border>

Grid>

Grid>

Window>


Window1.xaml.cs

Windows.xaml.cs
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Navigation;

using System.Windows.Shapes;

using System.ServiceModel;

using System.Transactions;

using Jillzhang.Wcf.Transactions.Contracts;


namespace Jillzhang.Wcf.BankClient



{


/**////

/// Interaction logic for Window1.xaml

///

public partial class Window1 : Window



{

BankProxy bankICBC;

BankProxy bankCCB;

static readonly string icbcAddress = "http://127.0.0.1:8654/ICBC";

static readonly string ccbAddress = "http://127.0.0.1:8655/CCB";

bool inited = false;

public Window1()



{

InitializeComponent();

}

void InitAccountInfo()



{

if (!inited)



{

return;

}

try



{

if (radioButton1.IsChecked == true)



{

AccountName.Text = "中國工商銀行";

Balance.Text = bankICBC.GetBalance().ToString();

}

else



{

AccountName.Text = "中國建設銀行";

Balance.Text = bankCCB.GetBalance().ToString();

}

}

catch (Exception ex)



{

MessageBox.Show(ex.Message);

}

}


private void radioButton2_Checked(object sender, RoutedEventArgs e)



{

InitAccountInfo();

}


private void radioButton1_Checked(object sender, RoutedEventArgs e)



{

InitAccountInfo();

}


private void Window_Initialized(object sender, EventArgs e)



{

inited = true;

WSDualHttpBinding bind = new WSDualHttpBinding();

bind.TransactionFlow = true;

bankICBC = new BankProxy(bind, new EndpointAddress(icbcAddress));

bankCCB = new BankProxy(bind, new EndpointAddress(ccbAddress));

InitAccountInfo();

}


private void Button_Click(object sender, RoutedEventArgs e)



{

try



{

button1.IsEnabled = false;

decimal money = Convert.ToDecimal(textBox1.Text.Trim());

using (TransactionScope tx = new TransactionScope())



{

if (radioButton1.IsChecked == true)



{

Balance.Text = bankICBC.Send(money).ToString();

bankCCB.Receive(money);

}

else



{

Balance.Text = bankCCB.Send(money).ToString();

bankICBC.Receive(money);

}

tx.Complete();

MessageBox.Show("轉帳操作完成!");

}

//if (radioButton1.IsChecked == true)

//{

// Balance.Text = bankICBC.SendOnServer(money, ccbAddress).ToString();

//}

//else

//{

// Balance.Text = bankCCB.SendOnServer(money, icbcAddress).ToString();

//}

}

catch (Exception ex)



{

MessageBox.Show(ex.Message);

}

finally



{

button1.IsEnabled = true;

textBox1.Text = "";

}

}

}

}


有兩種方式可以實現轉帳,上面代碼中是在客戶端實現事務,另外一種你可以將using (TransactionScope tx = new TransactionScope())塊注釋起來,然后將下面的注釋取消,這樣就可以在服務端實現事務。
下面就讓我們來看下運行效果吧:
ICBC-工商行服務
CCB-建設行服務
我們漂亮的客戶端效果為:
其實中國工商銀行和中國建設銀行的賬戶余額均為10,如上圖所示,選擇中國工商銀行,然后輸入轉帳金額4,點擊轉帳,運行后效果如下:
選擇中國建設銀行,可以看到余額為14,如圖:
范例項目下載
/Files/jillzhang/Jillzhang.Wcf.Transactions.rar
總結
WCF為我們提供了一種顯示控制事務提交的方式,即事務投票,我們通過OperationContext.Current.SetTransactionComplete();來投贊成票,而只有參與事務的全部方法均投贊成票的時候,事務才能被成功提交。顯示的投票方法比聲明TransactionAutoComplet為true更靈活。同時我們又體驗了一把WPF的魅力。