none
IClientMessageInspector and Reply.Headers.Action

    Question

  • Hi,

    I have implemented my own IClientMessageInspector which I inject as EndpointBehavior when consuming the service operation. Everything works as I expect apart from one thing. On the realisation of the interface method AfterReceiveReply the value Reply.Headers.Action always returns null, whereas the realisation of the method BeforeSendRequest returns the value I would expect for the Action. Can anyone help? 

    I have included my very very simple implementation of IClientMessageInspector and the definition of the service operation being invoked.

      public class ClientBehaviorInstance : IClientMessageInspector
      {
        #region IClientMessageInspector Members
    
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
          string action = request.Headers.Action;
    
          return null;
        }
    
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
          string action = reply.Headers.Action;
        }
    
        #endregion
      }
    

     

        [OperationContract(Action="Expense/Create", ReplyAction="Expense/Create")]
        [FaultContract(typeof(ContractFault))]
        ExpenseCreateResponseMessage CreateExpense(ExpenseCreateRequestMessage request);

     

    Note I have tried it with and without the ReplyAction parameter but no joy.

    Thanks

    Wednesday, January 19, 2011 10:33 AM

Answers

  • Since the actions in the contract should be unique, you can create a dictionary when creating your inspector keyed by the request action. On the BeforeSendRequest, you can then return that value, which will be passed to the correlationState parameter to AfterReceiveReply. At that point you can retrieve the reply action based on that dictionary:

      public class Post_19500d14_78b7_4356_b817_fcc9abc2afcf
      {
        [DataContract]
        public class MyDC
        {
          [DataMember]
          public string str = "The string";
        }
        [ServiceContract]
        public interface ITest
        {
          [OperationContract]
          string EchoString(string text);
          [OperationContract]
          int Add(int x, int y);
          [OperationContract]
          MyDC EchoDC(MyDC input);
        }
        public class Service : ITest
        {
          public string EchoString(string text)
          {
            return text;
          }
    
          public int Add(int x, int y)
          {
            return x + y;
          }
    
          public MyDC EchoDC(MyDC input)
          {
            return input;
          }
        }
        class MyBehavior : IEndpointBehavior
        {
          public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
          {
          }
    
          public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
          {
            clientRuntime.MessageInspectors.Add(new MyInspector(endpoint));
          }
    
          public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
          {
          }
    
          public void Validate(ServiceEndpoint endpoint)
          {
          }
        }
        class MyInspector : IClientMessageInspector
        {
          Dictionary<string, string> actionToReplyAction;
          public MyInspector(ServiceEndpoint endpoint)
          {
            this.actionToReplyAction = new Dictionary<string, string>();
            foreach (var operation in endpoint.Contract.Operations)
            {
              if (!operation.IsOneWay)
              {
                this.actionToReplyAction.Add(operation.Messages[0].Action, operation.Messages[1].Action);
              }
            }
          }
    
          public void AfterReceiveReply(ref Message reply, object correlationState)
          {
            string requestAction = (string)correlationState;
            string replyAction = this.actionToReplyAction[requestAction];
            Console.WriteLine("AfterReceiveReply, Action = {0}", reply.Headers.Action);
            Console.WriteLine("  -> ReplyAction: {0}", replyAction);
          }
    
          public object BeforeSendRequest(ref Message request, IClientChannel channel)
          {
            Console.WriteLine("BeforeSendRequest, Action = {0}", request.Headers.Action);
            return request.Headers.Action;
          }
        }
        public static void Test()
        {
          string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
          ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
          host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "basic");
          host.AddServiceEndpoint(typeof(ITest), new WSHttpBinding(SecurityMode.None), "ws");
          host.Open();
          Console.WriteLine("Host opened");
    
          for (int i = 0; i < 2; i++)
          {
            Binding binding;
            string relativeAddress;
            if (i == 0)
            {
              binding = new BasicHttpBinding();
              relativeAddress = "basic";
            }
            else
            {
              binding = new WSHttpBinding(SecurityMode.None);
              relativeAddress = "ws";
            }
    
            Console.WriteLine("Calling endpoint with SOAP version: {0}", binding.MessageVersion.Envelope);
    
            ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress + "/" + relativeAddress));
            factory.Endpoint.Behaviors.Add(new MyBehavior());
            ITest proxy = factory.CreateChannel();
    
            Console.WriteLine(proxy.EchoString("Hello"));
            Console.WriteLine(proxy.EchoDC(new MyDC()));
            Console.WriteLine(proxy.Add(3, 5));
    
            ((IClientChannel)proxy).Close();
            factory.Close();
    
            Console.WriteLine();
          }
    
          Console.Write("Press ENTER to close the host");
          Console.ReadLine();
          host.Close();
        }
      }
    
    
    Wednesday, January 19, 2011 11:23 PM

