今天写个下载文件的接口,可能对很多人来说很简单,但是我却遇到了这样那样的错误,这里记录一下。
Controller 层的返回类型#
在 Asp.net core(现在叫 .NET 6)中,想要写一个下载文件的 WebAPI 接口,直接返回 FileResult
就行,至于如何初始化一个 FileResult
对象,可以通过以下方式:
- 手动初始化一个
FileStreamResult
对象:
1
2
3
4
| return new FileStreamResult(fileStream, "application/stream")
{
FileDownloadName = fileName
};
|
1
| return File(fileStream, "application/stream", fileName);
|
当然,前提是得获取到文件的流(Stream
)。
中间层的实现方式#
先看 错误 的方式(伪代码):
1
2
3
4
5
6
7
8
9
10
11
| public async Task<Stream> GetFileStream()
{
// 生成 json 字符串,写入文本
var str = "This is content of the file.";
// 初始化一个在内存中的流对象:MemoryStream
using var stream = new MemoryStream();
// 初始化一个写入流对象
using var writer = new StreamWriter(stream);
await writer.WriteAsync(str);
return stream;
}
|
如果直接上述方法,则会报 无法读取已经关闭的流
错误。其实这个问题很容易看出来是怎么回事,一定是 using
语句块捣的鬼:离开 using
语句块的时候,流被自动关闭了,所以外层方法是获取不到流信息的。针对这个报错的改进就是去掉 using
语句,如下所示:
1
2
3
4
5
6
7
8
9
10
11
| public async Task<Stream> GetFileStream()
{
// 生成 json 字符串,写入文本
var str = "This is content of the file.";
// 初始化一个在内存中的流对象:MemoryStream
var stream = new MemoryStream();
// 初始化一个写入流对象
var writer = new StreamWriter(stream);
await writer.WriteAsync(str);
return stream;
}
|
此时调用接口,会发现生成的流还是有问题:接口返回的文件长度是 0,这个原因其实是因为生成的 stream
流对象长度为 0,而后者的进一步原因,可能是没有调用 writer.Flush()
方法以确保数据写入。
加上 writer.Flush()
语句后,虽然 stream
对象长度不为 0 了,但是接口返回的文件长度依然是 0。通过调试可以发现,stream
流对象的 Position
停在了文件末尾,所以返回时读取不到文件流数据,此时将其 Position
设置为 0,即可从头开始读取数据。
最终改进后的方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public async Task<Stream> GetFileStream()
{
// 生成 json 字符串,写入文本
var str = "This is content of the file.";
// 初始化一个在内存中的流对象:MemoryStream
var stream = new MemoryStream();
// 初始化一个写入流对象
var writer = new StreamWriter(stream);
await writer.WriteAsync(str);
// 以下两句需要加上
await writer.FlushAsync();
stream.Position = 0;
return stream;
}
|
如下总结,是在网上摘抄的:
MemoryStream
如果使用 using
语句块,会在离开代码块的时候自动关闭。而下载文件的时候不能提前关闭流,否则就会读取不到。.NET Core 其实会自动处理 using
语句的关闭问题,无需手动处理。- 文件生成的过程,是从开头到末尾的方向,所以生成文件后它的
Position
走到了末尾,最后在返回文件流的时候,我们需要手动将 Position
设置到开头(0 或者其他想要读取文件的位置),才能读取到文件内容。 - 使用
StreamWriter
时最好在写入内容后再调用其 Flush()
方法,确保缓冲数据写到了流中。