【问题标题】:FileStreamResult object disposedFileStreamResult 对象被释放
【发布时间】:2020-04-30 06:11:30
【问题描述】:

我正在使用 .NET Core 3.1。我想使用NpgsqlLargeObjectStream 将文件从数据库流式传输到客户端。我有以下代码:

第一种方法

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"
            };
        }
    }
}

如果我运行这段代码,我会得到以下异常:

InvalidOperationException: Object disposed
Npgsql.NpgsqlLargeObjectStream.CheckDisposed()

但是,如果我将整个流读入内存然后返回,一切正常。

第二种方法

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");
        }
    }
}

是什么导致了这个问题?显然我可以读取流并且它没有被处理(如第二种方法所示)。如何调试问题所在?

【问题讨论】:

  • FileStreamResult 是命令模式的一部分:直到 您的Download 操作返回后才会执行。自然,这意味着 usingFileStreamResult 尝试将其推送到响应之前清理了 StreamFileStreamResult 将在完成后为您处理 Stream,但这无助于处理 transaction

标签: c# asp.net-core stream


【解决方案1】:

我在完全相同的用例上遇到了同样的问题(在 ASP.NET 6 上)。
我不知道它是否对你仍然有用,但至少如果其他人现在来到这里,我会写这个。 所以,我能够通过编写一个简单的中间件 + 属性来解决它:

[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>();
}

然后在Startup.cs可以使用这种扩展方式注册中间件。
需要在UseEndpoint之前和UseRouting之后注册因为第一个是一个读取和处理来自FileStreamResult 的流,第二个将端点信息放入IEndpointFeature

完成后,您只需将此属性添加到需要返回NpgslLargeObjectStream 的每个控制器方法中。

所以为了快速解释一下,这个中间件只是要检测路由处理程序是否有这个属性,如果是的话,它会将所有后续中间件包装在一个事务中。所以流可以被读取,然后在控制器外部但在事务内部关闭。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-27
    • 2011-11-16
    • 2011-09-13
    • 2010-12-18
    相关资源
    最近更新 更多