国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
WCF4.0進(jìn)階系列--第七章 維持會(huì)話狀態(tài)和設(shè)置服務(wù)操作的順序

在之前章節(jié)所完成的練習(xí)中,客戶端調(diào)用WCF服務(wù)的一系列操作,但是這些操作的順序并不重要;因此先調(diào)用一個(gè)操作然后再調(diào)用另外一個(gè)操作,均不會(huì)對(duì)彼此產(chǎn)生影響,因?yàn)檫@些操作是相互獨(dú)立地。但在實(shí)際應(yīng)用中,服務(wù)的操作可能需按照一定的順序調(diào)用。比如,如果你在服務(wù)中實(shí)現(xiàn)了購(gòu)物車功能,那么在沒有將任何商品放進(jìn)購(gòu)物車之前,就執(zhí)行結(jié)算和支付操作顯然是沒有意義的。

按照一定的順序調(diào)用操作會(huì)使你考慮在如何兩次操作之間維持會(huì)話狀態(tài)信息。以購(gòu)物車為例,在何處存儲(chǔ)購(gòu)物車中商品的描述信息? 你至少有兩個(gè)選擇:

在客戶端維持購(gòu)物車。使用該方法,你將描述購(gòu)物車內(nèi)容的信息作為參數(shù)傳遞給服務(wù)端的操作,并將更新后的內(nèi)容返回給客戶端。傳統(tǒng)的Web應(yīng)用程序(包括ASP.NET Web程序)使用用戶計(jì)算機(jī)上的Cookie存儲(chǔ)信息就是一個(gè)該方案的變種。這種方案消除了web程序的維持客戶端調(diào)用之間狀態(tài)信息的邊界;但是該方案不能阻止客戶端程序直接修改存儲(chǔ)在Cookie中的內(nèi)容或者通過其他方式篡改Cookie數(shù)據(jù);此外,Cookie還可能帶來安全風(fēng)險(xiǎn)。因此,許多Web瀏覽器允許用戶禁止使用Cookie功能;其結(jié)果增加了在用戶計(jì)算機(jī)上存貯信息的難度。在Web服務(wù)環(huán)境下,客戶端程序可以通過自己的代碼而非依賴Cookie去維持狀態(tài)信息;但是該方案導(dǎo)致客戶端程序與Web服務(wù)之間緊密耦合,并導(dǎo)致客戶端和服務(wù)端非常脆弱從而增加維持方面的問題。

在服務(wù)端維持購(gòu)車車??蛻舳顺绦虻谝淮芜\(yùn)行時(shí),嘗試添加一些商品到購(gòu)物車,服務(wù)創(chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu)去呈現(xiàn)添加后的商品。如果用戶繼續(xù)添加商品到購(gòu)物車,服務(wù)可以計(jì)算商品總數(shù),通過客戶端程序建立支付方法使用戶進(jìn)行結(jié)算,然后安排這些商品的運(yùn)送。在WCF環(huán)境下,通過執(zhí)行在服務(wù)契約中事先定義所有客戶端與服務(wù)操作;此外,客戶端程序不需要知道服務(wù)是實(shí)現(xiàn)如何購(gòu)物車的細(xì)節(jié)。

第二種方式看起來似乎更理想,但當(dāng)在上述場(chǎng)景下構(gòu)建Web服務(wù)時(shí),你需要處理幾個(gè)問題。在本章中,你將調(diào)查并解決這些問題。

管理WCF服務(wù)的狀態(tài)

首先學(xué)習(xí)如何管理和維持WCF服務(wù)的狀態(tài),然后再返回到如何設(shè)置服務(wù)操作的順序。

在前面章節(jié)中的練習(xí)都是狀態(tài)無關(guān)的操作。在ProductsService服務(wù)中,所有用于執(zhí)行操作的信息都是做為參數(shù)通過客戶端傳遞給服務(wù)。當(dāng)操作完成,服務(wù)隨即"忘記"客戶端曾經(jīng)調(diào)用過該服務(wù)的方法。然后在購(gòu)物車場(chǎng)景中,情況發(fā)生了改變;你必須在服務(wù)操作之間維持購(gòu)物車的狀態(tài)。在本節(jié)的練習(xí)中,你將了解到維持購(gòu)物車的方式;盡管該方式相對(duì)比較簡(jiǎn)單,并且未過多考慮其可靠性和擴(kuò)展性。

創(chuàng)建ShoppingCartService服務(wù)

1. 按照如下要求創(chuàng)建ShoppingCartService項(xiàng)目:

方案名稱

ShoppingCart

項(xiàng)目名稱

ShoppingCartService

位置

*\Step.By.Step\Chapter7

項(xiàng)目類型

WCF Service Library

2. 創(chuàng)建好項(xiàng)目之后,重命名IService.cs為IShoppingCartService.cs;并且在出現(xiàn)的提示框中點(diǎn)擊"允許"按鈕,允許Visual Studio重命名所有的引用

3. 重命名Service.cs為ShoppingCartService.cs;并且在出現(xiàn)的提示框中點(diǎn)擊"允許"按鈕,允許Visual Studio重命名所有的引用

4. 添加引用ProductsEntityModel.dll

5. 添加引用System.Data.Entity; 因?yàn)镻roductsEntityModel組件需要使用該組件

6. 打開IShoppingCartService.cs文件;刪除所有的代碼和注釋,只保留using語言和命名空間

7. 添加ShoppingCartItem類到命名空間ShoppingCartService

8. 添加服務(wù)合約IShoppingCartService到命名空間ShoppingCartService下

9. 打開ShoppingCartService.cs文件,刪除所有的注釋和代碼,僅保留using語句和命名空間

10. 添加服務(wù)實(shí)現(xiàn)類ShoppingCartService

11. 生成項(xiàng)目,并確保沒有錯(cuò)誤。

為ShoppingCartService服務(wù)創(chuàng)建宿主程序

1. 按照下列要求添加一個(gè)控制臺(tái)應(yīng)用程序

項(xiàng)目名稱

ShoppingCartHost

位置

*\Step.By.Step\Chapter7

項(xiàng)目類型

Console application


2. 添加引用System.ServiceModel和System.Data.Entity

3. 添加app.config,并復(fù)制之前章節(jié)使用的數(shù)據(jù)庫連接字符串到該app.config

4. 使用WCF服務(wù)配置工具,按照下列要求創(chuàng)建服務(wù)端點(diǎn)

服務(wù)類型

ShoppingCartService.ShoppingCartService

服務(wù)合約

ShoppingCartService.IShoppingCartService

通訊模式

HTTP

互操作方法

Advanced Web Service interoperability (Simplex communication)

端點(diǎn)地址

http://localhost:9000/ShoppingCartService/ShoppingCartService.svc


創(chuàng)建服務(wù)向?qū)У淖詈箜撁嫒缦聢D所示:

5. 保存配置文件,并退出WCF服務(wù)配置管理工具

6. 打開app.config,確認(rèn)system.serviceModel片段如下圖所示:

7. 打開Program.cs文件,添加下面using語句

8. 修改Main方法

9. 生成項(xiàng)目,確認(rèn)沒有錯(cuò)誤

創(chuàng)建客戶端程序以測(cè)試ShoppingCartService服務(wù)

1. 按照下列要求創(chuàng)建一個(gè)控制臺(tái)程序項(xiàng)目

項(xiàng)目名稱

ShoppingCartClient

位置

*\Step.By.Step\Chapter7

項(xiàng)目類型

Console application

2. 添加引用System.ServiceModel

3. 使用管理員身份運(yùn)行Visual Studio Command Prompt運(yùn)行下列命令,使用svcutil使用工具生成ShoppingService服務(wù)的客戶端代理類

4. 關(guān)閉Visual Studio Command Prompt,回到Visual Studio,然后添加上一步生成的文件ShoppingCartServcieProxy.cs到項(xiàng)目ShoppingCartClient

5. 添加配置文件app.config到項(xiàng)目ShoppingCartClient

6. 使用WCF服務(wù)配置管理工具,配置app.config;選擇"Client"文件夾,然后點(diǎn)擊右邊面板中的鏈接"創(chuàng)建新的客戶端"啟動(dòng)創(chuàng)建客戶端元素向?qū)?。然后選擇從ShoppingCartHost的配置文件中生成客戶端。

然后點(diǎn)擊"下一步"按鈕

繼續(xù)點(diǎn)擊"下一步",在名字后面的文本框里,輸入"WS2007HttpBinding_IShoppingCartService"

繼續(xù)點(diǎn)擊下一步,你將得到如下結(jié)果:

確認(rèn)無誤后,點(diǎn)擊"完成"按鈕完成客戶端端點(diǎn)的創(chuàng)建;

