Writing a WCF Message Inspector
A WCF MessageInspector is a kind of a "message filter" that we can develop on the service or on the consumer side, in order to intercept and inspect the messages coming in or going out of the service layer infrastructure.
In order to define a Message Inspector on the consumer side we need to implement the IClientMessageInspector interface, while on the service side we need to implement the IDispatchMessageInspector interface. Here are their definitions:
public interface IClientMessageInspector { void AfterReceiveReply(ref Message reply, object correlationState); object BeforeSendRequest(ref Message request, IClientChannel channel); }
public interface IDispatchMessageInspector { object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext); void BeforeSendReply(ref Message reply, object correlationState); }
As you can see both these interfaces define a couple of methods that allow to access the Message (System.ServiceModel.Channels.Message) just before sending it, regardless it is a Request (IClientMessageInspector) or a Response (IDispatchMessageInspector), and just after receiveing it, again regardless its direction.
It's very important to underline that the message provided to this methods is a "by reference" parameter, because this allows our Message Inspector implementations to change the message while it is moving along the service model pipeline. In fact the ref Message parameter can be used to read the SOAP message using one of the methods of the Message type (like ToString(), GetBody<T>(), GetReaderAtBodyContents(), etc.) or can be completely changed using a new Message instance, written through the writing methods of the Message type (WriteBody(...), WriteBodyContents(...), WriteMessage(...), etc.).
One of the most useful methods of the Message type is the CreateBufferedCopy one, which allows to create a MessageBuffer instance that is a buffered copy of the source message useful to XPath navigate its content. The MessageBuffer type allows also to recreate a Message instance from the buffer using the CreateMessage() method.
Here is an example of a service-side Message Inspector used to output to the Console any received and sent message:
public class ConsoleOutputMessageInspector : IDispatchMessageInspector { public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue); request = buffer.CreateMessage(); Console.WriteLine("Received:\n{0}", buffer.CreateMessage().ToString()); return null; } public void BeforeSendReply(ref Message reply, object correlationState) { MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue); reply = buffer.CreateMessage(); Console.WriteLine("Sending:\n{0}", buffer.CreateMessage().ToString()); } }
As you can see I create a copy of the message instance, using the CreateBufferedCopy() method, and the I write it using the ToString() of the Message type.
Another example of Message Inspector could be the following one, used to write to the console every single SOAP Header contained in the message that moves through the message pipeline:
public class ConsoleOutputHeadersMessageInspector : IDispatchMessageInspector { public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue); request = buffer.CreateMessage(); Message originalMessage = buffer.CreateMessage(); foreach (MessageHeader h in originalMessage.Headers) { Console.WriteLine("\n{0}\n", h); } return null; } public void BeforeSendReply(ref Message reply, object correlationState) { MessageBuffer buffer = reply.CreateBufferedCopy(0x7fffffff); reply = buffer.CreateMessage(); Message originalMessage = buffer.CreateMessage(); foreach (MessageHeader h in originalMessage.Headers) { Console.WriteLine("\n{0}\n", h); } } }
Here I walk through each MessageHeader contained within the source Message browsing the Headers collection. One more time I work on a buffered copy of the message.
In order to configure these message inspectors we can use a custom behavior. Behaviros are classes that extend the service model defining custom extensions for: contracts, endpoints, services, operations. In these examples I defined two different kind of behaviors: one endpoint behavior and one servicebehavior.
Let's start from the EndpointBehavior:
public class ConsoleOutputBehavior : IEndpointBehavior { public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { throw new Exception("Behavior not supported on the consumer side!"); } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { ConsoleOutputMessageInspector inspector = new ConsoleOutputMessageInspector(); endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector); } public void Validate(ServiceEndpoint endpoint) { } }
As you can see I implement the IEndpointBehavior interface, which defines three methods (AddBindingParameter, ApplyClientBehavior, ApplyDispatchBehavior). The one I'm interested on is the ApplyDispatchBehavior that relates to the service-side. This method receives a parameter of type EndpointDispatcher that allows to add custom Message Inspectors instance to the service dispatching environment. Because we're defining an Endpoint Behavior, this behavior affects a single endpoint of a service. To map the behavior to the service endpoint we can use a custom configuration element in the configuration file of the service host. Otherwise we could apply the behavior directly through the ServiceHost instance. In this sample I used a custom configuration element. To do that we need a custom type describing the configuration element. It is a type inherited from BehaviorExtensionElement, like the following one:
public class ConsoleOutputBehaviorExtensionElement : BehaviorExtensionElement { protected override object CreateBehavior() { return new ConsoleOutputBehavior(); } public override Type BehaviorType { get { return typeof(ConsoleOutputBehavior); } } }
The implementation of the behavior extension element is really simple, it defines just the CreateBehavior method, used to create an instance of the behavior, and the BehaviorType property, to return the type of the behavior it defines and creates. In reality this class can define also custom properties useful to configure the behavior. In our example we don't do that, but we could add some configuration properties, too.
The previously declared extension element can be used in the .config file of the service host application, like in the following excerpt:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="DevLeap.WCF.MessageInspectors.Services.OrderService">
<endpoint
behaviorConfiguration="devleapBehavior"
address="http://localhost:8000/OrderService"
binding="wsHttpBinding" bindingConfiguration="devleapWsHttpBinding"
contract="DevLeap.WCF.MessageInspectors.Contracts.IOrderService" />
</service>
</services>
<extensions>
<behaviorExtensions>
<add name="consoleOutputBehavior" type="DevLeap.WCF.MessageInspectors.Extensions.ConsoleOutputBehaviorExtensionElement, DevLeap.WCF.MessageInspectors.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="devleapBehavior">
<consoleOutputBehavior />
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<binding name="devleapWsHttpBinding">
<security mode="None" />
</binding>
</wsHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
First of all we define the behaviorExtension element, inside which we define the new extension, through the add element. Keep in mind that we need to declare the fully qualified name of the extension element type inside the type attribute.
Then we declare the new custom behavior within the behaviors section of the configuration file.
While an Endpoint Behavior applies only to a single endpoint, we can also define a custom Service Behavior that applies to every single endpoint of a service. To do that we need to define a class that implements the IServiceBehavior interface. Here is an example:
[AttributeUsage(AttributeTargets.Class)] public class ConsoleHeaderOutputBehavior : Attribute, IServiceBehavior { public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { for (int i = 0; i < serviceHostBase.ChannelDispatchers.Count; i++) { ChannelDispatcher channelDispatcher = serviceHostBase.ChannelDispatchers[i] as ChannelDispatcher; if (channelDispatcher != null) { foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { ConsoleOutputHeadersMessageInspector inspector = new ConsoleOutputHeadersMessageInspector(); endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector); } } } } public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } }
The IServiceBehavior interface looks like the IEndpointBehavior, except the fact that it provides a different ApplyDispatchBehavior method definition. In fact a Service Behavior should apply its behavior to every single insatnce and endpoint published by the service to which it is applied. In this example I inherited the behavior class from the Attribute base class too, targeting it to class definitions. This way we can apply the behavior directly to the service definition, like shown in the following excerpt:
[ConsoleHeaderOutputBehavior]
public class OrderService : IOrderService
{
public OrderConfirmation InsertOrder(Order order)
{
OrderConfirmation result = new OrderConfirmation();
result.IdOrder = order.IdOrder;
result.ShipDateTime = DateTime.Now.AddDays(2);
return result;
}
}
So far you have seen how to define custom Message Inspector and how to map it to a single endpoint, using and Endpoint Behavior, or how to map it to an entire service, using a Service Behavior. You have also seen how to declare the behaviors using a custom configuration element or a custom behavior attribute. Hope you enjoyed this article, Here you can find the code sample used and described in this post.