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的運用
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>
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