7. 完成端點(diǎn)創(chuàng)建后,選擇端點(diǎn),然后更新Contract為ShoppingCartClient.ShoppingCartService.ShoppingCartService.因?yàn)楦鶕?jù)服務(wù)的配置文件創(chuàng)建的客戶端的端點(diǎn)是使用的ShoppingCartService.ShoppingCartService,這與代理類中的服務(wù)合約不匹配

8. 保存配置文件,然后退出WCF服務(wù)配置工具

9. 打開ShoppingCartClient下的app.config,確認(rèn)servcie.serviceModel片段如下圖所示

所以刪除上述配置文件中<identity>片段內(nèi)的內(nèi)容,因?yàn)楸菊聦⒉粫?huì)配置客戶端使用證書

10. 打開ShoppingCartClient項(xiàng)目下的Programm.cs文件,添加如下using語句

11. 完整的Programm.cs代碼如下

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using ShoppingCartClient.ShoppingCartService;

namespace ShoppingCartClient

{

class Program

{

static void Main(string[] args)

{

Console.WriteLine("Press ENTER when the service has started");

Console.ReadLine();

try

{

ShoppingCartServiceClient proxy =

new ShoppingCartServiceClient("WS2007HttpBinding_IShoppingCartService");

proxy.AddItemToCart("WB-H098");

proxy.AddItemToCart("WB-H098");

proxy.AddItemToCart("SA-M198");

string cartContents = proxy.GetShoppingCart();

Console.WriteLine(cartContents);

proxy.Close();

}

catch (Exception e)

{

Console.WriteLine("Exception: {0}", e.Message);

}

Console.WriteLine("Press ENTER to finish");

Console.ReadLine();

}

}

}

12. 使用管理員身份執(zhí)行Visual Studio Command Prompt運(yùn)行下列命令:

上述命令用于確保端口9000已經(jīng)被當(dāng)年用戶占用

13. 關(guān)閉Visual Studio Command Prompt,回到Visual Studio,設(shè)置ShoppingCartClient和ShoppingCartHost為ShoppingCart方案的啟動(dòng)項(xiàng)目(非調(diào)試)

14. 運(yùn)行項(xiàng)目;在客戶端控制臺(tái)窗口中按ENTER鍵后,你將得到如下結(jié)果:

15. 按ENTER鍵退出客戶端控制臺(tái)應(yīng)用程序;按ENTER鍵退出宿主控制臺(tái)應(yīng)用程序

從上面的練習(xí),你可以看到,ShoppingCartServcie服務(wù)維持了購(gòu)物車的信息,以供客戶端在兩次調(diào)用服務(wù)之間可以訪問該信息;看起來,服務(wù)所維持的購(gòu)物車信息正常工作。但是,上述的練習(xí)簡(jiǎn)單到令人生疑。確實(shí),所有事物剛出現(xiàn)的時(shí)候都非常簡(jiǎn)單。

服務(wù)實(shí)例模式

當(dāng)宿主程序創(chuàng)建服務(wù)實(shí)例時(shí),服務(wù)實(shí)例創(chuàng)建購(gòu)物車實(shí)例。 shoppingCart變量是ShoppingCartService類的一個(gè)私有實(shí)例變量。如果兩個(gè)客戶端同時(shí)訪問該shoppingCart時(shí),將會(huì)發(fā)生什么? 結(jié)果是每個(gè)客戶端各獲取一個(gè)服務(wù)實(shí)例,并且每個(gè)實(shí)例都實(shí)例化一個(gè)shoppingCart變量。 這是關(guān)鍵的地方。默認(rèn)情況下,每個(gè)客戶端第一次調(diào)用服務(wù)的操作時(shí),宿主程序創(chuàng)建為請(qǐng)求的客戶端創(chuàng)建一個(gè)新實(shí)例。那么該實(shí)例持續(xù)多長(zhǎng)時(shí)間?

可以從購(gòu)物車練習(xí)中看到,購(gòu)物車實(shí)例在兩次調(diào)用之間存在;否則,不可能在服務(wù)實(shí)例中維持該購(gòu)物車變量實(shí)例的狀態(tài)??蛻舳岁P(guān)閉與宿主程序的連接后,服務(wù)實(shí)例被銷毀?,F(xiàn)在考慮假如有10個(gè)并發(fā)客戶端,那么將產(chǎn)生10個(gè)服務(wù)實(shí)例。如果你有10000個(gè)客戶端,那么將產(chǎn)生10000個(gè)實(shí)例。如果客戶端程序與服務(wù)交互,并且運(yùn)行時(shí)間不確定,在該段時(shí)間內(nèi)用戶瀏覽產(chǎn)品目錄并決定購(gòu)買哪個(gè)產(chǎn)品。那么宿主服務(wù)的計(jì)算機(jī)需要配置大容量?jī)?nèi)存。

WCF服務(wù)實(shí)例創(chuàng)建后,它將負(fù)責(zé)處理來自特點(diǎn)客戶端程序的請(qǐng)求,并在該客戶端兩次調(diào)用之間維持狀態(tài)信息; 這就是一個(gè)會(huì)話。 更準(zhǔn)確地講,當(dāng)客戶端程序使用代理類連接到服務(wù)后,寄宿服務(wù)的WCF運(yùn)行時(shí)創(chuàng)建一個(gè)會(huì)話,該會(huì)話包含服務(wù)實(shí)例和服務(wù)實(shí)例所需的狀態(tài)數(shù)據(jù)。 客戶端程序關(guān)閉代理類后,該會(huì)話將終止。

注意:如果你使用TCP,命名管道通信協(xié)議,你可以通過MaxConnections來指定同時(shí)連接到服務(wù)的最大連接數(shù)。如果你使用IIS使用HTTP或者HTTPS協(xié)議,那么你可以通過設(shè)置IIS的最大連接數(shù)來限制連接到服務(wù)的最大連接數(shù)。具體信息請(qǐng)查閱MSDN

你可以通過ServiceBehavior特性類的InstanceContextMode屬性來控制客戶端程序和服務(wù)實(shí)例之間的關(guān)系。你需要在服務(wù)實(shí)現(xiàn)類上指定該屬性的值,如下圖所示:

關(guān)于InstanceContextMode屬性值及簡(jiǎn)單說明,請(qǐng)參考WCF4.0進(jìn)階系列--第二章 寄宿WCF服務(wù);而關(guān)于ConcurrentcyMode屬性值及簡(jiǎn)單說明,請(qǐng)看下表的內(nèi)容:

OK,讓我們進(jìn)一步了解三種服務(wù)實(shí)例模式的細(xì)節(jié)及服務(wù)實(shí)例與并發(fā)模式之間的關(guān)系。

PerSession實(shí)例模式

PerSession模式指明當(dāng)客戶端程序首次調(diào)用服務(wù)的操作時(shí),創(chuàng)建服務(wù)實(shí)例;一旦服務(wù)實(shí)例創(chuàng)建后,服務(wù)實(shí)例將保持活動(dòng)狀態(tài);并響應(yīng)該客戶端的后續(xù)所有請(qǐng)求直到該客戶端與服務(wù)間的連接被關(guān)閉。每當(dāng)客戶端程序創(chuàng)建一個(gè)新的會(huì)話時(shí),該會(huì)話獲取一個(gè)新的服務(wù)實(shí)例。當(dāng)使用PerSession模式時(shí),兩個(gè)會(huì)話之間不能共享一個(gè)服務(wù)實(shí)例。即便這兩個(gè)會(huì)話都由同一個(gè)客戶端實(shí)例而創(chuàng)建。

客戶端程序可以創(chuàng)建多個(gè)線程,然后嘗試同時(shí)調(diào)用同一個(gè)會(huì)話中的操作。默認(rèn)情況下,一個(gè)服務(wù)是單線程的因此不能處理多個(gè)并發(fā)的請(qǐng)求。當(dāng)新的請(qǐng)求到達(dá)時(shí),如果服務(wù)實(shí)例仍在處理上一個(gè)請(qǐng)求,WCF運(yùn)行時(shí)使新請(qǐng)求處于等待狀態(tài)直到上一個(gè)請(qǐng)求完成。新的請(qǐng)求可能會(huì)在等待處理的過程中發(fā)生超時(shí)。你可以通過ServcieBehavior特性的ConcurrentcyMode屬性來指定在同一個(gè)會(huì)話內(nèi)如何處理并發(fā)請(qǐng)求。

