欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET CORE WEBAPI文件下载

程序员文章站 2022-04-15 16:09:59
最近要使用ASP.NET CORE WEBAPI用来下载文件,使用的.NET CORE 3.1。考虑如下场景: 1. 文件是程序生成的。 2. 文件应该能兼容各种格式。 3. 浏览器可以感知进行下载。 准备 经过简单的调研,得到以下结论。 ASP.NET CORE 提供FileResult这种类型的 ......

最近要使用asp.net core webapi用来下载文件,使用的.net core 3.1。考虑如下场景:

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

准备

经过简单的调研,得到以下结论。

  • asp.net core 提供fileresult这种类型的actionresult,可以直接返回文件结果,不需要直接处理httpresponse。
  • 通过stream可以直接返回文件流供浏览器下载。
  • filestreamresult是fileresult的具体实现,返回值应该是此类对象。
  • stream有多种类型,适合直接内存中生成文件对象的是memorystream。
    对目标有了基础的了解,就可以开始动手实现了。

实现

建立好asp.net core webapi工程,把生成文件的代码独立出来一个函数。我这里需要是下载一个csv格式的文件,因此生成一个csv文件。
对于磁盘上的文件,可以使用filestream对象,由于我这里需要运行中生成这个文件,需要使用memorystream。

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语句,不再报相同错误。

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

单步显示stream长度有了,但是返回的长度还是0。继续单步调试发现stream的postion是停在文件结尾的,这个和直接开始读取文件完全不一样,文件读取一般是从开头开始的,于是直接设置postion为0,问题解决。

下载能够成功了,但是文件名一直显示的是随机生成的,体验很差。设置一下filedownloadname即可。

核心代码如下:

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调用,最后效果:

ASP.NET CORE WEBAPI文件下载

总结

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

  • memorystream如果使用using语句,会在离开代码块的时候自动关闭,实际上asp.net core会自动处理关闭的事项,不需要使用using语句。
  • 由于生成文件的过程是从文件流的开头一直进行到末尾的,因此向请求端返回结果时,应当重置stream的游标,从0开始传输。
  • 记得在使用writer之后使用flush()以确保数据有写入。
  • 如果不确定文件格式,可以直接返回mime值为application/oct-stream。
  • 设置filestreamresult的filedownloadname属性可以修改文件的默认名称。
  • (可选)可以通过设置response.contentlength来设置文件的长度。

参考资料: