Tuesday, 29 March 2011

Implementing IDispatchMessageInspector

Introduction

Have you ever needed to change the contract used by WCF service, but were held back by the impact it would have on existing clients? Sure, you could just ask your clients to update their service references and tweak the code which calls your service, but that requires that all your clients (of which there might be many) to do a code release on the same day you release your new server - not ideal.

One way to resolve this would be to leave the existing contract alone, but to create a second modified contract exposed via a new endpoint. That way clients could upgrade to the new contract (and the new endpoint) at their leisure. This will probably create some redundancy in the server-side code, as you'd have very similar code at both endpoints, although you could probably refactor any common code such that it was shared. There's also a risk that this approach would lead to end-point proliferation over time as you make various tweaks to the contract. So, is there an an alternative?

One approach which I used recently, and which is the subject of this post, is to intercept the calls which use the old contract and transform them into a call to the new contract on-the-fly. WCF has an extensibility interface designed just for this purpose - IDispatchMessageInspector. Note that, despite its name, implementing this interface allows us to both inspect and modify inbound and outbound messages.

To demonstrate this approach I'm going to:

  • create a WCF Service exposing a particular interface,
  • create a client which consumes this service,
  • modify the service in a manner which is not backwards compatible,
  • show that the client can no longer consume the service,
  • implement IDispatchMessageInspector in the service to modify calls on the fly, and
  • show that the client can now consume the service again.

Creating the WCF Service

