You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用FastEndpoints时,从Azure Blob返回FileStreamResult报错的解决方法

解决FastEndpoints返回FileStreamResult时的System.NotSupportedException错误

错误原因

  1. FastEndpoints响应类型约束:FastEndpoints要求泛型参数TResponse是具备无参数构造函数的普通DTO类,框架会自动实例化并序列化该类型作为API响应。而FileStreamResult是ASP.NET Core专门用于输出文件流的ActionResult类型,它没有无参数构造函数,也不是为序列化设计的DTO,因此框架尝试实例化它时抛出异常。
  2. 流提前释放问题:原代码中DownloadBlobAsync用using包裹MemoryStream,返回的FileStreamResult持有已被释放的流,即使解决了构造函数问题,后续也会出现流无法读取的错误。

解决方案

步骤1:修改Endpoint的响应类型

将Endpoint的泛型响应类型改为EmptyResponse,告诉FastEndpoints我们将手动处理响应,不需要自动序列化DTO。

步骤2:优化Blob下载逻辑

移除MemoryStream的using语句,改用BlobClient.OpenReadAsync直接获取Blob的只读流,既避免流提前释放,又减少内存缓存开销。

步骤3:手动发送FileStreamResult

在HandleAsync方法中使用SendAsync直接发送FileStreamResult,这是FastEndpoints支持发送ActionResult的标准方式。

修改后的完整代码

Endpoint类

public class Endpoint : Endpoint<DownloadFileRequest, EmptyResponse>
{
    private readonly DatabaseContext _context;
    private readonly ILogger<Endpoint> _logger;
    private readonly IConfiguration _configuration;

    public Endpoint(DatabaseContext context, ILogger<Endpoint> logger, IConfiguration configuration)
    {
        _context = context;
        _logger = logger;
        _configuration = configuration;
    }

    public override void Configure()
    {
        Get("/file/{id}/download");
        Roles(Role.Administrator, Role.ProjectManager, Role.GraphicDesigner, Role.Accountant);
        Description(b => b.WithName("DownloadFile"));
    }

    public override async Task HandleAsync(DownloadFileRequest r, CancellationToken c)
    {
        var file = await Data.GetTriadaFileAsync(_context, r.Id, c);

        if (file == null)
        {
            await SendNotFoundAsync(c);
            return;
        }
           
        var fileStreamResult = await Data.DownloadBlobAsync(file.AzureName, _configuration, c);

        if (fileStreamResult != null)
        {
            await SendAsync(fileStreamResult, cancellation: c);
        }
        else
        {
            await SendNotFoundAsync(c);
        }
    }
}

Data工具类

public static class Data
{
    public static async Task<TriadaFile?> GetTriadaFileAsync(DatabaseContext context, Guid id, CancellationToken c)
    {
        return await context.Files
            .FirstOrDefaultAsync(x => x.Id == id, c);
    }

    public static async Task<FileStreamResult?> DownloadBlobAsync(string blobFileName, IConfiguration configuration, CancellationToken c)
    {
        string connectionString = configuration.GetSection("Azure").GetValue<string>("BlobConnectionString")!;
        string containerName = configuration.GetSection("Azure").GetValue<string>("BlobContainerName")!;

        BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
        BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
        BlobClient blobClient = containerClient.GetBlobClient(blobFileName);

        if (!await blobClient.ExistsAsync(c))
        {
            return null;
        }

        // 直接获取Blob的只读流,无需内存缓存
        var stream = await blobClient.OpenReadAsync(cancellationToken: c);
        var properties = await blobClient.GetPropertiesAsync(cancellationToken: c);
        var contentType = properties.Value.ContentType ?? "application/octet-stream";

        return new FileStreamResult(stream, contentType)
        {
            FileDownloadName = blobFileName
        };
    }
}

补充说明

  • 原DownloadFileResponse类可以删除,因为我们不再使用自定义DTO作为响应。
  • 新增了Blob存在性检查,避免尝试下载不存在的Blob时抛出异常。

内容的提问来源于stack exchange,提问作者Petr Barabáš

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 19:15:05