Making it work - Fixing the Messages - Part 2

IProxyMessageInspector - Fixing Messages: Service Side

The article "Making it work - Fixing the Messages" describes how to fix the messages on the client side while trying to Interop with an alien service that sends invalid fault messages. This article describes fixing the messages either on the client side or service side. It also describes how to add these "MessageFixers" via config using the behavior extension feature of Indigo.

Just like IProxyMessageInspector that allows you to inspect and optionally modify the messages on the client side; the IStubMessageInspector allows you to inspect and optionally modify the messages on the service side. The IStubMessageInspector interface has the following methods.

     
    object AfterReceiveRequest(ref Message request, IProxyChannel channel, ServiceSite site);
    void BeforeSendReply(ref Message reply, object correlationState);

They are self explanatory, AfterReceiveRequest allows you to fix the requests received from the client before it is processed by the Indigo runtime. The object returned from this method will be returned as "correlationState" object in the BeforeSendReply method. This allows you to correlate request and responses. The BeforeSendReply allows you to fix the replies before they are sent to the client.

The IServiceBehavior implementation provides an opportunity to add message inspectors in the service processing pipeline. The easiest way to add a behavior to service or channel factory is via code as shown below. The service can have more than one endpoint. This example adds the IStubMessageInspector on all those endpoints.

   
    public class ServiceMessageFixer : IServiceBehavior, IStubMessageInspector
    {
        #region IServiceBehavior Members

        public void ApplyBehavior(ServiceDescription description, Collection<DispatchBehavior> behaviors)
        {
            foreach (DispatchBehavior dispatchBehavior in behaviors)
                dispatchBehavior.MessageInspectors.Add(this);
        }

        #endregion

Adding Message Fixers via Config

We add the message inspector as behavior of the channel or the service using IChannelBehavior and IServiceBehavior implementations respectively. Since the behaviors can be added via config, it is possible to add message inspector implementations via config. Hence adding message inspectors via config is same as adding custom behaviors via config.

The behaviors for the services and clients are specified under the "behaviors" section in the config. These behaviors are named and are referred by the "behaviorConfiguration" attribute on the "service" element.  The named behavior element lists the behaviors and their configuration. Each config section under the behavior element is handled by a BehaviorExtensionSection. The user defined config sections are registered by listed then under the behaviorExtensions element in extensions section.  

Following is the configuration for the custom behavior config handler that instantiates and adds the specified IServiceBehavior implementation to the service. The "type" attribute on the "CustomServiceBehavior" provides the IServiceBehavior implementation.    

   
    <system.serviceModel>

        <services>
            <service 
                serviceType="HelloWorld.HelloWorldService"
                behaviorConfiguration="ServiceBehavior">
                <endpoint address="http://localhost:8080/HelloWorld/Service"
                          bindingSectionName="wsProfileBinding"
                          contractType="HelloWorld.IHelloWorldService" />
            </service>
        </services>

        <behaviors>
            <behavior  
                configurationName="ServiceBehavior">
                <CustomServiceBehavior type="MessageFixer.ServiceMessageFixer, MessageFixer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
            </behavior>
        </behaviors>

        <extensions>
            <behaviorExtensions>
                <add name="CustomServiceBehavior" 
                    type="CustomBehaviorConfig.CustomServiceBehaviorSection,CustomBehaviorConfig, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
            </behaviorExtensions>
        </extensions>

    </system.serviceModel>

 

The Complete Example

The following sample demonstrate the implementation of IProxyMessageInspector, IStubMessageInspector for fixing the messages on the client side and service side respectively via code and config. The MessageFixer classes should be compiled in to the MessageFixer.dll assembly and the BehaviorConfig classes should be compiled in to CustomBehaviorConfig.dll assembly, as the App.config file for HelloWorld program that demonstrate the use of these message fixers uses these assembly names. Please do send your feedback!

Message Fixer Classes   - MessageFixer.dll

 ClientMessageFixer.cs      
      
// Copyright Notice 
//
// Use of this sample code is subject to the terms specified at 
// http://www.microsoft.com/info/cpyright.htm
//

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

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

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

        #endregion

        #region IProxyMessageInspector Members


        public object BeforeSendRequest(ref Message request, IProxyChannel channel)
        {
            // Decide if you need to fix the request
            // if "I need to fix the message" 
            {
                // Fix the request
                XmlDocument doc = new XmlDocument();
                doc.Load(request.GetBodyReader());

                // Perform the modification
                FixRequestMessage(doc);

                // Create new message
                XmlNodeReader reader = new XmlNodeReader(doc.DocumentElement);
                Message newMsg = Message.CreateMessage(request.Version,
                    null, reader);

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

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

                // Close the original message and return new message
                request.Close();
                request = newMsg;
            }
            // end if - "I need to fix the message"

            // The returned value can be used for request-reply correlation.
            //
            // The return object from this method will be supplied as the 
            // correlationState parameter of the AfterReceiveReply method. 
            return null;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            // Decide if you need to fix the reply
            // if "I need to fix the message" 
            {
                // Fix the reply
                XmlDocument doc = new XmlDocument();
                doc.Load(reply.GetBodyReader());

                // Perform the modification
                FixReplyMessage(doc);

                // Create new message
                XmlNodeReader reader = new XmlNodeReader(doc.DocumentElement);
                Message newMsg = Message.CreateMessage(reply.Version,
                    null, reader);

                if (reply.IsFault)
                {
                    MessageFault fault = MessageFault.CreateFault(newMsg);
                    newMsg.Close();

                    newMsg = Message.CreateMessage(reply.Version, fault,
                        null);
                }

                // 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;

            }

            // end if - "I need to fix the message"
        }

        private void FixRequestMessage(XmlDocument doc)
        {
            // Fix the request message here.
            Console.WriteLine("Fixing the request to be sent to the server.");
        }

        private void FixReplyMessage(XmlDocument doc)
        {
            // Fix the reply message here.
            Console.WriteLine("Fixing the reply received from the server.");
        }

        #endregion
    }
}

ServiceMessageFixer.cs

// Copyright Notice 
//
// Use of this sample code is subject to the terms specified at 
// http://www.microsoft.com/info/cpyright.htm
//

namespace MessageFixer
{
    using System;
    using System.Xml;
    using System.ServiceModel;
    using System.Collections.ObjectModel;

    public class ServiceMessageFixer : IServiceBehavior, IStubMessageInspector
    {
        #region IServiceBehavior Members

        public void ApplyBehavior(ServiceDescription description, Collection<DispatchBehavior> behaviors)
        {
            foreach (DispatchBehavior dispatchBehavior in behaviors)
                dispatchBehavior.MessageInspectors.Add(this);
        }

        #endregion

        #region IStubMessageInspector Members

        public object AfterReceiveRequest(ref Message request, IProxyChannel channel, ServiceSite site)
        {
            // Decide if you need to fix the request
            // if "I need to fix the message" 
            {
                // Fix the request
                XmlDocument doc = new XmlDocument();
                doc.Load(request.GetBodyReader());

                // Perform the modification
                FixRequestMessage(doc);

                // Create new message
                XmlNodeReader reader = new XmlNodeReader(doc.DocumentElement);
                Message newMsg = Message.CreateMessage(request.Version,
                    null, reader);

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

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

                // Close the original message and return new message
                request.Close();
                request = newMsg;
            }
            // end if - "I need to fix the message"

            // The returned value can be used for request-reply correlation.
            //
            // The return object from this method will be supplied as the 
            // correlationState parameter of the BeforeSendReply method. 
            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            // Decide if you need to fix the reply
            // if "I need to fix the message" 
            {
                // Fix the reply
                XmlDocument doc = new XmlDocument();
                doc.Load(reply.GetBodyReader());

                // Perform the modification
                FixReplyMessage(doc);

                // Create new message
                XmlNodeReader reader = new XmlNodeReader(doc.DocumentElement);
                Message newMsg = Message.CreateMessage(reply.Version,
                    null, reader);

                if (reply.IsFault)
                {
                    MessageFault fault = MessageFault.CreateFault(newMsg);
                    newMsg.Close();

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

                    Console.WriteLine("It is a fault !!!!!");
                }

                // 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;

            }

            // end if - "I need to fix the message"
        }

        private void FixRequestMessage(XmlDocument doc)
        {
            // Fix the request message here.
            Console.WriteLine("Fixing the request received from the client.");
        }

        private void FixReplyMessage(XmlDocument doc)
        {
            // Fix the reply message here.
            Console.WriteLine("Fixing the reply to be sent to the client.");
        }

        #endregion
    }
}

AssemblyInfo.cs

using System.Reflection;