I'm not going to go through this step-by-step. If you're interested in implementing IDispatchMessageInspector to modify WCF messages on the fly, then I'm sure you'll have created a WCF service or two in your time. So I'll just be providing source code listing of the various files:

  • IEmployeeService (the service contract)
  • EmployeeService (an implementation of that contract)
  • Employee (a data contract exposed via the service)
  • Server (contains the console application's entry point and service host provider)
IEmployeeService
  using System.ServiceModel;

  namespace WcfService
  {
    [ServiceContract]
    public interface IEmployeeService
    {
      [OperationContract]
      void SaveEmployee(Employee employee);
    }
  }
EmployeeService
  using System;

  namespace WcfService
  {
    public class EmployeeService : IEmployeeService
    {
      public void SaveEmployee(Employee employee)
      {
        Console.WriteLine("Saved {0}", employee);
      }
    }
  }
Employee
  using System;
  using System.Runtime.Serialization;

  namespace WcfService
  {
    [DataContract]
    public class Employee
    {
      [DataMember]
      public string Name { get; set; }

      [DataMember]
      public DateTime DateOfBirth { get; set; }

      [DataMember]
      public char Gender { get; set; }

      public override string ToString()
      {
        return string.Format("{0} (DOB:{1} Gender:{2})", Name, DateOfBirth.ToShortDateString(), Gender);
      }
    }
  }
Server
  using System;
  using System.ServiceModel;
  using System.ServiceModel.Description;

  namespace WcfService
  {
    class Server
    {
      static void Main()
      {
        // create a host and add an end-point to it
        ServiceHost serviceHost = new ServiceHost(typeof(EmployeeService));
        ContractDescription contractDescription = ContractDescription.GetContract(typeof(IEmployeeService), typeof(EmployeeService));
        NetTcpBinding netTcpBinding = new NetTcpBinding();
        EndpointAddress endpointAddress = new EndpointAddress("net.tcp://localhost:8001/EmployeeService");
        ServiceEndpoint serviceEndpoint = new ServiceEndpoint(contractDescription, netTcpBinding, endpointAddress);
        serviceHost.Description.Endpoints.Add(serviceEndpoint);

        // add a meta-data end-point
        ServiceMetadataBehavior serviceMetadataBehavior = new ServiceMetadataBehavior();
        serviceMetadataBehavior.HttpGetEnabled = true;
        serviceMetadataBehavior.HttpGetUrl = new Uri("http://localhost:8002/EmployeeService/mex");
        serviceHost.Description.Behaviors.Add(serviceMetadataBehavior);

        // open the host
        serviceHost.Open();
        Console.WriteLine("Press ENTER to terminate service.");

        // close the host once the user hits ENTER
        Console.ReadLine();
        serviceHost.Close();
      }
    }
  }

Okay, so that the server-side code sorted. As you can see it's all fairly basic stuff: the service merely accepts an Employee into the SaveEmployee method of its IEmployeeService and writes details of that Employee to the console.

Creating the WCF Client

The client is just as trivial. It consists of a single Client class (in addition to all the auto-generated Service Reference stuff and App.config) which creates an instance of the proxy Employee class and asks the service to save it.

Client
  using System;
  using WcfClient.EmployeeServiceProxy;

  namespace WcfClient
  {
    class Client
    {
      static void Main()
      {
        EmployeeServiceClient employeeServiceClient = new EmployeeServiceClient();
        employeeServiceClient.Open();
  
        Employee employee = new Employee
        {
          Name = "Jane Armstrong",
          DateOfBirth = new DateTime(1969, 7, 12),
          Gender = 'F'
        };

        try
        {
          Console.WriteLine("Saving {0} (DOB:{1} Gender:{2})", employee.Name, employee.DateOfBirth.ToShortDateString(), employee.Gender);
          employeeServiceClient.SaveEmployee(employee);
          employeeServiceClient.Close();
        }
        catch (Exception ex)
        {
          Console.WriteLine(ex);
          employeeServiceClient.Abort();
        }

        Console.WriteLine("Press ENTER to terminate client.");
        Console.ReadLine();
      }
    }
  }

I generated the EmployeeServiceProxy class and App.config simply by using the Add Service Reference dialog, pointing it at the meta-data end-point of the service, http://localhost:8002/EmployeeService/mex.

Testing the Client and Service

Okay, now we're ready to show how this client and service interact. They're both console applications, so you can just start them both up from the command-line. Let's start the service first:

  > WcfService\bin\debug\WcfService.exe
  Press ENTER to terminate service.

and then the client:

  > WcfClient\bin\Debug\WcfClient.exe
  Saving Jane Armstrong (DOB:12/07/1969 Gender:F)
  Press ENTER to terminate client.

If you glance back at the service, you'll notice that it now displays:

Saved Jane Armstrong (DOB:12/07/1969 Gender:F)

So, our extremely simple WCF client/service application works fine. Now let's break it.

Changing the Contract

In an ideal world, WCF contracts would be immutable. But life's not like that. Requirements change. In my scenario I'm going to imagine that the client/service above underwent a code review and someone said: "You're using a char for Gender? Hmmm. That allows a client to send a Gender of 'X' or some other invalid value. Perhaps an enumeration might be better?" The problem is, you've already deployed you client and have dozens of people using it. You really don't want to have to re-deploy it.

Before we look at how implementing IDispatchMessageInspector can solve this issue, let's change the contract (on the service-side only) and see what happens when a client calls it.

We're going to need a Gender enumeration:

  namespace WcfService
  {
    public enum Gender
    {
      Male,
      Female
    }
  }

And we're going to need to change Employee to use this enumeration:

  using System;
  using System.Runtime.Serialization;

  namespace WcfService
  {
    [DataContract]
    public class Employee
    {
      ...

      [DataMember]
      public Gender Gender { get; set; }
      
      ...
    }
  }

Once we've re-compiled the service, we can fire it up:

  > WcfService\bin\Debug\WcfService.exe
  Press ENTER to terminate service.

and watch what happens when the client (which is still sending a char Gender) tries to save an employee.

  > WcfClient\bin\Debug\WcfClient.exe
  Saving Jane Armstrong (DOB:12/07/1969 Gender:F)
  System.ServiceModel.FaultException: The formatter threw an exception while tryin
  g to deserialize the message: There was an error while trying to deserialize par
  ameter http://tempuri.org/:employee. The InnerException message was 'Invalid enu
  m value '70' cannot be deserialized into type 'WcfService.Gender'. Ensure that t
  he necessary enum values are present and are marked with EnumMemberAttribute att
  ribute if the type has DataContractAttribute attribute.'.  Please see InnerExcep
  tion for more details.

Exactly as expected, the client can't pass a char as the service is expecting a member of the Gender enumeration. (In case you're wondering, the enum value 70 in the message above comes from the ASCII value for the letter 'F'.)

Enter IDispatchMessageInspector

WCF allows us to both inspect and modify messages after they've been received by the service but, crucially, before they've been forwarded to an operation. This is achieved by providing a class which implements IDispatchMessageInspector and adding it as a service behaviour. Adding it as a service behaviour can be achieved in code or via a configuration file. As my service is code-only so far, I'm going to stick with that approach.

