Understanding Message Inspection

November 16,  2009

Service oriented applications exchange messages.

WCF provides the ability to extend the channel to enable inspection of messages which are created, transported, utilized and destroyed under the covers by the service and client runtimes. This tutorial is intended to provide a entry into the extensibity capabilities of WCF and demonstrate message inspection. 

The Service

The service is exposed through its endpoints. A service endpoint consist of three basic components: address (where the service is located), binding (how the service communicates) and contract (operations performed by the service.)

Address

The address is a uri that identifies where the service, service endpoint, is located. Address examples are the following:
http://www.koolkode.net/DCS/
Address of a service, located on the Internet, that calculates medicinal dosage
http://localhost:50167/DemoService/
Address of a service located locally on the development computer
The address consists of four parts: A scheme, in this case, http; a machine identification, localhost; a port, 50167, which is optional; and a path, DemoService.

Binding

The binding describes how the endpoint communicates including the access protocol and encoding. Built in bindings include the following:
wsHttpBinding
Provides secure and reliable sessions using Http transport
basicHttpBinding
Highly interoperable including support for Web services based on ASMX
netNamedPipeBinding
Efficient when the service is located on a single computer
netTcpBinding
Effective for peer-to-peer communication using TCP
netMsmqBinding
Enables communication using Microsoft Message Queue (MSMQ) transport
Contract

The ServiceContractAttribute defines a class or interface as a service. Best practice calls for defining the service as an interface. The OperationContractAttribute defines a method that implements a service operation in a service contract. Properties express the nature of the operation. For example, the IsOneWay property describes an operation that has no output and the AscynPattern property describes an operation that is ascy nchronous.

The following example illustrates the definition of a service contract with one method.
using System.ServiceModel;
namespace DemoService
{
 [ServiceContract]
 public interface ITheContract
 {
   [OperationContract]
   string TheMethod(string someData);
 }
}
The contract is implemented by the class TheContract, shown below, that inherits the interface ITheContract defining the service and operations.
using System.ServiceModel;
namespace DemoService
{
  public class TheContract : DemoService.ITheContract
  {
    public string TheMethod(string someData)
    {
      return string.Format("You entered: {0}", someData);
    }
  }
}
Specifing the Endpoint

Endpoints can be specified imperatively using code or declaratively in a configuration file, Web.config. Best practice calls for specification in a configuration files that enables changes to the endpoint without code compilaton. This is handy as the development environment and hosting environment are generally different, requiring changes in the endpoint specification, the address for example. The configuration file information is contained in a servicemodel element. The endpoint specification for DemoService.TheContract is shown below:
 <configuration> 
  <system.serviceModel>
    <services>
      <service name="DemoService.TheContract">
        <endpoint address=""
                  binding="wsHttpBinding"
                  contract="DemoService.ITheContract">
        </endpoint>
      </service>
    </services>
  <system.serviceModel>
<configuration>
The service is specified in service element The service element's name attribute is the type implementing the service. A service can have multiple endpoints each of which is defined in an endpoint element. The address, binding and contract are specified in the endpoint element's attributes.

Wiring-Up the Service>

When the host wires-up the service, the system looks in the configuration file for the service specified in the @ServiceHost directive.

The @ServiceHost directive is in an svc extension file that relates the programming elements needed to compile and host the service. For example, attributes identify the language and debug status. The Service attribute identifies the type of the service. Note that the name is fully qualified.
< %@ ServiceHost
   Language="C#"
   Debug="true"
   Service="DemoService.TheContract"
   CodeBehind="~/App_Code/TheContract.cs"
%>
If the element is found the system loads the class, creates the endpoints specified in the service's endpoint configuration element and starts the service.

The Client

The ServiceModel Metadata Utility Tool (Svcutil.exe) extends the ClientBase<TChannel> class to create a WCF client class that defines objects that can call a service. When Visual Studio’s Add - Service Reference is employed, Visual Studio uses Svcutil.exe to create a reference to the service at a specified endpoint address. In this case the endpoint address is

    http://localhost:50167/DemoService/Service.svc