ConcurrentcyMode屬性的默認(rèn)值為ConsurrencyMode.Single;它將使服務(wù)按照上述方式運(yùn)行。你還可以設(shè)置該屬性的值為ConcurrentcyMode.Multiple,在這種情況下,服務(wù)實(shí)例是多線程的,可以處理并發(fā)請(qǐng)求。但是,設(shè)置并發(fā)模式為多線程后并不能保證自動(dòng)同步。你必須確保服務(wù)端的代碼是線程安全的。(即開發(fā)人員需手動(dòng)同步對(duì)共享數(shù)據(jù)的所有訪問)

還有一種同步模式為ConcurrentcyMode.Reentrant;這種模式下服務(wù)實(shí)例也是單線程的,但是它允許服務(wù)中的代碼去調(diào)用其他服務(wù)或程序,因此你可以在服務(wù)中實(shí)現(xiàn)回調(diào)。但是,這種模式不能確保服務(wù)實(shí)例的狀態(tài)數(shù)據(jù)。開發(fā)人員必須負(fù)責(zé)確保服務(wù)實(shí)例狀態(tài)保持一致性,并且確保服務(wù)不會(huì)自我鎖死。

PerCall實(shí)例模式

PerCall模式下,客戶端程序每調(diào)用一個(gè)操作,都創(chuàng)建一個(gè)服務(wù)實(shí)例。當(dāng)操作完成,該服務(wù)實(shí)例自動(dòng)銷毀。該模式的優(yōu)點(diǎn)是宿主的資源在客戶端調(diào)用服務(wù)的操作之間被釋放,極大地提高了服務(wù)的擴(kuò)展性?;叵朐赑erSession模式下,當(dāng)10000個(gè)同步用戶連接至服務(wù)時(shí),主要的問題是服務(wù)端的宿主程序?qū)⑷菁{10000個(gè)服務(wù)實(shí)例,即使9999個(gè)實(shí)例當(dāng)前并沒有執(zhí)行任何操作。 如果你使用PerCall模式,此時(shí)宿主程序僅僅需要針對(duì)活動(dòng)用戶創(chuàng)建并維持一個(gè)實(shí)例。

該模式的缺點(diǎn)是在操作之間維持狀態(tài)將面臨更大的挑戰(zhàn)。你不能在服務(wù)實(shí)例變量中維持信息,因此你必須在持久化存貯設(shè)備(硬盤或數(shù)據(jù)庫)中保存任何需要的狀態(tài)信息。設(shè)計(jì)服務(wù)的操作同樣也是一個(gè)復(fù)雜的工作,因?yàn)榭蛻舳顺绦虮仨氉R(shí)別自己以使服務(wù)能從存貯設(shè)置中取回相應(yīng)的狀態(tài)(詳細(xì)信息,請(qǐng)參考第八章 使用工作流實(shí)現(xiàn)服務(wù))。

你可以看到服務(wù)實(shí)例的生命周期取決于服務(wù)執(zhí)行客戶端請(qǐng)求的操作所耗費(fèi)的時(shí)間,因此你需要盡量保持操作的簡(jiǎn)潔性。如果一個(gè)操作創(chuàng)建額外的線程時(shí),你需要格外小心;因?yàn)樵谶@些額外的線程完成之前,服務(wù)實(shí)例將一直存在;即使主線程在這些額外線程完成之前已經(jīng)將數(shù)據(jù)回傳至客戶端。這種情況將嚴(yán)重影響擴(kuò)展性。你應(yīng)該避免在服務(wù)中注冊(cè)回調(diào)。注冊(cè)回調(diào)不會(huì)阻礙服務(wù)完成,但回調(diào)的對(duì)象可能發(fā)現(xiàn)服務(wù)的實(shí)例已經(jīng)被回收;.NET Framework CLR是導(dǎo)致這個(gè)問題的根本原因,這不是一個(gè)安全風(fēng)險(xiǎn),但是這對(duì)于回調(diào)對(duì)象并不方便,因?yàn)榛卣{(diào)對(duì)象此時(shí)將接收到一個(gè)異常。

Single實(shí)例模式

Single模式下,當(dāng)客戶端程序調(diào)用操作時(shí),創(chuàng)建一個(gè)新的服務(wù)實(shí)例;然后使用該實(shí)例去處理來自該客戶端或連接至該服務(wù)的其他客戶端的所有后續(xù)請(qǐng)求。只有當(dāng)宿主程序關(guān)閉服務(wù)時(shí),該服務(wù)實(shí)例才被銷毀。

該模式的優(yōu)點(diǎn)是,除了減少資源的需求,所有用戶還可以共享數(shù)據(jù)。但是,它同時(shí)也是該模式最重要的缺點(diǎn)。

Single模式使用單個(gè)相同的實(shí)例去處理所有請(qǐng)求,減少了服務(wù)所使用的資源。如果你有10000個(gè)并發(fā)用戶,那么將會(huì)產(chǎn)生很多請(qǐng)求。同時(shí),如果服務(wù)是單線程的,并且操作不能快速完成,那么你遇到許多超時(shí)問題。所以,你應(yīng)該設(shè)置并發(fā)模式為多線程模式,并且實(shí)現(xiàn)同步機(jī)制以確保所有的操作時(shí)線程安全的。

實(shí)例模式和并發(fā)模式的性能影響    http://msdn.microsoft.com/zh-cn/library/cc681240.aspx
Discover Mighty Instance Management Techniques For Developing WCF Apps    http://msdn.microsoft.com/en-us/magazine/cc163590.aspx

在下面的練習(xí)中,你將調(diào)查如何使用服務(wù)實(shí)例的PerCall和Single模式

調(diào)查服務(wù)行為的實(shí)例模式屬性

1. 在Visual Studio中,打開ShoppingCartServcie項(xiàng)目下的ShoppingCartServcie.cs文件。

2. 設(shè)置InstanceContextMode屬性為PerCall

3. 運(yùn)行項(xiàng)目;在客戶端窗口中按ENTER鍵后,你將得到如下結(jié)果:

每次客戶端程序調(diào)用服務(wù)時(shí),服務(wù)端的WCF運(yùn)行時(shí)都創(chuàng)建一個(gè)新的服務(wù)實(shí)例。購(gòu)物車對(duì)象在每次調(diào)用結(jié)束后都被銷毀,因此GetShoppingCart操作返回的結(jié)果為一個(gè)空的購(gòu)物車對(duì)象。

4. 按ENTER鍵退出客戶端控制臺(tái)應(yīng)用程序;按ENTER鍵退出宿主控制臺(tái)應(yīng)用程序

5. 設(shè)置InstanceContextMode屬性為Single

6. 再次運(yùn)行項(xiàng)目;在客戶端窗口中按ENTER鍵后,你將得到如下結(jié)果:

此時(shí),看到有兩個(gè)Water Bottle商品和一個(gè)Mountain seat assembly。此時(shí),返回結(jié)果是正常的;但是真的就沒問題了嗎? 讓我們繼續(xù)實(shí)驗(yàn)。

7. 按ENTER鍵退出客戶端控制臺(tái)應(yīng)用程序;但是保留宿主程序繼續(xù)運(yùn)行。

8. 轉(zhuǎn)到*\Chapter7\ShoppingCart\ShoppingCartClient\bin\Debug,雙擊ShoppingCartClient.exe運(yùn)行ShoppingCartClient

9. 啟動(dòng)程序后,俺ENTER鍵,你將得到如下結(jié)果:

第二次運(yùn)行ShoppingCartClient時(shí),仍然使用第一次運(yùn)行ShoppingCartClient時(shí)所創(chuàng)建的服務(wù)實(shí)例,因此商品添加到同一個(gè)服務(wù)實(shí)例的購(gòu)物車上。

10. ENTER鍵退出客戶端控制臺(tái)應(yīng)用程序;按ENTER鍵退出宿主控制臺(tái)應(yīng)用程序

在PerCall實(shí)例模式下維持會(huì)話狀態(tài)

到目前為止,本章的練習(xí)側(cè)重點(diǎn)在于當(dāng)你改變實(shí)例模式后,服務(wù)將發(fā)生什么。 在ShoppingCartService服務(wù)中,到底該使用哪個(gè)實(shí)例模式? 在現(xiàn)實(shí)環(huán)境中,你使用的不是測(cè)試代碼構(gòu)建的客戶端,而是真正的客戶端;用戶在將商品放入購(gòu)物車之前,花費(fèi)了大量時(shí)間來瀏覽商品。 在這種情況下,應(yīng)使用PerCall實(shí)例模式。但是,你必須提供一種機(jī)制在每次客戶端程序調(diào)用一個(gè)操作時(shí)存儲(chǔ)和重建購(gòu)物車對(duì)象。有多種方法可以來完成此目標(biāo),比如當(dāng)服務(wù)創(chuàng)建一個(gè)購(gòu)物車時(shí),為該購(gòu)物車賦予一個(gè)標(biāo)識(shí);并將該標(biāo)識(shí)返回給客戶端;然后強(qiáng)制客戶端程序在后續(xù)調(diào)用服務(wù)時(shí),將該標(biāo)識(shí)符作為參數(shù)傳遞至服務(wù)。這項(xiàng)技術(shù),以及該技術(shù)的變種,被廣泛采用;但使用該技術(shù)需承受服務(wù)所關(guān)注安全方面的許多缺點(diǎn),比如Cookies;因?yàn)榭蛻舳丝赡軅卧煲粋€(gè)標(biāo)識(shí)符然后劫持其他用戶的購(gòu)物車。

另外一種可選方案采用用戶自身的標(biāo)識(shí)作為保存和獲取狀態(tài)信息的鍵(值)。在一個(gè)安全環(huán)境中,該信息與客戶端的請(qǐng)求一起傳送至服務(wù),同樣該信息與服務(wù)的響應(yīng)一起回傳至客戶端。比如,ws2007HttpBinding綁定使用Windows集成身份驗(yàn)證,默認(rèn)情況下,將傳送用戶憑據(jù)至服務(wù)。在下面的練習(xí)中,我們將學(xué)習(xí)如何利用該信息。

在ShoppingCartService服務(wù)中維持會(huì)話狀態(tài)

1. 打開路徑*\Step.by.Step\Solutions\Chapter7目錄下的解決方案ShoppingCartPerCall

2. 修改IShoppingCartService.cs;添加下圖中的黃色部分

3. 修改ShoppingCartService.cs,添加下列using語句

4. 添加下面的方法SaveShoppingCart到 ShoppingCartService.cs

該私有方法獲取運(yùn)行客戶端程序的用戶名,然后基于該用戶名創(chuàng)建一個(gè)xml文件。該用戶名可能包含域名及域名和用戶名的分隔符"\"。由于"\"符號(hào)不允許出現(xiàn)在文件名中,因此將該符號(hào)替換成"!",當(dāng)然你也可以替換成其他允許的符號(hào)。

注意:如果在Internaet環(huán)境中,你使用證書而非Windows用戶識(shí)別客戶端用戶,上述文件的文件名將依然有效。只不過文件名看起來有點(diǎn)不同,因?yàn)橛脩魳?biāo)識(shí)符采用了如下的形式:
CN=Bert; 64106fcaa45093f01739c82d8280c39153b5559b

在文件關(guān)閉前,使用XmlSerializer對(duì)象將用戶購(gòu)物車對(duì)象序列化到該文件中。

5. 添加下面的方法RestoreShoppingCart到 ShoppingCartService.cs

上述方法使用與SaveShoppingCart相同的方式來生成文件名;如果該文件存在,那么上述方法打開該文件,然后將內(nèi)容反序列化為購(gòu)物車對(duì)象;然后關(guān)閉該文件。如果該文件不存在,返回空對(duì)象(不能拋出異常,因?yàn)闉榭諘r(shí)允許的,比如客戶端第一次訪問)。

6. 修改方法AddItemToCart; 在客戶端程序訪問AddItemToCart時(shí),先根據(jù)客戶端用戶名查找文件,然后反序列化文件內(nèi)容以獲取shoppingCart對(duì)象;隨后再?gòu)脑搶?duì)象中查找詳細(xì)的商品;如果添加的商品在購(gòu)物車中存在,那么更新該商品的數(shù)量,然后序列化購(gòu)物車對(duì)象并保存到文件中;如果添加的商品的在購(gòu)物車中不存在,那么先添加該商品到購(gòu)物車,然后在序列化購(gòu)物車對(duì)象并保存到文件中

7. 同樣,修改方法RemoveItemFromCart

8. 然后,修改方法GetShoppingCart

9. 最后,我們修改服務(wù)實(shí)現(xiàn)了ShoppingCartService,設(shè)置實(shí)例模式為PerCall

測(cè)試ShoppingCartService服務(wù)管理會(huì)話狀態(tài)的能力

1. 運(yùn)行方案;在客戶端窗口中按ENTER鍵后,你將得到如下結(jié)果:

2. 退出ShoppingCartClient和ShoppingCartHost程序

3. 再次運(yùn)行方案;你會(huì)得到如下結(jié)果:

因?yàn)闋顟B(tài)信息已經(jīng)存儲(chǔ)在外部文件中,并且在服務(wù)重新啟動(dòng)和關(guān)閉間隔間持久化到文件中。

4. 按ENTER鍵退出ShoppingCartClient控制臺(tái)程序和ShoppingCartHost控制臺(tái)程序

5. 轉(zhuǎn)到目錄*\Step.by.Step\Solutions\Chapter7\ShoppingCartPerCall\ShoppingCartHost\bin\Debug下,你會(huì)發(fā)現(xiàn)名為"域名!用戶名.xml"的文件

6. 打開該文件,其內(nèi)容如下所示:

7. 關(guān)閉該文件,并回到Visual Studio中,修改ShoppingCartClient下的program.cs文件,使其使用Fred用戶去訪問ShoppingCartService服務(wù)

8. 再次運(yùn)行項(xiàng)目,你將得到如下結(jié)果:

9. 按ENTER鍵退出ShoppingCartClient控制臺(tái)程序和ShoppingCartHost控制臺(tái)程序

10. 轉(zhuǎn)到目錄*\Step.by.Step\Solutions\Chapter7\ShoppingCartPerCall\ShoppingCartHost\bin\Debug下,確認(rèn)"域名!Fred.xml"的文件已經(jīng)生成。

上述方案在資源占用和響應(yīng)方面實(shí)現(xiàn)平衡 。盡管每一個(gè)服務(wù)的操作都會(huì)創(chuàng)建新的服務(wù)實(shí)例,并且占用時(shí)間去獲取和保存會(huì)話狀態(tài),但是你不需要在內(nèi)存中為每個(gè)活動(dòng)的客戶端保留一份服務(wù)實(shí)例;因此當(dāng)訪問服務(wù)的用戶越來越多時(shí),該方案可以有效地提升服務(wù)的擴(kuò)展性。

關(guān)于上述示例代碼,有三個(gè)關(guān)鍵點(diǎn):

  • RestoreShoppingCart和SaveShoppingCart方法目前不是線程安全的。這看起來似乎不重要,因?yàn)镾hoppingCartService服務(wù)使用PerCall實(shí)例模式和單線程并發(fā)模式。但是,如果同一個(gè)用戶運(yùn)行兩個(gè)并發(fā)客戶端實(shí)例,那么將產(chǎn)生兩個(gè)并發(fā)服務(wù)實(shí)例,這兩個(gè)實(shí)例都試圖讀和寫同一個(gè)文件。NET Framework所定義的文件讀取類庫將阻止兩個(gè)服務(wù)實(shí)例在同一個(gè)時(shí)間同時(shí)寫入同一個(gè)文件,但是允許兩個(gè)服飾實(shí)例相互交互。特別地,SaveShoppingCart方法可重寫XML文件,因此一個(gè)服務(wù)實(shí)例可能抹去另外一個(gè)服務(wù)實(shí)例存貯的任何數(shù)據(jù)。在生產(chǎn)環(huán)境中,你應(yīng)該采取一些步驟去阻止上述情況發(fā)生,比如使用鎖或使用數(shù)據(jù)庫,而不要采用XML文件。
  • SaveShoppingCart方法創(chuàng)建了容易讀懂的XML文件。在生產(chǎn)環(huán)境中,你應(yīng)該安排將這些文件存儲(chǔ)到一個(gè)安全位置,而不是寄宿服務(wù)程序所在問文件夾。由于保護(hù)需要隱私性,你不希望其他用戶可以讀取或修改這些XML文件
  • 該方案依賴于經(jīng)過驗(yàn)證的用戶,而且這些用戶還擁有唯一標(biāo)識(shí);用戶不可以是匿名的。如果沒有驗(yàn)證或標(biāo)識(shí),那么對(duì)于客戶端用戶將沒有標(biāo)識(shí),其結(jié)果將導(dǎo)致ShoppingCartService不能為該用戶生成具有唯一名字的、存儲(chǔ)用戶會(huì)話狀態(tài)信息的XML文件。

你將在本章后續(xù)內(nèi)容中回到上述問題,并且學(xué)習(xí)使用持續(xù)性服務(wù)和持持續(xù)性操作來解決這些問題。但是在這之前,我們需要先了解服務(wù)的一些其他特性,以及如果控制客戶端執(zhí)行服務(wù)操作的順序。它們也關(guān)系到你維持狀態(tài)信息方式。

選擇性控制服務(wù)實(shí)例停用(失效)

服務(wù)實(shí)例模式確定服務(wù)實(shí)例的生命周期。該屬性是服務(wù)的一個(gè)全局變量;你在服務(wù)實(shí)現(xiàn)類中設(shè)置一次,WCF運(yùn)行時(shí)處理客戶端請(qǐng)求,分配這些請(qǐng)求到服務(wù)的實(shí)例(也可能創(chuàng)建一個(gè)新的服務(wù)實(shí)例),無論客戶端程序是否調(diào)用操作。

當(dāng)服務(wù)實(shí)例處于非激活狀態(tài)時(shí),基于客戶端所調(diào)用的操作,通過WCF運(yùn)行時(shí)你可以選擇性地控制服務(wù)的實(shí)例。 你可以在實(shí)現(xiàn)服務(wù)的每個(gè)操作上標(biāo)記OperationBehavior特性。你可以使用該特性的屬性ReleaseInstanceMode修改服務(wù)實(shí)例模式的行為。你可以按照下面的方式來使用OperationBehavior特性類

ReleaseInstanceMode屬性還有下列值:

描述

AfterCall

當(dāng)操作完成時(shí),WCF運(yùn)行時(shí)將釋放服務(wù)實(shí)例以回收資源。如果客戶端調(diào)用另一個(gè)操作,WCF運(yùn)行時(shí)將創(chuàng)建一個(gè)新的實(shí)例來處理該請(qǐng)求

BeforeCall

如果當(dāng)前客戶端對(duì)應(yīng)的服務(wù)實(shí)例已經(jīng)存在,WCF運(yùn)行時(shí)將釋放該服務(wù)實(shí)例以回收資源,并創(chuàng)建一個(gè)新的服務(wù)實(shí)例處理客戶端請(qǐng)求

BeforeAndAfterCall

上述兩種情況的集合;WCF運(yùn)行時(shí)創(chuàng)建一個(gè)新的服務(wù)實(shí)例處理操作并在操作完成時(shí)釋放服務(wù)實(shí)例以回收資源

None

默認(rèn)值。服務(wù)實(shí)例由服務(wù)實(shí)例模式管理

你應(yīng)注意到你僅僅可以使用ReleaseInstanceMode屬性減少服務(wù)實(shí)例的生命周期,并且你應(yīng)理解ServiceBehavior特性類的InstanceContextMode屬性和OperationBehavior特性類的ReleaseInstanceMode屬性兩者之間的相互作用。比如,如果你InstanceContextMode屬性為PerCall,并且某一個(gè)操作的ReleaseInstanceMode屬性為BeforeCall,那么WCF運(yùn)行時(shí)將仍舊在操作完成后釋放服務(wù)實(shí)例。 從語義上來看,InstanceContextMode.PerCall使服務(wù)實(shí)例在操作調(diào)用結(jié)束時(shí)被釋放,而ReleaseInstanceMode屬性不能強(qiáng)制WCF運(yùn)行時(shí)使服務(wù)實(shí)例保持激活狀態(tài)。 另一方面, 如果你指定InstanceContextMode屬性為Single,并且某一個(gè)操作的leaseInstanceMode屬性的值為AfterCall,那么WCF運(yùn)行時(shí)將在操作結(jié)束時(shí)釋放服務(wù)實(shí)例,并銷毀進(jìn)程中共享的資源。

OperationBehavior特性類的ReleaseInstanceMode屬性經(jīng)常和服務(wù)實(shí)例模式的PerSession一起使用。如果你需要?jiǎng)?chuàng)建一個(gè)服務(wù)使用Persession實(shí)例化,你應(yīng)該仔細(xì)的評(píng)估是否需要在整個(gè)會(huì)話期間內(nèi)一直保持一個(gè)服務(wù)實(shí)例。比如,如果你知道一個(gè)客戶端程序經(jīng)常在一個(gè)邏輯片段的工作結(jié)束時(shí)調(diào)用一個(gè)特定操作一個(gè)一組特定的操作,你可以考慮設(shè)置OperationBehavior特性類的ReleaseInstanceMode屬性值為AfterCall

另外一個(gè)可選技術(shù)是使用用一些操作屬性,使用這些屬性你可以控制會(huì)話中操作的順序。這就是下面要介紹的內(nèi)容

設(shè)置WCF服務(wù)操作的順序

使用PerSession實(shí)例模式可以方便地控制客戶端程序調(diào)用服務(wù)操作的順序?;仡橲hoppingCartService服務(wù),假設(shè)你使用Persession實(shí)例模式,那么在這種模式下,如果用戶實(shí)際上并沒有添加任何商品到購(gòu)物車,那么允許客戶端程序從購(gòu)物車中移除一個(gè)商品、查詢購(gòu)物車中的商品、或結(jié)算操作都可能變得沒有意義。實(shí)際上,ShoppingCartService服務(wù)中的操作有順序的,而且需要按照這個(gè)順序來執(zhí)行。這個(gè)順序就是:

  1. 添加一個(gè)商品到購(gòu)物車
  2. 添加另外一個(gè)商品、移除已經(jīng)添加的商品、或者查詢購(gòu)物車中的商品信息
  3. 結(jié)算并清空購(gòu)物車

當(dāng)你在服務(wù)合約中定義操作時(shí),你可以使用OperationContract特性類提供的兩個(gè)屬性來控制操作的順序以及操作在服務(wù)中的生命周期

  • IsInitiating    如果設(shè)置該屬性的值為True,客戶端程序調(diào)用該操作以實(shí)例化一個(gè)新的會(huì)話并且創(chuàng)建一個(gè)新的服務(wù)實(shí)例。 如果會(huì)話已經(jīng)存在,該屬性將沒有進(jìn)一步的影響。默認(rèn)情況下,該屬性的值為True。 如果你設(shè)置該值為False,那么客戶端程序不能調(diào)用該操作,直到另外一個(gè)操作已經(jīng)實(shí)例化會(huì)話并且創(chuàng)建了服務(wù)的實(shí)例。在一個(gè)服務(wù)合約中,至少有一個(gè)操作的該屬性值必須設(shè)置為True
  • IsTerminating    如果你設(shè)置該值為False,WCF運(yùn)行時(shí)在操作調(diào)用結(jié)束時(shí),將中止會(huì)話并釋放服務(wù)的實(shí)例。在調(diào)用該服務(wù)的另外一個(gè)操作前,客戶端程序必須創(chuàng)建一個(gè)新的連接,而且該操作的IsInitiating屬性的值必須為True。 該屬性的默認(rèn)值為False。如果服務(wù)中沒有一個(gè)操作的IsTerminating屬性值設(shè)置為True,那么會(huì)話將持續(xù)直到客戶端程序關(guān)閉與該服務(wù)的連接。

WCF運(yùn)行時(shí)檢查這些屬性的值,以確保運(yùn)行時(shí)和另外一個(gè)服務(wù)合約屬性SessionMode的一致性 。SessionMode用來指定服務(wù)服務(wù)是否實(shí)現(xiàn)Session。該屬性可能含有下列值:

  • Required    如果客戶端對(duì)應(yīng)的Session不存在,服務(wù)將創(chuàng)建一個(gè)Session以處理客戶端的請(qǐng)求;否則,將該客戶端將使用現(xiàn)存的Session。 此時(shí),要求服務(wù)所使用的綁定必須支持Session,比如,ws2007HttpBinding綁定支持session,但是basicHttpBinding不支持。
  • Allowed    服務(wù)將創(chuàng)建或者使用現(xiàn)存的Session,當(dāng)然服務(wù)的綁定需要支持Session。否則,服務(wù)將不會(huì)實(shí)現(xiàn)Session
  • NotAllowed    服務(wù)將不是使用Session,即使服務(wù)的綁定支持Session。

如果你指定任何操作的IsInitiating屬性值為false,那么你必須設(shè)置Session屬性的值為Required;如果你不這么做,WCF運(yùn)行時(shí)將拋出一個(gè)異常,同樣地,如果你設(shè)置Session模式為Required,那么你必須設(shè)置IsTerminate屬性的值為True

在下面的練習(xí)中,你將學(xué)習(xí)到如何使用IsInitiating和IsTernminating屬性

控制ShoppingCartService服務(wù)操作的順序

1. 打開空白解決方案ShoppingCartWithSequence;(該方案復(fù)制了上一個(gè)方案ShoppingCartPerCall),然后打開ShoppingCartService項(xiàng)目下的IShoppingCartService.cs文件

2. 指定IShoppingCartService服務(wù)SessionMode值為SessionMode.Required; 并按照下列所示指定該服務(wù)操作的IsInitiating屬性和IsTerminating屬性

3. 打開ShoppingCartService.cs文件,并指定服務(wù)實(shí)現(xiàn)類的InstranceContextMode值為InstanceContextMode.PerSession

4. 注釋掉ShoppingCartService.cs文件中AddItemToCart方法里的RestoreShoppingCart方法和SaveShoppingCart方法

5. 注釋掉ShoppingCartService.cs文件中RemoveItemFromCart方法里的RestoreShoppingCart方法和SaveShoppingCart方法

7. 注釋掉ShoppingCartService.cs文件中GetShoppingCart方法里的RestoreShoppingCart

現(xiàn)在你需要修改客戶端以測(cè)試上述修改產(chǎn)出的結(jié)果。

測(cè)試ShoppingCartService服務(wù)中操作的順序

1. 打開ShoppingCartClient項(xiàng)目下的ShoppingCartServiceProxy.cs。該文件由之前版本的服務(wù)生成。 由于你已經(jīng)修改了服務(wù)合約,因此你必須更新客戶端代理類以反映服務(wù)合約的更新。你可以繼續(xù)使用svcutil使用工具重新生成客戶端代理類;但是由于服務(wù)合約的修改比較少,因?yàn)槲覀兺耆梢酝ㄟ^手動(dòng)方式修改服務(wù)代理類。所需要做的更新是為服務(wù)代理類指定SessionMode,以及為操作添加IsInitiating和IsTerminating屬性;

2. 修改ShoppingCartClient項(xiàng)目下的Program.cs文件;添加下面圖片中紅色矩形內(nèi)的內(nèi)容:

3. 在非調(diào)適模式下運(yùn)行方案。在ShoppingCartClient控制臺(tái)窗口中,按ENTER鍵。 客戶端程序添加三個(gè)商品到購(gòu)物車然后輸出購(gòu)物車的內(nèi)容。然后將輸出錯(cuò)誤消息

上述結(jié)果證實(shí)了第一次調(diào)用Checkout操作后,終止了客戶端與服務(wù)的會(huì)話。此外,當(dāng)會(huì)話結(jié)束時(shí),服務(wù)關(guān)閉了與客戶端程序的連接。 因此,客戶端程序與服務(wù)再次通信之前必須重新打開一個(gè)新的連接并創(chuàng)建一個(gè)新的會(huì)話。

4. 在ShoppingCartClient控制臺(tái)窗口中,按ENTER鍵以退出控制臺(tái)程序;在ShoppingCartHost控制臺(tái)窗口中按ENTER鍵退出宿主控制臺(tái)程序。

5. 在步驟二的方法前重新創(chuàng)建ShoppingCartServiceClient對(duì)象;
proxy = new ShoppingCartServiceClient("WS2007HttpBinding_IShoppingCartService");

6. 再次運(yùn)行方案,你將得到如下的結(jié)果:

7. 在ShoppingCartClient控制臺(tái)窗口中,按ENTER鍵以退出控制臺(tái)程序;在ShoppingCartHost控制臺(tái)窗口中按ENTER鍵退出宿主控制臺(tái)程序。

你還可以自己嘗試在調(diào)用AddItemToCart之前,調(diào)用RemoveItemFromCart操作、GetShoppingCart或Checkout操作。 由于后面三個(gè)操作都不創(chuàng)建一個(gè)新的會(huì)話,因?yàn)榭蛻舳顺绦蚨紝⑹。伋霎惓?。如下圖所示

綁定和Sessions之間的關(guān)系

你應(yīng)該意識(shí)到sessions需要連接至服務(wù)所采用的協(xié)議支持。并非所有協(xié)議都提供了session的支持;相應(yīng)地并非所有的標(biāo)準(zhǔn)綁定都支持session。 你如你嘗試在不支持session的綁定上配置服務(wù)支持session,服務(wù)啟動(dòng)將會(huì)失敗。 下面的列表列出了標(biāo)準(zhǔn)綁定對(duì)session的支持情況

綁定

支持Sessions

BasicHttpBinding

No

BasicHttpContextBinding

No

WSHttpBinding

Yes

WSHttpContextBinding

Yes

WS2007Binding

Yes

WSDualHttpBinding

Yes

WebHttpBinding

No

WSFederationHttpBinding

Yes

WS2007FederationHttpBinding

Yes

NetTcpBinding

Yes

NetTcpContextBinding

Yes

NetPeerTcpBinding

No

NetNamePipeBinding

No

NetMsmqBinding

No

MsmqIntegrationBinding

No

使用持續(xù)性服務(wù)維持會(huì)話狀態(tài)

在本章的前部分"PerCall實(shí)例模式中維持狀態(tài)",你已經(jīng)了解到在如何通過使用代碼序列化和反序列化狀態(tài)信息的方式提供一個(gè)session-less服務(wù)。你還了解到如果采用這種實(shí)現(xiàn)方式提供更專業(yè)的程序所面臨的問題。這些都并非小問題。幸運(yùn)的是,WCF提供持續(xù)性服務(wù)和持續(xù)性操作可以幫助你解決上述問題。

WCF的持續(xù)性服務(wù)指使用會(huì)話維持狀態(tài)。你可以中斷、甚至停止一個(gè)會(huì)話, 在保存會(huì)話狀態(tài)的同時(shí)釋放服務(wù)實(shí)例所使用的資源。然后,你可以啟動(dòng)一個(gè)新的服務(wù)實(shí)例,恢復(fù)一個(gè)會(huì)話,并且重新裝載會(huì)話的狀態(tài)到該新服務(wù)實(shí)例。持續(xù)性服務(wù)對(duì)于長(zhǎng)時(shí)間運(yùn)行的會(huì)話是完美地,客戶端程序激活一段時(shí)間后可以停用;非激活的客戶端所使用會(huì)話和資源可以替換出去、當(dāng)被激活時(shí)再重新裝載回來。用于構(gòu)建可擴(kuò)展性WCF服務(wù)的工作流模型就是基于持續(xù)性服務(wù)(你將在第八章看到相關(guān)內(nèi)容);如果你在企業(yè)環(huán)境中寄宿WCF服務(wù),那么Windows Server AppFabric也依賴于持續(xù)性服務(wù)維持狀態(tài)。當(dāng)然,你也可以在上述場(chǎng)景之外使用持續(xù)性服務(wù)。在后面的練習(xí)中你將研究如何使用。

你通過DurableService特性來指定一個(gè)服務(wù)是持續(xù)性的。 持續(xù)性服務(wù)要求一個(gè)數(shù)據(jù)存儲(chǔ)來持久化該服務(wù)所有會(huì)話的狀態(tài)。WCF和WF提供SQL持久化程序在SQL數(shù)據(jù)庫中存儲(chǔ)會(huì)話狀態(tài)。如果你想使用一些其他的機(jī)制你也可以構(gòu)建自己的持久化程序。你配置一個(gè)持續(xù)性服務(wù)引用一個(gè)持久化程序,并提供連接到所采用持久化程序存儲(chǔ)的詳細(xì)信息。

啟動(dòng)一個(gè)服務(wù)實(shí)例時(shí)將創(chuàng)建一個(gè)新的會(huì)員,WCF運(yùn)行時(shí)為該會(huì)話生成一個(gè)唯一實(shí)例ID。 該實(shí)例ID存貯在SOAP消息的頭部,并在客戶端和服務(wù)端之間來回傳輸,WCF運(yùn)行時(shí)使用該實(shí)例ID關(guān)聯(lián)客戶端請(qǐng)求和對(duì)應(yīng)的服務(wù)實(shí)例。當(dāng)客戶端關(guān)閉與服務(wù)的連接后,WCF運(yùn)行時(shí)存儲(chǔ)會(huì)話的狀態(tài)和實(shí)例ID到持久化存儲(chǔ)。 然后,如果客戶端程序重新連接到服務(wù),將早期會(huì)話對(duì)應(yīng)的實(shí)例ID填充到請(qǐng)求消息的頭部,然后傳送至WCF服務(wù)。服務(wù)端的WCF運(yùn)行時(shí)創(chuàng)建一個(gè)新的會(huì)話,從持久存儲(chǔ)中查詢會(huì)話狀態(tài),然后將這些信息填充到新的會(huì)話中。 對(duì)于客戶端程序,新會(huì)話與舊會(huì)話完全一致。