A skeleton IDispatchMessageInspector implementation would look like this:

  using System;
  using System.IO;
  using System.ServiceModel;
  using System.ServiceModel.Channels;
  using System.ServiceModel.Dispatcher;
  using System.Xml;
  using System.Xml.XPath;
  using System.Xml.Xsl;

  namespace WcfService
  {
    public class MessageInspector : IDispatchMessageInspector
    {
      public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
      {
        // inspect and/or modify the request

        // return any object which will be passed to BeforeSendReply to correlate requests and replies
        return null;
      }
  
      public void BeforeSendReply(ref  Message reply, object correlationState)
      {
        // inspect and/or modify the reply
      }
    }
  }

(In case you're wondering about all those redundant using statements - we'll need them later when we provide some real implementations.)

The object you return in AfterReceivedRequest for a given request will be passed to BeforeSendReply when processing the matching reply. I've used this in the past to optionally transform the response - i.e. AfterReceiveRequest inspects the request and establishes whether it came from a legacy client (in our example, that would be one which is passing a char Gender) and returns true/false so BeforeSendReply knows whether it needs to transform the reply such that it can be processed by a legacy client (in our example, the reply hasn't changed, so there's no need to do anything in BeforeSendReply).

You'll notice that the request parameter passed to AfterReceiveRequest, and the reply parameter passed to BeforeSendRepy, are passed by reference. This is because the body of a Message object is immutable - it can't be modified. If you want to modify the message (as we do) you need to create a new Message object with the message you want to use instead of the original and assign that to the request (or reply) parameter. One other wrinkle is that the body of a Message object can only be read once. So if you read it in AfterReceiveRequest (just to log its contents to a file, for example) then it can't be read again by the rest of the WCF stack. Fun, eh? We'll ignore this issue for the moment (don't worry, we'll get back to it) and plough on. If you're just doing a straight transformation this isn't a problem anyway: you read the Message body (once) and create a new Message containing the body you want to be passed to the receiving operation.

A Message object essentially represents a SOAP message and, like a SOAP message, contains both headers and a body. In order for us to write some code which transforms the message, we're going to need to see what the message looks like as it arrives from the client. So we need an IDispatchMessageInspector implementation to inspect the message. We can simply use the template above, and change the AfterReceiveRequest method to read:

   public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
   {
     // write the request to the console (only works for small messages)
     Console.WriteLine(request.ToString());

     // we don't need to pass a correlation state
     return null;
   }

This code breaks one of the rules I mentioned above whereby a message can only be read once - it seems that ToString is allowed to ready the message, up to a point. Although I've not seen it documented anywhere I believe that there must be a small internal buffer which is used by the ToString method such that it can output a representation of the string without consuming its contents. For a larger message, the output produced by ToString would contain elipses (...) to indicate the message body.

To cause our MessageInspector to be invoked, we need to create a new class which implememnts IEndpointBehavior and add our MessageInspector to the list of IMessageInspectors when its ApplyDispatchBehavior method is called:

  using System.ServiceModel.Channels;
  using System.ServiceModel.Description;
  using System.ServiceModel.Dispatcher;

  namespace WcfService
  {
    public class EndpointBehaviour : IEndpointBehavior
    {
      public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
      {
        // do nothing
      }

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

      public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
      {
        endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MessageInspector());
      }

      public void Validate(ServiceEndpoint endpoint)
      {
        // do nothing
      }
    }
  }

To cause our EndpointBehaviour to be used, we need to add it to the list of end-point behaviours by adding the following line to Server.cs, immediately after the serviceEndpoint has been initialised:

  serviceEndpoint.Behaviors.Add(new EndpointBehaviour());

When we start the WcfService and WcfClient this time, the WcfClient will still fail (we haven't corrected the actual problem yet) but at least we'll see in the WcfService what the SOAP message sent by the WcfClient was:

Press ENTER to terminate service.
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w
3.org/2003/05/soap-envelope">
  <s:Header>
    <a:Action s:mustUnderstand="1">http://tempuri.org/IEmployeeService/SaveEmplo
yee</a:Action>
    <a:MessageID>urn:uuid:796c06e4-03b9-4012-899c-935199d5e81e</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
    <a:To s:mustUnderstand="1">net.tcp://localhost:8001/EmployeeService</a:To>
  </s:Header>
  <s:Body>
    <SaveEmployee xmlns="http://tempuri.org/">
      <employee xmlns:b="http://schemas.datacontract.org/2004/07/WcfService" xml
ns:i="http://www.w3.org/2001/XMLSchema-instance">
        <b:DateOfBirth>1969-07-12T00:00:00</b:DateOfBirth>
        <b:Gender>70</b:Gender>
        <b:Name>Jane Armstrong</b:Name>
      </employee>
    </SaveEmployee>
  </s:Body>
</s:Envelope>

The change we want to make to this Message is trivial: we simply need to change <b:Gender>70</b:Gender> to <b:Gender>Female</b:Gender>. Now, we could use XSLT to do this (which is what I'd typically do if the transformation required were non-trivial), but let's just use an XmlDocument for the modification.

So, we'll modify of the AfterReceiveRequest so it reads:

  public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  {
    // copy the contents of the original message's body into a MemoryStream
    MemoryStream memoryStream = new MemoryStream();
    XmlDictionaryWriter xmlDictionaryWriter = XmlDictionaryWriter.CreateTextWriter(memoryStream);
    request.WriteBodyContents(xmlDictionaryWriter);
    xmlDictionaryWriter.Flush();
    
    // write the contents of the MemoryStream to the Console
    memoryStream.Position = 0L;
    Console.WriteLine(new StreamReader(memoryStream).ReadToEnd());
    
    // load the stream into an XmlDocument
    memoryStream.Position = 0L;
    XmlDocument xmlDocument = new XmlDocument();
    xmlDocument.Load(memoryStream);
    
    // register the namespaces we'll need to xpath to the <gender> node
    XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(xmlDocument.NameTable);
    xmlNamespaceManager.AddNamespace("t", "http://tempuri.org/");
    xmlNamespaceManager.AddNamespace("b", "http://schemas.datacontract.org/2004/07/WcfService");
    
    // modify the <gender> node
    XmlNode genderNode = xmlDocument.SelectSingleNode("/t:SaveEmployee/t:employee/b:Gender", xmlNamespaceManager);
    if (genderNode != null)
    {
      genderNode.InnerText = genderNode.InnerText == "70" ? "Female" : "Male";
    }
    
    // copy the modified XmlDocument back into the MemoryStream
    memoryStream.Position = 0L;
    xmlDocument.Save(memoryStream);
    
    // write the contents of the MemoryStream to the Console
    memoryStream.Position = 0L;
    Console.WriteLine(new StreamReader(memoryStream).ReadToEnd());
    
    // create a new Message from the MemoryStream (copying the original's properties and headers)
    memoryStream.Position = 0L;
    XmlReader xmlReader = XmlReader.Create(memoryStream);
    Message modifiedRequest = Message.CreateMessage(request.Headers.MessageVersion, request.Headers.Action, xmlReader);
    modifiedRequest.Properties.CopyProperties(request.Properties);
    modifiedRequest.Headers.CopyHeadersFrom(request);
    
    // re-assign the request parameter to our new modified request
    request = modifiedRequest;
    
    // return
    return null;
  }

If you're like me you'll hate this code. Let's count the number of copies of the message are required: there's obviously the original message (one), then we copy it into a MemoryStream (two), then we copy that into an XmlDocument (three); once we've modified it, we copy it back into a MemoryStream (four) and finally into a new Message (five). Really? We need to have five copies of the message? In reality there we only four as we re-used the MemoryStream. Also, the final Message we create is passed XmlReader which wraps the MemoryStream - the Message doesn't have a separate copy of this data, it simply reads it from the MemoryStream via the XmlReader when necessarily, not at construction time. So there are actually only three copies the message in memory at any one time. But that's still two too many but my count. In this example the message is clearly tiny, but imagine if it weren't. Those extra copies of the message could really hurt scalability.

Before we look at alternative ways to code this, let's just see if it actually works first. So we'll fire up WcfService:

Press ENTER to terminate service.

And then WcfClient:

Saving Jane Armstrong (DOB:12/07/1969 Gender:F)
Press ENTER to terminate client.

Note that the client didn't experience an exception this time. Glancing back at the WcfService we now see:

Press ENTER to terminate service.
<SaveEmployee xmlns="http://tempuri.org/"><employee xmlns:b="http://schemas.data
contract.org/2004/07/WcfService" xmlns:i="http://www.w3.org/2001/XMLSchema-insta
nce"><b:DateOfBirth>1969-07-12T00:00:00</b:DateOfBirth><b:Gender>70</b:Gender><b
:Name>Jane Armstrong</b:Name></employee></SaveEmployee>
<SaveEmployee xmlns="http://tempuri.org/">
  <employee xmlns:b="http://schemas.datacontract.org/2004/07/WcfService" xmlns:i
="http://www.w3.org/2001/XMLSchema-instance">
    <b:DateOfBirth>1969-07-12T00:00:00</b:DateOfBirth>
    <b:Gender>Female</b:Gender>
    <b:Name>Jane Armstrong</b:Name>
  </employee>
</SaveEmployee>
Saved Jane Armstrong (DOB:12/07/1969 Gender:Female)

(Yours won't be in colour - I've colour-coded the output to explain what you're seeing). The orange XML is the original message received by WcfService. You'll note that Gender is 70 (the ASCII value for 'F'). The green XML is once we've translate it; after translation the Gender is Female. You'll also note that the Saved message shows that the deserialised Employee object contains the correct value for Gender.

Alternate Implementations

So, are there any better solutions? Well, you might try using the XPathNavigator returned by the MessageBuffer's CreateNavigator() method:

   public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
   {
    // copy the original request into a buffer which we can read and modify
    MessageBuffer messageBuffer = request.CreateBufferedCopy(Int32.MaxValue);

    // do the necessary prep work to navigate over the request
    XPathNavigator xpathNagivator = messageBuffer.CreateNavigator();
    XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(xpathNagivator.NameTable);
    xmlNamespaceManager.AddNamespace("s", "http://www.w3.org/2003/05/soap-envelope");
    xmlNamespaceManager.AddNamespace("t", "http://tempuri.org/");
    xmlNamespaceManager.AddNamespace("b", "http://schemas.datacontract.org/2004/07/WcfService");
     
    // modify the <gender> node
    XPathNavigator genderNode = xpathNagivator.SelectSingleNode("/s:Envelope/s:Body/t:SaveEmployee/t:employee/b:Gender", xmlNamespaceManager);
    if (genderNode != null)
    {
      genderNode.SetValue(genderNode.Value == "70" ? "Female" : "Male");
    }

    // re-assign the request parameter to a new Message built from our modified buffer
    request = messageBuffer.CreateMessage();

    // tidy up and return
    messageBuffer.Close();
    return null;
  }

Unfortunately, the XPathNavigator created is read-only (its CanEdit property is set to false) so the call to its SetValue method throws a NotSupportedException.

What about an XSLT-based approach? How would that work? Let's start by creating the XSLT itself. We can use the orange XML above as our sample input, and the green XML above as out expected output. The XSLT is pretty simple, as you'd expect.

   <?xml version="1.0" encoding="utf-8"?>
   <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:t="http://tempuri.org/" xmlns:b="http://schemas.datacontract.org/2004/07/WcfService">
     <xsl:output method="xml" indent="yes"/>

     <!-- transform the Gender node -->
     <xsl:template match="t:SaveEmployee/t:employee/b:Gender/text()">
       <xsl:choose>
         <xsl:when test=".='70'">Female</xsl:when>
         <xsl:otherwise>Male</xsl:otherwise>
       </xsl:choose>
     </xsl:template>

     <!-- simply copy all other nodes as-is -->
     <xsl:template match="@* | node()">
       <xsl:copy>
         <xsl:apply-templates select="@* | node()"/>
       </xsl:copy>
     </xsl:template>
   </xsl:stylesheet>

We need to add this file (I called mine TransformRequest.xslt) to the WcfService project and set its Build Action to Embedded Resource. We won't want to load a compile this XSLT every time a request comes in, so we'll place that logic in the MessageInspector's constructor:

  private XslCompiledTransform _compiledTransform;

  public MessageInspector()
  {
    string transformResourceName = string.Format("{0}.{1}", GetType().Namespace, TransformFileName);
    using (Stream stream = GetType().Assembly.GetManifestResourceStream(transformResourceName))
    {
      if (stream == null)
      {
        throw new Exception(string.Format("Unable to load the resource named '{0}' - is it embedded within the the '{1}' assembly?", transformResourceName, GetType().Assembly.FullName));
      }
      _compiledTransform = new XslCompiledTransform();
      _compiledTransform.Load(XmlReader.Create(stream));
    }
  }

Once we have the XSLT in memory, using it within a revised AfterReceiveRequest becomes quite simple.

  public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  {
    // transform the original request, via the XSLT, into a new MemoryStream
    MemoryStream memoryStream = new MemoryStream();
    _compiledTransform.Transform(request.GetReaderAtBodyContents(), null, memoryStream);

    // create a new Message from the MemoryStream (copying the original's properties and headers)
    memoryStream.Position = 0L;
    XmlReader xmlReader = XmlReader.Create(memoryStream);
    Message modifiedRequest = Message.CreateMessage(request.Headers.MessageVersion, request.Headers.Action, xmlReader);
    modifiedRequest.Properties.CopyProperties(request.Properties);
    modifiedRequest.Headers.CopyHeadersFrom(request);

    // re-assign the request parameter to our new modified request and return
    request = modifiedRequest;
    return null;
  }

You note that there are now only two copies of the request message - the original message and the revised messages stored in the MemoryStream. As was noted before, although the Message object we create is passed an XmlReader which wraps the MemoryStream - the Message doesn't have a separate copy of the data, it simply reads it from the MemoryStream via the XmlReader when necessarily.

Although using XSLT might be overkill for this simple requirement, I actually prefer it. It nicely separates the act of performing the transformation (the XSLT) from the plumbing necessary to hook it up to WCF (the AfterReceiveRequest method).

Conclusion

IDispatchMessageInspector isn't something you'd design into an appliction at the outset - you should really aim to get your WCF contract fixed before you start allowing clients to connect. But as a get-out-of-jail-free card when you've no choice but to change the interface, it's invaluable.

See Also

10 comments:

  1. Hi Ian,

    i've had some trouble with your example specifically around re-creating the message after processing the body .. if you have the time would you mind reviewing this ? http://stackoverflow.com/questions/6139632/how-to-edit-wcf-message-wcf-message-interceptors

    Thanks!
    Paul

    ReplyDelete
  2. @Paul: I've just replied to your StackOverflow post. The code you posted there seems to work fine and plugs into the IDispatchMessageInspector above without any problems.

    ReplyDelete
  3. Hi Ian,

    I have a post on
    http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataservices/thread/1d0de553-64c2-442c-8c72-9e7a92eb7a73

    I am guessing AfterReceiveRequest is the best place to intercept the incoming Message so that it can be reconstructed using the ABC format.

    It comes is as
    MessageContentType "application/soap+msbin1"

    Can you please help.

    Thanks!
    Rosy

    ReplyDelete
  4. Good day Ian, I've tried implementing your code on server side which adds the Behavior on the ServiceEndpoint. I added a MessageInspector of IClientMessageInspector type on the AddClientBehavior. The problem is, the BeforeSendRequest and AfterReceiveReply seem not being invoked. Below is my code:

    public class ClientMessageInspector:IClientMessageInspector
    {

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
    Console.WriteLine(reply.ToString());
    Console.Write("AfterReceiveReply");
    //throw new NotImplementedException();
    }

    public object BeforeSendRequest(ref Message request, System.ServiceModel.IClientChannel channel)
    {
    Console.WriteLine(request.ToString());
    Console.Write("BeforeSendRequest");
    //throw new NotImplementedException();
    return request;
    }
    }

    public class DispatchMessageInspector:IDispatchMessageInspector
    {

    public object AfterReceiveRequest(ref Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
    {
    Console.WriteLine(request.ToString());
    Console.WriteLine("AfterReceiveRequest");
    return request;
    }

    public void BeforeSendReply(ref Message reply, object correlationState)
    {
    //throw new NotImplementedException();
    Console.WriteLine(reply.ToString());
    Console.WriteLine("BeforeSendReply");
    }
    }


    public class EndpointMessageInspectorBehavior : IEndpointBehavior
    {

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    //throw new NotImplementedException();
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    clientRuntime.MessageInspectors.Add(new ClientMessageInspector());
    //throw new NotImplementedException();
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new DispatchMessageInspector());
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    //throw new NotImplementedException();
    }
    }

    My implementation was just for displaying the message since I am new to WCF. Regards...

    Thanks a lot.

    ReplyDelete
  5. You say that your implementing the code "on server side" but that you're using an IClientMessageInspector. It seems to me that this might be your problem. If you're using an IClientMessageInspector you need to hook it up on the client; if you're attempting to intercept the message on the server you need an IDispatchMessageInspector. Your code actually shows both, and you're hooking-up both via your EndpointMessageInspectorBehavior (which implements IEndpointBehavior). This interface can be used on the client and/or the server. If you're using it on the client, you'd want to react to the the ApplyClientBehavior method, and it you're using on the server, you'd want to react to the ApplyDispatchBehavior method.

    Are you able to share the code you're using to hook-up the EndpointMessageInspectorBehavior into the rest of your application? My guess is that you're adding the behaviour to the end-point in the same way I was doing in the article, i.e. something like: serviceEndpoint.Behaviors.Add(new EndpointMessageInspectorBehavior());

    That's fine if you're server-side, but if you want to intercept message on the client (i.e. using an IClientMessageInspector) you'll probably want something like: channelFactory.Endpoint.Behaviors.Add(new EndpointMessageInspectorBehavior());

    Hope this helps.

    ReplyDelete
  6. Hello, Is source code available for download?

    ReplyDelete
  7. I've tried to get your code to work using the XSLT approach. However I having difficulty in making the Behaviour work. For example, in the original code you speicfied that Gender was a char and then you changed it so it accepts an enum.

    Now in the client code i have specified the employee object like this:
    var employee = new Employee
    {
    Name = "Testing 123",
    DateOfBirth = new DateTime(1977, 7, 12),
    Gender = (Gender)'F'
    };

    I get an exception saying that Enum value '70' is an invalid type. Am i doing something wrong? I was expecting it to convert the value 70 to use the value Female.

    System.ServiceModel.CommunicationException: There was an error while trying to s
    erialize parameter http://tempuri.org/:employee. The InnerException message was
    'Enum value '70' is invalid for type 'WcfClient.EmployeeServiceClient.Gender' an
    d cannot be serialized. Ensure that the necessary enum values are present and ar
    e marked with EnumMemberAttribute attribute if the type has DataContractAttribut
    e attribute.'. Please see InnerException for more details. ---> System.Runtime.
    Serialization.SerializationException: Enum value '70' is invalid for type 'WcfCl
    ient.EmployeeServiceClient.Gender' and cannot be serialized. Ensure that the nec
    essary enum values are present and are marked with EnumMemberAttribute attribute
    if the type has DataContractAttribute attribute.
    at System.Runtime.Serialization.EnumDataContract.WriteEnumValue(XmlWriterDele
    gator writer, Object value)

    ReplyDelete
    Replies
    1. The scenario the article covers is where the contract has changed (i.e. the Gender property of the Employee class has changed from a char to an enum) but we had to abstract away this change from existing clients - so existing clients still send a char.

      It looks like your client's Employee class is expecting Gender to be a enum - i.e. it is aware of the update made on the server. Did you have Visual Studio auto-generate the Employee class on the client from the original contract? Or are you sharing the same implementation between client and server?

      If you're sharing the implementation (i.e. both client and server reference the same Employee class) then there's no transformation to do - the client knows about the change already. But if your client has an auto-generated proxy for the server's Employee class (which is the scenario covered by the article) then the client should still be sending 'F' based upon the out-of-date proxy - which you be converted 'in-flight' to an enum value.

      Does that make sense?

      Delete
    2. Hello,



      Could you try this:
      [DataContract]
      public enum Gender
      {
      [EnumMember]
      Male,
      [EnumMember]
      Female
      }
      And then in the employee class add this:
      [DataMember]
      public Gender Gender { get; set; }

      A great Thanks to: Ian Picknell

      Delete
  8. Hello.
    I'm windering is there any way to use, for example, German umlauts within EndpointAddress definition?
    EndpointAddress endpointAddress = new EndpointAddress("net.tcp://localhost:8001/EmployeeService");
    For example instead of localhost to use süd.
    Thank you in advance.
    Nenad

    ReplyDelete