在之前章節(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)查并解決這些問題。
首先學(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)單。
當(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)用程序
到目前為止,本章的練習(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):
你將在本章后續(xù)內(nèi)容中回到上述問題,并且學(xué)習(xí)使用持續(xù)性服務(wù)和持持續(xù)性操作來解決這些問題。但是在這之前,我們需要先了解服務(wù)的一些其他特性,以及如果控制客戶端執(zhí)行服務(wù)操作的順序。它們也關(guān)系到你維持狀態(tài)信息方式。
服務(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)容
使用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è)順序就是:
當(dāng)你在服務(wù)合約中定義操作時(shí),你可以使用OperationContract特性類提供的兩個(gè)屬性來控制操作的順序以及操作在服務(wù)中的生命周期
WCF運(yùn)行時(shí)檢查這些屬性的值,以確保運(yùn)行時(shí)和另外一個(gè)服務(wù)合約屬性SessionMode的一致性 。SessionMode用來指定服務(wù)服務(wù)是否實(shí)現(xiàn)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 |
在本章的前部分"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<string, string> 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):
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)用程序
在本章,你了解到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ì)話的完美解決方案。
聯(lián)系客服