'FileStreamResult object disposed
I am using .NET Core 3.1. I want to stream the file from the database to the client using NpgsqlLargeObjectStream. I have the following code:
1ST APPROACH
public IActionResult Download(int id)
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
using (NpgsqlLargeObjectStream stream = _dbContext.GetLargeObjectStream(1))
{
return new FileStreamResult(stream, new MediaTypeHeaderValue("application/octet-stream"))
{
FileDownloadName = "test.txt"
};
}
}
}
If I run this code, I get the following exception:
InvalidOperationException: Object disposed
Npgsql.NpgsqlLargeObjectStream.CheckDisposed()
However, if I read the whole stream into memory and then return it, everything works normally.
2ND APPROACH
public IActionResult Download(int id)
{
using (var transaction = _dbContext.Database.BeginTransaction())
{
using (NpgsqlLargeObjectStream stream = _dbContext.GetLargeObjectStream(1))
{
stream.Position = 0;
byte[] buffer = new byte[stream.Length];
for (int totalBytesCopied = 0; totalBytesCopied < stream.Length;)
totalBytesCopied += stream.Read(buffer, totalBytesCopied, Convert.ToInt32(stream.Length) - totalBytesCopied);
return File(buffer, "application/octet-stream", "test.txt");
}
}
}
What is causing this problem? Clearly I can read the stream and it is not disposed (as seen in 2nd approach). How can I debug what is going wrong?
Solution 1:[1]
I had the same problem with the exact same use-case (on ASP.NET 6).
I don't know if it is still useful for you, but I write this at least if someone else is getting here for now.
So, I was able to solve it writing a simple middleware + attribute :
[AttributeUsage(AttributeTargets.Method)]
public class TransactionalAttribute : Attribute {
}
public class TransactionalMiddleware {
private readonly RequestDelegate next;
public TransactionalMiddleware(RequestDelegate next) => this.next = next;
public async Task InvokeAsync(HttpContext context) {
var attributeMetadata = context.Features.Get<IEndpointFeature>()?.Endpoint?.Metadata
.GetMetadata<TransactionalAttribute>();
if (attributeMetadata == null) {
await next(context);
return;
}
var entities = context.RequestServices.GetRequiredService<EntitiesContext>();
await using var transaction = await entities.Database.BeginTransactionAsync();
await next(context);
await entities.Database.CommitTransactionAsync();
}
}
public static class TransactionalConfig {
public static IApplicationBuilder UseTransactional(this IApplicationBuilder builder)
=> builder.UseMiddleware<TransactionalMiddleware>();
}
Then in the Startup.cs you can use this extension method to register the middleware.
It needs to be registered before UseEndpoint and after UseRouting as the first one is the one reading and disposing the stream from the FileStreamResult and the second one the one putting the Endpoint information in the IEndpointFeature.
To finished you just need to add this attribute to every controller method which needs to return an NpgslLargeObjectStream.
So just to explain it quickly, this middleware is just going to detect if the route handler has this attribute, and if it is the case it wraps all the subsequent middlewares in a transaction. So the stream can be read and then closed outside the controller but inside a transaction.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
