diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs index 5bba1b386a..8ae0500f3d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter.cs @@ -34,9 +34,20 @@ public class RemoteStreamContentOutputFormatter : OutputFormatter context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString(); } + var cancellationToken = context.HttpContext.RequestAborted; + using (remoteStream) { - await remoteStream.GetStream().CopyToAsync(context.HttpContext.Response.Body); + var stream = remoteStream.GetStream(); + + try + { + await stream.CopyToAsync(context.HttpContext.Response.Body, cancellationToken); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + // The request was aborted, nothing can be written to the response anymore. + } } } } diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter_Tests.cs new file mode 100644 index 0000000000..8e89378ac7 --- /dev/null +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ContentFormatters/RemoteStreamContentOutputFormatter_Tests.cs @@ -0,0 +1,40 @@ +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters; +using Shouldly; +using Volo.Abp.Content; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.ContentFormatters; + +public class RemoteStreamContentOutputFormatter_Tests +{ + [Fact] + public async Task Should_Not_Copy_The_Stream_When_The_Request_Is_Aborted() + { + using (var cancellationTokenSource = new CancellationTokenSource()) + { + await cancellationTokenSource.CancelAsync(); + + var httpContext = new DefaultHttpContext(); + httpContext.RequestAborted = cancellationTokenSource.Token; + httpContext.Response.Body = new MemoryStream(); + + var writeContext = new OutputFormatterWriteContext( + httpContext, + (stream, encoding) => new StreamWriter(stream, encoding), + typeof(IRemoteStreamContent), + new RemoteStreamContent( + new MemoryStream(Encoding.UTF8.GetBytes("DownloadAsync")), + "download.rtf", + "application/rtf")); + + await new RemoteStreamContentOutputFormatter().WriteResponseBodyAsync(writeContext); + + httpContext.Response.Body.Length.ShouldBe(0); + } + } +}