diff --git a/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XDocument.cs b/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XDocument.cs index b3b9cf563e312..55cea1d11b17d 100644 --- a/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XDocument.cs +++ b/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XDocument.cs @@ -633,7 +633,8 @@ public async Task SaveAsync(Stream stream, SaveOptions options, CancellationToke } } - using (XmlWriter w = XmlWriter.Create(stream, ws)) + XmlWriter w = XmlWriter.Create(stream, ws); + await using (w.ConfigureAwait(false)) { await WriteToAsync(w, cancellationToken).ConfigureAwait(false); await w.FlushAsync().ConfigureAwait(false); @@ -706,7 +707,8 @@ public async Task SaveAsync(TextWriter textWriter, SaveOptions options, Cancella ws.Async = true; - using (XmlWriter w = XmlWriter.Create(textWriter, ws)) + XmlWriter w = XmlWriter.Create(textWriter, ws); + await using (w.ConfigureAwait(false)) { await WriteToAsync(w, cancellationToken).ConfigureAwait(false); await w.FlushAsync().ConfigureAwait(false); diff --git a/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XElement.cs b/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XElement.cs index 9d9445bcdbdb6..e71ac289188d7 100644 --- a/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XElement.cs +++ b/src/libraries/System.Private.Xml.Linq/src/System/Xml/Linq/XElement.cs @@ -1080,7 +1080,8 @@ public async Task SaveAsync(Stream stream, SaveOptions options, CancellationToke ws.Async = true; - using (XmlWriter w = XmlWriter.Create(stream, ws)) + XmlWriter w = XmlWriter.Create(stream, ws); + await using (w.ConfigureAwait(false)) { await SaveAsync(w, cancellationToken).ConfigureAwait(false); } @@ -1141,7 +1142,8 @@ public async Task SaveAsync(TextWriter textWriter, SaveOptions options, Cancella ws.Async = true; - using (XmlWriter w = XmlWriter.Create(textWriter, ws)) + XmlWriter w = XmlWriter.Create(textWriter, ws); + await using (w.ConfigureAwait(false)) { await SaveAsync(w, cancellationToken).ConfigureAwait(false); } diff --git a/src/libraries/System.Private.Xml.Linq/tests/misc/LoadSaveAsyncTests.cs b/src/libraries/System.Private.Xml.Linq/tests/misc/LoadSaveAsyncTests.cs index 5fcab26a8fba3..0e436827430fe 100644 --- a/src/libraries/System.Private.Xml.Linq/tests/misc/LoadSaveAsyncTests.cs +++ b/src/libraries/System.Private.Xml.Linq/tests/misc/LoadSaveAsyncTests.cs @@ -202,5 +202,69 @@ public static IEnumerable RoundtripOptions_MemberData } } + public static IEnumerable IsAsync_SaveOptions_Data + { + get + { + foreach (bool isAsync in new[] { true, false }) + foreach (SaveOptions saveOptions in Enum.GetValues(typeof(SaveOptions))) + yield return new object[] { isAsync, saveOptions }; + } + } + + [Theory] + [MemberData(nameof(IsAsync_SaveOptions_Data))] + public async Task SaveAsync_CallsAsyncOnly_SaveSync_CallsSyncOnly(bool isAsync, SaveOptions saveOptions) + { + XDocument document = XDocument.Parse("Test document async save"); + var element = new XElement("Test"); + using (ForceSyncAsyncStream stream = new ForceSyncAsyncStream(isAsync)) + { + if (isAsync) + { + await document.SaveAsync(stream, saveOptions, CancellationToken.None); + await element.SaveAsync(stream, saveOptions, CancellationToken.None); + } + else + { + document.Save(stream); + element.Save(stream); + } + } + } + } + + public class ForceSyncAsyncStream : MemoryStream + { + private bool _isAsync; + + public ForceSyncAsyncStream(bool async) + { + _isAsync = async; + } + + public override void Flush() + { + Assert.False(_isAsync, "Stream is in asynchronous mode when synchronous Flush is called"); + base.Flush(); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + Assert.True(_isAsync, "Stream is not in asynchronous mode when asynchronous Flush is called"); + return Task.CompletedTask; + } + + public override void Write(byte[] buffer, int offset, int count) + { + Assert.False(_isAsync, "Stream is in asynchronous mode when synchronous Write is called"); + base.Write(buffer, offset, count); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + Assert.True(_isAsync, "Stream is not in asynchronous mode when asynchronous Write is called"); + return Task.CompletedTask; + } } -} \ No newline at end of file +} diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlAsyncCheckWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlAsyncCheckWriter.cs index 0d7289e680c54..78dc6ac0bde4e 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlAsyncCheckWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlAsyncCheckWriter.cs @@ -576,6 +576,13 @@ public override Task WriteNodeAsync(XPathNavigator navigator, bool defattr) _lastTask = task; return task; } + + protected override ValueTask DisposeAsyncCore() + { + CheckAsync(); + return _coreWriter.DisposeAsync(); + } + #endregion } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlEncodedRawTextWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlEncodedRawTextWriter.cs index 5e170f0d817bf..713f5bcc262af 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlEncodedRawTextWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlEncodedRawTextWriter.cs @@ -823,8 +823,11 @@ protected virtual void FlushBuffer() } else { - // Write text to TextWriter - writer.Write(bufChars, 1, bufPos - 1); + if (bufPos - 1 > 0) + { + // Write text to TextWriter + writer.Write(bufChars, 1, bufPos - 1); + } } } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlEncodedRawTextWriterAsync.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlEncodedRawTextWriterAsync.cs index 3dd97591e12f9..13a694c1f98aa 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlEncodedRawTextWriterAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlEncodedRawTextWriterAsync.cs @@ -73,6 +73,62 @@ internal override Task WriteXmlDeclarationAsync(string xmldecl) return Task.CompletedTask; } + protected override async ValueTask DisposeAsyncCore() + { + try + { + await FlushBufferAsync().ConfigureAwait(false); + } + finally + { + // Future calls to Close or Flush shouldn't write to Stream or Writer + writeToNull = true; + + if (stream != null) + { + try + { + await stream.FlushAsync().ConfigureAwait(false); + } + finally + { + try + { + if (closeOutput) + { + await stream.DisposeAsync().ConfigureAwait(false); + } + } + finally + { + stream = null; + } + } + } + else if (writer != null) + { + try + { + await writer.FlushAsync().ConfigureAwait(false); + } + finally + { + try + { + if (closeOutput) + { + await writer.DisposeAsync().ConfigureAwait(false); + } + } + finally + { + writer = null; + } + } + } + } + } + // Serialize the document type declaration. public override async Task WriteDocTypeAsync(string name, string pubid, string sysid, string subset) { @@ -586,8 +642,11 @@ protected virtual async Task FlushBufferAsync() } else { - // Write text to TextWriter - await writer.WriteAsync(bufChars, 1, bufPos - 1).ConfigureAwait(false); + if (bufPos - 1 > 0) + { + // Write text to TextWriter + await writer.WriteAsync(bufChars, 1, bufPos - 1).ConfigureAwait(false); + } } } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawTextWriterGenerator.ttinclude b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawTextWriterGenerator.ttinclude index 99c9681661fb7..10464300bf5a7 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawTextWriterGenerator.ttinclude +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawTextWriterGenerator.ttinclude @@ -820,8 +820,11 @@ namespace System.Xml if (!writeToNull) { <# if (WriterType == RawTextWriterType.Utf8) { #> - Debug.Assert(stream != null); - stream.Write(<#= BufferName #>, 1, bufPos - 1); + if (bufPos - 1 > 0) + { + Debug.Assert(stream != null); + stream.Write(<#= BufferName #>, 1, bufPos - 1); + } <# } else { #> Debug.Assert(stream != null || writer != null); @@ -849,8 +852,11 @@ namespace System.Xml } else { - // Write text to TextWriter - writer.Write(<#= BufferName #>, 1, bufPos - 1); + if (bufPos - 1 > 0) + { + // Write text to TextWriter + writer.Write(<#= BufferName #>, 1, bufPos - 1); + } } <# } #> } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawTextWriterGeneratorAsync.ttinclude b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawTextWriterGeneratorAsync.ttinclude index 772a269eb1cee..52032722d5d9a 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawTextWriterGeneratorAsync.ttinclude +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawTextWriterGeneratorAsync.ttinclude @@ -75,6 +75,64 @@ namespace System.Xml return Task.CompletedTask; } + protected override async ValueTask DisposeAsyncCore() + { + try + { + await FlushBufferAsync().ConfigureAwait(false); + } + finally + { + // Future calls to Close or Flush shouldn't write to Stream or Writer + writeToNull = true; + + if (stream != null) + { + try + { + await stream.FlushAsync().ConfigureAwait(false); + } + finally + { + try + { + if (closeOutput) + { + await stream.DisposeAsync().ConfigureAwait(false); + } + } + finally + { + stream = null; + } + } + } +<# if (WriterType == RawTextWriterType.Encoded) { #> + else if (writer != null) + { + try + { + await writer.FlushAsync().ConfigureAwait(false); + } + finally + { + try + { + if (closeOutput) + { + await writer.DisposeAsync().ConfigureAwait(false); + } + } + finally + { + writer = null; + } + } + } +<# } #> + } + } + // Serialize the document type declaration. public override async Task WriteDocTypeAsync(string name, string pubid, string sysid, string subset) { @@ -563,8 +621,11 @@ namespace System.Xml if (!writeToNull) { <# if (WriterType == RawTextWriterType.Utf8) { #> - Debug.Assert(stream != null); - await stream.WriteAsync(bufBytes, 1, bufPos - 1).ConfigureAwait(false); + if (bufPos - 1 > 0) + { + Debug.Assert(stream != null); + await stream.WriteAsync(bufBytes, 1, bufPos - 1).ConfigureAwait(false); + } <# } else { #> Debug.Assert(stream != null || writer != null); @@ -592,8 +653,11 @@ namespace System.Xml } else { - // Write text to TextWriter - await writer.WriteAsync(<#= BufferName #>, 1, bufPos - 1).ConfigureAwait(false); + if (bufPos - 1 > 0) + { + // Write text to TextWriter + await writer.WriteAsync(<#= BufferName #>, 1, bufPos - 1).ConfigureAwait(false); + } } <# } #> } @@ -689,7 +753,7 @@ namespace System.Xml <#= BufferType #>* pDst = pDstBegin + bufPos; int ch = 0; - for (;;) + while (true) { <#= BufferType #>* pDstEnd = pDst + (pSrcEnd - pSrc); if (pDstEnd > pDstBegin + bufLen) @@ -879,7 +943,7 @@ namespace System.Xml <#= BufferType #>* pDst = pDstBegin + bufPos; int ch = 0; - for (;;) + while (true) { <#= BufferType #>* pDstEnd = pDst + (pSrcEnd - pSrc); if (pDstEnd > pDstBegin + bufLen) @@ -1105,7 +1169,7 @@ namespace System.Xml char* pSrc = pSrcBegin; int ch = 0; - for (;;) + while (true) { <#= BufferType #>* pDstEnd = pDst + (pSrcEnd - pSrc); if (pDstEnd > pDstBegin + bufLen) @@ -1266,7 +1330,7 @@ namespace System.Xml <#= BufferType #>* pDst = pDstBegin + bufPos; int ch = 0; - for (;;) + while (true) { <#= BufferType #>* pDstEnd = pDst + (pSrcEnd - pSrc); if (pDstEnd > pDstBegin + bufLen) @@ -1454,7 +1518,7 @@ namespace System.Xml <#= BufferType #>* pDst = pDstBegin + bufPos; int ch = 0; - for (;;) + while (true) { <#= BufferType #>* pDstEnd = pDst + (pSrcEnd - pSrc); if (pDstEnd > pDstBegin + bufLen) @@ -1630,7 +1694,7 @@ namespace System.Xml <#= BufferType #>* pDst = pDstBegin + bufPos; int ch = 0; - for (;;) + while (true) { <#= BufferType #>* pDstEnd = pDst + (pSrcEnd - pSrc); if (pDstEnd > pDstBegin + bufLen) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriterAsync.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriterAsync.cs index 77ebbf5034a94..b602201b1eb20 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriterAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlRawWriterAsync.cs @@ -226,5 +226,10 @@ internal virtual Task WriteEndBase64Async() // The Flush will call WriteRaw to write out the rest of the encoded characters return base64Encoder.FlushAsync(); } + + internal virtual ValueTask DisposeAsyncCore(WriteState currentState) + { + return DisposeAsyncCore(); + } } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlUtf8RawTextWriter.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlUtf8RawTextWriter.cs index d989bf2e9cd8b..3781b016706e5 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlUtf8RawTextWriter.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlUtf8RawTextWriter.cs @@ -670,8 +670,11 @@ protected virtual void FlushBuffer() // Output all characters (except for previous characters stored at beginning of buffer) if (!writeToNull) { - Debug.Assert(stream != null); - stream.Write(bufBytes, 1, bufPos - 1); + if (bufPos - 1 > 0) + { + Debug.Assert(stream != null); + stream.Write(bufBytes, 1, bufPos - 1); + } } } catch diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlUtf8RawTextWriterAsync.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlUtf8RawTextWriterAsync.cs index 526f12feb17ed..d4916eb83445a 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlUtf8RawTextWriterAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlUtf8RawTextWriterAsync.cs @@ -37,7 +37,6 @@ internal override async Task WriteXmlDeclarationAsync(XmlStandalone standalone) // Output xml declaration only if user allows it and it was not already output if (!omitXmlDeclaration && !autoXmlDeclaration) { - await RawTextAsync(" 0) + { + Debug.Assert(stream != null); + await stream.WriteAsync(bufBytes, 1, bufPos - 1).ConfigureAwait(false); + } } } catch diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWellFormedWriterAsync.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWellFormedWriterAsync.cs index 5c8bb96723ecf..44e396c8a89fb 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWellFormedWriterAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWellFormedWriterAsync.cs @@ -1472,5 +1472,63 @@ private Task StartElementContentAsync() } return Task.CompletedTask; } + + protected override async ValueTask DisposeAsyncCore() + { + if (_currentState != State.Closed) + { + try + { + if (_writeEndDocumentOnClose) + { + while (_currentState != State.Error && _elemTop > 0) + { + await WriteEndElementAsync().ConfigureAwait(false); + } + } + else + { + if (_currentState != State.Error && _elemTop > 0) + { + //finish the start element tag '>' + try + { + await AdvanceStateAsync(Token.EndElement).ConfigureAwait(false); + } + catch + { + _currentState = State.Error; + throw; + } + } + } + + if (InBase64 && _rawWriter != null) + { + await _rawWriter.WriteEndBase64Async().ConfigureAwait(false); + } + + await _writer.FlushAsync().ConfigureAwait(false); + } + finally + { + try + { + if (_rawWriter != null) + { + await _rawWriter.DisposeAsyncCore(WriteState).ConfigureAwait(false); + } + else + { + await _writer.DisposeAsync().ConfigureAwait(false); + } + } + finally + { + _currentState = State.Closed; + } + } + } + } } } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWriterAsync.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWriterAsync.cs index 961be36f42f31..ea052e68a3ead 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWriterAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlWriterAsync.cs @@ -18,7 +18,7 @@ namespace System.Xml { // Represents a writer that provides fast non-cached forward-only way of generating XML streams containing XML documents // that conform to the W3C Extensible Markup Language (XML) 1.0 specification and the Namespaces in XML specification. - public abstract partial class XmlWriter : IDisposable + public abstract partial class XmlWriter : IDisposable, IAsyncDisposable { // Write methods // Writes out the XML declaration with the version "1.0". @@ -599,5 +599,21 @@ private async Task WriteLocalNamespacesAsync(XPathNavigator nsNav) await WriteAttributeStringAsync("xmlns", prefix, XmlReservedNs.NsXmlNs, ns).ConfigureAwait(false); } } + + public async ValueTask DisposeAsync() + { + await DisposeAsyncCore().ConfigureAwait(false); + Dispose(false); + GC.SuppressFinalize(this); + } + + protected virtual ValueTask DisposeAsyncCore() + { + if (WriteState != WriteState.Closed) + { + Dispose(true); + } + return default; + } } } diff --git a/src/libraries/System.Private.Xml/tests/XmlWriter/DisposeTests.cs b/src/libraries/System.Private.Xml/tests/XmlWriter/DisposeTests.cs index 7757543bd6622..7701003b2c186 100644 --- a/src/libraries/System.Private.Xml/tests/XmlWriter/DisposeTests.cs +++ b/src/libraries/System.Private.Xml/tests/XmlWriter/DisposeTests.cs @@ -4,6 +4,8 @@ using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace System.Xml.Tests @@ -121,5 +123,161 @@ public static void XmlWriterDisposeWorksWithDerivingClasses() mywriter.Dispose(); Assert.True(mywriter.IsDisposed); } + + [Fact] + public static async Task AsyncWriter_DisposeAsync_ShouldCall_FlushAsyncWriteAsyncOnly_StreamWriter() + { + using (var stream = new AsyncOnlyStream()) + await using (var writer = XmlWriter.Create(stream, new XmlWriterSettings() { Async = true })) + { + await writer.WriteStartDocumentAsync(); + await writer.WriteStartElementAsync(string.Empty, "root", null); + await writer.WriteStartElementAsync(null, "test", null); + await writer.WriteAttributeStringAsync(string.Empty, "abc", string.Empty, "1"); + await writer.WriteEndElementAsync(); + await writer.WriteEndElementAsync(); + } + } + + [Fact] + public static async Task XmlWriter_AsyncSyncResult_ShouldBeSame_AfterDispose_StreamWriter() + { + var settings = new XmlWriterSettings() { Async = true, Encoding = new UTF8Encoding(false) }; + using (var stream1 = new MemoryStream()) + using (var stream2 = new MemoryStream()) + using (var stream3 = new MemoryStream()) + { + using (var asyncWriter = XmlWriter.Create(stream1, settings)) + { + await asyncWriter.WriteStartDocumentAsync(); + await asyncWriter.WriteStartElementAsync(string.Empty, "root", null); + await asyncWriter.WriteStartElementAsync(null, "test", null); + await asyncWriter.WriteAttributeStringAsync(string.Empty, "abc", string.Empty, "1"); + await asyncWriter.WriteEndElementAsync(); + await asyncWriter.WriteEndElementAsync(); + } + + await using (var asyncWriter = XmlWriter.Create(stream2, settings)) + { + await asyncWriter.WriteStartDocumentAsync(); + await asyncWriter.WriteStartElementAsync(string.Empty, "root", null); + await asyncWriter.WriteStartElementAsync(null, "test", null); + await asyncWriter.WriteAttributeStringAsync(string.Empty, "abc", string.Empty, "1"); + await asyncWriter.WriteEndElementAsync(); + await asyncWriter.WriteEndElementAsync(); + } + + settings.Async = false; + using (var syncWriter = XmlWriter.Create(stream3, settings)) + { + syncWriter.WriteStartDocument(); + syncWriter.WriteStartElement(string.Empty, "root", null); + syncWriter.WriteStartElement(null, "test", null); + syncWriter.WriteAttributeString(string.Empty, "abc", string.Empty, "1"); + syncWriter.WriteEndElement(); + syncWriter.WriteEndElement(); + } + + Assert.Equal(stream1.GetBuffer(), stream2.GetBuffer()); + Assert.Equal(stream2.GetBuffer(), stream3.GetBuffer()); + + Assert.Equal(@"", ReadAsString(stream1.GetBuffer(), stream1.Length)); + Assert.Equal(@"", ReadAsString(stream2.GetBuffer(), stream2.Length)); + Assert.Equal(@"", ReadAsString(stream3.GetBuffer(), stream3.Length)); + } + } + + private static string ReadAsString(byte[] bytes, long length) => Encoding.UTF8.GetString(bytes, 0, (int)length); + + [Fact] + public static async Task AsyncWriterDispose_ShouldCall_FlushAsyncWriteAsyncOnly_TextWriter() + { + using (var sw = new AsyncOnlyWriter()) + await using (var writer = XmlWriter.Create(sw, new XmlWriterSettings() { Async = true })) + { + await writer.WriteStartElementAsync(null, "book", null); + await writer.WriteElementStringAsync(null, "price", null, "19.95"); + await writer.WriteEndElementAsync(); + } + } + + [Fact] + public static async Task XmlWriter_AsyncSyncResult_ShouldBeSame_AfterDispose_TextWriter() + { + using (var sw1 = new StringWriter()) + using (var sw2 = new StringWriter()) + using (var sw3 = new StringWriter()) + { + using (var asyncWriter = XmlWriter.Create(sw1, new XmlWriterSettings() { Async = true })) + { + await asyncWriter.WriteStartElementAsync(null, "book", null); + await asyncWriter.WriteElementStringAsync(null, "price", null, "19.95"); + await asyncWriter.WriteEndElementAsync(); + } + + await using (var asyncWriter = XmlWriter.Create(sw2, new XmlWriterSettings() { Async = true })) + { + await asyncWriter.WriteStartElementAsync(null, "book", null); + await asyncWriter.WriteElementStringAsync(null, "price", null, "19.95"); + await asyncWriter.WriteEndElementAsync(); + } + + using (var syncWriter = XmlWriter.Create(sw3, new XmlWriterSettings() { Async = false })) + { + syncWriter.WriteStartElement(null, "book", null); + syncWriter.WriteElementString(null, "price", null, "19.95"); + syncWriter.WriteEndElement(); + } + + Assert.Equal(sw1.ToString(), sw2.ToString()); + Assert.Equal(sw1.ToString(), sw3.ToString()); + } + } + + internal class AsyncOnlyWriter : StringWriter + { + public override void Flush() + { + throw new InvalidOperationException("Sync operations are not allowed."); + } + + public override Task FlushAsync() + { + return Task.CompletedTask; + } + + public override void Write(char[] buffer, int offset, int count) + { + throw new InvalidOperationException("Sync operations are not allowed."); + } + + public override Task WriteAsync(char[] buffer, int offset, int count) + { + return Task.CompletedTask; + } + } + + internal class AsyncOnlyStream : MemoryStream + { + public override void Flush() + { + throw new InvalidOperationException("Sync operations are not allowed."); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new InvalidOperationException("Sync operations are not allowed."); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } } } diff --git a/src/libraries/System.Private.Xml/tests/XmlWriter/WriteWithEncoding.cs b/src/libraries/System.Private.Xml/tests/XmlWriter/WriteWithEncoding.cs index b764701630e65..549e714246d38 100644 --- a/src/libraries/System.Private.Xml/tests/XmlWriter/WriteWithEncoding.cs +++ b/src/libraries/System.Private.Xml/tests/XmlWriter/WriteWithEncoding.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using System.Xml.Xsl; using Xunit; @@ -71,5 +72,37 @@ public void WriteWithUtf32EncodingNoBom() // Then, last '>' will be cut off in resulting string if BOM is present Assert.Equal("", string.Concat(resultString.Take(39))); } + + [Fact] + public static async Task AsyncSyncWrite_StreamResult_ShouldMatch() + { + using (var syncStream = new MemoryStream()) + using (var asyncStream = new MemoryStream()) + { + await using (var writer = XmlWriter.Create(asyncStream, new XmlWriterSettings() { Async = true })) + { + await writer.WriteStartDocumentAsync(); + await writer.WriteStartElementAsync(string.Empty, "root", null); + await writer.WriteStartElementAsync(null, "test", null); + await writer.WriteAttributeStringAsync(string.Empty, "abc", string.Empty, "1"); + await writer.WriteStringAsync("value"); + await writer.WriteEndElementAsync(); + await writer.WriteEndElementAsync(); + } + + using (var writer = XmlWriter.Create(syncStream, new XmlWriterSettings() { Async = false })) + { + writer.WriteStartDocument(); + writer.WriteStartElement("root"); + writer.WriteStartElement("test"); + writer.WriteAttributeString("abc", "1"); + writer.WriteString("value"); + writer.WriteEndElement(); + writer.WriteEndElement(); + } + + Assert.Equal(syncStream.ToArray(), asyncStream.ToArray()); + } + } } } diff --git a/src/libraries/System.Xml.ReaderWriter/ref/System.Xml.ReaderWriter.cs b/src/libraries/System.Xml.ReaderWriter/ref/System.Xml.ReaderWriter.cs index 83fa6c584289c..8eab71f3d4251 100644 --- a/src/libraries/System.Xml.ReaderWriter/ref/System.Xml.ReaderWriter.cs +++ b/src/libraries/System.Xml.ReaderWriter/ref/System.Xml.ReaderWriter.cs @@ -1201,7 +1201,7 @@ public partial class XmlWhitespace : System.Xml.XmlCharacterData public override void WriteContentTo(System.Xml.XmlWriter w) { } public override void WriteTo(System.Xml.XmlWriter w) { } } - public abstract partial class XmlWriter : System.IDisposable + public abstract partial class XmlWriter : System.IDisposable, System.IAsyncDisposable { protected XmlWriter() { } public virtual System.Xml.XmlWriterSettings Settings { get { throw null; } } @@ -1306,6 +1306,8 @@ public virtual void WriteValue(float value) { } public virtual void WriteValue(string value) { } public abstract void WriteWhitespace(string ws); public virtual System.Threading.Tasks.Task WriteWhitespaceAsync(string ws) { throw null; } + public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } + protected virtual System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; } } public sealed partial class XmlWriterSettings {