dotnet / dotnet/aspnetcore

File(stream, ...) does not dispose the stream

Open
#60,860 0 comments 0 reactions 0 assignees View on GitHub
area-mvc
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Describe the bug

Not sure if this is actually a bug or not, but it's a surprise. If this is intended behavior, it needs to be spelled out in the documentation because it violates expectations.

We have code that looks like this:

```
[HttpPost]
public IActionResult Run(ExportRequestModel request) {
var result = this.ExportService.Export(request);
string suffix = "." + result.DefaultExtension;
if (request.FileName?.EndsWith(suffix) == true) {
request.FileName = request.FileName[..^suffix.Length];
}
var name = (request.FileName ?? "export").Replace('.', '_') + suffix;
return File(result.Contents, result.ContentType, name);
}
```

However the stream from ExportService really does need to be disposed. There's a finalizer that will take care of it eventually, but that's unexpected. Making it work:

```
[HttpPost]
public IActionResult Run(ExportRequestModel request) {
var result = this.ExportService.Export(request);
string suffix = "." + result.DefaultExtension;
if (request.FileName?.EndsWith(suffix) == true) {
request.FileName = request.FileName[..^suffix.Length];
}
var name = (request.FileName ?? "export").Replace('.', '_') + suffix;
HttpContext.Response.RegisterForDisposeAsync(result.Contents);
return File(result.Contents, result.ContentType, name);
}
```

Yup. File() doesn't dispose the stream. Surprise!

### Expected Behavior

Option 1) File(stream, ...) disposes the stream

Option 2) Unexpected behavior documented at https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.filestreamresult.-ctor?view=aspnetcore-9.0#microsoft-aspnetcore-mvc-filestreamresult-ctor(system-io-stream-system-string)

### Steps To Reproduce

_No response_

### Exceptions (if any)

_No response_

### .NET Version

8.0.10

### Anything else?

Hand verified by putting a breakpoint in the dispose method.

You don't have to worry about breaking *me* at least; I can handle being double-disposed.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.