0

WCFとPIABを組み合わせて、ロギング、検証、監査などの横断的関心事に対処することはまったく問題ありません(http://msdn.microsoft.com/en-us/magazine/cc136759.aspxにアクセスしてください)。

ただし、bogの標準ログ呼び出しハンドラーは、ログの「拡張プロパティ」の限定されたセットのみをサポートします。クライアントIPアドレス、ユーザーIDなどの追加情報をログに記録する必要がある場合はどうなりますか?

回答(評価の低いメンバーに対するstackoverflowの奇妙なポリシーにより、後で回答として追加されます):

4

1 に答える 1

0

多くのことを掘り下げた後、私はこの解決策を思いつきました。これは同じクエリで他の人に利益をもたらすことを願っています。

まず、ログに必要なすべての追加データを含めるためのカスタム呼び出しハンドラーが必要です。entlibソースコードを参照して、LogCallHandlerを探すことができます。GetLogEntryプライベートメソッドに追加のデータを追加します。

    private TraceLogEntry GetLogEntry(IMethodInvocation input)
    {
        var logEntry = new CustomLogEntry();
        var formatter = new CategoryFormatter(input.MethodBase);
        foreach (string category in categories)
        {
            logEntry.Categories.Add(formatter.FormatCategory(category));
        }

        //slot = Thread.GetNamedDataSlot("PatientId");
        //logEntry.PatientId = Thread.GetData(slot).ToString();
        //logEntry.PatientId = CallContext.GetData("__PatientId").ToString();
        logEntry.AppName = ApplicationContext.Current["AppName"].ToString();
        logEntry.ClientIp = ApplicationContext.Current["ClientIp"].ToString();
        logEntry.UserId = ApplicationContext.Current["UserId"].ToString();
        logEntry.PatientId = ApplicationContext.Current["PatientId"].ToString();
        logEntry.EventId = eventId;
        logEntry.Priority = priority;
        logEntry.Severity = severity;
        logEntry.Title = LogCallHandlerDefaults.Title;

        if (includeParameters)
        {
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            for (int i = 0; i < input.Arguments.Count; ++i)
            {
                parameters[input.Arguments.GetParameterInfo(i).Name] = input.Arguments[i];
            }

            logEntry.ExtendedProperties = parameters;
        }

        if (includeCallStack)
        {
            logEntry.CallStack = Environment.StackTrace;
        }

        logEntry.TypeName = input.Target.GetType().FullName;
        logEntry.MethodName = input.MethodBase.Name;
        return logEntry;
    }

その後、コンテキストデータをクライアントからサーバーに伝播するためのインフラストラクチャを作成する必要があります。コンテキストデータのディクショナリオブジェクトを格納するためのCallContextのラッパークラスがあります。

[Serializable]
public class ApplicationContext : Dictionary<string, object>
{
    private const string CALL_CONTEXT_KEY = "__Context";
    public const string ContextHeaderLocalName = "__Context";
    public const string ContextHeaderNamespace = "urn:tempuri.org";

    private static void EnsureSerializable(object value)
    {
        if (value == null)
        {
            throw new ArgumentNullException("value");
        }
        if (!value.GetType().IsSerializable)
        {
            throw new ArgumentException(string.Format("The argument of the type \"{0}\" is not serializable!", value.GetType().FullName));
        }
    }

    public new object this[string key]
    {
        get { return base[key]; }
        set
        { EnsureSerializable(value); base[key] = value; }
    }

    public int Counter
    {
        get { return (int)this["__Count"]; }
        set { this["__Count"] = value; }
    }

    public static ApplicationContext Current
    {
        get
        {
            if (CallContext.GetData(CALL_CONTEXT_KEY) == null)
            {
                CallContext.SetData(CALL_CONTEXT_KEY, new ApplicationContext());
            }

            return CallContext.GetData(CALL_CONTEXT_KEY) as ApplicationContext;
        }
        set
        {
            CallContext.SetData(CALL_CONTEXT_KEY, value);
        }
    }
}

サービスクライアントでは、このコンテキストはIClientMessageInspectorを実装することでリクエストメッセージヘッダーに追加されます。

public class ClientAuditInfoInspector : IClientMessageInspector
{
    #region Implementation of IClientMessageInspector

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        var contextHeader = new MessageHeader<ApplicationContext>(ApplicationContext.Current);
        request.Headers.Add(contextHeader.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace));
        return null;
    }

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        if (reply.Headers.FindHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace) < 0) { return; }
        var context = reply.Headers.GetHeader<ApplicationContext>(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace);
        if (context == null) { return; }
        ApplicationContext.Current = context;
    }

    #endregion
}

