Skip to content

Commit dad78b3

Browse files
authored
Use DisposeAsync in FileStreamResultExecutor (#55524)
1 parent 16c2374 commit dad78b3

File tree

3 files changed

+72
-2
lines changed

3 files changed

+72
-2
lines changed

src/Mvc/Mvc.Core/src/Infrastructure/FileStreamResultExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public virtual async Task ExecuteAsync(ActionContext context, FileStreamResult r
2828
ArgumentNullException.ThrowIfNull(context);
2929
ArgumentNullException.ThrowIfNull(result);
3030

31-
using (result.FileStream)
31+
await using (result.FileStream)
3232
{
3333
Log.ExecutingFileResult(Logger, result);
3434

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Extensions.Logging.Abstractions;
5+
using Microsoft.AspNetCore.Http;
6+
7+
namespace Microsoft.AspNetCore.Mvc.Infrastructure;
8+
9+
public class FileStreamResultExecutorTest
10+
{
11+
[Fact]
12+
public async Task ExecuteAsync_DisposesStreamAsync()
13+
{
14+
// Arrange
15+
var executor = CreateExecutor();
16+
17+
var httpContext = new DefaultHttpContext();
18+
var actionContext = new ActionContext() { HttpContext = httpContext };
19+
20+
var stream = new AsyncOnlyStream();
21+
var result = new FileStreamResult(stream, "text/plain");
22+
23+
// Act
24+
await executor.ExecuteAsync(actionContext, result);
25+
26+
// Assert
27+
Assert.True(stream.DidDisposeAsync);
28+
}
29+
30+
private static FileStreamResultExecutor CreateExecutor()
31+
{
32+
return new FileStreamResultExecutor(NullLoggerFactory.Instance);
33+
}
34+
35+
private class AsyncOnlyStream : Stream
36+
{
37+
public override bool CanRead => true;
38+
39+
public override bool CanSeek => false;
40+
41+
public override bool CanWrite => false;
42+
43+
public override long Length => throw new NotImplementedException();
44+
45+
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
46+
47+
public override void Flush() { }
48+
49+
public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException("Must use ReadAsync");
50+
51+
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
52+
=> Task.FromResult(0);
53+
54+
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
55+
56+
public override void SetLength(long value) => throw new NotImplementedException();
57+
58+
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
59+
60+
protected override void Dispose(bool disposing) => throw new NotSupportedException("Must use DisposeAsync");
61+
62+
public bool DidDisposeAsync { get; private set; }
63+
64+
public override ValueTask DisposeAsync()
65+
{
66+
DidDisposeAsync = true;
67+
return ValueTask.CompletedTask;
68+
}
69+
}
70+
}

src/Shared/ResultsHelpers/FileResultHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ internal static async Task WriteFileAsync(HttpContext context, Stream fileStream
3030
{
3131
const int BufferSize = 64 * 1024;
3232
var outputStream = context.Response.Body;
33-
using (fileStream)
33+
await using (fileStream)
3434
{
3535
try
3636
{

0 commit comments

Comments
 (0)