31. 虚拟文件系统
📝 模块更新日志
以下内容仅限 Furion 2.5.0 +
版本使用。
31.1 关于文件系统
本章所谓的 文件系统
有点名不副实,其实根本算不上一个系统,它仅仅是利用一个抽象化的 IFileProvider
以统一的方式提供所需的文件而已。通过该 文件系统
可以读取物理文件和嵌入资源文件,包括目录结果读取,文件内容读取,文件内容监听等等。
31.1.1 文件系统类型
Furion
提供了两种文件系统类型:
Physical
:物理文件系统类型,也就是物理机中实际存在的文件Embedded
:嵌入资源文件系统类型,也就是资源文件嵌入到了程序集中,常用于模块化开发
31.2 注册虚拟文件系统服务
services.AddVirtualFileServer();
31.3 获取文件系统 IFileProvider
实例
31.3.1 Func<FileProviderTypes, object, IFileProvider>
方式
Furion
框架提供了 Func<FileProviderTypes, object, IFileProvider>
委托供构造函数注入或解析服务,如:
public class PersonServices
{
private readonly IFileProvider _physicalFileProvider;
private readonly IFileProvider _embeddedFileProvider;
public PersonServices(Func<FileProviderTypes, object, IFileProvider> fileProviderResolve)
{
// 解析物理文件系统
_physicalFileProvider = fileProviderResolve(FileProviderTypes.Physical, @"c:/test");
// 解析嵌入资源文件系统
_embeddedFileProvider = fileProviderResolve(FileProviderTypes.Embedded, Assembly.GetEntryAssembly());
}
}
31.3.2 FS
静态类方式
Furion
框架也提供了 FS
静态类方式创建,如:
// 解析物理文件系统
var physicalFileProvider = FS.GetPhysicalFileProvider(@"c:/test");
// 解析嵌入资源文件系统
var embeddedFileProvider = FS.GetEmbeddedFileProvider(Assembly.GetEntryAssembly());
31.4 IFileProvider
常见操作
31.4.1 读取文件内容
byte[] buffer;
using (Stream readStream = _fileProvider.GetFileInfo("你的文件路径").CreateReadStream())
{
buffer = new byte[readStream.Length];
await readStream.ReadAsync(buffer.AsMemory(0, buffer.Length));
}
// 读取文件内容
var content = Encoding.UTF8.GetString(buffer);
31.4.2 获取文件目录内容(需递归查找)
var rootPath = "当前目录路径";
var fileinfos = _fileProvider.GetDirectoryContents(rootPath);
foreach (var fileinfo in fileinfos)
{
if(fileinfo.IsDirectory)
{
// 这里递归。。。
}
}
31.4.3 监听文件变化
ChangeToken.OnChange(() => _fileProvider.Watch("监听的文件"), () =>
{
// 这里写你的逻辑
});
31.5 模块化静态资源配置
通常我们采用模块化开发,静态资源都是嵌入进程序集中,这时候我们需要通过配置 UseFileServer
指定模块静态资源路径,如:
// 默认静态资源调用,wwwroot
app.UseStaticFiles();
// 配置模块化静态资源
app.UseFileServer(new FileServerOptions
{
FileProvider = new EmbeddedFileProvider(模块程序集),
RequestPath = "/模块名称", // 后续所有资源都是通过 /模块名称/xxx.css 调用
EnableDirectoryBrowsing = true
});
31.6 文件上传下载
在应用开发中,文件上传下载属于非常常用的功能,这里贴出常见的文件上传下载示例。
31.6.1 文件下载
- 文件路径的方式
[HttpGet, NonUnify]
public IActionResult FileDownload()
{
string filePath = "这里获取完整的文件下载路径";
return new FileStreamResult(new FileStream(filePath, FileMode.Open), "application/octet-stream")
{
FileDownloadName = fileName // 配置文件下载显示名
};
}
byte[]
方式
[HttpGet, NonUnify]
public IActionResult FileDownload()
{
return new FileContentResult(byte数组, "application/octet-stream")
{
FileDownloadName = fileName // 配置文件下载显示名
};
}
stream
方式
[HttpGet, NonUnify]
public async Task<IActionResult> FileDownload()
{
var (stream, _) = await "http://furion.baiqian.ltd/img/rm1.png".GetAsStreamAsync();
// 将 stream 转 byte[]
byte[] bytes = new byte[stream.Length];
await stream.ReadAsync(bytes);
stream.Seek(0, SeekOrigin.Begin);
return new FileContentResult(bytes, "application/octet-stream")
{
FileDownloadName = fileName // 配置文件下载显示名
};
}
如果前端获取不到文件,可添加以下配置:
_httpContextAccessor.HttpContext.Response.Headers.Add("Content-Disposition", $"attachment; filename={文件名}");
_httpContextAccessor.HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition");
如果依然不能解决问题可尝试添加以下配置:
{
"CorsAccessorSettings": {
"WithExposedHeaders": [
"Content-Disposition",
"Access-Control-Expose-Headersx-access-token"
]
}
}
31.6.2 文件上传
IFormFile
类型对应前端的 Content-Type
为: multipart/form-data
- 单文件
IFormFile
类型参数(存储到硬盘)
[HttpPost]
public async Task<string> UploadFileAsync(IFormFile file)
{
// 如:保存到网站根目录下的 uploads 目录
var savePath = Path.Combine(App.HostEnvironment.ContentRootPath, "uploads");
if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
//// 这里还可以获取文件的信息
// var size = file.Length / 1024.0; // 文件大小 KB
// var clientFileName = file.FileName; // 客户端上传的文件名
// var contentType = file.ContentType; // 获取文件 ContentType 或解析 MIME 类型
// 避免文件名重复,采用 GUID 生成
var fileName = Guid.NewGuid().ToString("N") + Path.GetExtension(file.FileName);
var filePath = Path.Combine(savePath, fileName);
// 保存到指定路径
using (var stream = System.IO.File.Create(filePath))
{
await file.CopyToAsync(stream);
}
// 返回文件名(这里可以自由返回更多信息)
return fileName;
}
- 单文件
Base64
类型参数(存储到硬盘)
[HttpPost]
public async Task<string> UploadFileAsync([FromBody] string fileBase64, string clientFileName)
{
// 如:保存到网站根目录下的 uploads 目录
var savePath = Path.Combine(App.HostEnvironment.ContentRootPath, "uploads");
if (!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
// 将 base64 字符串转 byte[]
var bytes = Convert.FromBase64String(fileBase64);
// 这里还可以获取文件的信息
// var size = bytes.Length / 1024.0; // 文件大小 KB
// 避免文件名重复,采用 GUID 生成
var fileName = Guid.NewGuid().ToString("N") + Path.GetExtension(clientFileName);
var filePath = Path.Combine(savePath, fileName);
// 保存到指定路径
using (var fs = new FileStream(filePath, FileMode.Create))
{
await fs.WriteAsync(bytes);
}
// 返回文件名(这里可以自由返回更多信息)
return filename;
}
文件 Base64
字符串如果带 data:text/plain;base64,
开头则,需要手动去掉 ,
之前(含逗号)的字符串。
- 多文件
List<IFormFile>
类型参数(存储到硬盘)
通常多文件上传用的最多的是 List<IFormFile> files
参数,但 .NET5+
更推荐使用 IFormFileCollection files
。
代码和 单文件处理一致
,只需 foreach
即可。
[HttpPost]
public async Task<object> UploadFileAsync(List<IFormFile> files) // 可改为 IFormFileCollection files
{
// 保存到网站根目录下的 uploads 目录
var savePath = Path.Combine(App.HostEnvironment.ContentRootPath, "uploads");
if(!Directory.Exists(savePath)) Directory.CreateDirectory(savePath);
// 总上传大小
long size = files.Sum(f => f.Length);
// 遍历所有文件逐一上传
foreach (var formFile in files)
{
if (formFile.Length > 0)
{
// 避免文件名重复,采用 GUID 生成
var fileName = Guid.NewGuid().ToString("N") + Path.GetExtension(formFile.FileName);
var filePath = Path.Combine(savePath, fileName);
// 保存到指定路径
using (var stream = System.IO.File.Create(filePath))
{
await formFile.CopyToAsync(stream);
}
}
}
// 这里可自行返回更多信息
return new { count = files.Count, size };
}
- 多文件
List<string>
Base64
类型参数(存储到硬盘)
代码和 单文件处理一致
,只需 foreach
即可(参上)。
31.6.3 将 IFormFile
转 byte[]
有时候我们需要将文件转换成 byte[]
存储到数据库,而不是存储到硬盘中。
[HttpPost]
public async Task<IActionResult> UploadFileAsync(IFormFile file)
{
var fileLength = file.Length;
using var stream = file.OpenReadStream();
var bytes = new byte[fileLength];
stream.Read(bytes, 0, (int)fileLength);
// 这里将 bytes 存储到你想要的介质中即可
}
在 Furion v3.2.0
新增了 IFormFile
的 ToByteArray
拓展,如:
[HttpPost]
public async Task<IActionResult> UploadFileAsync(IFormFile file)
{
var bytes = file.ToByteArray();
// 这里将 bytes 存储到你想要的介质中即可
}
31.6.4 将 byte[]
输出为 Url
地址
由于一些项目直接将文件二进制存储在数据库中,读取到内存的时候都是 byte[]
数组,比如我们将图片文件存储在数据库中,然后前端通过 Url
链接进行访问,这个时候就需要将 byte[]
转换为有效的资源路径格式,如:
[NonUnify, HttpGet, AllowAnonymous]
public async Task<IActionResult> attachment(string resourceId)
{
// 根据 resourceId 查询 byte[] 字节数组和 content-type
// 返回 FileContentResult 类型
return new FileContentResult(字节数组,content-type);
}
之后我们就可以通过 https://localhost/attachment/资源id
访问文件或图片了。