[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Custom Config Classes   - CustomBehaviorConfig.dll

 
CustomClientBehaviorSection.cs

// Copyright Notice 
//
// Use of this sample code is subject to the terms specified at 
// http://www.microsoft.com/info/cpyright.htm
//

namespace CustomBehaviorConfig
{
    using System;
    using System.Configuration;
    using System.ServiceModel;
    using System.ServiceModel.Configuration;

    /// <summary>
    /// Adds the custom behavior to the client. 
    /// </summary>
    class CustomClientBehaviorSection : BehaviorExtensionSection
    {
        [ConfigurationProperty("type")]
        public string TypeName
        {
            get
            {
                return (string)base["type"];
            }
        }

        protected override object CreateBehavior()
        {
            object behavior = null;

            if ((this.TypeName != null) && (this.TypeName.Trim().Length > 0))
            {
                Type channelBehaviorImplType = Type.GetType(this.TypeName, true);
                if (typeof(IChannelBehavior).IsAssignableFrom(channelBehaviorImplType))
                {
                    behavior = Activator.CreateInstance(channelBehaviorImplType);
                }
            }

            return behavior;
        }

        public override string ConfiguredSectionName
        {
            get { return "CustomClientBehavior"; }
        }
    }
}

CustomServiceBehaviorSection.cs

// Copyright Notice 
//
// Use of this sample code is subject to the terms specified at 
// http://www.microsoft.com/info/cpyright.htm
//

namespace CustomBehaviorConfig
{
    using System;
    using System.Configuration;
    using System.ServiceModel;
    using System.ServiceModel.Configuration;

    /// <summary>
    /// Adds the custom behavior to the service. 
    /// </summary>
    class CustomServiceBehaviorSection : BehaviorExtensionSection
    {
        [ConfigurationProperty("type")]
        public string TypeName
        {
            get
            {
                return (string)base["type"];
            }
        }

        protected override object CreateBehavior()
        {
            object behavior = null;

            if ((this.TypeName != null) && (this.TypeName.Trim().Length > 0))
            {
                Type serviceBehaviorImplType = Type.GetType(this.TypeName, true);
                if (typeof(IServiceBehavior).IsAssignableFrom(serviceBehaviorImplType))
                {
                    behavior = Activator.CreateInstance(serviceBehaviorImplType);
                }
            }

            return behavior;
        }

        public override string ConfiguredSectionName
        {
            get { return "CustomServiceBehavior"; }
        }
    }
}

AssemblyInfo.cs

using System.Reflection;

[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

HelloWorld Sample using MessageFixers

 
HelloWorld.cs

// Copyright Notice 
//
// Use of this sample code is subject to the terms specified at 
// http://www.microsoft.com/info/cpyright.htm
//

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

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

    [ServiceBehavior]
    public class HelloWorldService : IHelloWorldService
    {
        public string Greetings(string name)
        {
            return "Hello " + name;
        }
    }

    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>();

            // Uncomment the following line to add the ServiceMessageFixer via code
            // serviceHost.Description.Behaviors.Add(new MessageFixer.ServiceMessageFixer());

            serviceHost.Open();

            Console.WriteLine("HelloWorldService is ready");
        }

        private static void StartClient()
        {
            Console.WriteLine("Connecting the HelloWorld server.");

            ChannelFactory<IHelloWorldService> factory =
                new ChannelFactory<IHelloWorldService>("HelloWorldClientConfig");

            // Uncomment the following line to add ClientMessageFixer via code 
            // factory.Description.Behaviors.Add(new MessageFixer.ClientMessageFixer());
            IHelloWorldService proxy = factory.CreateChannel();

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

App.config

<?xmlversion="1.0"encoding="utf-8"?>
<!-- 
    Copyright Notice 

    Use of this sample code is subject to the terms specified at 
    http://www.microsoft.com/info/cpyright.htm
-->
<configuration>
    <system.serviceModel>

        <services>
            <service 
                serviceType="HelloWorld.HelloWorldService"
                behaviorConfiguration="ServiceBehavior">
                <endpoint address="http://localhost:8080/HelloWorld/Service"
                          bindingSectionName="wsProfileBinding"
                          contractType="HelloWorld.IHelloWorldService" />
            </service>
        </services>

        <client>
            <endpoint 
                configurationName="HelloWorldClientConfig" 
                address="http://localhost:8080/HelloWorld/Service" 
                bindingSectionName="wsProfileBinding" 
                contractType="HelloWorld.IHelloWorldService"
                behaviorConfiguration="ClientBehavior"
            />
        </client>

        <behaviors>
            <behavior  
                configurationName="ServiceBehavior">
                <!-- Uncomment the following line to add the ServiceMessageFixer via config. -->
                <!-- 
                <CustomServiceBehavior type="MessageFixer.ServiceMessageFixer, MessageFixer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
                -->
            </behavior>

            <behavior  
                configurationName="ClientBehavior">
                <!-- Uncomment the following line to add the ClientMessageFixer via config. -->
                <!-- 
                <CustomClientBehavior type="MessageFixer.ClientMessageFixer, MessageFixer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
                -->
            </behavior>
        </behaviors>

        <extensions>
            <behaviorExtensions>
                <add name="CustomServiceBehavior" 
                    type="CustomBehaviorConfig.CustomServiceBehaviorSection,CustomBehaviorConfig, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>

                <add name="CustomClientBehavior" 
                type="CustomBehaviorConfig.CustomClientBehaviorSection,CustomBehaviorConfig, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
            </behaviorExtensions>
        </extensions>

    </system.serviceModel>
</configuration>

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.