Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass parameter (id) to message inspector in WCF?

I Just want to bound WCF incoming and outgoing messages with some id, what will be logged to database.

As it planned to be used in high multi threaded environment, there is some issues occurred.


POST EDIT

Here is how I want to log:

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;
        }
    }
like image 961
kseen Avatar asked Mar 25 '13 06:03

kseen


2 Answers

If I understood correctly, you want to use an ID as correlationState to link the request with the reply. To do this, return your ID as an object from BeforeSendRequest, and it will be passed as an argument to 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
}

UPDATE

The issue is how to pass parameter to BeforeSendRequest method.

In that case, if you don't have all the information you need already in your request message, the obvious solution is to us a ThreadStatic field to communicate information from your caller to BeforeSendRequest.

Set the field before calling your service, then extract and use the value in BeforeSendRequest.

UPDATE 2

Another option I can use is to set parameter to message itself, but how can I access it in request parameter in BeforeSendRequest?

I don't understand this question. If I understood right, you want to be able to pass a parameter from the caller to the BeforeSendRequest method. You know how to pass it from BeforeSendRequest to AfterReceiveReply, using correlationState.

The way to do this is to use a ThreadStatic field. For example, you could create the following class:

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

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

Then your caller would set CallerContext.State before calling the web service, and it would be available to both BeforeSendRequest and AfterReceiveReply. E.g.

...
try
{
    CallerContext.State = myId;

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

and this will be available in BeforeSendRequest:

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

    ... do your stuff here

    return correlationState;
}

Note that use of try/finally to reset CallerContext.State to null isn't strictly necessary, but prevents unrelated code from accessing possibly sensitive data that you store there.

like image 162
Joe Avatar answered Oct 07 '22 01:10

Joe


I think simplest solution would be to put threadstatic variable in LogMessageInspector. Set the value in BeforeSendRequest and use it again in AfterReceiveReply.

I gets little bit more complex if you are using async operations, but all you would have to do is to pay closer attention to how you access and pass this local var.

MessageInspectors are intended to work on Message itself, so best solution would be to have something in the message to correlate to, e.g. requestId, OrderId, etc.

EDIT:

I would imagine service contract to look something like this:

[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 would be generated on client side, and now you can do xpath on your messages and get RequestID to correlate request and response.

like image 32
Enes Avatar answered Oct 07 '22 01:10

Enes