如何在WCF中将参数(id)传递给消息检查器?

9
我希望将WCF传入和传出的消息绑定到一些ID,这些ID将记录在数据库中。
由于计划在高多线程环境中使用,因此会出现一些问题。

编辑后:

以下是我想要记录的方式:

public class LogMessageInspector : IClientMessageInspector
    {
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            Dictionary<string, object> logParams = (Dictionary<string, object>)correlationState;
            logParams["description"] = reply.ToString();

            Logger log = LogManager.GetCurrentClassLogger();
            log.InfoEx(String.Format("response_{0}", logParams["_action"]), logParams);
        }

        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {            
            string iteration_id = "";
// here comes seeking for custom, previously setted header with id
            for (int i = 0; i < request.Headers.Count; i++)
            {
                if ((request.Headers[i].Name == "_IterationId") && (request.Headers[i].Namespace == "http://tempuri2.org"))
                {
                    iteration_id = request.Headers.GetHeader<string>(i);
                    request.Headers.RemoveAt(i);

                    break;
                }
            }

            string pair_id = StringGenerator.RandomString(10);
            string action_name = request.Headers.Action.Substring(request.Headers.Action.LastIndexOf("/") + 1);
            Dictionary<string, object> logParams = new Dictionary<string,object>() { {"iteration_id", iteration_id}, { "description", request.ToString() }, { "request_response_pair", pair_id }, { "_action", action_name } };

            Logger log = LogManager.GetCurrentClassLogger();
            log.InfoEx(String.Format("request_{0}", action_name), logParams);            

            return logParams;
        }
    }

2
http://mattgemmell.com/2008/12/08/what-have-you-tried/ - Soner Gönül
其实,我甚至不知道如何开始解决那个问题 :) - kseen
1
@Alexander 我想定义自己的自定义ID,与数据库中的其他实体相关联。 - kseen
你能否至少发布服务接口并说明生成ID的计划? - Enes
我们在AfterReceiveRequest中生成一个GUID,它是IDispatchMessageInspector中的全局变量,BeforeSendReply可以访问该变量并将其写入数据库。 - lcryder
显示剩余3条评论
2个回答

10

如果我理解正确,您想使用一个ID作为correlationState来链接请求和回复。为了实现这个目的,在BeforeSendRequest中返回您的ID作为一个对象,它将被传递作为一个参数到AfterReceiveReply

public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
    var correlationState = ... // for example, a new GUID, or in your case 
                               // an id extracted from the message

    ... do your stuff here

    return correlationState;
}

public void AfterReceiveReply(ref Message reply, object correlationState)
{
    // the correlationState parameter will contain the value you returned from
    // BeforeSendRequests
}

更新

问题是如何将参数传递给BeforeSendRequest方法。

在这种情况下,如果您的请求消息中还没有需要的所有信息,显而易见的解决方案是使用ThreadStatic字段来从调用者向BeforeSendRequest通信。

在调用服务之前设置字段,然后提取并在BeforeSendRequest中使用该值。

更新2

我可以使用的另一种选择是将参数设置为消息本身,但是如何在BeforeSendRequest的请求参数中访问它?

我不理解这个问题。 如果我理解正确,您想能够从调用者向BeforeSendRequest方法传递参数。 您知道如何使用correlationState从BeforeSendRequest传递到AfterReceiveReply。

完成此操作的方法是使用ThreadStatic字段。 例如,您可以创建以下类:

public static class CallerContext
{
    [ThreadStatic]
    private static object _state;

    public static object State
    {
        get { return _state; }
        set { _state = value; }
    }
}

接下来,您的调用者需要在调用 Web 服务之前设置 CallerContext.State,然后它将在 BeforeSendRequestAfterReceiveReply 中都可用。例如:

...
try
{
    CallerContext.State = myId;

    ... call web service here ...
}
finally
{
    CallerContext.State = null;
}

并且这将在 BeforeSendRequest 中可用:

public object BeforeSendRequest(ref Message request, IClientChannel channel)
{
    var correlationState = CallerContext.State;

    ... do your stuff here

    return correlationState;
}

请注意,使用try/finally将CallerContext.State重置为null并不是严格必要的,但可以防止未经授权的代码访问您在其中存储的可能包含敏感数据的信息。

正如您在我的代码示例中所看到的,我已经这样做了。问题是如何将参数传递给 BeforeSendRequest 方法。 - kseen
我可以使用的另一个选项是将参数设置为消息本身,但是我如何在BeforeSendRequest中的request参数中访问它呢? - kseen

4
我认为最简单的解决方案是在 LogMessageInspector 中放置一个 threadstatic 变量。在 BeforeSendRequest 中设置它的值,并在 AfterReceiveReply 中再次使用它。
如果您正在使用异步操作,则会变得有点复杂,但您只需要更加注意如何访问和传递此本地变量即可。
消息检查器旨在处理消息本身,因此最好的解决方案是在消息中具有一些相关性,例如 requestId、OrderId 等。
编辑:
我想象服务契约看起来像这样:
[ServiceContract]
public interface IService1
{
    [OperationContract]
    ServiceResponse Process( ServiceRequest request );
}


class ServiceRequest
{
    public Guid RequestID { get; set; }
    public string RequestData { get; set; }
}

class ServiceResponse
{
    public Guid RequestID { get; set; }
    public string ResponseData { get; set; }
}

RequestID将在客户端生成,您现在可以对消息执行xpath并获取RequestID以关联请求和响应。


请问您能否给我一些关于RequestID或OrderID的示例?我找不到这些属性。 - kseen
看起来不错,但是在BeforeSendRequest中没有获取RequestID的机会。 - kseen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接