dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.42k stars 4.76k forks source link

XslCompiledTransform is calling sync Flush #66735

Open stianl opened 2 years ago

stianl commented 2 years ago

Description

It's not possible to write to Response.Body stream using XslCompiledTransform. Since synchronous writes are not allowed, I've set Async = true on the XmlWriter used with XslCompiledTransform, but this fails as well.

Using an XmlWriter directly and async write/flush methods works fine so I suspect the problem lies with XslCompiledTransform.

Reproduction Steps

Can be reproduced with this method:

[HttpGet]
public async Task TransformXml()
{
    var xsltStream = new StringReader(@"
            <xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"">
            </xsl:stylesheet>");
    using var xsltReader = XmlReader.Create(xsltStream);
    var xslt = new XslCompiledTransform();
    xslt.Load(xsltReader);
    var xmlStream = new StringReader("<test></test>");
    using var xml = XmlReader.Create(xmlStream);
    await using var writer = XmlWriter.Create(Response.Body, new XmlWriterSettings { Async = true });
    xslt.Transform(xml, writer);
}

Without setting Async = true in XmlWriterSettings the following (expected) exception is thrown:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HMG7HQK4AIRI", Request id "0HMG7HQK4AIRI:00000003": An unhandled exception was thrown by the application.
      System.InvalidOperationException: Set XmlWriterSettings.Async to true if you want to use Async Methods.

Expected behavior

No exception

Actual behavior

Application throws exception:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HMG7HLMHUG8T", Request id "0HMG7HLMHUG8T:00000002": An unhandled exception was thrown by the application.
      System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count)
         at System.Xml.XmlUtf8RawTextWriter.FlushBuffer()
         at System.Xml.XmlUtf8RawTextWriter.Flush()
         at System.Xml.XmlWellFormedWriter.Flush()
         at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter writer)
         at System.Xml.Xsl.XslCompiledTransform.Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results, XmlResolver documentResolver)
         at System.Xml.Xsl.XslCompiledTransform.Transform(XmlReader input, XmlWriter results)

Regression?

No response

Known Workarounds

No response

Configuration

.NET 6, tested on macOS (M1) and Windows (x64).

Other information

May be related to #31104.

ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/area-system-xml See info in area-owners.md if you want to be subscribed.

Issue Details
### Description It's not possible to write to `Response.Body` stream using `XslCompiledTransform`. Since synchronous writes are not allowed, I've set `Async = true` on the `XmlWriter` used with `XslCompiledTransform`, but this fails as well. Using an XmlWriter directly and async write/flush methods works fine so I suspect the problem lies with `XslCompiledTransform`. ### Reproduction Steps Can be reproduced with this method: ```c# [HttpGet] public async Task TransformXml() { var xsltStream = new StringReader(@" "); using var xsltReader = XmlReader.Create(xsltStream); var xslt = new XslCompiledTransform(); xslt.Load(xsltReader); var xmlStream = new StringReader(""); using var xml = XmlReader.Create(xmlStream); await using var writer = XmlWriter.Create(Response.Body, new XmlWriterSettings { Async = true }); xslt.Transform(xml, writer); } ``` Without setting `Async = true` in XmlWriterSettings the following (expected) exception is thrown: ``` fail: Microsoft.AspNetCore.Server.Kestrel[13] Connection id "0HMG7HQK4AIRI", Request id "0HMG7HQK4AIRI:00000003": An unhandled exception was thrown by the application. System.InvalidOperationException: Set XmlWriterSettings.Async to true if you want to use Async Methods. ``` ### Expected behavior No exception ### Actual behavior Application throws exception: ``` fail: Microsoft.AspNetCore.Server.Kestrel[13] Connection id "0HMG7HLMHUG8T", Request id "0HMG7HLMHUG8T:00000002": An unhandled exception was thrown by the application. System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead. at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count) at System.Xml.XmlUtf8RawTextWriter.FlushBuffer() at System.Xml.XmlUtf8RawTextWriter.Flush() at System.Xml.XmlWellFormedWriter.Flush() at System.Xml.Xsl.XmlILCommand.Execute(Object defaultDocument, XmlResolver dataSources, XsltArgumentList argumentList, XmlWriter writer) at System.Xml.Xsl.XslCompiledTransform.Transform(XmlReader input, XsltArgumentList arguments, XmlWriter results, XmlResolver documentResolver) at System.Xml.Xsl.XslCompiledTransform.Transform(XmlReader input, XmlWriter results) ``` ### Regression? _No response_ ### Known Workarounds _No response_ ### Configuration .NET 6, tested on macOS (M1) and Windows (x64). ### Other information May be related to #31104.
Author: stianl
Assignees: -
Labels: `area-System.Xml`, `untriaged`
Milestone: -
krwq commented 2 years ago

I think changing the XSL compiler to work async would be a bit of work and not just a simple fix. The exception is coming from the stream. Perhaps an acceptable workaround would be to use MemoryStream when creating XmlWriter and then copy content from MemoryStream to the response body using async method, something like:

using var ms = new MemoryStream();
await using var writer = XmlWriter.Create(ms, new XmlWriterSettings { Async = true });
ms.Position = 0;
await ms.CopyToAsync(Response.Body);