and the reference's namspace is specified as TheServiceReference.

The client class, included in the reference namespace, is the TheContractClient which is an extension of the ClientBase<TChannel> class where TChannel is

    TheClient.TheServiceReference.ITheContract

type.

This class, TheContractClient, can be used to instantiate a TheService proxy object. An instance of a class that can call TheService is instantiated with the statement
      TheServiceReference.TheContractClient proxy =
        new TheServiceReference.TheContractClient();
The instance of TheContractClient class, proxy, is created to accesses the service with the following endpoint properties.
Address
//localhost:50167/DemoService/Service.svc (This service is running locally, typically the service is at a remote location and the address would be similar to the following: http://www.koolkode.net/dcs/Service.svc.)
Binding
System.ServiceModel.wsHttpBinding
Contract
ITheContract
Behaviors
Behaviors automatically added to the Behaviors collection by the WCF Service template when the service was created.
These properties can be observed by inserting a breakpoint following creation of the proxy object and opening the Local Window.

A program accessing TheService is shown below:
using System.ServiceModel;
using System.IO;

namespace TheClient
{
  class Program
  {
    static void Main(string[] args)
    {
      TheServiceReference.TheContractClient proxy =
        new TheServiceReference.TheContractClient();

      proxy.Open();

      string result = proxy.TheMethod("Input Data");
      System.Console.WriteLine(result);

      System.Console.ReadLine();

      proxy.Close();
    }
  }
}
Following instantiation of the proxy the major program steps are the following:

    Open is invoked,

    TheMethod is called, and

    Closed is invoked.

The program's output is shown below :
You entered: Input Data


Extending Client ‘Under the Hood’ Functionality

For example, how can I see the message that is created by the runtime for transmission to the service?

The endpoint behaviors provide for extending the functionality of the client. 

Shown below is the Web.config file created by Visual Studio when the WCF Service template is used to create a service.
<system.serviceModel>
  <services>
    <!-- name must refer to a type and the behaviorconfiguration (the
         behavior used to instantiate the service) default is an empty
         string -->
    <service name="DemoService.TheContract"
             behaviorConfiguration="ServiceBehavior">
      <!-- Service Endpoints -->
      <endpoint address=""
                binding="wsHttpBinding"
                contract="DemoService.ITheContract">
        <!-- Upon deployment, the following identity element should be
             removed or replaced to reflect the identity under which
             the deployed sce runs. If removed, WCF will infer an
             appropriate identity automatically.
        -->
        <identity>
          <dns value="localhost"/>
        </identity>
      </endpoint>
      <endpoint address="mex"
                binding="mexHttpBinding"
                contract="IMetadataExchange"/>
    </service>
  </services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="ServiceBehavior">
        <!-- To avoid disclosing metadata information, set the value
             below to false and remove the metadata endpoint above before
             deployment
        -->
        <serviceMetadata httpGetEnabled="true"/>
        <!-- To receive exception details in faults for debugging
             purposes, set the value below to true. Set to false before
             deployment to avoid disclosing exception information
        -->
        <serviceDebug includeExceptionDetailInFaults="false"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>
 
Note the following:

First, the service has a behaviorConfiguration attribute in addition to the name attribute. The behaviorContribution identifies the name of the behavior element specifing the service's behavior. In this case the name is ServiceBehavior.

Second, the service has a second endpoint. The address is mex, the binding is mexHttpBinding and the contract is IMetadataExchange. The IMetadataExchange interface exposes methods for retrieving service metadata, mexHttpBinding enables metadata publication using Http and mex is a metadata relative address.

Third, the behavior named ServiceBehavior has a serviceMetadata element with httpGetEnabled attribute set to true. When the httpGetEnabled attribute is set equal to true allows the service to respond to metadata requests.

This is an example of extending the endpoint behavior to fulfill metadata requests.

When the channel stack is built the operating system modifies the components comprising the stack utilizing information contined in the configuration behavior elements. This customization is performed before the service is started or the proxy Open is invoked.

