WCF 扩展之我见: InstanceContextProvider

关于 InstanceContext 的话题,在前几篇文章中也有介绍,大家不妨了解一下。

IntanceContext 翻译成中文可以理解为实例的上下文,类似线程上下文,它提供了与实例有关的一切环境信息,如 ServiceHost 的信息、事务的信息、并发的信息、Channel 的信息等。下面是 InstanceContext 的类图 (由于方法太多,只截取了部分,请留意黄色高亮部分):


-- <InstanceContext 和 InstanceContextMode.PerCall>

通过上面的文字,不难发现 InstanceContext 是一个相当重要的对象,它封装了一个 Instance 所需要的所有环境,同时通过调用 InstanceContext 的 GetServiceInstance 便可以获取的实例对象本身。


作 用

InstanceContext 的主要目的是为了在不同的调用之间共享同一个服务实例,实现服务实例的对象池,控制对象实例的生命周期(e.g. 来自同一个或不同的客户端的不同请求就会分发给同一个服务实例所处理)。

WCF 中预置了三种 InstanceContextMode : PerCall,PerSession 和 Single。


什么时候需要自定义 InstanceContextProvider

InstanceContextProvider 类似于其它 Provider, 目的是用于提供 InstanceContext。我们可以通过自定义来实现自己的 Provider。

当系统自定义的三种 Mode 无法满足需求的时候,比如

1, 所使用的 Binding 不支持 Session(e.g. BasicHttpBinding),但又希望能支持 Instance 的共享;

2, 希望不同的客户端共享 Instance 实例;


WCF 预置的三种 Mode 对于自定义的 InstanceContextProvider 无效

如何扩展 InstanceContextProvider

InstanceContextProvider 实现了 IInstanceContextProvider 的接口:

public interface IInstanceContextProvider
{
    InstanceContext GetExistingInstanceContext(Message message,IContextChannel channel);
    void InitializeInstanceContext(InstanceContext instanceContext,Message message, IContextChannel channel);
    bool IsIdle(InstanceContext instanceContext);
    void NotifyIdle(InstanceContextIdleCallback callback, InstanceContextinstanceContext);
}

当 Message 抵达 WCF 时,先会调用 GetExistingInstnaceContext 去获取一个 InstanceContext。如果已经存在,则会直接返回,如果不存在,则会返回 null。

如果 GetExistingInstanceContext 返回 null,WCF 运行时会构造一个新的 InstanceContext,然后调用 InitializeInstanceContext 去进行初始化。

当服务调用完毕,或者闲置了一段时间后,WCF 运行时会调用 IsIdle 去判断当前是否可以对该 InstanceContext 进行回收,如果不希望 InstanceContxt 被回收,则此处应该返回 false。

如果 IsIdle 返回 true,InstanceContext 将会被回收,否则 NotifyIdle 就会被调用,该方法会传递一个 IdleCallback,后续代码可以通过使用这个 callback 来通知 WCF 运行时可以进行回收了。


使用自定义的类型实现了该接口后,利用任意一个类型的 Behavior 把该实现挂接到 DispatchRuntime 这个对象上即可:

public class MyBehavior : IContractBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }
 
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    }
 
    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        endpointDispatcher.DispatchRuntime.InstanceContextProvider = new myProvider();
    }
 
    public void Validate(ServiceEndpoint endpoint)
    {
    }
}


至于,如果把 Behavior 应用到 WCF 应用程序中,请大家参考《WCF 扩展之我见: Behaviors》。


内置的三类 InstanceContextProvider

//PerCall

    internal class PerCallInstanceContextProvider : InstanceContextProviderBase
    {    
        internal PerCallInstanceContextProvider(DispatchRuntime dispatchRuntime)
            : base(dispatchRuntime)
        {
        } 
        
        #region IInstanceContextProvider Members 
        public override InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel)
        {        
            //Always return null so we will create new InstanceContext for each message
            return null;
        } 
        
        public override void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel)
        {      
              //no-op
        } 
        
        public override bool IsIdle(InstanceContext instanceContext)
        {         
            //By default return true if no channels are bound to this context
            return true;
        } 
        
        public override void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext)
        {      
              //no-op
        } 
        #endregion
    }


