-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Android] Deserialization issue when consuming ASMX Web Service #80935
Comments
Tagging subscribers to 'arch-android': @steveisok, @akoeplinger Issue DetailsDescriptionI'm upgrading an existing Xamarin Traditional (Android) application into .NET 6 but I'm running into deserialization problems. Our Android app integrates with a .NET Framework 4.8 ASMX Web Service on the backend. This has been working fine for years but doesn't work after we move to .NET 6. Reproduction StepsI was able to create a repro project on my GitHub: https://github.com/adolfo1981/Net6AndroidWebServiceTest My GitHub repro repo consists of the following projects:
Expected behaviorGet string response from HelloWorldAsync() method Actual behaviorException: StackTrace:
Regression?Yes, this worked in legacy Xamarin Android and should work in the .NET versions. Known WorkaroundsNone Configuration.NET 6, .NET 7 Other informationIMPORTANT: The repro project points to my local IP (Bottom of Things that I've tried:
|
@simonrozsival Please triage this when you have a moment. |
Triage: From the customer's report it's clear that there is an Android specific issue (the same code works in a .NET 6 console application) somewhere in the code that processes the response from the server. At a first glance this could be one of the following issues:
We should investigate further to identify the cause of the problem and fix it to align the behavior of all supported platforms. The repro project didn't work on my PC out-of-the-box because one of the server project failed to load. I'll give it another try later this week. |
Is there a solution to this yet? I am running into the exact same issue running .NET 7. Web Services run fine in a Windows environment, as soon as I try executing the same code in an Android environment I receive the error posted in this issue. |
I was able to reproduce the bug in both .NET 6 and .NET 7. It appears to be a problem with response content decompression in AndroidMessageHandler. For some reason the decompressed XML is incomplete /cc @grendello @jonathanpeppers There are two workarounds that might apply in some scenarios:
|
@simonrozsival if the decompression finished then the payload is probably "valid" (in the sense that the compressed data contains all that was sent/received), but it of course depends on the compression algorithm used. If it's GZip (most likely?), then the decompression may return a short result if one block of data fails to decompress. All the preceding blocks will still be decompressed. That begs the question - is the compressed data length (as reported by the If the length is different, then there might be a bug in that we somehow fail to read the last chunk of data from the service. If that's the case, then it might be a problem with the underlying Java client (a short read caused by something) or in our content stream handling. I find it hard to believe the latter is the case, since we've had this code for many years without any such issues, but nothing is impossible - perhaps some subtle semantics of stream reading changed between the mono/mono and dotnet BCLs? Another test would be to see if changing the compression algorithm makes things work? The underlying Java client supports only GZip and Deflate IIRC, both should also be supported by the ASMX service server. |
@adrianpbv the second issue you mentioned about |
@grendello Yes, it's
It reminds me of another issue with the iOS native handler: dotnet/wcf#4903. I think this is a bug in WCF which doesn't correctly work with native handlers 🤔 |
@simonrozsival hm, that sounds a bit like WCF takes the compressed data length and applies it to the decompressed content length? |
@grendello It seems that way to me. If I remember correctly, SocketsHttpHandler doesn't expose the
I'm not very familiar with the WCF codebase so I might be wrong. I can try confirming my hypothesis next week. I just need to find a better way to debug the wcf code on Android. /cc @mconnew |
@simonrozsival so it looks like WCF would work fine if we reset the content length to the size of data after decompression, however doing that would be both tricky (we wouldn't know the length until content were decompressed, and that may happen outside our control) and not entirely correct from the HTTP protocol POV. Looks like the correct fix is to teach WCF how to deal with compression. |
@grendello, if WCF was handling the raw bytes from the connection and had to instantiate a GzipStream to decompress the contents, it would be examining the Content-Encoding header to do so. In that scenario, it would already have code that's aware about different compression encoding methods so it makes sense for it to handle the content-length header differently. That's not the case here though. HttpClientHandler (the implementation type we use) auto-magically handles compressed content for WCF. Whether it's GzipStream, DeflateStream, or something else, WCF is blissfully unaware that compression has been used. Here's a list of Http implementations where WCF hasn't needed to special case compression:
Here's the list of Http implementations which require special handling:
Thinking beyond WCF, as the iOS and Android native wrappers still present as HttpClientHandler, I think it's a better developer experience that HttpClientHandler provides consistent behavior wherever it's being used as libraries which were never written for or tested on .NET MAUI are going to be used there. If a native implementation is specifically being used by instantiating a platform specific class, I think it's fair to say different implementations will behave differently. That's not the case here, the api being used is HttpClientHandler so whenever possible it should behave the same everywhere. Every .NET HTTP implementation has behaved the exact same way on every platform for decades except the new .NET MAUI mobile platforms. I don't think it's WCF that needs to change behavior. I'm strongly of the opinion that it's .NET MAUI that has the bug. If you aren't going to provide the same behavior that HttpClientHandler has everywhere else, then don't provide an HttpClientHandler wrapper around the native implementation and only provide a differently named managed wrapper implementation and have HttpClientHandler use SocketsHttpHandler. |
@mconnew I both agree and disagree with what you wrote, but it doesn't really matter what I think because I found why the working client handlers work and the native wrappers don't. The working ones remove the |
Context: dotnet#7230 Context: dotnet/runtime#80935 When a WCF application receives compressed content that is automatically decompressed in `AndroidMessageHandler`, it will most of the time fail to properly read the response, cutting reading of the decompressed content short. The reason for this is that when `AndroidMessageHandler` creates a wrapper decompression stream, it does not update the `Content-Length` to match the length of the decoded content, because it doesn't have a way to know what the length is without first reading the stream to the end, and that might prevent the end user to read the content. Additionally, I think the `Content-Length` header should reflect the **original** content length, for the end user to be able to interpret the response as it was sent. WCF, on the other hand, looks at the `Content-Length` header and, if found, it takes its value and reads only this many bytes from the content stream and no more - it will almost always result in short reads and failure to correctly interpret the response. I implemented this workaround which makes `AndroidMessageHandler` behave the same way as other handlers implemented in the BCL. What they do in this situation, is to remove the `Content-Length` header, making WCF read stream to the end. Additionally, the clients remove the compressed content encoding identifier from the `Content-Encoding` header. As a bonus, this commit also adds support for decompression of responses compressed with the `Brotli` compression (using the `br` encoding ID in the `Content-Encoding` header)
Looks like this is being worked on with dotnet/android#7785 |
…7785) Context: #7230 Context: dotnet/runtime#80935 When a WCF application invokes an endpoint which returns compressed content, and `AndroidMessageHandler` is doing the network requests ([the default when `$(UseNativeHttpHandler)`=True][0]): var soapClient = new WebServiceSoapClient(WebServiceSoapClient.EndpointConfiguration.WebServiceSoap); //Async test var helloResponse = await soapClient.HelloWorldAsync(); then the method will throw: The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:HelloWorldResponse. ---> There was an error deserializing the object of type ServiceReference1.HelloWorldResponseBody. Unexpected end of file. Following elements are not closed: HelloWorldResult, HelloWorldResponse, Body, Envelope. Line 1, position 298. at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName, DataContractResolver dataContractResolver) at System.Runtime.Serialization.XmlObjectSerializer.ReadObjectHandleExceptions(XmlReaderDelegator reader, Boolean verifyObjectName) at System.Runtime.Serialization.DataContractSerializer.ReadObject(XmlDictionaryReader reader, Boolean verifyObjectName) at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.PartInfo.ReadObject(XmlDictionaryReader reader, XmlObjectSerializer serializer) in /_/src/System.Private.ServiceModel/src/System/ServiceModel/Dispatcher/DataContractSerializerOperationFormatter.cs:line 657 at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.PartInfo.ReadObject(XmlDictionaryReader reader) in /_/src/System.Private.ServiceModel/src/System/ServiceModel/Dispatcher/DataContractSerializerOperationFormatter.cs:line 652 at System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter.DeserializeParameterPart(XmlDictionaryReader reader, PartInfo part, Boolean isRequest) in /_/src/System.Private.ServiceModel/src/System/ServiceModel/Dispatcher/DataContractSerializerOperationFormatter.cs:line 521 The reason for this is that when `AndroidMessageHandler` creates a wrapping decompression stream, it does not update `Content-Length` to match the length of the decoded content, because it doesn't have a way to know what the length is without first reading the stream to the end, and that might prevent the end user to read the content. (Additionally, I think the `Content-Length` header should reflect the *original* content length, for the end user to be able to interpret the response as it was sent.) WCF, on the other hand, looks at the `Content-Length` header and, if found, takes the value and reads only that many bytes from the content stream and no more, which will almost always result in short reads and failure to correctly interpret the response. Workaround this issue by making `AndroidMessageHandler` behave the same way as other handlers implemented in the BCL. What they do in this situation is remove the `Content-Length` header, making WCF read the stream to the end. Additionally, the clients remove the compressed content encoding identifier from the `Content-Encoding` header. var handler = new AndroidMessageHandler { AutomaticDecompression = DecompressionMethods.All }; var client = new HttpClient (handler); var response = await client.GetAsync ("https://httpbin.org/gzip"); // response.Content.Headers won't contain Content-Length, // and response.Content.Headers.ContentEncoding won't contain `gzip`. As a bonus, also adds support for decompression of responses compressed with the `Brotli` compression which use the `br` encoding ID in the `Content-Encoding` header. [0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options?pivots=dotnet-7-0
Is there a workaround yet I am facing similar issue on Maui Android on .NET 7 [0:] System.ServiceModel.ProtocolException: The number of bytes available is inconsistent with the HTTP Content-Length header. There may have been a network error or the client may be sending invalid requests. |
@psudodev It looks like dotnet/android#7785 was fixed, but I'm not sure it was backported to .NET 7. @grendello can you confirm? |
Since this is not a runtime issue and has been fixed elsewhere, I'm going to close this. |
Transferred customer issue from dotnet/android#7230
Description
I'm upgrading an existing Xamarin Traditional (Android) application into .NET 6 but I'm running into deserialization problems. Our Android app integrates with a .NET Framework 4.8 ASMX Web Service on the backend. This has been working fine for years but doesn't work after we move to .NET 6.
Reproduction Steps
I was able to create a repro project on my GitHub: https://github.com/adolfo1981/Net6AndroidWebServiceTest
My GitHub repro repo consists of the following projects:
Expected behavior
Get string response from HelloWorldAsync() method
Actual behavior
Exception:
The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:HelloWorldResponse. The InnerException message was 'There was an error deserializing the object of type ServiceReference1.HelloWorldResponseBody. Unexpected end of file. Following elements are not closed: HelloWorldResult, HelloWorldResponse, Body, Envelope. Line 1, position 298.'. Please see InnerException for more details.
StackTrace:
Regression?
Yes, this worked in legacy Xamarin Android and should work in the .NET versions.
Known Workarounds
None
Configuration
.NET 6, .NET 7
Android (any architecture)
Other information
IMPORTANT:
The repro project points to my local IP (Bottom of
Reference.cs
file) and needs to be changed to IP of the user running the code. I could not use localhost because I was getting connectivity issues and only worked when setting my local IP.Things that I've tried:
The text was updated successfully, but these errors were encountered: