Share via


Changing the ChannelFactory Contract

A ChannelFactory is a local client endpoint that can stamp out proxy instances for a remote service endpoint. Knowing this detail about the local endpoint is essential when the client endpoint that gets automatically generated for the ChannelFactory doesn’t do what you want. Let's take a generic untyped echo service as the remote service endpoint for an example.

 [ServiceContract]
public interface IService
{
   [OperationContract(Action="*", ReplyAction="*")]
   Message Echo(Message message);
}

public class Service : IService
{
   public Message Echo(Message message)
   {
      return Message.CreateMessage(message.Version, "", message.GetReaderAtBodyContents());
   }
}

The fact that the interface is called IService is pretty unimportant. The only operation on the interface has the basic form of request-reply with no fixed action required for either the request or reply message. Rather than having to know about IService, it makes sense for clients to simply refer to this service using the built-in IRequestChannel. Sometimes this works without doing anything special, but we can find circumstances where the automatically generated local client endpoint doesn't correctly guess what you're trying to do. Here's some code that starts an instance of IService and tries to connect to it as an IRequestChannel over TCP.

 string uri = "net.tcp://localhost:800/";
Binding binding = new NetTcpBinding();

ServiceHost service = new ServiceHost(typeof(Service));
service.AddServiceEndpoint(typeof(IService), binding, uri);
service.Open();

ChannelFactory<IRequestChannel> factory = new ChannelFactory<IRequestChannel>(binding, new EndpointAddress(uri));
IRequestChannel channel = factory.CreateChannel();

XmlReader reader = XmlReader.Create(new StringReader("<MyData>text</MyData>"));
Message message = Message.CreateMessage(binding.MessageVersion, "", reader);
Console.WriteLine(channel.Request(message));
message.Close();

factory.Close();
service.Close();

If we try to run this code, it fails with an InvalidOperationException: "Contract does not allow Session, but Binding 'NetTcpBinding' does not support Datagram or is not configured properly to support it."

Where is the contract definition? Well, we don't have access to it because IRequestChannel is a built-in type. However, we can modify the contract that has been associated with the ChannelFactory because it is just a local client endpoint.

 factory.Endpoint.Contract.SessionMode = SessionMode.Allowed;

Now, we have a contract that makes both the local and remote endpoints happy so we can get our echoed message.

 <s:Envelope xmlns:s="www.w3.org/2003/05/soap-envelope" xmlns:a="www.w3.org/2005/08/addressing">
  <s:Header>
    <a:Action s:mustUnderstand="1" />
    <a:RelatesTo>urn:uuid:c41a2b38-e8de-4215-ab70-69ce823af7dc</a:RelatesTo>
    <a:To s:mustUnderstand="1">www.w3.org/2005/08/addressing/anonymous</a:To>
  </s:Header>
  <s:Body>
    <MyData>text</MyData>
  </s:Body>
</s:Envelope>

Next time: Session Lifetime on the Server