如何在 ASP.NET Core 中返回存储为 byte[] 的文件?

Posted

技术标签:

【中文标题】如何在 ASP.NET Core 中返回存储为 byte[] 的文件?【英文标题】:How do I return a file stored as byte[] in ASP.NET Core? 【发布时间】:2020-05-16 14:21:17 【问题描述】:

我已经搜索了一段时间,虽然它应该很简单,但我无法让它工作。根据我看到的例子,这是我到目前为止的地方:

SomeAppService.cs

public async Task<FileStream> Download(long? id)

    var attachment = await _repository.FirstOrDefaultAsync(x => x.Id == id);

    var fileStream = new FileStream($"attachment.FileName.attachment.FileExtension", 
        FileMode.Create, FileAccess.Write);
    fileStream.Write(attachment.File, 0, attachment.File.Length);

    return fileStream;

可以注意到,“FileName”、“FileExtension”和“File”(即前面提到的字节数组)存储在数据库中。附件可以是任何类型的文件,除了上传方法中禁止的扩展名(未显示)。然后在我的控制器中:

SomeController.cs

[AllowAnonymous]
[HttpGet("Download/id")]
public async Task<IActionResult> Download(long? id)

    var fileStream = await appService.Download(id);
    return new FileStreamResult(fileStream, "application/octet-stream");

但是,当我点击下载端点时,我最终得到了一个名为“response”的文件,没有扩展名,只有 0 个字节。

资源:

Return file in ASP.Net Core Web API

Return a FileResult from a byte[]

Save and load MemoryStream to/from a file(255 票的回复让我了解了如何将字节数组转换为文件流,但我不知道这是否可行)

【问题讨论】:

您可能需要将流的位置重置为开头。但是,您似乎可以将attachment.File 包装在MemoryStream 中,然后直接将其传递给FileStreamResult 构造函数。还有一个FileContentResult,您可以将attachment.File 传递给而不是创建流。 returnDownload() 编辑的 FileStream return 将立即定位在您刚刚写入的数据之后。 Seek()ing 在returning 之前回到Stream 的开头会产生不同的结果吗? 对于您提供下载的文件,必须明确指定(首选)文件名和长度(编辑:和位置)等内容。我自己不知道这种模式,我只使用过 HTTP 处理程序:c-sharpcorner.com/UploadFile/dacca2/…,甚至在不久前。 @BACON 刚试过。没有骰子。 类似这样,return File(byteArray, "contentType"); 内容类型例如是application/pdf 【参考方案1】:

谢谢大家,最后FileContentResult成功了。

看起来是这样的:

服务

public async Task<Attachment> Download(long? id)

    return await _repository.FirstOrDefaultAsync(x => x.Id == id);

控制器

[AllowAnonymous]
[HttpGet("Download/id")]
public async Task<IActionResult> Download(long? id)

    var attachment = await appService.Download(id);
    return new FileContentResult(attachment.File, 
        MimeTypeMap.GetMimeType(attachment.FileExtension))
    
        FileDownloadName = $"attachment.NomeFile.attachment.FileExtension"
    ;

MimeTypeMap可以找到here

【讨论】:

【参考方案2】:

如果您想从实体框架的数据库 blob 流式传输文件而不将其加载到内存中。首先将数据模型拆分为两部分;

public class Attachment
    public int Id  get; set; 
    public string Filename  get; set; 
    public string ContentType  get; set; 
    public virtual AttachmentBlob Blob  get; set; 
    //...


public class AttachmentBlob
    public int Id  get; set; 
    public byte[] File  get; set; 

将它们映射到同一个表,但不是作为拥有的类型;

   modelBuilder.Entity<Attachment>(e => 
       e.HasOne(a => a.Blob)
        .WithOne()
        .HasForeignKey<AttachmentBlob>(b => b.Id);
   );
   modelBuilder.Entity<AttachmentBlob>(e => 
       e.ToTable("Attachment");
   );

然后您可以将它们作为字节数组或流进行读写;

   public static async Task Read(DbContext db, Attachment attachment, Func<Stream,Task> callback)
   
      await db.Database.OpenConnectionAsync();
      try 
         var conn = db.Database.GetDbConnection();
         var cmd = conn.CreateCommand();
         var parm = cmd.CreateParameter();
         cmd.Parameters.Add(parm);
         parm.ParameterName = "@id";
         parm.Value = attachment.Id;
         cmd.CommandText = "select File from Attachment where Id = @id";
         using (var reader = await cmd.ExecuteReaderAsync())
            if (await reader.ReadAsync())
               await callback(reader.GetStream(0));
         
       finally 
         await db.Database.CloseConnectionAsync();
      
   

   public class AttachmentResult : FileStreamResult
   
      private readonly DbContext db;
      private readonly Attachment attachment;

      public AttachmentResult(DbContext db, Attachment attachment) : base(new MemoryStream(), attachment.ContentType)
      
         this.db = db;
         this.attachment = attachment;
      

      public override async Task ExecuteResultAsync(ActionContext context)
      
         await Read(db, attachment, async s => 
            FileStream = s;
            await base.ExecuteResultAsync(context);
         );
      
   

   public static async Task Write(DbContext db, Attachment attachment, Stream content)
   
      await db.Database.OpenConnectionAsync();
      try 
         var conn = db.Database.GetDbConnection();
         var cmd = conn.CreateCommand();
         cmd.Transaction = db.Database.CurrentTransaction?.GetDbTransaction();
         var parm = cmd.CreateParameter();
         cmd.Parameters.Add(parm);
         parm.ParameterName = "@id";
         parm.Value = attachment.Id;
         parm = cmd.CreateParameter();
         cmd.Parameters.Add(parm);
         parm.ParameterName = "@content";
         parm.Value = content;
         cmd.CommandText = "update Attachment set File = @content where Id = @id";
         await cmd.ExecuteNonQueryAsync();
       finally 
         await db.Database.CloseConnectionAsync();
      
   

   public static Task InsertAttachment(DbContext db, Attachment attachment, Stream content)
      var strat = db.Database.CreateExecutionStrategy();
      return strat.ExecuteAsync(async () => 
         using (var trans = await db.Database.BeginTransactionAsync())
         
             db.Set<Attachment>.Add(attachment);
             await db.SaveChangesAsync();
             await Write(db, attachment, content);
             trans.Commit();
             db.ChangeTracker.AcceptAllChanges();
         
      );
   

【讨论】:

这个方法也可以适配reader.GetTextReader()或者parm.Value = [TextReader]的大字符串。

以上是关于如何在 ASP.NET Core 中返回存储为 byte[] 的文件?的主要内容,如果未能解决你的问题,请参考以下文章

如何在我的ASP.NET Core应用程序中显示来自我的Azure Blob存储帐户的图像?

如何在 ASP.NET Core MVC 中使用依赖注入设计存储库模式?

ASP.NET Core从存储过程中获取Json的重构方法

如何在 ASP.NET Core MVC 中使用 ADO.NET 向存储过程添加参数?

如何按照存储库模式在 Asp.Net Core 5.0 项目上实现 .Net Core Identity?

ASP.Net Core 2.0 - 如何从中间件返回自定义 json 或 xml 响应?