wcf icon indicating copy to clipboard operation
wcf copied to clipboard

Whitespace lost when using MTOM message encoding

Open bogatykh opened this issue 3 years ago • 6 comments

Whitespace characters are trimmed on all XML content when calling WCF service using MTOM message encoding.

Steps to reproduce the behavior:

  1. Host a WCF service using MTOM message encoding.
  2. Call a method returning whitespaces from client app.
  3. No whitespace is observed on response.

Host app:

string baseAddress = "http://localhost:8080/wcfselfhost/";
ServiceHost host = new ServiceHost(typeof(HelloWorldService), new Uri(baseAddress));

BasicHttpBinding binding = new BasicHttpBinding();
binding.MessageEncoding = WSMessageEncoding.Mtom;

host.AddServiceEndpoint(typeof(IHelloWorldService), binding, "hello");

host.Open();

Console.ReadLine();

Host app service:

[ServiceContract]
public interface IHelloWorldService
{
    [OperationContract]
    string SayHello(string name);
}

public class HelloWorldService : IHelloWorldService
{
    public string SayHello(string name)
    {
        return string.Format("    whitespace hello, {0}    ", name);
    }
}

Test client:

var channelFactory = new ChannelFactory<IHelloWorldService>(
    new BasicHttpBinding() { MessageEncoding = WSMessageEncoding.Mtom },
    new EndpointAddress("http://localhost:8080/wcfselfhost/hello")
);

var channel = channelFactory.CreateChannel();

string resp = channel.SayHello("test");
Console.ReadKey();

Test client is built using .NET 6.0 and System.ServiceModel.Http version 4.9.0.

'resp' variable contents is "whitespace hello, test "

Expected behavior is to get " whitespace hello, test "

bogatykh avatar Dec 04 '21 17:12 bogatykh

As you can see in related pull request test failed. White space is trimmed.

Error: expected response from service: ' Hello ' Actual was: 'Hello '

This should be fixed as top priority because we use response singing on client side and signature verification on server side. And because XML content is different, signature checks are failed.

bogatykh avatar Dec 12 '21 18:12 bogatykh

Can you please verify that you don't see this issue when using a .NET Framework WCF client and that this is a regression?

When an XML element isn't being output in a separate multi-part segment, the MTOM reader/writer defers most of the logic to the regular System.Xml XmlReader and XmlWriter. If this is a regression, I suspect this is a difference in behavior in those implementations. I made very few changes to the code base from .NET Framework. Basically implemented a couple more async methods and made some internal book-keeping classes async too, but other than that, the code is identical from the MTOM reader/writer part.

mconnew avatar Dec 15 '21 07:12 mconnew

I can confirm this is a regression.

When targeting .NET 4.7.2+ everything work as expected - whitespaces are not trimmed. After targeting .NET 6.0 MTOM encoded messages behavior is as I described earlier.

bogatykh avatar Dec 15 '21 11:12 bogatykh

We use following workaround for now: public void AfterReceiveReply(ref Message reply, object correlationState) { reply = Message.CreateMessage( reply.Version, reply.Headers.Action, reply.GetReaderAtBodyContents()); }

Applying this IClientMessageInspector to WCF call no whitespaces are lost.

bogatykh avatar Dec 15 '21 11:12 bogatykh

Looks like an issue is deep in corefx. Putting a breakpoint at HttpResponseMessageHelper.GetStreamAsync and trying to read content as string: _httpResponseMessage.Content.ReadAsStringAsync().Result results in decoded XML content with lost whitespace.

bogatykh avatar Feb 13 '22 13:02 bogatykh

@bogatykh, it's not an issue in corefx. I checked the result from ReadAsStringAsync and the spaces are there. I also tested the repro from above on NetFx and the spaces are missing on NetFx too. I reduced the problem down to something which didn't require a running service and was able to repro the issue, and I get the same behavior on NetFx and Core.

var bufferManager = BufferManager.CreateBufferManager(0, 0);
var mmebe = new MtomMessageEncodingBindingElement();
mmebe.MessageVersion = MessageVersion.Soap11;
var encoderFactory = mmebe.CreateMessageEncoderFactory();
var encoder = encoderFactory.Encoder;

string testMessage = @"--uuid:a6d79f8f-bf20-4725-9016-b011f266c927+id=2
Content-ID: <http://tempuri.org/0>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type=""text/xml""

<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/""><s:Body><SayHelloResponse xmlns=""http://tempuri.org/MyService""><SayHelloResult>    whitespace hello, test    </SayHelloResult></SayHelloResponse></s:Body></s:Envelope>
--uuid:a6d79f8f-bf20-4725-9016-b011f266c927+id=2--";

string contentType = @"multipart/related; type=""application/xop+xml""; start=""<http://tempuri.org/0>""; boundary=""uuid:a6d79f8f-bf20-4725-9016-b011f266c927+id=2""; start-info=""text/xml""";

var bytes = Encoding.UTF8.GetBytes(testMessage);
var readMessage = encoder.ReadMessage(new ArraySegment<byte>(bytes, 0, bytes.Length), bufferManager, contentType);
var reader = readMessage.GetReaderAtBodyContents();
reader.Read(); // Skip wrapping element
string resp = reader.ReadElementContentAsString();

Because this isn't a regression, this will need to wait to be addressed in the next release as regressions are taking priority at the moment.

mconnew avatar Aug 15 '22 23:08 mconnew