DataContractSerializer serializing an IXmlSerializable doesn't support XmlWriter.WriteValue(object)
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 48/100
Research direction
Start by reading src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/XmlSerializableWriter.cs, especially its WriteValue(object value) handling, and compare it with the inner XmlMtomWriter behavior described in the issue. Reproduce the failure with the ByteArrayWrapper example and XmlDictionaryWriter; done means DataContractSerializer can pass the IStreamProvider through without the InvalidCastException.
Written by the indexing model from the issue text.
Description
Description
This is an mtom scenario. When writing with an XmlMtomWriter, if you call WriteValue(object value) and the object you pass implements IStreamProvider, then it stores the IStreamProvider instance and writes out a place holder in the xml. When you call WriteEndElement on the final element, it uses IStreamProvider.GetStream() to get the payload for a multi-part mime part.
When serializing using XmlSerializer, calling Serialize and passing in an XmlMtomWriter, if a class in the object graph implements IXmlSerializable, it calls IXmlSerializable.WriteXml passing the XmlMtomWriter. This enables a class to pass an IStreamProvider to WriteValue(object value) and the correct mtom output to be generated.
When serializing with DataContractSerializer and having implemented IXmlSerializable in a type in the object graph, the XmlWriter passed to IXmlSerializable.WriteXml is not the same XmlWriter as was passed to DataContractSerializer.WriteObject. It's wrapped in a XmlSerializableWriter instance. This class passes through most of the calls to the inner XmlWriter, which is the XmlMtomWriter that was passed to WriteObject, but not with the method WriteValue(object value). This method falls through to the base XmlWriter class which attempts to convert the object to a String via a convertor, and then would pass that string to WriteString (if it didn't currently thrown an exception) which then passes the string through to the inner writer. By not overriding WriteValue(object value), serialization using IStreamProvider is not possible with DataContractSerializer.
Reproduction Steps
Serialize the following object using the xml writer from XmlDictionaryWriter.CreateTextWriter. All the XmlDictionaryWriter implementations support writing IStreamProvider via WriteValue(object value).
public class ByteArrayWrapper : IStreamProvider, IXmlSerializable
{
public byte[] Data { get; set; } = Array.Empty<byte>();
public Stream GetStream()
{
return new FixedMemoryStream(Data);
}
public void ReleaseStream(Stream stream)
{
stream.Dispose();
}
public XmlSchema? GetSchema() => null;
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement();
var buffer = new byte[8192]; // 8KB buffer size
using (var ms = new MemoryStream())
{
int bytesRead;
while ((bytesRead = reader.ReadContentAsBase64(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, bytesRead);
}
Data = ms.ToArray();
}
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteValue(this);
}
// Implicit conversion from ByteArrayWrapper to byte[]
public static implicit operator byte[](ByteArrayWrapper wrapper)
{
return wrapper?.Data ?? Array.Empty<byte>();
}
// Implicit conversion from byte[] to ByteArrayWrapper
public static implicit operator ByteArrayWrapper(byte[] data)
{
return new ByteArrayWrapper { Data = data ?? Array.Empty<byte>() };
}
}
public class FixedMemoryStream : MemoryStream
{
public FixedMemoryStream(byte[] buffer) : base(buffer) { }
public FixedMemoryStream(byte[] buffer, int index, int count) : base(buffer, index, count) { }
public FixedMemoryStream(int capacity) : base(capacity) { }
public FixedMemoryStream() : base() { }
}
Expected behavior
The XmlDictionaryWriter passed to DataContractSerializer.WriteObject has WriteValue(object value) called and doesn't throw an exception (unless the type happens to be convertable to a string).
Actual behavior
The serializer throws:
Unhandled exception. System.InvalidCastException: Xml type 'List of xdt:untypedAtomic' does not support a conversion from Clr type 'ByteArrayWrapper' to Clr type 'String'.
Regression?
I don't believe this is a regression.
Known Workarounds
Use XmlSerializer instead. This could be a big deal for WCF if you have a large contract.
Configuration
No response
Other information
The use of FixedMemoryStream is to work around #117789 which breaks this scenario using a regular MemoryStream with WCF. It's probably replaceable with MemoryStream outside of WCF if you aren't using BufferedStream.
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/runtime
-
agentic-workflows untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
area-System.Reflection blocking-clean-ci-optional Known Build Error os-mac-os-x untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
area-CodeGen-coreclr untriaged
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
agentic-workflows untriaged
Difficulty 1/5 Under an hour Newbie friendliness 78/100
-
area-VM-meta-mono untriaged
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
-
:watch: Not Triaged 11.0 fundamentals/subsvc
Difficulty 2/5 1-3 hours Newbie friendliness 92/100
dotnet/AspNetCore.Docs#37699 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
SubtitleEdit/subtitleedit#15108 · 1 comment ·
-
area/docs-content Bug pulumi/docs
Difficulty 1/5 1-3 hours Newbie friendliness 94/100
-
Create parent directories only after the containment check in InstallHelper.TryExtractToDirectory Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
PowerShell/PSResourceGet#2056 ·