ASP.NET CORE WEBAPI文件下载

最近要使用ASP.NET CORE WEBAPI用来下载文件,使用的.NET CORE 3.1。考虑以下场景:web

  1. 文件是程序生成的。
  2. 文件应该能兼容各类格式。
  3. 浏览器能够感知进行下载。

准备

通过简单的调研,获得如下结论。c#

  • ASP.NET CORE 提供FileResult这种类型的ActionResult,能够直接返回文件结果,不须要直接处理HttpResponse。
  • 经过Stream能够直接返回文件流供浏览器下载。
  • FileStreamResult是FileResult的具体实现,返回值应该是此类对象。
  • Stream有多种类型,适合直接内存中生成文件对象的是MemoryStream。
    对目标有了基础的了解,就能够开始动手实现了。

实现

创建好ASP.NET CORE WEBAPI工程,把生成文件的代码独立出来一个函数。我这里须要是下载一个CSV格式的文件,所以生成一个CSV文件。
对于磁盘上的文件,可使用FileStream对象,因为我这里须要运行中生成这个文件,须要使用MemoryStream。api

using var stream = new MemoryStream();
using var writer = new StreamWriter(stream);
//生成标题
var propCollection = ttype.GetProperties();
foreach (var n in propCollection)
{
    writer.Write(n.Name);
    writer.Write(",");
}
writer.WriteLine();
//生成内容
foreach (var item in res)
{
    foreach (var n in propCollection)
    {
        writer.Write(Convert.ToString(n.GetValue(item)));
        writer.Write(",");
    }
    writer.WriteLine();
}
  1. 请不要考虑里面反射的相关内容,按照本身的逻辑生成CSV便可,我只是懒得改代码而已。
  2. 代码中使用到了一些新的语法特性,请注意对低版本的.NET不必定适用。
    直接返回Stream对象给Controller处理,处理代码以下:
var res = await info.GetAllQueryResult();
var actionresult = new FileStreamResult(res, new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("text/csv"));
return actionresult;

CSV的Content-Type是text/csv,若是下载别的文件,请自行查询MIME格式。浏览器

调试

直接执行上面的代码,直接报错“没法读取已经关闭的流”。猜想是离开using语句块的时候,stream自动被关闭了。改动很简单,去掉using语句,再也不报相同错误。app

可是返回的文件长度一直是0,单步调试发现Writer执行完毕以后,stream返回的长度是0,内容实际上并无写入,想起有一个Flush(),能够添加以确保数据写入。async

单步显示stream长度有了,可是返回的长度仍是0。继续单步调试发现Stream的Postion是停在文件结尾的,这个和直接开始读取文件彻底不同,文件读取通常是从开头开始的,因而直接设置Postion为0,问题解决。函数

下载可以成功了,可是文件名一直显示的是随机生成的,体验不好。设置一下FileDownloadName便可。.net

核心代码以下:调试

public async Task<Stream> GetAllQueryResult()
{
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    //生成标题
    var propCollection = ttype.GetProperties();
    foreach (var n in propCollection)
    {
        writer.Write(n.Name);
        writer.Write(",");
    }
    writer.WriteLine();
    //生成内容
    foreach (var item in res)
    {
        foreach (var n in propCollection)
        {
            writer.Write(Convert.ToString(n.GetValue(item)));
            writer.Write(",");
        }
        writer.WriteLine();
    }
    writer.Flush();
    stream.Position = 0;
    return stream;
}
[HttpPost("file")]
[ProducesResponseType(typeof(FileResult), Status200OK)]
public async Task<FileResult> Download()
{
    var info = new Info();
    var res = await info.GetAllQueryResult();

    var actionresult = new FileStreamResult(res, new Microsoft.Net.Http.Headers.MediaTypeHeaderValue("text/csv"));
    actionresult.FileDownloadName = "Carinfos.csv";
    //Response.ContentLength = res.Length;
    return actionresult;
}

使用swagger调用,最后效果:code

总结

后来查了一些资料,总结了一下:

  • MemoryStream若是使用using语句,会在离开代码块的时候自动关闭,实际上ASP.NET CORE会自动处理关闭的事项,不须要使用using语句。
  • 因为生成文件的过程是从文件流的开头一直进行到末尾的,所以向请求端返回结果时,应当重置Stream的游标,从0开始传输。
  • 记得在使用writer以后使用Flush()以确保数据有写入。
  • 若是不肯定文件格式,能够直接返回MIME值为application/oct-stream。
  • 设置FileStreamResult的FileDownloadName属性能够修改文件的默认名称。
  • (可选)能够经过设置Response.ContentLength来设置文件的长度。

参考资料:

https://darchuk.net/2019/05/31/asp-net-core-web-api-returning-a-filestream/

相关文章
相关标签/搜索