與持續(xù)性服務(wù)一樣,你可以指定持續(xù)性操作以使服務(wù)保存和恢復(fù)會(huì)話狀態(tài)。 你通過DurableOperation特性來實(shí)現(xiàn)持續(xù)性操作。該特性類提供與OperationBehavios特性類中IsInitiating和IsTerminating屬性相似的功能。你可以設(shè)置屬性CanCreateInstance的值為true,以指定WCF運(yùn)行時(shí)在操作被調(diào)用時(shí)創(chuàng)建一個(gè)新的持續(xù)性服務(wù)實(shí)例。 你也可是這是該屬性值為false以標(biāo)明服務(wù)僅僅通過已存的實(shí)例運(yùn)行。 此外,你可以設(shè)置CompleteInstance實(shí)行為true以標(biāo)明操作結(jié)束當(dāng)前的session,任何保存在持久存儲(chǔ)的狀態(tài)當(dāng)操作完成時(shí)應(yīng)該被移除。

在下面的練習(xí)中,你將修改ShoppingCartService為持續(xù)性服務(wù),并檢查如何通過一個(gè)簡(jiǎn)單的圖形化客戶端與該持續(xù)性服務(wù)交互

檢查ShoppingCartGUIClient程序

1. 打開*\Step.by.Step\Chapter7\DurableShoppingCartService文件夾下的DurableShoppingCartService方案

2. 設(shè)置ShoppingCartHost和ShoppingCartGUIClient為該方案的啟動(dòng)項(xiàng)目

3. 在非調(diào)適模式下啟動(dòng)方案,然后切換ShoppingCartGUIClient客戶端程序的窗口,在產(chǎn)品編碼處輸入WB-H098;然后點(diǎn)擊"添加商品"按鈕,你將看到這項(xiàng)目將被添加到購(gòu)物車并顯示在窗口的下方

4. 再次點(diǎn)擊擊"添加商品"按鈕,確保購(gòu)物車中商品數(shù)量發(fā)生變化。

5. 在產(chǎn)品編碼出輸入"SA-M198";然后點(diǎn)擊"添加商品";確認(rèn)該商品添加到購(gòu)物車中并正確地顯示

6. 點(diǎn)擊"刪除商品",確認(rèn)商品從購(gòu)物車中移除。

7. 點(diǎn)擊"結(jié)算",確認(rèn)購(gòu)物車中的商品被清空。

8. 關(guān)閉ShoppingCartGUIClient;并推出ShoppingCartHost程序

9. 打開MainWindow.xaml.cs,你可以看到ShoppingCartGUIClient程序的邏輯

public partial class MainWindow : Window

{

private ShoppingCartServiceClient proxy = null;

public MainWindow()

{

InitializeComponent();

}

private void Window_Loaded(object sender, RoutedEventArgs e)

{

proxy = new ShoppingCartServiceClient("WS2007HttpBinding_IShoppingCartService");

}

private void Window_Unloaded(object sender, RoutedEventArgs e)

{

proxy.Close();

}

private void btnAddItem_Click(object sender, RoutedEventArgs e)

{

try

{

proxy.AddItemToCart(txtProductNumber.Text);

txtShoppingCartContents.Text = proxy.GetShoppingCart();

}

catch (Exception ex)

{

MessageBox.Show(ex.Message, "Error adding item to cart",

MessageBoxButton.OK, MessageBoxImage.Error);

}

}

private void btmRemoveItem_Click(object sender, RoutedEventArgs e)

{

try

{

proxy.RemoveItemFromCart(txtProductNumber.Text);

txtShoppingCartContents.Text = proxy.GetShoppingCart();

}

catch (Exception ex)

{

MessageBox.Show(ex.Message, "Error removing item to cart",

MessageBoxButton.OK, MessageBoxImage.Error);

}

}

private void btnCheckout_Click(object sender, RoutedEventArgs e)

{

try

{

proxy.CheckOut();

txtShoppingCartContents.Clear();

}

catch (Exception ex)

{

MessageBox.Show(ex.Message, "Error checking out",

MessageBoxButton.OK, MessageBoxImage.Error);

}

}

}

該客戶端程序非常簡(jiǎn)單,但是該程序的實(shí)用性很差;因?yàn)榻柚摮绦虿⒉荒軜?gòu)建專業(yè)的、可擴(kuò)展的系統(tǒng)。問題在于當(dāng)用戶啟動(dòng)程序并打開窗口時(shí),建立到服務(wù)的連接然后創(chuàng)建會(huì)話;該連接和繪畫將一直駐留在宿主計(jì)算機(jī)的內(nèi)存中,直到用戶關(guān)閉窗口停止客戶端。如果用戶忘記關(guān)閉客戶端程序,然后離開辦公室去度兩個(gè)星期假;那么會(huì)話所使用的資源在這段時(shí)間內(nèi)將一直保留。此外,如果服務(wù)在這段時(shí)間內(nèi)關(guān)閉,那么會(huì)話的狀態(tài)信息將丟失。在下面的練習(xí)者中,你將為持續(xù)性服務(wù)創(chuàng)建持久存儲(chǔ)。

使用SQL持久化程序?yàn)閃CF服務(wù)創(chuàng)建持久存儲(chǔ)

1. 啟動(dòng)SQL Server管理工具;連接到本機(jī)SQL Server實(shí)例

2. 創(chuàng)建一個(gè)新的數(shù)據(jù)庫WCFPersistence

3. 然后連接到該數(shù)據(jù)庫,打開文件C:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en\SqlPersistenceProviderSchema.sql;切換到數(shù)據(jù)庫WCFPersistence,然后執(zhí)行該文件。

4. 確認(rèn)文件執(zhí)行成功

5. 重復(fù)步驟3和步驟4,執(zhí)行文件C:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en\SqlPersistenceProviderLogic.sql

重新配置ShoppingCartService服務(wù)為持續(xù)性服務(wù)

1.在解決方案中,在ShoppingCartService項(xiàng)目上點(diǎn)擊右鍵,選擇ShoppingCartService屬性

2. 在屬性頁中,選擇"Application"標(biāo)簽,然后設(shè)置Target framework為.NET Framework 4

3. 添加System.WorkflowServices引用到ShoppingCartService項(xiàng)目。該組件包含DurableService和DurableOperation特性類

4. 打開IShoppingCartService.cs文件,確認(rèn)ShoppingCartItem類標(biāo)記了Serializable特性

5. 打開ShoppingCartService.cs文件,添加using語句

6. 確認(rèn)ShoppingCartService類的InstanceContextModel屬性為PerSession。

7. 添加Serializable和DurableService特性到ShoppingCartService類

8. 添加DurableOperaton特性到ShopingCartService服務(wù)的操作上

9. 在ShoppingCartHost項(xiàng)目上點(diǎn)擊右鍵,選擇ShoppingCartService屬性;在屬性頁中,選擇"Application"標(biāo)簽,然后設(shè)置Target framework為.NET Framework 4

10. 添加連接到WCFPersistence的數(shù)據(jù)庫連接字符串

<add name="DurableServiceConnectionService"connectionString="metadata=res://*/ProductsModel.csdl|res://*/ProductsModel.ssdl|res://*/ProductsModel.msl;provider=System.Data.SqlClient;provider connection string="data source=localhost;initial catalog=WCFPersistence;integrated security=True; multipleactiveresultsets=True;App=EntityFramework"providerName="System.Data.EntityClient"/>

11. 保存app.config;然后使用WCF服務(wù)配置工具打開該app.config

12. 在WCF服務(wù)配置工具左邊面板中,選擇高級(jí)—服務(wù)行為,然后右鍵,選擇"創(chuàng)建新的服務(wù)行為配置"

13. 上述操作將會(huì)創(chuàng)建一個(gè)為命名的服務(wù)行為,你更新其名字為"DurableServiceBehavior",然后在右下面板中點(diǎn)擊"添加"按鈕,添加服務(wù)行為元素"persistenceProvider"

14. 在WCF服務(wù)配置左邊面板中,選擇"高級(jí)—服務(wù)行為—DurableServiceBahavior--persistenceProvider",然后切換到右邊面板。設(shè)置persistentenceProvider的類型為System.ServiceModel.Persistence.SqlPersistenceProvider

15. 完成上述步驟后,選擇選擇"高級(jí)—服務(wù)行為—DurableServiceBahavior—persistenceProvider-- persistenceProviderArgument",然后在WCF服務(wù)配置工具的右下角,點(diǎn)擊"創(chuàng)建"按鈕,在彈出的對(duì)話框中的名字出輸入"connectionStringName";值處輸入"DurableServiceConnectionService"。然后點(diǎn)擊"確定"按鈕

16. 選擇"服務(wù)—ShoppingCartService.ShoppingCartService",在右邊面板中選擇該服務(wù)的行為配置:

17. 在WCF服務(wù)配置工具中,選擇"服務(wù)—端點(diǎn)—空名",在右邊面板中更改綁定為wsHttpContextBinding

如前文所述,持續(xù)性服務(wù)生成的實(shí)例ID在包含SOAP消息的頭部中并在客戶端和服務(wù)之間交換。服務(wù)所使用的協(xié)議必須自動(dòng)地填充和檢查該信息;wsHttpContextBinding綁定提供了該功能。

18. 保存配置文件,并退出WCF服務(wù)配置工具。

更新ShoppingCartGUIClient程序

1. 打開ShoppingCartGUIClient程序的app.config文件,修改binding為"wsHttpContext"

2. 打開MainWindow.xaml.cs文件,注釋掉Window_Loaded和Window_Unloaded方法內(nèi)的內(nèi)容

3. 添加using語句

4. 在MainWindow類中,添加一個(gè)如下的私有變量

private IDictionary<stringstring> context = null;

5. 修改btnAddItem_Click的邏輯

private void btnAddItem_Click(object sender, RoutedEventArgs e)

{

try

{

using (proxy = newShoppingCartServiceClient("WS2007HttpBinding_IShoppingCartService"))

{

IContextManager contextManager =

proxy.InnerChannel.GetProperty<IContextManager>();

if (context != null)

contextManager.SetContext(context);

proxy.AddItemToCart(txtProductNumber.Text);

if (context == null)

{

context = contextManager.GetContext();

MessageBox.Show(context["instanceId"], "New context created",

MessageBoxButton.OK, MessageBoxImage.Information);

}

txtShoppingCartContents.Text = proxy.GetShoppingCart();

}

}

catch (Exception ex)

{

MessageBox.Show(ex.Message, "Error adding item to cart",

MessageBoxButton.OK, MessageBoxImage.Error);

}

}

上述代碼有如下需要注意的幾點(diǎn):

  • 與服務(wù)通信代理對(duì)象的創(chuàng)建方法
  • 創(chuàng)建代理對(duì)象所使用端點(diǎn)的名字
  • 用于讀取代理對(duì)象收發(fā)SOAP的頭部信息的IContextManager對(duì)象的創(chuàng)建方法
  • 檢查context變量的方法
  • 在調(diào)用AddItemToCarth操作后如果context變量為空,如果存儲(chǔ)頭部信息到SQL持久化存儲(chǔ)中,從context中獲取服務(wù)返回的SOAP消息頭的代碼

6. 修改btmRemoveItem_Click與btnCheckout_Click邏輯;

private void btmRemoveItem_Click(object sender, RoutedEventArgs e)

{

try

{

using (proxy = newShoppingCartServiceClient("WS2007HttpBinding_IShoppingCartService"))

{

IContextManager contextManager =

proxy.InnerChannel.GetProperty<IContextManager>();

contextManager.SetContext(context);

proxy.RemoveItemFromCart(txtProductNumber.Text);

txtShoppingCartContents.Text = proxy.GetShoppingCart();

}

}

catch (Exception ex)

{

MessageBox.Show(ex.Message, "Error removing item to cart",

MessageBoxButton.OK, MessageBoxImage.Error);

}

}

 

private void btnCheckout_Click(object sender, RoutedEventArgs e)

{

try

{

using (proxy = newShoppingCartServiceClient("WS2007HttpBinding_IShoppingCartService"))

{

IContextManager contextManager =

proxy.InnerChannel.GetProperty<IContextManager>();

contextManager.SetContext(context);

proxy.CheckOut();

txtShoppingCartContents.Clear();

}

}

catch (Exception ex)

{

MessageBox.Show(ex.Message, "Error checking out",

MessageBoxButton.OK, MessageBoxImage.Error);

}

}

測(cè)試持續(xù)性服務(wù)

1. 運(yùn)行方案DurableShoppingCartService;在ShoppingCartGUIClient窗口中,在產(chǎn)品編碼處輸入WB-H098;然后點(diǎn)擊添加商品按鈕。下圖所示的消息框窗出現(xiàn)。該消息框顯示了持續(xù)性服務(wù)創(chuàng)建的會(huì)話的實(shí)例的ID。

2. 點(diǎn)擊OK鍵后,確認(rèn)產(chǎn)品已經(jīng)添加到購(gòu)物車中

3. 保持ShoppingCartGUIClient和ShoppingCartHost程序處于運(yùn)行狀態(tài);然后轉(zhuǎn)到SQL客戶端管理工具;執(zhí)行下面的T-SQL語句,你將會(huì)得到一條紀(jì)錄。該記錄包含一個(gè)id的列,其值為步驟一里消息提示框的實(shí)例ID的值。

4. 返回到ShoppingCartGUIClient窗口中,在產(chǎn)品編碼處輸入SA-M198;然后點(diǎn)擊"添加商品"按鈕。此時(shí),將不會(huì)出現(xiàn)消息提示框。因?yàn)閎tnAddItem_Click確認(rèn)context變量已經(jīng)有值存在,所以一個(gè)會(huì)話必定已經(jīng)創(chuàng)建。Context被傳送至ShoppingCartService服務(wù)。服務(wù)端的WCF運(yùn)行時(shí)創(chuàng)建一個(gè)新的服務(wù)實(shí)例,并從context中提取實(shí)例ID,從WCFPersistence數(shù)據(jù)庫的InstanceData表中獲取該實(shí)例ID對(duì)應(yīng)的會(huì)話數(shù)據(jù),并且將這些數(shù)據(jù)自動(dòng)填充到新創(chuàng)建的服務(wù)實(shí)例中。 當(dāng)btnAddItem_Click方法結(jié)束, 服務(wù)端的WCF運(yùn)行時(shí)保存會(huì)話數(shù)據(jù)到InstanceData表中,然后再銷毀服務(wù)實(shí)例。

4. 保持ShoppingCartGUIClient處于運(yùn)行狀態(tài),關(guān)閉ShoppingCartHost控制臺(tái)程序。

5. 重新啟動(dòng)ShoppingCartHost控制臺(tái)程序

6. 切換回ShoppingCartGUIClient窗口,在產(chǎn)品編碼處然后輸入PU-M044,然后點(diǎn)擊"添加商品"按鈕。Mountain Pump商品添加到購(gòu)物車中。請(qǐng)注意WCF運(yùn)行時(shí)恢復(fù)了之前購(gòu)物車中已經(jīng)存在的商品,盡管ShoppingCartService宿主程序已經(jīng)關(guān)閉并重新啟動(dòng)。

7. 點(diǎn)擊"結(jié)算"按鈕。該操作結(jié)束會(huì)話,購(gòu)物車也被情況

8. 保持ShoppingCartGUIClient和ShoppingCartHost程序處于運(yùn)行狀態(tài);然后轉(zhuǎn)到SQL客戶端管理工具;執(zhí)行下面的T-SQL語句。 由于結(jié)算操作已經(jīng)完成,會(huì)話終止,保存在持久化存儲(chǔ)中的數(shù)據(jù)也已經(jīng)被移除。

9. 關(guān)閉ShoppingCartGUIClient程序和ShoppingCartHost控制臺(tái)應(yīng)用程序

總結(jié)

在本章,你了解到WCF運(yùn)行時(shí)為創(chuàng)建服務(wù)實(shí)例提供了不同的選擇。在單個(gè)操作或者整個(gè)會(huì)話期間內(nèi),服務(wù)實(shí)例可以一直存在,直到客戶端程序關(guān)閉與服務(wù)之間的連接。在很多情況下,服務(wù)實(shí)例為某一個(gè)特定的客戶端專有,但是WCF也提供供多個(gè)客戶端實(shí)例共享單個(gè)服務(wù)實(shí)例。 你還了解到如何選擇性地控制哪一個(gè)操作創(chuàng)建會(huì)話,哪一個(gè)操作關(guān)閉會(huì)話。 最后,你了解到如何創(chuàng)建持續(xù)性服務(wù),使用該服務(wù),你可以維持會(huì)話狀態(tài),而不需要對(duì)應(yīng)的服務(wù)實(shí)例一直處于活動(dòng)狀態(tài)。 持續(xù)性服務(wù)是構(gòu)建會(huì)話時(shí)間長(zhǎng)、 可以從關(guān)閉的服務(wù)中恢復(fù)會(huì)話信息、或者重啟服務(wù)并恢復(fù)會(huì)話的完美解決方案。

源代碼下載

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
WCF事務(wù)屬性
[老老實(shí)實(shí)學(xué)WCF] 第八篇 實(shí)例化
由一個(gè)需求聊聊WCF(二)
DW網(wǎng)頁設(shè)計(jì)35:構(gòu)建Java插入模塊
8. 錯(cuò)誤和異常
<font style="vertical-align: inherit;"><font style="vertical-align: inherit;">8.錯(cuò)誤和異常</font></font>
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服