Extending Endpoint Behavior

Instances of a class that inherits the IEndpointBehavior interface can be added to the Behaviors collection.

The IEndpointBehavior interface prototypes methods that are called by the operating system while the channel is being built. Code within these methods can be used to implement extension extensions.
AddBindingParameters
Customization of binding behavior
ApplyClientBehavior
Extension to client endpoint behavior
ApplyDispatchBehavior
Extension of service endpoint behavior
Validate
Confirmation of endpoint configuration settings
The parameters passed in the call make the extensions possible. For example, the parameters passed to ApplyClientBehavior are the following:
  • SeviceEndpoint -- The endpoint being customized
  • ClientRuntime -- The runtime
The implementation shown below add and
public class TheEndpointBehavion : IEndpointBehavior
{
  public void AddBindingParameters(ServiceEndpoint endpoint,
    System.ServiceModel.Channels.BindingParameterCollection
      bindingParameters)
  { }

  public void ApplyClientBehavior(ServiceEndpoint endpoint,
    ClientRuntime clientRuntime)
  { }

  public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
    EndpointDispatcher endpointDispatcher)
  { }

  public void Validate(ServiceEndpoint endpoint)
  { }
}
An instance of the TheEndpointBehavior class can be added to the endpoint's Behaviors collection.
TheEndpointBehavion theEndpointBehavior =
    new TheEndpointBehavion();
proxy.Endpoint.Behaviors.Add(theEndpointBehavior);
This code is added after the proxy instance is created and before Open is called.

Again, insertion of a breakpoint after the proxy object is instantiated will stop program execution and properties of the object, including the added TheEndpointBehavior in the Behaviors collection, are displayed in Visual Studio's Locals Window.

Moreover, stepping through the code, after the proxy object is instantiated will reveal calls to the client related methods in TheEndpointBehavior object.

When the ApplyClientBehavior is called it is passed objects representing the endpoint and client runtime. The ClientRuntime type object exposes the MessageInspectors property, a collection of IClientMessageInspector type objects.

The following code adds an instance of the TheMessageInspector class to the MessageInspectors collection. TheMessageInspector class implements the IClientMessageInspector interface.
public void ApplyClientBehavior(ServiceEndpoint endpoint,
    ClientRuntime clientRuntime)
  {
    TheMessageInspector theMessageInspector =
      new TheMessageInspector();
    clientRuntime.MessageInspectors.Add(theMessageInspector);
  }
}
Message

Messages transported between the service and client are modeled by the Message class. The message is abstracted by the SOAP message and such contains multiple headers and a body. The body is intended to be streamed and thus access to a message is constrained in that it can only be accessed once. Subsequent attempts to access the message will result in  an exception being thrown. The status of the message is characterized by its state. When it is created it assigned the Created state. Reading, writing, copying or closing the message results in a transistion to the Read, Written, Copied and Closed state respectively. Any attempt to access the message when it is not in the Created state causes an exception. A work-arround is provided by creating a message buffer that contians a copy of the message. This buffer can than be used to recreate the original message and create additional copies for other processing.

Consider the code shown below. Assume that requestMsg is the Message object created by the framework. It can only be accessed once, typically when streamed to the line connecting the client and service. The Message class contains the CreateBufferedCopy method that returns a MessageBuffer object which is a buffered copy of the message.

The MessageBuffer class contains a CreateMessage that returns a Message object representing the buffered message. This message is identical to the message used to create the message used to create the buffered copy. 

Note the creation of a file stream and XmlWriter writing to the stream.

The MessageBuffer class also has a WriteMessage method that writes the buffered message ot a stream passed as a parameter. The encoding of the WriteMessage method is encoded to Xml by the XmlWriter object passed as a parameter. 

Note that both the writer and streamed are flushed.

Finally, the buffered message is used to create, again, the original message.

The MessageBuffer, copy of the message, is used repeately to create a new object identical to the original message in the Create state.
MessageBuffer messageBuffer =
          requestMsg.CreateBufferedCopy(System.Int32.MaxValue);
