分層架構中常需傳遞審計、記錄等非業務上下文資訊。若作為方法參數傳遞會污染 API、降低穩定性。解決方案是建立 Ambient Context:ApplicationContext 繼承 Dictionary<string, object>,透過 CallContext 存儲於 TLS (Thread Local Storage),實現同執行緒內自由存取。
[Serializable]
public class ApplicationContext : Dictionary<string, object>
{
private const string CallContextKey = "__ApplicationContext";
internal const string ContextHeaderLocalName = "__ApplicationContext";
internal const string ContextHeaderNamespace = "urn:artech.com";
public new object this[string key]
{
get => base[key];
set { if (value == null || !value.GetType().IsSerializable) throw new ArgumentException("Value must be serializable."); base[key] = value; }
}
public int Counter { get => (int)this["__Count"]; set => this["__Count"] = value; }
public static ApplicationContext Current
{
get
{
if (CallContext.GetData(CallContextKey) == null) CallContext.SetData(CallContextKey, new ApplicationContext());
return CallContext.GetData(CallContextKey) as ApplicationContext;
}
set => CallContext.SetData(CallContextKey, value);
}
}
public class ContextAttachingMessageInspector : IClientMessageInspector
{
public bool IsBidirectional { get; set; }
public ContextAttachingMessageInspector(bool isBidirectional = false) => IsBidirectional = isBidirectional;
public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
var header = new MessageHeader<ApplicationContext>(ApplicationContext.Current);
request.Headers.Add(header.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace));
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState)
{
if (!IsBidirectional) return;
if (reply.Headers.FindHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace) < 0) return;
var context = reply.Headers.GetHeader<ApplicationContext>(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace);
if (context != null) ApplicationContext.Current = context;
}
}
public class ContextReceivalCallContextInitializer : ICallContextInitializer
{
public bool IsBidirectional { get; set; }
public ContextReceivalCallContextInitializer(bool isBidirectional = false) => IsBidirectional = isBidirectional;
public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
{
var context = message.Headers.GetHeader<ApplicationContext>(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace);
if (context == null) return null;
ApplicationContext.Current = context;
return ApplicationContext.Current;
}
public void AfterInvoke(object correlationState)
{
if (!IsBidirectional) return;
var context = correlationState as ApplicationContext;
if (context == null) return;
var header = new MessageHeader<ApplicationContext>(context);
OperationContext.Current.OutgoingMessageHeaders.Add(header.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace));
ApplicationContext.Current = null;
}
}
public class ContextPropagationBehavior : IEndpointBehavior
{
public bool IsBidirectional { get; set; }
public ContextPropagationBehavior(bool isBidirectional = false) => IsBidirectional = isBidirectional;
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
=> clientRuntime.MessageInspectors.Add(new ContextAttachingMessageInspector(IsBidirectional));
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
foreach (var op in endpointDispatcher.DispatchRuntime.Operations)
op.CallContextInitializers.Add(new ContextReceivalCallContextInitializer(IsBidirectional));
}
// 其他介面成員空實作...
}
public class ContextPropagationBehaviorElement : BehaviorExtensionElement
{
[ConfigurationProperty("isBidirectional", DefaultValue = false)]
public bool IsBidirectional { get => (bool)this["isBidirectional"]; set => this["isBidirectional"] = value; }
public override Type BehaviorType => typeof(ContextPropagationBehavior);
protected override object CreateBehavior() => new ContextPropagationBehavior(IsBidirectional);
}
DoSomething() 印出 ApplicationContext.Current.Counter 並遞增。Counter = 100 呼叫服務,觀察服務端印出 100、101...,呼叫結束後 Client 端 Counter 變為 101 (服務端修改回傳)。endpointBehaviors 參考 contextPropagationElement isBidirectional="true"。SOA 強調 Stateless Service,Context 傳遞實質引入狀態,違背 SOA 原則。若將 WCF 視為傳統分散式 RPC 框架,此機制極具便利性;若嚴守 SOA,請審慎評估。