Is it possible to make the WcfTestClient work for custom transport channels?

EnocNRoll - AnandaGopal Pardue picture EnocNRoll - AnandaGopal Pardue · Jan 27, 2009 · Viewed 12.5k times · Source

Goal

I would like to be able to both host and connect to a vanilla sockets server via WCF, within the hosting framework I am devising. I want to be able to use WCF to codify the transport and protocol communications that have to be manually managed by sockets programmers today. This would allow me the ultimate interoperability with Linux server daemons that expose nothing but traditional sockets and proprietary protocols. I am only interested in verifying that transport channel layer generically using WcfTestClient at this point. My understanding is that the WcfTestClient does not support complex service methods.

Does anybody think it is possible to make the WcfTestClient work for custom transport channels? It would be really sweet to be able to generically use this client to test any number of custom transport channels.

Overview

I am working to understand the WCF Udp sample that is included in the Windows SDK, located typically at C:\Program Files\Microsoft SDKs\Windows\v6.1\Samples\WCFSamples\TechnologySamples\Extensibility\Transport\Udp\CS, assuming that the WCFSamples.zip file is fully extracted from the Samples directory.

These are the steps I have taken thus far:

  • (success): Run the solution's service and client successfully within Visual Studio 2008.

  • (success): connect to a MEX endpoint using the WcfTestClient typically located at C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE

  • (failure): use the WcfTestClient to attempt to execute a method from either the ICalculatorContract or the IDatagramContract service contract.

When I execute the Hello() method, for example, I receive the following error:

Friendly message:

Failed to invoke the service. Possible causes: The service is offline or inaccessible; the client-side configuration does not match the proxy; the existing proxy is invalid. Refer to the stack trace for more detail. You can try to recover by starting a new proxy, restoring to default configuration, or refreshing the service.

Error Details:

The CustomBinding on the ServiceEndpoint with contract 'IDatagramContract' lacks a TransportBindingElement. Every binding must have at least one binding element that derives from TransportBindingElement. at System.ServiceModel.Channels.Binding.EnsureInvariants(String contractName) at System.ServiceModel.Description.ServiceEndpoint.EnsureInvariants() at System.ServiceModel.Channels.ServiceChannelFactory.BuildChannelFactory(ServiceEndpoint serviceEndpoint) at System.ServiceModel.ChannelFactory.CreateFactory() at System.ServiceModel.ChannelFactory.OnOpening() at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout) at System.ServiceModel.ChannelFactory.EnsureOpened() at System.ServiceModel.ChannelFactory1.CreateChannel(EndpointAddress address, Uri via) at System.ServiceModel.ChannelFactory1.CreateChannel() at System.ServiceModel.ClientBase1.CreateChannel() at System.ServiceModel.ClientBase1.CreateChannelInternal() at System.ServiceModel.ClientBase`1.get_Channel() at DatagramContractClient.Hello()

Understanding the Client Error

The UdpTransportBindingElement that is defined in the UdpTransport project definitely derives from TransportBindingElement, as shown below, so I'm thinking that there must be something missing from the WcfTestClient configuration file and/or possibly that I have to somehow provide the test client with more information. I tried basically copying the System.ServiceModel section of the Udp solution's client project into the WcfTestClient's config file, as well as copying the transport assembly dll to the same folder as the test client, but I receive the same error.

My understanding is that the MEX endpoint should be enough to get the information required to invoke simple methods on the service. Of course, I understand that there is probably more to the story, considering that I'm trying to make a client that was designed to test the out-of-the-box transport channels work with custom transport channels.

/// <summary>
/// Udp Binding Element.  
/// Used to configure and construct Udp ChannelFactories and ChannelListeners.
/// </summary>
public class UdpTransportBindingElement 
    : TransportBindingElement // to signal that we're a transport
    , IPolicyExportExtension // for policy export
    , IWsdlExportExtension

Is ITransportPolicyImport Important for Custom Transport Channels?

I ask about ITransportPolicyImport because that is an interface that is implemented by the standard WCF derivations of TransportBindingElement, but the Udp sample does not implement this interface, and I cannot find anything useful in web searches or on Safari. It may not even be relevant.

For example...

public class HttpTransportBindingElement :
    : TransportBindingElement
    , IWsdlExportExtension
    , IPolicyExportExtension
    , ITransportPolicyImport

Sample Configs...

The service config looks like this:

  <system.serviceModel>

    <!-- 
         add our udpTransport handler for use by binding declarations 
     -->
    <!-- 
         add our standard binding handler for use by binding declarations 
     -->
    <extensions>
      <bindingElementExtensions>
        <add name="udpTransport" type="Microsoft.ServiceModel.Samples.UdpTransportElement, UdpTransport" />
      </bindingElementExtensions>
      <bindingExtensions>
        <add name="sampleProfileUdpBinding" type="Microsoft.ServiceModel.Samples.SampleProfileUdpBindingCollectionElement, UdpTransport" />
      </bindingExtensions>
    </extensions>

    <services>
      <service name="Microsoft.ServiceModel.Samples.ConfigurableCalculatorService" behaviorConfiguration="udpServiceBehavior">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8000/udpsample"/>
          </baseAddresses>
        </host>
        <endpoint address="soap.udp://localhost:8001/" 
            binding="sampleProfileUdpBinding" 
            bindingConfiguration="CalculatorServer"
            contract="Microsoft.ServiceModel.Samples.ICalculatorContract" />
        <endpoint address="soap.udp://localhost:8002/datagram" 
            binding="customBinding" 
            bindingConfiguration="DatagramServer"
            contract="Microsoft.ServiceModel.Samples.IDatagramContract" />
        <endpoint address="mex"
            binding="mexHttpBinding"
            contract="IMetadataExchange" />
      </service>
    </services>

    <bindings>
        <!-- 
         server bindings 
          -->
        <sampleProfileUdpBinding>
            <binding name="CalculatorServer" clientBaseAddress="soap.udp://localhost:8003/" />
            <binding name="DatagramServer" reliableSessionEnabled="false" />
        </sampleProfileUdpBinding>

        <customBinding>
        <binding name="DatagramServer">
            <binaryMessageEncoding />
            <udpTransport/>
        </binding>
      </customBinding>
    </bindings>

    <behaviors>
      <serviceBehaviors>
        <behavior name="udpServiceBehavior">
          <serviceMetadata httpGetEnabled="True"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <client>
      <endpoint address="" binding="sampleProfileUdpBinding" bindingConfiguration="CalculatorServer"
        contract="Microsoft.ServiceModel.Samples.ICalculatorContract"
        name="CalculatorClient" />
    </client>    
  </system.serviceModel>

The client config from the solution looks like this:

  <system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="CustomBinding_IDatagramContract">
          <binaryMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"
              maxSessionSize="2048">
            <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          </binaryMessageEncoding>
          <UpdTransportElementClientSide maxBufferPoolSize="524288" maxMessageSize="65536"
              multicast="false" />
        </binding>
      </customBinding>
      <sampleProfileUdpBinding>
        <binding name="SampleProfileUdpBinding_ICalculatorContract" closeTimeout="00:01:00"
            openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
            orderedSession="true" reliableSessionEnabled="true" sessionInactivityTimeout="00:10:00" 
            clientBaseAddress="soap.udp://localhost:8003/" />
      </sampleProfileUdpBinding>
    </bindings>
    <client>
      <endpoint address="soap.udp://localhost:8001/" binding="sampleProfileUdpBinding"
          bindingConfiguration="SampleProfileUdpBinding_ICalculatorContract"
          contract="ICalculatorContract" name="SampleProfileUdpBinding_ICalculatorContract" />
      <endpoint address="soap.udp://localhost:8002/datagram" binding="customBinding"
          bindingConfiguration="CustomBinding_IDatagramContract" contract="IDatagramContract"
          name="CustomBinding_IDatagramContract" />
    </client>
    <extensions>
      <bindingElementExtensions>
        <add name="UpdTransportElementClientSide" type="Microsoft.ServiceModel.Samples.UdpTransportElement, UdpTransport" />
      </bindingElementExtensions>
      <!-- This was added manually because svcutil.exe does not add this extension to the file -->
      <bindingExtensions>
        <add name="sampleProfileUdpBinding" type="Microsoft.ServiceModel.Samples.SampleProfileUdpBindingCollectionElement, UdpTransport" />
      </bindingExtensions>
    </extensions>
  </system.serviceModel>

Answer

Tim Roberts picture Tim Roberts · Oct 5, 2010

It is possible to create custom transports in WCF. It's quite involved though!

Roman Kiss has built a Null Transport that allows you to consume and host the services from within the same process without the overhead of marshalling the data through one of the built in transports. His CodePlex article is available at:

http://www.codeproject.com/KB/WCF/NullTransportForWCF.aspx

It may be a good starting point for helping you learn how to build your own transport. Good luck!