requestMsg = messageBuffer.CreateMessage();

FileStream fileStream = new FileStream("theRequestMessageFile.xml",
                                          FileMode.Create);
XmlWriterSettings xmlWriterSetting = new XmlWriterSettings();
xmlWriterSetting.Encoding = System.Text.Encoding.UTF8;
XmlWriter xmlWriter = XmlWriter.Create(fileStream, xmlWriterSetting);

rerequestMsg.WriteMessage(xmlWriter);

xmlWriter.Flush();
fifileStream.Flush();

requestMsg = messageBuffer.CreateMessage();
Remember the MessageInspector type object that was added to the MessageInspectors collection. TheMessageInspector class, shown below, inherits the IClientMessageInspectore interface.The IClientMessageInspectors interface contains two methods that are automatically  called by the framework:
  • BeforeSendRequest
  • AfterReceiveReply
Both contain a reference to the message that is to be sent or the message that has just been received. Copies of this message object can be processed, including being written to a file as shown below:
 public class TheMessageInspector : IClientMessageInspector
{
  public void AfterReceiveReply(
    ref System.ServiceModel.Channels.Message replyMsg, object obj)
  {
    MessageBuffer messageBuffer =
          replyMsg.CreateBufferedCopy(System.Int32.MaxValue);
    replyMsg = messageBuffer.CreateMessage();

    FileStream fileStream = new FileStream("theRequestMessageFile.xml",
                                              FileMode.Create);
    XmlWriterSettings xmlWriterSetting = new XmlWriterSettings();
    xmlWriterSetting.Encoding = System.Text.Encoding.UTF8;
    XmlWriter xmlWriter = XmlWriter.Create(fileStream, xmlWriterSetting);

    replyMsg.WriteMessage(xmlWriter);

    xmlWriter.Flush();
    fileStream.Flush();

    replyMsg = messageBuffer.CreateMessage();
  }

  public object BeforeSendRequest(
    ref System.ServiceModel.Channels.Message requestMsg,
    System.ServiceModel.IClientChannel clientChannel)
  {
    MessageBuffer messageBuffer =
          requestMsg.CreateBufferedCopy(System.Int32.MaxValue);
    requestMsg = messageBuffer.CreateMessage();

    FileStream fileStream = new FileStream("theRequestMessageFile.xml",
                                              FileMode.Create);
    XmlWriterSettings xmlWriterSetting = new XmlWriterSettings();
    xmlWriterSetting.Encoding = System.Text.Encoding.UTF8;
    XmlWriter xmlWriter = XmlWriter.Create(fileStream, xmlWriterSetting);

    rerequestMsg.WriteMessage(xmlWriter);

    xmlWriter.Flush();
    fileStream.Flush();

requestMsg = messageBuffer.CreateMessage();
}
}
The message sent to the service is shown below. Note the message body -- the operation and  parameter, as described in the contract, are clearly noted.:
<?xml version="1.0" encoding="utf-8" ?>
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing"
     xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">
      http://tempuri.org/ITheContract/TheMethod
    </a:Action>
    <a:MessageID>
      urn:uuid:db2f06be-e678-43e4-89bc-0507c500e342
    </a:MessageID>
    <a:ReplyTo>
      <a:Address>
        http://www.w3.org/2005/08/addressing/anonymous
      </a:Address>
    </a:ReplyTo>
  </s:Header>
  <s:Body>
    <TheMethod xmlns="http://tempuri.org/">
      <someData>Input Data</someData>
    </TheMethod>
  </s:Body>
</s:Envelope>
The body element, contained in the message dispatched, by the service to the client is shown below :
   <s:Body u:Id="_0">
    <TheMethodResponse xmlns="http://tempuri.org/">
      <TheMethodResult>
        You entered: Input Data
      </TheMethodResult>
    </TheMethodResponse>
  </s:Body> 

Author's Note

The above material has been gleaned from Microsoft's documentation.

I hope the tutorial has been informative. Plese forward any comment or observation and I will add it to the tutorial with credit.

Dudley