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


WCF後續之旅(6): 通過WCF Extension實現Context信息的傳遞

一、 Ambient Context

  • 將Context作為參數傳遞:將context作為API的一部分,context的提供者在調用context接收者的API的時候顯式地設置這些Context信息,context的接收者則直接通過參數將context取出。這雖然能夠解決問題,但決不是一個好的解決方案,因為API應該隻和具體的業務邏輯有關,而context 一般是與非業務邏輯服務的,比如Audit、Logging等等。此外,將context納入API作為其一部分,將降低API的穩定性, 比如,今天隻需要當前user所在組織的信息,明天可能需求獲取當前客戶端的IP地址,你的API可以會經常變動,這顯然是不允許的。
  • 創建Ambient Context來保存這些context信息:Ambient Context可以在不同的層次之間、甚至是分布式環境中每個節點之間共享或者傳遞。比如在ASP.NET 應用中,我們通過SessionSate來存儲當前Session的信息;通過HttpContext來存儲當前Http request的信息。在非Web應用中,我們通過CallContext將context信息存儲在TLS(Thread Local Storage)中,當前線程下執行的所有代碼都可以訪問並設置這些context數據。

二、Application Context

 

   1: namespace Artech.ContextPropagation
   2: {
   3:     [Serializable]
   4:     public class ApplicationContext : Dictionary<string, object>
   5:     {
   6:         private const string CallContextKey = "__ApplicationContext";
   7:         internal const string ContextHeaderLocalName = "__ApplicationContext";
   8:         internal const string ContextHeaderNamespace = "urn:artech.com";
   9:  
  10:         private void EnsureSerializable(object value)
  11:         {
  12:             if (value == null)
  13:             {
  14:                 throw new ArgumentNullException("value");
  15:             }
  16:             if (!value.GetType().IsSerializable)
  17:             {
  18:                 throw new ArgumentException(string.Format("The argument of the type \"{0}\" is not serializable!", value.GetType().FullName));
  19:             }
  20:         }
  21:  
  22:         public new object this[string key]
  23:         {
  24:             get{return base[key];}
  25:             set
  26:             {this.EnsureSerializable(value);base[key] = value;}
  27:         }
  28:  
  29:         public int Counter
  30:         {
  31:             get{return (int)this["__Count"];}
  32:             set{this["__Count"] = value;}
  33:         }
  34:  
  35:         public static ApplicationContext Current
  36:         {
  37:             get
  38:             {
  39:                 if (CallContext.GetData(CallContextKey) == null)
  40:                 {
  41:                     CallContext.SetData(CallContextKey, new ApplicationContext());
  42:                 }
  43:  
  44:                 return CallContext.GetData(CallContextKey) as ApplicationContext;
  45:             }
  46:             set
  47:             {
  48:                 CallContext.SetData(CallContextKey, value);
  49:             }
  50:         }
  51:     }
  52: }

private const string CallContextKey = "__ApplicationContext"; internal const string ContextHeaderLocalName = "__ApplicationContext";
internal const string ContextHeaderNamespace = "urn:artech.com";

   1: public static ApplicationContext Current
   2: {
   3:     get
   4:     {
   5:         if (CallContext.GetData(CallContextKey) == null)
   6:         {
   7:             CallContext.SetData(CallContextKey, new ApplicationContext());
   8:         }
   9:  
  10:         return CallContext.GetData(CallContextKey) as ApplicationContext;
  11:     }
  12:     set
  13:     {
  14:         CallContext.SetData(CallContextKey, value);
  15:     }
  16: } 

三、通過MessageInspector將AppContext置於SOAP header中

   1: namespace Artech.ContextPropagation
   2: {
   3:     public class ContextAttachingMessageInspector : IClientMessageInspector
   4:     {
   5:         public bool IsBidirectional{ get; set; }
   6:  
   7:         public ContextAttachingMessageInspector(): this(false){ }
   8:  
   9:         public ContextAttachingMessageInspector(bool isBidirectional)
  10:         {
  11:             this.IsBidirectional = IsBidirectional;
  12:         }
  13:  
  14:         public void AfterReceiveReply(ref Message reply, object correlationState)
  15:         {
  16:             if (IsBidirectional){return;}
  17:             if (reply.Headers.FindHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace) < 0){return;}
  18:             ApplicationContext context = reply.Headers.GetHeader<ApplicationContext>(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace);
  19:             if (context == null){return;}
  20:             ApplicationContext.Current = context;
  21:         }
  22:  
  23:         public object BeforeSendRequest(ref Message request, IClientChannel channel)
  24:         {
  25:             MessageHeader<ApplicationContext> contextHeader = new MessageHeader<ApplicationContext>(ApplicationContext.Current);
  26:             request.Headers.Add(contextHeader.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace));
  27:             return null;
  28:         }
  29:  
  30:     }
  31: } 

   1: public object BeforeSendRequest(ref Message request, IClientChannel channel)
   2: {
   3:             MessageHeader<ApplicationContext> contextHeader = new MessageHeader<ApplicationContext>(ApplicationContext.Current);
   4:             request.Headers.Add(contextHeader.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace));
   5:             return null;
   6: } 

   1: public void AfterReceiveReply(ref Message reply, object correlationState)
   2: {
   3:     if (IsBidirectional){return;}
   4:     if (reply.Headers.FindHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace) < 0){return;}
   5:     ApplicationContext context = reply.Headers.GetHeader<ApplicationContext>(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace);
   6:     if (context == null){return;}
   7:     ApplicationContext.Current = context;
   8: }

