閱讀691 返回首頁    go 阿裏雲 go 技術社區[雲棲]


WCF技術剖析之五:利用ASP.NET兼容模式創建支持會話(Session)的WCF服務

在《基於IIS的WCF服務寄宿(Hosting)實現揭秘》中,我們談到在采用基於IIS(或者說基於ASP.NET)的WCF服務寄宿中,具有兩種截然不同的運行模式:ASP.NET並行(Side by Side)模式和ASP.NET兼容模式。對於前者,WCF通過HttpModule實現了服務的寄宿,而對於後者,WCF的服務寄宿通過一個HttpHandler實現。隻有在ASP.NET兼容模式下,我們熟悉的一些ASP.NET機製才能被我們使用,比如通過HttpContext的請求下下文;基於文件或者Url的授權;HttpModule擴展;身份模擬(Impersonation)等。

由於在ASP.NET兼容模式下,ASP.NET采用與.aspx Page完全一樣的方式處理基於.svc的請求,換言之,我們就可以借助當前HttpContext的SessionState維護會話狀態,進而創建一個支持會話的WCF Service。接下來,我們就通過一個簡單的例子,一步步地創建這樣的會話服務。本案例采用如圖1所示的3層結構。 (Source Code從這裏下載)

clip_image002 
圖1 ASP.NET兼容模式案例應用結構

 

案例依然沿用計算服務的例子,不過通過原來直接與傳入操作數並得到運算結果的方式不同,為了體現會話狀態的存在,我們將本案例的WCF服務定義成“累積計算服務”:保留上一次運算的結果,並將其作為後續運算的操作數。為此,定義了如下一個接口作為服務契約:前麵4個操作代表基本的加、減、乘、除運算,計算結果通過GetResult方法獲得。

   1: using System.ServiceModel;
   2: namespace Artech.AspCompatibleServices.Contracts
   3: {
   4:     [ServiceContract]
   5:     public interface ICalculator
   6:     {
   7:         [OperationContract]
   8:         void Add(double x);
   9:         [OperationContract]
  10:         void Subtract(double x);
  11:         [OperationContract]
  12:         void Multiply(double x);
  13:         [OperationContract]
  14:         void Divide(double x);
  15:         [OperationContract]
  16:         double GetResult();
  17:     }
  18: }

服務的實現和.svc都定義在一個ASP.NET Web站點項目中。對於定義在 CalculatorService中的每次運算,先通過HttpContext從SessionState中取出上一次運算的結果,完成運算後再將新的運算結果保存到SessionState中。通過在CalculatorService上應用AspNetCompatibilityRequirementsAttribute實現對ASP.NET兼容模式的支持。

   1: using System.ServiceModel.Activation;
   2: using System.Web;
   3: using Artech.AspCompatibleServices.Contracts;
   4: [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
   5: public class CalculatorService : ICalculator
   6: {
   7:     public void Add(double x)
   8:     {
   9:         HttpContext.Current.Session["__Result"] = GetResult() + x;
  10:     }
  11:     public void Subtract(double x)
  12:     {
  13:         HttpContext.Current.Session["__Result"] = GetResult() - x;
  14:     }
  15:     public void Multiply(double x)
  16:     {
  17:         HttpContext.Current.Session["__Result"] = GetResult() * x;
  18:     }
  19:     public void Divide(double x)
  20:     {
  21:         HttpContext.Current.Session["__Result"] = GetResult() / x;
  22:     }
  23:     public double GetResult()
  24:     {
  25:         if (HttpContext.Current.Session["__Result"] == null)
  26:         {
  27:             HttpContext.Current.Session["__Result"] = 0.0;
  28:         }
  29:         return (double)HttpContext.Current.Session["__Result"];
  30:     }
  31: }

下麵是CalculatorService對應的.svc的定義和Web.config。為了簡潔,在<@ServiceHost%>指令中,僅僅設置一個必需屬性Service。對於ASP.NET兼容模式的支持,配置<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>必不可少。

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
   5:     <services>
   6:       <service name="CalculatorService">
   7:         <endpoint binding="wsHttpBinding" contract="Artech.AspCompatibleServices.Contracts.ICalculator" />
   8:       </service>
   9:     </services>
  10:   </system.serviceModel>
  11: </configuration> 

CalculatorService的客戶端應用通過一個Console應用程序模擬,其服務調用方式並無特別之處,下麵是相關的代碼和配置。

   1: using System;
   2: using System.ServiceModel;
   3: using Artech.AspCompatibleServices.Contracts;
   4: namespace Artech.AspCompatibleServices.Clients
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:         {
  10:             using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("CalculatorService"))
  11:             {
  12:                 ICalculator proxy = channelFactory.CreateChannel(); 
  13:                 Console.WriteLine("初始值為: {0}", proxy.GetResult()); proxy.Add(1); 
  14:                 Console.WriteLine("Add(3)", proxy.GetResult()); 
  15:                 Console.WriteLine("運算結果為: {0}", proxy.GetResult()); proxy.Multiply(10);
  16:                 Console.WriteLine("Multiply(10)", proxy.GetResult()); Console.WriteLine("運算結果為: {0}", proxy.GetResult()); proxy.Subtract(2); 
  17:                 Console.WriteLine("Subtract(2)", proxy.GetResult()); Console.WriteLine("運算結果為: {0}", proxy.GetResult());
  18:             } Console.Read();
  19:         }
  20:     }
  21: }

 

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <client>
   5:             <endpoint  address="https://localhost/AspCompatibleServices/CalculatorService.svc"
   6:                 binding="wsHttpBinding" contract="Artech.AspCompatibleServices.Contracts.ICalculator"
   7:                 name="CalculatorService"/>
   8:         </client>            </system.serviceModel>
   9: </configuration>

但是,但我們運行客戶端的程序,輸出的結果並不像我們希望的那樣。從下麵的結果可以看出,每次通過GetResult()方法得到的結果都是0,也就是說,服務端並沒有將運算結果保存下來。

   1: 初始值為:0
   2: Add(3)運算結果為:0
   3: Multiply(10)運算結果為:0
   4: Subtract(2)運算結果為:0

要解釋這個問題,得從Session的實現機製說起。眾所周知,HTTP是無狀態(Stateless)的傳輸協議,對服務端來說,它收到的每個HTTP請求都是全新的請求。ASP.NET會話(Session)的實現很簡單,就是讓每次HTTP請求攜帶Session的識別信息(Session ID),那麼服務就可以根據此信息判斷請求來自哪個客戶端了。關於Session識別信息的保存,ASP.NET有兩種方式:Cookie和URL,前者將其放到Cookie中,每次HTTP請求將會攜帶該Cookie的值,後者則將其作為請求URL的一部分。一般情況下采用基於Cookie的實現機製,如果Cookie禁用則采用後者。

那麼對於ASP.NET兼容模式下的WCF也一樣,要想讓服務端能夠識別會話,就需要讓每個服務調用的HTTP請求攜帶Session的識別信息,我們也可以通過傳遞Cookie的方式來解決這個問題。對於WCF來說,Cookie傳遞能夠通過Binding來控製,對於WsHttpBinding來說,默認情況下並不允許Cookie的傳遞。我們可以通過WsHttpBinding的AllowCookies來控製是否允許傳遞Cookie,該屬性可以通過配置進行設置。為此,我們對客戶端的配置進行了如下的修改。再次運行我們的案例程序,將會得到你期望的輸出。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:     <system.serviceModel>
   4:         <client>
   5:             <endpoint address="https://localhost/AspCompatibleServices/CalculatorService.svc"
   6:                 binding="wsHttpBinding" contract="Artech.AspCompatibleServices.Contracts.ICalculator"
   7:                 name="CalculatorService"  bindingConfiguration="CookieAllowableBinding"/>
   8:         </client>
   9:         <bindings>
  10:         <wsHttpBinding>
  11:             <binding name="CookieAllowableBinding" allowCookies="true"/>
  12:         </wsHttpBinding>
  13:         </bindings>
  14: </system.serviceModel>
  15: </configuration>

客戶端輸出結果:

   1: 初始值為:0
   2: Add(3)運算結果為:3
   3: Multiply(10)運算結果為:30
   4: Subtract(2)運算結果為:28

作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-30 14:34:18

  上一篇:go  WCF技術剖析之四:基於IIS的WCF服務寄宿(Hosting)實現揭秘
  下一篇:go  WCF技術剖析之六:為什麼在基於ASP.NET應用寄宿(Hosting)下配置的BaseAddress無效