Search code examples
c#web-servicesvariablesidispatchmessageinspector

Shared information between a MessageInspector and a web-service


I have a question regarding sharing information between a MessageInspector and a web-service.

I have an identifier (Guid) that I use to "bind" AfterReceiveRequest and BeforeSendReply. It works fine. But I would like this identifier to be available also in the methods used in the web-service. This is very useful e.g. for tracking issues in a log.

Below is a small demo example to illustrate the idea. My problem is to find a solution there can get access to the return object from the method AfterReceiveRequest. In the line with "MagicStuff".

namespace Demo.MessageInspector
{
    public class DemoMessageInspector : IDispatchMessageInspector
    {
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            Guid activityId = Guid.NewGuid();
            MyLog.Message("AfterReceiveRequest", activityId);
            return activityId;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            Guid activityId = (Guid)correlationState;
            MyLog.Message("BeforeSendReply", activityId);
        }
    }
}

namespace Demo.WebServices
{
    [ServiceBehavior]
    [MessageInterceptionServiceBehaviour]
    public class MyWebService : IMyWebService
    {
        public void MyWebServiceMethod()
        {
            Guid activityId = (Guid)MagicStuff; // <-- correlationState from AfterReceiveRequest

            bool success = DoSomthing();

            if (!success)
                MyLog.Message("Error happened in MyWebServiceMethod", activityId);
        }
    }
}

I would appreciate any kind of help or hints.


Solution

  • You can use the OperationContext.IncomingMessageProperties Property. Here's the usage in your scenario:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Dispatcher;
    using Demo.MessageInspector;
    using Demo.Utilities;
    
    namespace Demo.WebServices
    {
        public class MyWebService : IMyWebService, IServiceBehavior
        {
            public void MyWebServiceMethod()
            {
                // get the activityId from the incoming message properties
                var activityIdProperty = OperationContext.Current.IncomingMessageProperties
                    .FirstOrDefault(property => property.Key == Properties.ActivityId.ToString());
    
                // create an empty Guid
                Guid activityId = new Guid();
                if (activityIdProperty.Value != null)
                {
                    // replace the empty Guid with the activityId
                    activityId = (Guid)activityIdProperty.Value;
                }
                bool success = DoSomething();
                if (!success)
                    MyLog.Message("Error happened in MyWebServiceMethod", activityId);
            }
    
            private bool DoSomething()
            {
                // TODO: implement
                return false;
            }
    
            public void AddBindingParameters(
              ServiceDescription serviceDescription,
              ServiceHostBase serviceHostBase,
              System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
              BindingParameterCollection bindingParameters
            )
            {
                return;
            }
    
            public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
                ServiceHostBase serviceHostBase)
            {
                foreach (ChannelDispatcher chDisp in serviceHostBase.ChannelDispatchers)
                {
                    foreach (EndpointDispatcher epDisp in chDisp.Endpoints)
                    {
                        var messageInspector = new DemoMessageInspector();
                        epDisp.DispatchRuntime.MessageInspectors.Add(messageInspector);
                    }
                }
            }
    
            public void Validate(ServiceDescription serviceDescription, 
                ServiceHostBase serviceHostBase)
            {
                // TODO: implement validation
                //throw new NotImplementedException();
            }
        }
    }
    
    namespace Demo.MessageInspector
    {
        public class DemoMessageInspector : IDispatchMessageInspector
        {
            public object AfterReceiveRequest(ref Message request, 
                IClientChannel channel, InstanceContext instanceContext)
            {
                Guid activityId = Guid.NewGuid();
    
                // add the activityId to the incoming message properties
                OperationContext.Current.IncomingMessageProperties
                    .Add(Properties.ActivityId.ToString(), activityId);
    
                MyLog.Message("AfterReceiveRequest", activityId);
                return activityId;
            }
    
            public void BeforeSendReply(ref Message reply, object correlationState)
            {
                Guid activityId = (Guid)correlationState;
                MyLog.Message("BeforeSendReply", activityId);
            }
        }
    }
    
    namespace Demo.Utilities
    {
        public enum Properties
        {
            ActivityId
        }
    
        public class MyLog
        {
            internal static void Message(string p, Guid guid)
            {
                File.AppendAllText(@"C:\Temp\log.txt", 
                    String.Format("{0} {1} {2}\r\n", DateTime.Now, p, guid));
            }
        }
    }
    

    [UPDATE]

    Alternatively, you could use the CorrelationManager.ActivityId Property. To do so, first add the following to your config files (both client and server):

    <system.diagnostics>
      <sources>
        <source name="System.ServiceModel" propagateActivity="true">
          <listeners>
            <add name="ignored" type="System.Diagnostics.DefaultTraceListener" />
          </listeners>
        </source>
      </sources>
    </system.diagnostics>
    

    Then, add the following method in a Demo.Utilities.Helper class:

    namespace Demo.Utilities
    {
        internal class Helper
        {
            internal static Guid GetCorrelationId()
            {
                var headerPosition = OperationContext.Current.IncomingMessageHeaders.FindHeader("ActivityId",
                    "http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics");
                if (headerPosition > -1)
                {
                    var activityIdHeader = OperationContext.Current.IncomingMessageHeaders
                        .GetReaderAtHeader(headerPosition);
                    var activityIdAttribute = activityIdHeader.GetAttribute("CorrelationId");
                    return Guid.Parse(activityIdAttribute);
                }
                else
                {
                    return Guid.Empty;
                }
            }
        }
    }
    

    Now you can use the method like this in the DemoMessageInspector:

    public object AfterReceiveRequest(ref Message request,
        IClientChannel channel, InstanceContext instanceContext)
    {
        var correlationId = Helper.GetCorrelationId();
        MyLog.Message("AfterReceiveRequest\tCorrelationId", correlationId);
        // ...
    }
    

    and like this in your service's method:

    public void MyWebServiceMethod()
    {
        var correlationId = Helper.GetCorrelationId();
        MyLog.Message("MyWebServiceMethod\tCorrelationId", correlationId);
    
        // ...
    }