四、通過ContextInitializer實現對Context的接收

   1: namespace Artech.ContextPropagation
   2: {
   3:     public class ContextReceivalCallContextInitializer : ICallContextInitializer
   4:     {
   5:         public bool IsBidirectional{ get; set; }
   6:         public ContextReceivalCallContextInitializer(): this(false){ }
   7:         public ContextReceivalCallContextInitializer(bool isBidirectional)
   8:         {
   9:             this.IsBidirectional = isBidirectional;
  10:         }
  11:         public void AfterInvoke(object correlationState)
  12:         {
  13:             if (!this.IsBidirectional)
  14:             {
  15:                 return;
  16:             }
  17:  
  18:             ApplicationContext context = correlationState as ApplicationContext;
  19:             if (context == null)
  20:             {
  21:                 return;
  22:             }
  23:             MessageHeader<ApplicationContext> contextHeader = new MessageHeader<ApplicationContext>(context);
  24:             OperationContext.Current.OutgoingMessageHeaders.Add(contextHeader.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace));
  25:             ApplicationContext.Current = null;
  26:         }
  27:  
  28:         public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
  29:         {
  30:             ApplicationContext context = message.Headers.GetHeader<ApplicationContext>(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace);
  31:             if (context == null){return null;}
  32:  
  33:             ApplicationContext.Current = context;
  34:             return ApplicationContext.Current;
  35:         }
  36:     }
  37: } 

五、為MessageInspector和CallContextInitializer創建behavior

   1: namespace Artech.ContextPropagation
   2: {
   3:     public class ContextPropagationBehavior : IEndpointBehavior
   4:     {
   5:         public bool IsBidirectional{ get; set; }
   6:         public ContextPropagationBehavior(): this(false){ }
   7:         public ContextPropagationBehavior(bool isBidirectional)
   8:         {
   9:             this.IsBidirectional = isBidirectional;
  10:         }
  11:         public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters){}
  12:         public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  13:         {
  14:             clientRuntime.MessageInspectors.Add(new ContextAttachingMessageInspector(this.IsBidirectional));
  15:         }
  16:         public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  17:         {
  18:             foreach (var operation in endpointDispatcher.DispatchRuntime.Operations)
  19:             {
  20:                 operation.CallContextInitializers.Add(new ContextReceivalCallContextInitializer(this.IsBidirectional));
  21:             }
  22:         }
  23:         public void Validate(ServiceEndpoint endpoint){}
  24:     }
  25: } 

   1: namespace Artech.ContextPropagation
   2: {
   3:     public class ContextPropagationBehaviorElement : BehaviorExtensionElement
   4:     {
   5:         [ConfigurationProperty("isBidirectional", DefaultValue = false)]
   6:         public bool IsBidirectional
   7:         {
   8:             get{return (bool)this["isBidirectional"];}
   9:             set{this["isBidirectional"] = value;}
  10:         }
  11:         public override Type BehaviorType
  12:         {
  13:             get{return typeof(ContextPropagationBehavior);}
  14:         }
  15:         protected override object CreateBehavior()
  16:         {
  17:             return new ContextPropagationBehavior(this.IsBidirectional);
  18:         }
  19:     }
  20: } 

六、Context Propagation的運用

