Ron Ratzlaff
Ron Ratzlaff

Reputation: 298

SOAP Version Returned in WSDL from WCF Endpoint with Custom Binding Does Not Match Config

We've got an existing WCF endpoint that currently uses the basicHttpBinding with the default SOAP 1.1 binding for one endpoint. It is called by a .NET Compact Framework 3.5 client application that we control. We want some customized logging, including the number of bytes on the wire for both requests and responses. To get this, I'm developing a custom MessageEncoder and IDispatchMessageInspector with the related custom bindings in configuration. That's all working fine and the client is able to call the endpoint without issue using a proxy class that was generated before I made my changes. The problem I'm having is that when I attempt to regenerate the proxy class using NetCFSvcUtil.exe, it's failing because the WSDL says that the endpoint is using SOAP 1.2.

Error: .NET Compact Framework does not support any bindings offered by this service.

CustomBinding_IAssetService: .NET Compact Framework does not support binding element option TextMessageEncodingBindingElement.MessageVersion = Soap12 (http://www.w3.org/2003/05/soap-envelope) AddressingNone (http://schemas.microsoft.com/ws/2005/05/addressing/none).

So, I used a browser to look at the WSDL and it is reporting the endpoint as using SOAP 1.2.

The WSDL generated for my custom binding:

<wsdl:binding name="CustomBinding_IAssetService" type="tns:IAssetService">
    <soap12:binding transport="http://schemas.xmlsoap.org/soap/http"/>

The WSDL generated with the old binding:

<wsdl:binding name="BasicHttpBinding_IAssetService" type="tns:IAssetService">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>

Interestingly, if I replace the custom encoder in my custom binding config with a <textMessageEncoding messageVersion="Soap11"> element, the WSDL contains the expected SOAP 1.1 binding:

<wsdl:binding name="CustomBinding_IAssetService" type="tns:IAssetService">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>

That leads me to believe that it's an issue in my custom classes. I debugged the code to look at the value of MessageVersion that's returned from the inner TextMessageEncodingBindingElement object and from my custom binding classes (ThroughputEncoderBindingElement, ThroughputEncoderExtensionElement, ThroughputEncoderFactory, and ThroughputEncoder) and it's SOAP 1.1 in all cases when the WSDL request is made.

Are there any issues in my code or config here? Anything else I can try?

Original Binding

<basicHttpBinding>
    <binding name="HttpBinding" maxReceivedMessageSize="2147483647">
        <readerQuotas maxDepth="2147483647" ... />
    </binding>
</basicHttpBinding>

Original Config for Service

<service name="Company.AssetService" behaviorConfiguration="DefaultServiceBehavior">
    <host>
        <baseAddresses>
            <add baseAddress="http://localhost:8000/Asset/service"/>
        </baseAddresses>
    </host>
    <endpoint address=""
              binding="wsHttpBinding"
              bindingConfiguration="WsBinding"
              contract="Company.IAssetService" />
    <endpoint address="basic"
              binding="basicHttpBinding"
              bindingConfiguration="HttpBinding"
              contract="Company.IAssetService" />
</service>

New Binding

<customBinding>
    <binding name="throughputEncoding" maxReceivedMessageSize="2147483647">
      <throughputEncoding>
        <textEncoding messageVersion="Soap11">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" ... />
        </textEncoding>
      </throughputEncoding>
      <httpTransport/>
    </binding>
</customBinding>

New Config for Service

<service name="Company.AssetService" behaviorConfiguration="DefaultServiceBehavior">
    <host>
        <baseAddresses>
            <add baseAddress="http://localhost:8000/Asset/service"/>
        </baseAddresses>
    </host>
    <endpoint address=""
              binding="wsHttpBinding"
              bindingConfiguration="WsBinding"
              contract="Company.IAssetService" />
    <endpoint address="basic"
              binding="customBinding"
              bindingConfiguration="throughputEncoding"
              contract="Company.IAssetService" />
</service>

Extension Config

<extensions>
    ...
  <bindingElementExtensions>
    <add name="throughputEncoding"
         type="Company.Infrastructure.ThroughputEncoderExtensionElement, Company, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  </bindingElementExtensions>
</extensions>

Classes

public class ThroughputEncoder : MessageEncoder
{
    private readonly MessageEncoder innerEncoder;
    private readonly WcfMessageLogger logger;

    public ThroughputEncoder(MessageEncoder innerEncoder)
    {
        this.innerEncoder = innerEncoder;
        logger = new WcfMessageLogger();
    }

    public override string ContentType
    {
        get { return "text/xml"; }
    }

    public override string MediaType
    {
        get { return "text/xml"; }
    }

    public override MessageVersion MessageVersion
    {
        get { return innerEncoder.MessageVersion; }
    }
    ...
}

public class ThroughputEncoderFactory : MessageEncoderFactory
{
    private readonly ThroughputEncoder throughputEncoder;

    public ThroughputEncoderFactory(MessageEncoder messageEncoder)
    {
        throughputEncoder = new ThroughputEncoder(messageEncoder);
    }

    public override MessageEncoder Encoder
    {
        get { return throughputEncoder; }
    }

    public override MessageVersion MessageVersion
    {
        get { return throughputEncoder.MessageVersion; }
    }
}

public class ThroughputEncoderExtensionElement : BindingElementExtensionElement
{
    private const string TextEncodingPropertyName = "textEncoding";

    [ConfigurationProperty(TextEncodingPropertyName)]
    public TextMessageEncodingElement TextEncoding
    {
        get { return (TextMessageEncodingElement)this[TextEncodingPropertyName]; }
        set { this[TextEncodingPropertyName] = value; }
    }

    protected override BindingElement CreateBindingElement()
    {
        var textBindingElement = new TextMessageEncodingBindingElement();

        if (TextEncoding != null)
            TextEncoding.ApplyConfiguration(textBindingElement);

        return new ThroughputEncoderBindingElement(textBindingElement);
    }

    public override Type BindingElementType
    {
        get { return typeof (ThroughputEncoderBindingElement); }
    }
}

public class ThroughputEncoderBindingElement : MessageEncodingBindingElement
{
    private readonly MessageEncodingBindingElement innerBindingElement;

    public ThroughputEncoderBindingElement(MessageEncodingBindingElement innerBindingElement)
    {
        this.innerBindingElement = innerBindingElement;
    }

    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
    {
        context.BindingParameters.Add(this);
        return base.BuildChannelFactory<TChannel>(context);
    }

    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
    {
        context.BindingParameters.Add(this);
        return base.BuildChannelListener<TChannel>(context);
    }

    public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
    {
        context.BindingParameters.Add(this);
        return base.CanBuildChannelFactory<TChannel>(context);
    }

    public override bool CanBuildChannelListener<TChannel>(BindingContext context)
    {
        context.BindingParameters.Add(this);
        return base.CanBuildChannelListener<TChannel>(context);
    }

    public override MessageEncoderFactory CreateMessageEncoderFactory()
    {
        var innerFactory = innerBindingElement.CreateMessageEncoderFactory();
        return new ThroughputEncoderFactory(innerFactory.Encoder);
    }

    public override BindingElement Clone()
    {
        return new ThroughputEncoderBindingElement(innerBindingElement);
    }

    public override T GetProperty<T>(BindingContext context)
    {
        return innerBindingElement.GetProperty<T>(context) ?? context.GetInnerProperty<T>();
    }

    public override MessageVersion MessageVersion
    {
        get { return innerBindingElement.MessageVersion; }
        set { innerBindingElement.MessageVersion = value; }
    }
}

Upvotes: 0

Views: 2340

Answers (1)

eajusti
eajusti

Reputation: 11

You need to implement the IWsdlExportExtension interface on your BindingElement.

With it you can tell WCF how it should generate the WSDL for your service by implementing the ExportContract and ExportEndpoint functions.

You can see how it is done in Microsoft's CustomTextMesssageEncoder WCF sample.

See: https://msdn.microsoft.com/en-us/library/ms751486(v=vs.110).aspx

Download the sample and there you have it.

Upvotes: 1

Related Questions