首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WCF 訊息檢查器實戰:跨層 Context 資訊傳遞機制

     編輯:關於.NET

Ambient Context 模式與 ApplicationContext

分層架構中常需傳遞審計、記錄等非業務上下文資訊。若作為方法參數傳遞會污染 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);
    }
}

ClientMessageInspector:封裝 Context 至 SOAP Header


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;
    }
}

CallContextInitializer:服務端接收與還原 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;
    }
}

EndpointBehavior 整合雙端擴展


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);
}

實戰驗證:雙向 Counter 傳遞

  • Service:DoSomething() 印出 ApplicationContext.Current.Counter 並遞增。
  • Client:設定 Counter = 100 呼叫服務,觀察服務端印出 100、101...,呼叫結束後 Client 端 Counter 變為 101 (服務端修改回傳)。
  • 設定檔:雙端 endpointBehaviors 參考 contextPropagationElement isBidirectional="true"。

架構權衡提醒

SOA 強調 Stateless Service,Context 傳遞實質引入狀態,違背 SOA 原則。若將 WCF 視為傳統分散式 RPC 框架,此機制極具便利性;若嚴守 SOA,請審慎評估。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved