Making it work - Fixing the Messages

Indigo provides great interoperability with various web services stacks. However one or the other time you will encounter an implementation that in certain scenario either produces or accepts invalid messages. For example, messages which are not complaint with SOAP schema. In such situations Indigo will do the right thing and reject the message. What if you don’t want to reject the message and want to Interop with the invalid implementation! What should you do?

How about fixing the invalid message before sending or receiving on the Indigo side? That sound neat, but how can you do such a thing? Let's explore from the client side. As a client you want to fix the request before sending it to the alien service and/or fix the response received from the alien service. The IProxyMessageInspector is the way to go. The MessageInspector in Indigo allows you to inspect and optionally modify the messages generated by runtime or consumed by runtime. The IProxyMessageInspector is a client side message inspector that has following methods

     
    object BeforeSendRequest(ref Message request, IProxyChannel channel);
    void AfterReceiveReply(ref Message reply, object correlationState);

They are self explanatory, BeforeSendRequest allows you to fix the requests before sending them and AfterReceieveReply allow you to fix the reply before it is processed by the Indigo runtime. For example consider trying to Interop with an alien service that sends invalid fault messages, the fault messages that are not complaint with SOAP schema. We want the Indigo runtime to process these faults and return appropriate faults to our client application. Following is the simple implementation of AfterReceiveReply that achieves the purpose. The FixMessage method performs the required modification on the message. This implementation works fine for simple fixes on small messages.

     public class FaultFixer : IProxyMessageInspector
    {
        #region IProxyMessageInspector Members

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            if (reply.IsFault)
            {
                // Load the reply message in DOM for easier modification
                XmlDocument doc = new XmlDocument();
                doc.Load(reply.GetBodyReader());

                // Perform the modification
                FixMessage(doc);

                // Create new message
                XmlNodeReader reader = new XmlNodeReader(doc.DocumentElement);
                Message tempMsg = Message.CreateMessage(reply.Version,
                    reply.Headers.Action, reader);
                MessageFault fault = MessageFault.CreateFault(tempMsg);
                tempMsg.Close();

                Message newMsg = Message.CreateMessage(reply.Version, fault,
                    reply.Headers.Action);

                // Preserve the headers of the original message
                newMsg.Headers.CopyHeadersFrom(reply);

                foreach(string propertyKey in reply.Properties.Keys)
                    newMsg.Properties.Add(propertyKey, reply.Properties[propertyKey]);

                // Close the original message and return new message
                reply.Close();
                reply = newMsg;
            }
        }

        private void FixMessage(XmlDocument doc)
        {
            // Fix the fault message here.
            Console.WriteLine("Fixing the fault received from the alien service ..");
        }

        public object BeforeSendRequest(ref Message request, IProxyChannel channel)
        {
            return null;
        }

        #endregion
    }

We now have the code that fixes the replies from the alien service, but how can we bring this code to life? We must hook it up to the message processing pipeline of Indigo. There are few ways of doing this; however the easiest one is to do it imperatively in code. We add the IChannelBehavior implementation to the contract description. When the runtime is created, the ApplyBehavior method of IChannelBehavior implementation will be called, giving us the opportunity to modify the behavior of the created proxy. We add our IMessageInspector implementation to the proxy behavior.  

 public class FaultFixerCreator : IChannelBehavior
{
    #region IChannelBehavior Members

    public void ApplyBehavior(ChannelDescription description, ProxyBehavior behavior)
    {
        behavior.MessageInspectors.Add(new FaultFixer());
    }

    #endregion
}

As mentioned above we add our FaultFixerCreator to the contract description of the channel factory. 

 
    proxy.ChannelFactory.Description.Behaviors.Add(new FaultFixerCreator());
    

We are done, from now on whenever a fault message is sent from the alien service; our code in the FaultFixer will be invoked. Following is the complete implementation. I have combined the implementation of IChannelBehavior in the FaultFixer thus avoiding the need for additional FaultFixerCreator class.

In the future posting we will learn about fixing the messages on the server side as well as fixing the messages on the client side for DuplexContract. Do send me your feedback!

 
namespace HelloWorld
{
    using System;
    using System.Xml;
    using System.ServiceModel;

    public class Constants
    {
        public const string Address = "http://localhost:8089/HelloWorld/Server";
        public static Binding binding;
        static Constants()
        {
            binding = new WSProfileBinding();
        }
    }

    [ServiceContract]
    public interface IHelloWorldService
    {
        [OperationContract]
        string Greetings(string name);
    }


    [ServiceBehavior(ReturnUnknownExceptionsAsFaults = true)]
    public class HelloWorldService : IHelloWorldService
    {
        public string Greetings(string name)
        {
            throw new ApplicationException("Just like that!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            StartService();
            StartClient();

            Console.WriteLine("\nPress Enter to exit.");
            Console.ReadLine();
        }

        private static void StartService()
        {
            HelloWorldService serviceObj = new HelloWorldService();
            ServiceHost<HelloWorldService> serviceHost = new ServiceHost<HelloWorldService>(
                    new Uri(Constants.Address));
            serviceHost.AddEndpoint(typeof(IHelloWorldService), Constants.binding);
            serviceHost.Open();
            Console.WriteLine("HelloWorldService is ready");
        }

        private static void StartClient()
        {
            Console.WriteLine("Connecting to server at " + Constants.Address);

            ChannelFactory<IHelloWorldService> factory = new ChannelFactory<IHelloWorldService>(
                new Uri(Constants.Address), Constants.binding);

            // Add the fault fixer
            factory.Description.Behaviors.Add(new FaultFixer());

            IHelloWorldService proxy = factory.CreateChannel();

            Console.WriteLine(proxy.Greetings("Indigo Developer!"));
            factory.Close();
        }
    }

    public class FaultFixer : IChannelBehavior, IProxyMessageInspector
    {
        #region IChannelBehavior Members

        public void ApplyBehavior(ChannelDescription description, ProxyBehavior behavior)
        {
            behavior.MessageInspectors.Add(this);
        }

        #endregion

        #region IProxyMessageInspector Members

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            if (reply.IsFault)
            {
                // Load the reply message in DOM for easier modification
                XmlDocument doc = new XmlDocument();
                doc.Load(reply.GetBodyReader());

                // Perform the modification
                FixMessage(doc);

                // Create new message
                XmlNodeReader reader = new XmlNodeReader(doc.DocumentElement);
                Message newMsg = Message.CreateMessage(reply.Version,
                    reply.Headers.Action, reader);
                MessageFault fault = MessageFault.CreateFault(newMsg);
                newMsg.Close();

                newMsg = Message.CreateMessage(reply.Version, fault,
                    reply.Headers.Action);

                // Preserve the headers of the original message
                newMsg.Headers.CopyHeadersFrom(reply);

                foreach(string propertyKey in reply.Properties.Keys)
                    newMsg.Properties.Add(propertyKey, reply.Properties[propertyKey]);

                // Close the original message and return new message
                reply.Close();
                reply = newMsg;
            }
        }

        private void FixMessage(XmlDocument doc)
        {
            // Fix the fault message here.
            Console.WriteLine("Fixing the fault received from the alien service ..");
        }

        public object BeforeSendRequest(ref Message request, IProxyChannel channel)
        {
            return null;
        }

        #endregion
    }
}

This posting is provided "AS IS" with no warranties, and confers no rights. Use of included script samples are subject to the terms specified at http://www.microsoft.com/info/cpyright.htm.