//PerSession

    class PerSessionInstanceContextProvider : InstanceContextProviderBase
    { 
        internal PerSessionInstanceContextProvider(DispatchRuntime dispatchRuntime)
            : base(dispatchRuntime)
        {
        } 
        public override InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel)
        {        
            // Here is the flow for a Sessionful channel
            //  1. First request comes in on new channel.
            //  2. ServiceChannel.InstanceContext is returned which is null.
            //  3. InstanceBehavior.EnsureInstanceContext will create a new InstanceContext.
            //  4. this.InitializeInstanceContext is called with the newly created InstanceContext and the channel.
            //  5. If the channel is sessionful then its bound to the InstanceContext.
            //  6. Bind channel to the InstanceContext.
            //  7. For all further requests on the same channel, we will return ServiceChannel.InstanceContext which will be non null.
            ServiceChannel serviceChannel = this.GetServiceChannelFromProxy(channel);
            return (serviceChannel != null) ? serviceChannel.InstanceContext : null;
        } 
        
        public override void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel)
        {     
             ServiceChannel serviceChannel = GetServiceChannelFromProxy(channel);      
             if (serviceChannel != null && serviceChannel.HasSession)
             {          
                  instanceContext.BindIncomingChannel(serviceChannel);
             }
        } 
 
        public override bool IsIdle(InstanceContext instanceContext)
        {       
            //By default return true
            return true;
        } 
        
        public override void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext)
        {       
             //no-op
        }
    }


//Single

    internal class SingletonInstanceContextProvider : InstanceContextProviderBase
    {   
        InstanceContext singleton;        
        object thisLock; 
        
        internal SingletonInstanceContextProvider(DispatchRuntime dispatchRuntime)
            : base(dispatchRuntime)
        {        
            this.thisLock = new Object();
        } 
        
        internal InstanceContext SingletonInstance
        {         
            get
            {             
                if (this.singleton == null)
                {                 
                    lock (this.thisLock)
                    {                     
                        if (this.singleton == null)
                        {                        
                            InstanceContext instanceContext = this.DispatchRuntime.SingletonInstanceContext; 
                            if (instanceContext == null)
                            {                            
                                 instanceContext = new InstanceContext(this.DispatchRuntime.ChannelDispatcher.Host, false);                                
                                 instanceContext.Open();
                            }                            
                            else if (instanceContext.State != CommunicationState.Opened)
                            {                             
                                // we need to lock against the instance context for open since two different endpoints could
                                // share the same instance context, but different providers. So the provider lock does not guard
                                // the open process
                                lock (instanceContext.ThisLock)
                                {                                 
                                    if (instanceContext.State != CommunicationState.Opened)
                                    {                                     
                                       instanceContext.Open();
                                    }
                                }
                            } 
                            //Set the IsUsercreated flag to false for singleton mode even in cases when users create their own runtime.
                            instanceContext.IsUserCreated = false; 
                            //Delay assigning the potentially newly created InstanceContext (till after its opened) to this.Singleton 
                            //to ensure that it is opened only once.
                            this.singleton = instanceContext;
                        }
                    }
                }             
                
                return this.singleton;
            }
        } 
        
        #region IInstanceContextProvider Members 
        public override InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel)
        {        
            ServiceChannel serviceChannel = this.GetServiceChannelFromProxy(channel);            
            if (serviceChannel != null && serviceChannel.HasSession)
            {            
                this.SingletonInstance.BindIncomingChannel(serviceChannel);
            }            
            return this.SingletonInstance;
        } 
        
        public override void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel)
        {          
            //no-op
        } 
        public override bool IsIdle(InstanceContext instanceContext)
        {          
            //By default return false
            return false;
        } 
        public override void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext)
        {        
            //no-op
        } 
        #endregion
    }



扩展阅读

InstanceContextSharing

IInstanceContextProvider Interface

WCF Extensibility – IInstanceContextProvider

文章索引

[隐 藏]

本站采用知识共享署名 3.0 中国大陆许可协议进行许可。 ©2014 Charley Box | About Site | 浙ICP备13014059号