wcf_02_06_01

   1: namespace Artech.ContextPropagation.Contract
   2: {
   3:     [ServiceContract]
   4:     public interface IContract
   5:     {
   6:         [OperationContract]
   7:         void DoSomething();
   8:     }
   9: } 

   1: namespace Artech.ContextPropagation.Services
   2: {
   3:     public class Service:IContract
   4:     {
   5:         public void DoSomething()
   6:         {
   7:             Console.WriteLine("ApplicationContext.Current.Count = {0}", ApplicationContext.Current.Counter);
   8:             ApplicationContext.Current.Counter++;
   9:         } 
  10:     }
  11: } 

   1: <configuration>
   2:     <system.serviceModel>
   3:         <behaviors>
   4:             <endpointBehaviors>
   5:                 <behavior name="contextPropagationBehavior">
   6:                     <contextPropagationElement isBidirectional="true" />
   7:                 </behavior>
   8:             </endpointBehaviors>
   9:         </behaviors>
  10:         <client>
  11:             <endpoint address="https://127.0.0.1/service" behaviorConfiguration="contextPropagationBehavior"
  12:                 binding="basicHttpBinding" contract="Artech.ContextPropagation.Contract.IContract"
  13:                 name="service" />
  14:         </client>
  15:         <extensions>
  16:             <behaviorExtensions>
  17:                 <add name="contextPropagationElement" type="Artech.ContextPropagation.ContextPropagationBehaviorElement, Artech.ContextPropagation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  18:             </behaviorExtensions>
  19:         </extensions>
  20:     </system.serviceModel>
  21: </configuration>

   1: namespace Artech.ContextPropagation.Client
   2: {
   3:     class Program
   4:     {
   5:         static void Main(string[] args)
   6:         {
   7:             using (ChannelFactory<IContract> channelFactory = new ChannelFactory<IContract>("service"))
   8:             {
   9:                 IContract proxy = channelFactory.CreateChannel();
  10:                 ApplicationContext.Current.Counter = 100;
  11:                 Console.WriteLine("Brfore service invocation: ApplicationContext.Current.Count = {0}", ApplicationContext.Current.Counter);
  12:                 proxy.DoSomething();
  13:                 Console.WriteLine("After service invocation: ApplicationContext.Current.Count = {0}", ApplicationContext.Current.Counter);
  14:                 Console.Read();
  15:             }
  16:         }
  17:     }
  18: } 

   1: <configuration>
   2:     <system.serviceModel>
   3:         <behaviors>
   4:             <endpointBehaviors>
   5:                <behavior name="contextPropagationBehavior">
   6:                     <contextPropagationElement isBidirectional="true" />
   7:                 </behavior>
   8:             </endpointBehaviors>
   9:         </behaviors>
  10:         <client>
  11:             <endpoint address="https://127.0.0.1/service" behaviorConfiguration="contextPropagationBehavior"
  12:                 binding="basicHttpBinding" contract="Artech.ContextPropagation.Contract.IContract"
  13:                 name="service" />
  14:         </client>
  15:         <extensions>
  16:             <behaviorExtensions>
  17:                 <add name="contextPropagationElement" type="Artech.ContextPropagation.ContextPropagationBehaviorElement, Artech.ContextPropagation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  18:             </behaviorExtensions>
  19:         </extensions>
  20:     </system.serviceModel>
  21: </configuration> 

wcf_02_06_02

wcf_02_06_03

 

P.S: SOA主張Stateless的service,也就是說每次調用service都應該是相互獨立的。context的傳遞實際上卻是讓每次訪問有了狀態,這實際上是違背了SOA的原則。所以,如何對於真正的SOA的設計與架構,個人覺得這種方式是不值得推薦的。但是,如何你僅僅是將WCF作為傳統的分布式手段,那麼這可能會給你的應用帶了很大的便利。 

 

WCF後續之旅:
WCF後續之旅(1): WCF是如何通過Binding進行通信的
WCF後續之旅(2): 如何對Channel Layer進行擴展——創建自定義Channel
WCF後續之旅(3): WCF Service Mode Layer 的中樞—Dispatcher
WCF後續之旅(4):WCF Extension Point 概覽
WCF後續之旅(5): 通過WCF Extension實現Localization
WCF後續之旅(6): 通過WCF Extension實現Context信息的傳遞
WCF後續之旅(7):通過WCF Extension實現和Enterprise Library Unity Container的集成
WCF後續之旅(8):通過WCF Extension 實現與MS Enterprise Library Policy Injection Application Block 的集成
WCF後續之旅(9):通過WCF的雙向通信實現Session管理[Part I]
WCF後續之旅(9): 通過WCF雙向通信實現Session管理[Part II]
WCF後續之旅(10): 通過WCF Extension實現以對象池的方式創建Service Instance
WCF後續之旅(11): 關於並發、回調的線程關聯性(Thread Affinity)
WCF後續之旅(12): 線程關聯性(Thread Affinity)對WCF並發訪問的影響
WCF後續之旅(13): 創建一個簡單的WCF SOAP Message攔截、轉發工具[上篇]
WCF後續之旅(13):創建一個簡單的SOAP Message攔截、轉發工具[下篇]
WCF後續之旅(14):TCP端口共享
WCF後續之旅(15): 邏輯地址和物理地址
WCF後續之旅(16): 消息是如何分發到Endpoint的--消息篩選(Message Filter)
WCF後續之旅(17):通過tcpTracer進行消息的路由

 


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

最後更新:2017-10-30 17:04:58

  上一篇:go  WCF後續之旅(5): 通過WCF Extension實現Localization
  下一篇:go  WCF後續之旅(7):通過WCF Extension實現和Enterprise Library Unity Container的集成