サービス側では、着信メッセージからメッセージヘッダーを取得し、それを発信メッセージに戻すためのICallContextInitializerのカスタム実装があります。

public class AuditInfoCallContextInitializer : ICallContextInitializer
{
    #region Implementation of ICallContextInitializer
    /// <summary>
    /// Extract context data from message header through local name and namespace,
    /// set the data to ApplicationContext.Current.
    /// </summary>
    /// <param name="instanceContext"></param>
    /// <param name="channel"></param>
    /// <param name="message"></param>
    /// <returns></returns>
    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;

    }

    /// <summary>
    /// Retrieve context from correlationState and store it back to reply message header for client.
    /// </summary>
    /// <param name="correlationState"></param>
    public void AfterInvoke(object correlationState)
    {
        var context = correlationState as ApplicationContext;
        if (context == null)
        {
            return;
        }
        var contextHeader = new MessageHeader<ApplicationContext>(context);
        OperationContext.Current.OutgoingMessageHeaders.Add(contextHeader.GetUntypedHeader(ApplicationContext.ContextHeaderLocalName, ApplicationContext.ContextHeaderNamespace));
        ApplicationContext.Current = null;

    }

    #endregion
}

これは基本的に、メッセージヘッダーペイロードが移動するためのラウンドトリップです。AfterInvokeメソッドでは、メッセージヘッダーを変更してから送り返すことができます。最後に、MessageInspectorとCallContextInitializerを適用するためのエンドポイント動作を作成しました。

public class AuditInfoContextPropagationEndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
{
    #region Overrides of BehaviorExtensionElement

    protected override object CreateBehavior()
    {
        return new AuditInfoContextPropagationEndpointBehavior();
    }

    public override Type BehaviorType
    {
        get { return typeof(AuditInfoContextPropagationEndpointBehavior); }
    }

    #endregion

    #region Implementation of IEndpointBehavior

    public void Validate(ServiceEndpoint endpoint)
    {
        return;
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
        return;
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
        foreach (var operation in endpointDispatcher.DispatchRuntime.Operations)
        {
            operation.CallContextInitializers.Add(new AuditInfoCallContextInitializer());
        }

    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(new ClientAuditInfoInspector());
    }

    #endregion
}

また、サービス/コントラクトをbehavior属性で装飾することにより、同じことを実現するコントラクトビヘイビアーを作成することもできます。

これで、サービスクライアントから、次のようにすべてのコンテキストデータを設定できます。

using (var channelFactory = new ChannelFactory<ICustomerService>("WSHttpBinding_ICustomerService"))
        {
            var client = channelFactory.CreateChannel();
            ApplicationContext.Current["AppName"] = "Test application";
            ApplicationContext.Current["ClientIp"] = @"1.1.0.1";
            ApplicationContext.Current["UserId"] = "foo";
            ApplicationContext.Current["PatientId"] = "bar123";

            Console.WriteLine("Retreiving Customer 1");
            Customer cust = client.GetCustomer("1");
            Console.WriteLine("Retreived Customer, Name: [" + cust.Name + "]");
        }

これは、entlib.codeplexのディスカッション掲示板にも掲載されています:http://entlib.codeplex.com/discussions/266963

于 2011-10-05T09:03:52.360 に答える