2

I downloaded a C# implementation example from NVMS to understand how to implement their web services. It uses SOAP requests to call different services. In the source code, there is an internal class implementing IEndpointBehavior and IClientMessageInspector, and in the BeforeSendRequest they cleared the SOAP headers but judging by the response I get, the final request still has headers. I tried both requests (with and without headers) that are printed in the console in SOAPUI and the headerless request works, while the other gets me the same message I get in the C# app itself.

Here is the class :

internal class CustomMessageInspector : IEndpointBehavior, IClientMessageInspector
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(this);
    }

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

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    /*public void BeforeSendReply(ref Message reply, object correlationState)
    {
        reply.Headers.Clear();
    }*/

    //add using directive Message = System.ServiceModel.Channels.Message;
    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        // WORKAROUND for WCF-Exception "The MessageHeader is already understood" 
        // (Note: The message still gets validated)
        reply.Headers.Clear();
        Console.WriteLine("received Response:");
        Console.WriteLine("{0}\r\n", reply);
    }

    /// <summary>
    /// Shows the sent message with and without SOAP-Header
    /// </summary>
    /// <param name="request"></param>
    /// <param name="channel"></param>
    /// <returns></returns>
    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        // Fait la même chose que le clear, mais ne fonctione pas non plus...
        /*request.Headers.ReplyTo = null;
        request.Headers.Action = null;
        request.Headers.MessageId = null;*/
        Console.WriteLine("original Request:");
        Console.WriteLine("{0}\r\n", request);
        // Ne semble pas fonctionner, la requête est envoyée avec les headers...
        request.Headers.Clear();

        Console.WriteLine("without Header Request:");
        Console.WriteLine("{0}\r\n", request);

        return null;
    }
}

The "request.Headers.Clear();" line should work here. The request argument is passed by reference, so it should clear the headers from the source object. But this is the result I get :

received Response:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <s:Header xmlns:s="http://www.w3.org/2003/05/soap-envelope" />
  <soap:Body>
    <soap:Fault>
      <soap:Code>
        <soap:Value>soap:MustUnderstand</soap:Value>
      </soap:Code>
      <soap:Reason>
        <soap:Text xml:lang="en">MustUnderstand headers: [{http://www.w3.org/2005/08/addressing}To] are not understood.</soap:Text>
      </soap:Reason>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

Here are the 2 requests (with and without headers, as printed by the beforesendrequest method) :

original Request:
<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">ns:G110RequestMessage</a:Action>
    <a:MessageID>urn:uuid:405c0e93-f39d-4d8b-bef8-72cf82f88203</a:MessageID>
    <a:ReplyTo>
      <a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
    </a:ReplyTo>
  </s:Header>
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <G110Request xmlns="urn:wsdltypes.nmvs.eu:v3.0">
      <Header xmlns="urn:types.nmvs.eu:v3.0">
        <Auth>
          <ClientLoginId>ABC</ClientLoginId>
          <UserId>test123</UserId>
          <Password>123456</Password>
        </Auth>
        <UserSoftware d5p1:name="Test Soft" d5p1:supplier="Comp Any" d5p1:version="V2" xmlns:d5p1="urn:types.nmvs.eu:v3.0" />
        <Transaction>
          <ClientTrxId>7775559966aaa</ClientTrxId>
          <Language>eng</Language>
        </Transaction>
      </Header>
      <Body xmlns="urn:types.nmvs.eu:v3.0">
        <Product>
          <ProductCode d6p1:scheme="GTIN" xmlns:d6p1="urn:types.nmvs.eu:v3.0">PK001C854A8EE536949</ProductCode>
          <Batch>
            <Id>TESTA1596337CF</Id>
            <ExpDate>231130</ExpDate>
          </Batch>
        </Product>
        <Pack d5p1:sn="PK001C854A8EE536949" xmlns:d5p1="urn:types.nmvs.eu:v3.0" />
      </Body>
    </G110Request>
  </s:Body>
</s:Envelope>

without Header Request:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
  <s:Header />
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <G110Request xmlns="urn:wsdltypes.nmvs.eu:v3.0">
      <Header xmlns="urn:types.nmvs.eu:v3.0">
        <Auth>
          <ClientLoginId>ABC</ClientLoginId>
          <UserId>test123</UserId>
          <Password>123456</Password>
        </Auth>
        <UserSoftware d5p1:name="Test Soft" d5p1:supplier="Comp Any" d5p1:version="V2" xmlns:d5p1="urn:types.nmvs.eu:v3.0" />
        <Transaction>
          <ClientTrxId>7775559966aaa</ClientTrxId>
          <Language>eng</Language>
        </Transaction>
      </Header>
      <Body xmlns="urn:types.nmvs.eu:v3.0">
        <Product>
          <ProductCode d6p1:scheme="GTIN" xmlns:d6p1="urn:types.nmvs.eu:v3.0">PK001C854A8EE536949</ProductCode>
          <Batch>
            <Id>TESTA1596337CF</Id>
            <ExpDate>231130</ExpDate>
          </Batch>
        </Product>
        <Pack d5p1:sn="PK001C854A8EE536949" xmlns:d5p1="urn:types.nmvs.eu:v3.0" />
      </Body>
    </G110Request>
  </s:Body>
</s:Envelope>

I tried to change the mustUnderstand attribute but I can't find where to change it.

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
Aru
  • 31
  • 6
  • Hi Aru, welcome to Stack Overflow! I have exactly the same problem as you. The example C# code works with the V2 interface SinglePackServices but not the V3 interface. I activated System.ServiceModel.MessageLogging and yes, the server is correct. The "To" header is indeed present with mustUnderstand set to 1. I've just posted a more specific question on how to prevent that header being added (on SO it's best to keep questions precise). I'll let you know if I progress. Incidentally, have you managed to use the SupportServices endpoint? I can't get that working in either V2 or V3 :-( – Mark Willis Dec 06 '18 at 10:07
  • Hi Mark. Thanks for your reply! I barely started development on V2 so I went straight to the V3. If the V2 works, I think I'll go with that one for the time being. I tested the SupportServices on V3 with the Ping Request, it works. Haven't tried the others yet. – Aru Dec 07 '18 at 05:00
  • Lol yes the V3 came out a week after I started too. Thanks for the info on the Support Services - I'll try that next. Good luck. – Mark Willis Dec 07 '18 at 07:31
  • Welcome to the club. I'm working on this too! :-) Started a few month ago and it all worked. Tried to finish the job now and nothing worked. – dwo Dec 07 '18 at 16:11
  • Seems like you have to choose between using the V2 or trying the answer below and see if it works for you, for the V3. – Aru Dec 10 '18 at 04:41
  • @dwo, nice to have you with us. Now we can share our NMVS problems - and solutions ;-) - on SO – Mark Willis Dec 10 '18 at 08:31
  • I sent an email to NVMS support regarding the MustUnderstand issue and said they are aware of it. It seems to impact only SinglePack services. – Aru Dec 11 '18 at 04:16
  • @Aru I did the same last week, with a link to this page ;-) In my case no reply yet... Tell me, are you explicitly setting TLS1.2? I have a suspicion that the example application just hopes that TLS1.2 or higher will be negotiated - I can find no attempt to force it. On one of our old test servers with no TLS the application still tries to talk - resulting in a chain of exceptions being generated. Not the best way to find that your server doesn't support the application – Mark Willis Dec 11 '18 at 08:12
  • 1
    Yeah, I put this line I got from the latest source code in my program : System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12; – Aru Dec 11 '18 at 10:01
  • Thanks. It's a shame we can't just specify a minimum. Still the spec requires TLS1.2 and nothing else. – Mark Willis Dec 11 '18 at 14:00
  • They replied to my email saying a new version of the C# source code that fixes the MustUnderstand issue is available on the portal. – Aru Dec 14 '18 at 10:16
  • I think they've updated the server side to understand "TO". Even the old code works now... – Mark Willis Dec 14 '18 at 11:34

2 Answers2

2

Well, after a over a day searching, I have a solution. In fact Nicolas Giannone provided all the necessary code here WSHttpBinding in .NetStandard or .NET core Below is the SinglePackPing function from the NMVS's C# example, modified to work with the sandbox and the V3 API.

