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="https://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
// https://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
// https://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
// https://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
// https://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
// https://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
https://www.microsoft.com/info/cpyright.htm
-->
<configuration>
<system.serviceModel>
<services>
<service
serviceType="HelloWorld.HelloWorldService"
behaviorConfiguration="ServiceBehavior">
<endpoint address="https://localhost:8080/HelloWorld/Service"
bindingSectionName="wsProfileBinding"
contractType="HelloWorld.IHelloWorldService" />
</service>
</services>
<client>
<endpoint
configurationName="HelloWorldClientConfig"
address="https://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 https://www.microsoft.com/info/cpyright.htm.
Comments
- Anonymous
September 16, 2005
The modification of the body is a common task required in many applications. The Message Fixer explained... - Anonymous
May 08, 2008
There is no such method called reply.GetBodyReader(). Also can you please tell me how to use GetBody<T>. I want to extract out contents of the message. Thanks, Kulbhushan