All replies

  • If you use a binding with SOAP 1.2 (such as WSHttpBinding), the value of the action header on the reply will not be null. If you use BasicHttpBinding (or WebHttpBinding, or bindings with SOAP 1.1/none), then you'll see the behavior you're describing.

    On Soap 1.1, the Action header is extracted out of the request body, and added to the HTTP header SOAPAction in the response. In the response, the Action header simply isn't exposed, which is why the client can't access the value on AfterReceiveResponse.

    The code below shows the difference. You can also use Fiddler to see the exact request/response which are being exchanged between the client and server.

      public class Post_19500d14_78b7_4356_b817_fcc9abc2afcf
      {
        [DataContract]
        public class MyDC
        {
          [DataMember]
          public string str = "The string";
        }
        [ServiceContract]
        public interface ITest
        {
          [OperationContract]
          string EchoString(string text);
          [OperationContract]
          int Add(int x, int y);
          [OperationContract]
          MyDC EchoDC(MyDC input);
        }
        public class Service : ITest
        {
          public string EchoString(string text)
          {
            return text;
          }
    
          public int Add(int x, int y)
          {
            return x + y;
          }
    
          public MyDC EchoDC(MyDC input)
          {
            return input;
          }
        }
        class MyInspector : IClientMessageInspector, IEndpointBehavior
        {
          public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
          {
          }
    
          public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
          {
            clientRuntime.MessageInspectors.Add(this);
          }
    
          public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
          {
          }
    
          public void Validate(ServiceEndpoint endpoint)
          {
          }
    
          public void AfterReceiveReply(ref Message reply, object correlationState)
          {
            Console.WriteLine("AfterReceiveReply, Action = {0}", reply.Headers.Action);
          }
    
          public object BeforeSendRequest(ref Message request, IClientChannel channel)
          {
            Console.WriteLine("BeforeSendRequest, Action = {0}", request.Headers.Action);
            return request.Headers.Action;
          }
        }
        public static void Test()
        {
          string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
          ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
          host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "basic");
          host.AddServiceEndpoint(typeof(ITest), new WSHttpBinding(SecurityMode.None), "ws");
          host.Open();
          Console.WriteLine("Host opened");
    
          for (int i = 0; i < 2; i++)
          {
            Binding binding;
            string relativeAddress;
            if (i == 0)
            {
              binding = new BasicHttpBinding();
              relativeAddress = "basic";
            }
            else
            {
              binding = new WSHttpBinding(SecurityMode.None);
              relativeAddress = "ws";
            }
    
            Console.WriteLine("Calling endpoint with SOAP version: {0}", binding.MessageVersion.Envelope);
    
            ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress + "/" + relativeAddress));
            factory.Endpoint.Behaviors.Add(new MyInspector());
            ITest proxy = factory.CreateChannel();
    
            Console.WriteLine(proxy.EchoString("Hello"));
            Console.WriteLine(proxy.EchoDC(new MyDC()));
            Console.WriteLine(proxy.Add(3, 5));
    
            ((IClientChannel)proxy).Close();
            factory.Close();
    
            Console.WriteLine();
          }
    
          Console.Write("Press ENTER to close the host");
          Console.ReadLine();
          host.Close();
        }
      }
    
    
    Wednesday, January 19, 2011 3:18 PM
  • Thanks Carlos, that answers why I have seen it work in the past, I was obviously using a SOAP 1.2 binding.

    Can you suggest any workaround to how I could get access to the operation's action name. I am using the Action as a filter to determine if there are 3rd party extensions (MEF Imported parts) that want to extend the behavior of the operation at this particular part of the process.

    My code that adds the behavior is as follows:

          ChannelFactory<IExpenseServiceChannel> factory = new ChannelFactory<IExpenseServiceChannel>("BasicHttpBinding_IExpenseService");
    
          // add a new extensibility behaviour
          factory.Endpoint.Behaviors.Add(new EndpointBehaviorExtension());
    
          channel = factory.CreateChannel();
          channel.Open();
    

    And here is the code that instantiates a new ClientMessageInspector:

      public class EndpointBehaviorExtension : IEndpointBehavior
      {
    
        #region IEndpointBehavior Members
    
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }
    
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
    
        public void Validate(ServiceEndpoint endpoint) { }
    
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
          IClientMessageInspector inspector = new EndpointBehaviorInstance() as IClientMessageInspector;
          clientRuntime.MessageInspectors.Add(inspector);
        }
    
        #endregion
      }
    

    The EndpointBehaviorInstance is as follows:

      public class EndpointBehaviorInstance : IClientMessageInspector
      {
    
        #region IClientMessageInspector Members
    
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
          string action = request.Headers.Action;
    
          // Before the operation is invoked
    
          foreach (IEndpointBehaviorExtender item in ExtensionsFactory.GetExtensions(action))
          {
            if (item != null)
              item.BeforeSendRequest(action, ref request, _formData);
          }
    
          return null;
        }
    
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
          string action = reply.Headers.Action;
    
          if (reply.IsFault == true)
            return;
    
          // Before the operation is invoked
          foreach (IEndpointBehaviorExtender item in ExtensionsFactory.GetExtensions(action))
          {
            if (item != null)
              item.AfterReceiveReply(action, ref reply, correlationState);
          }
        }
    
        #endregion
      }
    

     

    Any help would be much appreciated :)

    Thanks

     

    Wednesday, January 19, 2011 4:07 PM
  • Since the actions in the contract should be unique, you can create a dictionary when creating your inspector keyed by the request action. On the BeforeSendRequest, you can then return that value, which will be passed to the correlationState parameter to AfterReceiveReply. At that point you can retrieve the reply action based on that dictionary:

      public class Post_19500d14_78b7_4356_b817_fcc9abc2afcf
      {
        [DataContract]
        public class MyDC
        {
          [DataMember]
          public string str = "The string";
        }
        [ServiceContract]
        public interface ITest
        {
          [OperationContract]
          string EchoString(string text);
          [OperationContract]
          int Add(int x, int y);
          [OperationContract]
          MyDC EchoDC(MyDC input);
        }
        public class Service : ITest
        {
          public string EchoString(string text)
          {
            return text;
          }
    
          public int Add(int x, int y)
          {
            return x + y;
          }
    
          public MyDC EchoDC(MyDC input)
          {
            return input;
          }
        }
        class MyBehavior : IEndpointBehavior
        {
          public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
          {
          }
    
          public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
          {
            clientRuntime.MessageInspectors.Add(new MyInspector(endpoint));
          }
    
          public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
          {
          }
    
          public void Validate(ServiceEndpoint endpoint)
          {
          }
        }
        class MyInspector : IClientMessageInspector
        {
          Dictionary<string, string> actionToReplyAction;
          public MyInspector(ServiceEndpoint endpoint)
          {
            this.actionToReplyAction = new Dictionary<string, string>();
            foreach (var operation in endpoint.Contract.Operations)
            {
              if (!operation.IsOneWay)
              {
                this.actionToReplyAction.Add(operation.Messages[0].Action, operation.Messages[1].Action);
              }
            }
          }
    
          public void AfterReceiveReply(ref Message reply, object correlationState)
          {
            string requestAction = (string)correlationState;
            string replyAction = this.actionToReplyAction[requestAction];
            Console.WriteLine("AfterReceiveReply, Action = {0}", reply.Headers.Action);
            Console.WriteLine("  -> ReplyAction: {0}", replyAction);
          }
    
          public object BeforeSendRequest(ref Message request, IClientChannel channel)
          {
            Console.WriteLine("BeforeSendRequest, Action = {0}", request.Headers.Action);
            return request.Headers.Action;
          }
        }
        public static void Test()
        {
          string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
          ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
          host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "basic");
          host.AddServiceEndpoint(typeof(ITest), new WSHttpBinding(SecurityMode.None), "ws");
          host.Open();
          Console.WriteLine("Host opened");
    
          for (int i = 0; i < 2; i++)
          {
            Binding binding;
            string relativeAddress;
            if (i == 0)
            {
              binding = new BasicHttpBinding();
              relativeAddress = "basic";
            }
            else
            {
              binding = new WSHttpBinding(SecurityMode.None);
              relativeAddress = "ws";
            }
    
            Console.WriteLine("Calling endpoint with SOAP version: {0}", binding.MessageVersion.Envelope);
    
            ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, new EndpointAddress(baseAddress + "/" + relativeAddress));
            factory.Endpoint.Behaviors.Add(new MyBehavior());
            ITest proxy = factory.CreateChannel();
    
            Console.WriteLine(proxy.EchoString("Hello"));
            Console.WriteLine(proxy.EchoDC(new MyDC()));
            Console.WriteLine(proxy.Add(3, 5));
    
            ((IClientChannel)proxy).Close();
            factory.Close();
    
            Console.WriteLine();
          }
    
          Console.Write("Press ENTER to close the host");
          Console.ReadLine();
          host.Close();
        }
      }
    
    
    Wednesday, January 19, 2011 11:23 PM
  • Brilliant thanks Carlos - hadn't thought of using the correlation state
    Thursday, January 20, 2011 12:02 PM