WCF中的Session
我们知道,WCF是MS基于SOA建立的一套在分布式环境中各个相对独立的Application进行Communication的构架。他实现了最新的基于WS-*规范。按照SOA的原则,相对独自的业务逻辑以service的形式封装,调用者通过Messaging的方式调用Service。对于承载着某个业务功能的实现的Service应该具有Context无关性、甚至是Solution无关性,也就是说个构成Service的operation不应该绑定到具体的调用上下文,对于任何调用,具有什么样的输入,就会有与之对应的输出。因为SOA的一个最大的目标就是尽可能地实现重用,只有具有Context无关性/Solution无关性,Service才能实现最大限度的重用。此外Service的Context无关性/Solution无关性还促进了另一个重要的面向服务的特征的实现:可组合性,把若干相关细粒度的Service封装成一个整体业务流程的Service。 在一个C/S(Client/Service)场景中,Context无关性体现在Client对Service的每次调用都是完全不相关的。但是在有些情况下,我们却希望系统为我们创建一个Session来保留某个Client和Service的进行交互的状态。所以,像Web Service一样,WCF也提供了对Session的支持。对于WCF来说,Client和Service之间的交互都通过Soap Message来实现的,每次交互的过程就是一次简单的Message Exchange。所以从Messaging的角度来讲,WCF的Session就是把某个把相关的Message Exchange纳入同一个Conversation。每个Session用一个Session ID来唯一标识。 WCF中的Session和ASP.NET的Session 在WCF中,Session属于Service Contract的范畴,是一个相对抽象的概念,并在Service Contract定义中通过SessionModel参数来实现。他具有以下几个重要特征:
我们知道,在WCF中Client通过创建的Proxy对象来和service的交互,在默认的支持Session的情况下,Session和具体的Proxy对象绑定在一起,当Client通过调用Proxy的某个方法来访问Service的时候,Session被初始化,直到Proxy被关闭,Session被终止,我们可以通过下面两种方式来关闭Proxy:
此外,我们也可以人为地指定通过调用Service的某个operation来初始化、或者终止Session。我们一般通过System.ServiceModel. OperationContractAttribute的IsInitiating和IsTerminating参数来指定初始化和终止Session的Operation。
说道WCF中的Session,我们很自然地联想到ASP.NET中的Session。实际上,他们之间具有很大的差异:
WCF中的Session的实现和Instancing Management 在上面我们说了,虽然WCF支持Session,但是并没有相关的状态信息被保存在某种介质中。WCF是通过怎样的方式来支持Session的呢?这就是我们本节要讲的Instancing Management。 对于Client来说,它实际上不能和Service进行直接交互,它只能通过客户端创建的Proxy来间接地实现和service的交互。Session的表现体现在以下两种方式:
我们很清楚,真正的逻辑实现是通过调用真正的Service instance中。在一个分布式环境中,我们把通过Client的调用来创建最终的Service Instance的过程叫做Activation。在Remoting中我们有两种Activation方式:Server Activation(Singleton和SingleCall),Client Activation。实际上对WCF也具有相似的Activation。不过WCF不仅仅创建对应的Service Instance,而且还构建相关的Context, 我们把这些统称为Instance Context。不同的Activation方式在WCF中体现为的Instance context model。不同的Instance Context Mode体现为Proxy、Service 调用和Service Instance之间的对应关系。可以这么说,Instance Context Mode决定着不同的Session表现。在WCF中,支持以下3中不同级别的Instance Context Mode:
WCF的默认的Instance Context Mode为PerSession,但是对于是否对Session的支持,Instancing的机制有所不同。如果通过以下的方式定义ServiceContract使之不支持Session,或者使用不支持Session的Binding(顺便说一下,Session的支持是通过建立Sessionful Channel来实现的,但是并不是所有的Binding都支持Session,比如BasicHttpBinding就不支持Session),WCF实际上会为每个Service调用创建一个Service Instance,这实质上就是PerCall的Instance Context Mode,但我为什么会说默认的是PerSession呢?我个人觉得我们可以这样地来看看Session:Session按照本意就是Client和Service之间建立的一个持续的会话状态,不过这个Session状态的持续时间有长有短,可以和Client的生命周期一样,也可以存在于某两个特定的Operation调用之间,最短的则可以看成是每次Service的调用,所以按照我的观点,PerCall也可以看成是一种特殊的Session(我知道会有很多人不认同我的这种看法。) [ServiceContract(SessionMode = SessionMode.NotAllowed)]
Simple 接下来我们来看看一个简单的Sample,相信大家会对Session和Instancing Management会有一个深入的认识。这个Sample沿用我们Calculator的例子,Solution的结构如下,4个Project分别用于定义SeviceContract、Service Implementation、Hosting和Client。
1. Service Contract:ICalculator using System;
using System.Collections.Generic; using System.Text; using System.ServiceModel; namespace Artech.SessionfulCalculator.Contract { [ServiceContract] public interface ICalculator { [OperationContract(IsOneWay = true)] void Adds(double x); [OperationContract] double GetResult(); } } 2. Service Implementation:CalculatorService using System;
using System.Collections.Generic; using System.Text; using System.ServiceModel; using Artech.SessionfulCalculator.Contract; namespace Artech.SessionfulCalculator.Service { public class CalculatorService:ICalculator { private double _result; ICalculator Members#region ICalculator Members public void Adds(double x) { Console.WriteLine("The Add method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId); this._result += x; } public double GetResult() { Console.WriteLine("The GetResult method is invoked and the current session ID is: {0}", OperationContext.Current.SessionId); return this._result; } #endregion public CalculatorService() { Console.WriteLine("Calculator object has been created"); } ~CalculatorService() { Console.WriteLine("Calculator object has been destoried"); } } } 为了让大家对Service Instance的创建和回收有一个很直观的认识,我特意在Contructor和Finalizer中作了一些指示性的输出。同时在每个Operation中输出的当前的Session ID 3. Hosting Program using System;
using System.Collections.Generic; using System.Text; using System.ServiceModel; using Artech.SessionfulCalculator.Service; using System.Threading; namespace Artech.SessionfulCalculator.Hosting { class Program { static void Main(string[] args) { using(ServiceHost host = new ServiceHost(typeof(CalculatorService))) { host.Opened += delegate { Console.WriteLine("The Calculator service has begun to listen"); }; host.Open(); Timer timer = new Timer(delegate { GC.Collect(); }, null, 0, 100); Console.Read(); } } } } 除了Host CalculatorService之外,我还通过一个Timer对象每隔一个很短的时间(0.1s)作一次强制的垃圾回收,使我们通过输出看出Service Instance是否被回收了。 Configuration <?xml version="1.0" encoding="utf-8" ?>
<configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="CalculatorBehavior"> <serviceMetadata httpGetEnabled="true" /> </behavior> </serviceBehaviors> </behaviors> <services> <service behaviorConfiguration="CalculatorBehavior" name="Artech.SessionfulCalculator.Service.CalculatorService"> <endpoint address="" binding="basicHttpBinding" bindingConfiguration="" contract="Artech.SessionfulCalculator.Contract.ICalculator" /> <host> <baseAddresses> <add baseAddress="http://localhost:9999/SessionfulCalculator" /> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration> 我们使用的是basicHttpBinding 4. Client using System;
using System.Collections.Generic; using System.Text; using System.ServiceModel; using Artech.SessionfulCalculator.Contract; namespace Artech.SessionfulCalculator.Client { class Program { static void Main(string[] args) { ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("httpEndpoint"); Console.WriteLine("Create a calculator proxy: proxy1"); ICalculator proxy1 = calculatorChannelFactory.CreateChannel(); Console.WriteLine("Invocate proxy1.Adds(1)"); proxy1.Adds(1); Console.WriteLine("Invocate proxy1.Adds(2)"); proxy1.Adds(2); Console.WriteLine("The result return via proxy1.GetResult() is : {0}", proxy1.GetResult()); Console.WriteLine("Create a calculator proxy: proxy2"); ICalculator proxy2= calculatorChannelFactory.CreateChannel(); Console.WriteLine("Invocate proxy2.Adds(1)"); proxy2.Adds(1); Console.WriteLine("Invocate proxy2.Adds(2)"); proxy2.Adds(2); Console.WriteLine("The result return via proxy2.GetResult() is : {0}", proxy2.GetResult()); Console.Read(); } } } 我创建了两个Proxy:Proxy1和Proxy2,并以同样的方式调用它们的方法:Add->Add->GetResult。 Configuration <?xml version="1.0" encoding="utf-8" ?>
<configuration> <system.serviceModel> <client> <endpoint address="http://localhost:9999/SessionfulCalculator" binding="basicHttpBinding" contract="Artech.SessionfulCalculator.Contract.ICalculator" name="httpEndpoint" /> </client> </system.serviceModel> </configuration> 我们来看看运行的结果: Client端:
<endpoint address="" binding="wsHttpBinding" bindingConfiguration=""
contract="Artech.SessionfulCalculator.Contract.ICalculator" /> 和Client的Endpoint的配置: <endpoint address="http://localhost:9999/SessionfulCalculator"
binding="wsHttpBinding" contract="Artech.SessionfulCalculator.Contract.ICalculator" name="httpEndpoint" /> 现在再来看看执行的结果,首先看看Client:
我现在就来通过修改Client端的来Close掉Proxy:通过ICommunicationObject.Close来显式地close掉Proxy static void Main(string[] args)
{ ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("httpEndpoint"); Console.WriteLine("Create a calculator proxy: proxy1"); ICalculator proxy1 = calculatorChannelFactory.CreateChannel(); Console.WriteLine("Invocate proxy1.Adds(1)"); proxy1.Adds(1); Console.WriteLine("Invocate proxy1.Adds(2)"); proxy1.Adds(2); Console.WriteLine("The result return via proxy1.GetResult() is : {0}", proxy1.GetResult()); (proxy1 as ICommunicationObject).Close(); Console.WriteLine("Create a calculator proxy: proxy2"); ICalculator proxy2= calculatorChannelFactory.CreateChannel(); Console.WriteLine("Invocate proxy2.Adds(1)"); proxy2.Adds(1); Console.WriteLine("Invocate proxy2.Adds(2)"); proxy2.Adds(2); Console.WriteLine("The result return via proxy2.GetResult() is : {0}", proxy2.GetResult()); (proxy1 as ICommunicationObject).Close(); Console.Read(); } 那么我们现在看运行后Host的输出,就会发现Finalizer被调用了:
[ServiceContract(SessionMode = SessionMode.NotAllowed)]
public interface ICalculator { [OperationContract(IsOneWay = true)] void Adds(double x); [OperationContract] double GetResult(); } 看看Client的输出:
using System;
using System.Collections.Generic; using System.Text; using System.ServiceModel; namespace Artech.SessionfulCalculator.Contract { [ServiceContract(SessionMode = SessionMode.Required)] public interface ICalculator { [OperationContract(IsOneWay = true, IsInitiating = true, IsTerminating = false)] void Adds(double x); [OperationContract(IsInitiating = false,IsTerminating =true)] double GetResult(); } } 为了模拟当Session终止后继续调用Proxy的场景,我进一步修改了Client的代码: class Program
{ static void Main(string[] args) { ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("httpEndpoint"); Console.WriteLine("Create a calculator proxy: proxy1"); ICalculator proxy1 = calculatorChannelFactory.CreateChannel(); Console.WriteLine("Invocate proxy1.Adds(1)"); proxy1.Adds(1); Console.WriteLine("Invocate proxy1.Adds(2)"); proxy1.Adds(2); Console.WriteLine("The result return via proxy1.GetResult() is : {0}", proxy1.GetResult()); Console.WriteLine("Invocate proxy1.Adds(1)"); try { proxy1.Adds(1); } catch (Exception ex) { Console.WriteLine("It is fail to invocate the Add after terminating session because \"{0}\"", ex.Message); } Console.WriteLine("Create a calculator proxy: proxy2"); ICalculator proxy2= calculatorChannelFactory.CreateChannel(); Console.WriteLine("Invocate proxy2.Adds(1)"); proxy2.Adds(1); Console.WriteLine("Invocate proxy2.Adds(2)"); proxy2.Adds(2); Console.WriteLine("The result return via proxy2.GetResult() is : {0}", proxy2.GetResult()); Console.Read(); } 现在看看 Client的输出结果:
以上我们对采用默认的Instance Context Model,不同的Session Model。现在我们反过来,在Session支持的前提下,采用不同Instance Context Model,看看结果又如何: 我们把Client端的代码回到最初的状态: static void Main(string[] args)
{ ChannelFactory<ICalculator> calculatorChannelFactory = new ChannelFactory<ICalculator>("httpEndpoint"); Console.WriteLine("Create a calculator proxy: proxy1"); ICalculator proxy1 = calculatorChannelFactory.CreateChannel(); Console.WriteLine("Invocate proxy1.Adds(1)"); proxy1.Adds(1); Console.WriteLine("Invocate proxy1.Adds(2)"); proxy1.Adds(2); Console.WriteLine("The result return via proxy1.GetResult() is : {0}", proxy1.GetResult()); Console.WriteLine("Create a calculator proxy: proxy2"); ICalculator proxy2= calculatorChannelFactory.CreateChannel(); Console.WriteLine("Invocate proxy2.Adds(1)"); proxy2.Adds(1); Console.WriteLine("Invocate proxy2.Adds(2)"); proxy2.Adds(2); Console.WriteLine("The result return via proxy2.GetResult() is : {0}", proxy2.GetResult()); Console.Read(); } 通过在Calculator Service上面运用ServiceBehavior,并指定InstanceContextMode为PerCall: [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class CalculatorService:ICalculator { } 虽然我们ServiceContract被显式指定为支持Session,看看运行的结果是否如此:
我们来看看Single的Instance Context Mode: ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class CalculatorService:ICalculator { } 我们这次先来看Hosting的输出结果,这是在刚刚启动Hosting,Client尚未启动时的Screenshot。
现在启动Client:
|
|
来自: waywin > 《Technology》