public static Boolean SinglePackPing( MyConfig myConfig, String pingString )
{
    Boolean ok = false;

    string endPoint = myConfig.SinglePackServicesEndPoint;

    //Defines a secure binding with certificate authentication
    WSHttpBinding binding = new WSHttpBinding();
    binding.Security.Mode = SecurityMode.Transport;
    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;

    // create a new binding based on the existing binding
    var customTransportSecurityBinding = new CustomBinding( binding );
    // locate the TextMessageEncodingBindingElement - that's the party guilty of the inclusion of the "To"
    var ele = customTransportSecurityBinding.Elements.FirstOrDefault( x=>x is TextMessageEncodingBindingElement );
    if( ele != null )
    {
        // and replace it with a version with no addressing
        // replace {Soap12 (http://www.w3.org/2003/05/soap-envelope) Addressing10 (http://www.w3.org/2005/08/addressing)}
        //    with {Soap12 (http://www.w3.org/2003/05/soap-envelope) AddressingNone (http://schemas.microsoft.com/ws/2005/05/addressing/none)}
        int index = customTransportSecurityBinding.Elements.IndexOf( ele );
        var textBindingElement = new TextMessageEncodingBindingElement
        {
            MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None)
        };
        customTransportSecurityBinding.Elements[index] = textBindingElement;
    }

    //Creates ServiceClient, attach transport-binding, Endpoint and the loaded certificate
    using( SinglePackServicesClient service = new SinglePackServicesClient( customTransportSecurityBinding, new EndpointAddress( endPoint ) ) )
    {
        using( X509Certificate2 cert = new X509Certificate2( myConfig.CertificateFilePath, myConfig.PrivateKeyPassword, X509KeyStorageFlags.PersistKeySet ) )
        {

            //Creates ServiceClient, attach transport-binding, Endpoint and the loaded certificate          
            service.ClientCredentials.ClientCertificate.Certificate = cert;
            service.Endpoint.EndpointBehaviors.Add( new CustomMessageInspector() );
            var bindingTest = service.Endpoint.Binding;

            //Creates PingRequest and set the ping Input
            SinglePackPingRequest ping = new SinglePackPingRequest();
            ping.Input = pingString;

            //Creates PingResponse, result of the request. Displays the response
            SinglePackPingResponse pingResponse = service.PingSinglePack(ping);

            ok = pingResponse.Output == pingString;

            //Displays the response. If the request is successful, the output is equal to the input
            Console.WriteLine( pingResponse.Output );
        }
    }

    return ok;
}

I hope that helps you. Let me know how you get on.

Mark Willis
  • 818
  • 12
  • 19
  • Thanks a lot! I had to change a few things because FirstOrDefault didn't work for me. I'll post my code in an answer below. – Aru Dec 07 '18 at 10:45
1

As suggested by Mark, the solution is to create a CustomBinding and change the AdressingVersion to None.

Here is the code I used, based on the one above but with the necessary rework I had to do to get it working for me.

       //add using directive System.ServiceModel;
        //Defines a secure binding with certificate authentication
        WSHttpBinding binding = new WSHttpBinding();
        binding.Security.Mode = SecurityMode.Transport;
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;

        // create a new binding based on the existing binding
        var customTransportSecurityBinding = new CustomBinding(binding);
        var customTransportSecurityBindingCopy = new CustomBinding(binding);

        // locate the TextMessageEncodingBindingElement - that's the party guilty of the inclusion of the "To"

        foreach(BindingElement element in customTransportSecurityBinding.Elements)
        {
            // and replace it with a version with no addressing
            // replace {Soap12 (http://www.w3.org/2003/05/soap-envelope) Addressing10 (http://www.w3.org/2005/08/addressing)}
            //    with {Soap12 (http://www.w3.org/2003/05/soap-envelope) AddressingNone (http://schemas.microsoft.com/ws/2005/05/addressing/none)}
            int index = customTransportSecurityBinding.Elements.IndexOf(element);

            if (element is TextMessageEncodingBindingElement)
            {
                var textBindingElement = new TextMessageEncodingBindingElement
                {
                    MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None)
                };

                customTransportSecurityBindingCopy.Elements[index] = textBindingElement;
            }
        }

        customTransportSecurityBinding = customTransportSecurityBindingCopy;
        customTransportSecurityBindingCopy = null;

        //Creates ServiceClient, attach transport-binding, Endpoint and the loaded certificate
        SinglePackServicesClient service = new SinglePackServicesClient(customTransportSecurityBinding, new EndpointAddress(EndPoint));
Aru
  • 31
  • 6