C# 实现自己的虚拟文件系统 C#如何创建一个内存或数据库支持的IFileProvider

来源:这里教程网 时间:2026-02-21 17:42:45 作者:

为什么
IFileProvider
不能直接 new 出来

因为

IFileProvider
是抽象接口,.NET 没提供开箱即用的内存或数据库实现。你看到的
PhysicalFileProvider
EmbeddedFileProvider
都只负责读取物理路径或程序集资源,不支持写入、动态挂载或持久化到数据库。

想用内存或数据库做后端,必须自己实现

IFileProvider
和配套的
IFileInfo
—— 这不是配置问题,是补全缺失抽象的责任。

常见错误现象:
new PhysicalFileProvider("memory://")
报错或静默失败,因为协议前缀不被识别
根本原因:所有内置
IFileProvider
实现都依赖
System.IO
,而
System.IO
不处理内存/DB 路径语义
关键点:你得重写
GetDirectoryContents
GetFileInfo
Watch
三个方法,其中
Watch
在内存场景可返回空实现

内存版
IFileProvider
怎么写才不崩

核心是用

ConcurrentDictionary<string byte></string>
存文件内容,再套一层线程安全的
IFileInfo
实现。别用
Dictionary
,并发读写会丢数据;也别在
GetFileInfo
里每次 new 对象,容易 GC 压力大。

示例关键片段:

public class MemoryFileProvider : IFileProvider
{
    private readonly ConcurrentDictionary<string, byte[]> _files = new();
    public IDirectoryContents GetDirectoryContents(string subpath)
    {
        var entries = _files.Keys
            .Where(k => k.StartsWith(subpath + "/", StringComparison.Ordinal))
            .Select(k => new MemoryFileInfo(k, _files[k]))
            .ToArray();
        return new MemoryDirectoryContents(entries);
    }
    public IFileInfo GetFileInfo(string subpath) =>
        _files.TryGetValue(subpath, out var data)
            ? new MemoryFileInfo(subpath, data)
            : new NotFoundFileInfo(subpath);
}
subpath
传进来默认不带开头斜杠,但你的内存 key 可以统一存为
"wwwroot/index.html"
这种格式,别自动拼
/
注意大小写:Windows 下路径不区分大小写,但
ConcurrentDictionary
默认区分,建议用
StringComparer.OrdinalIgnoreCase
构造
MemoryFileInfo
必须实现
Exists
Length
CreateReadStream
,其中
CreateReadStream
返回
new MemoryStream(data)
,别复用同一份
MemoryStream

数据库支持的
IFileProvider
要绕过哪些坑

数据库本质是异步 IO,但

IFileProvider
全是同步方法。硬塞
GetAwaiter().GetResult()
会死锁(尤其在 ASP.NET Core 请求上下文中)。唯一安全做法:用同步 DB 驱动(如 SQLite 的
Microsoft.Data.Sqlite
同步 API),或者把 DB 层包装成同步假象。

别用
EntityFrameworkCore
直接查:它没有同步查询入口,强行
.Result
在 IIS 或 Kestrel 下极易卡主线程
表结构至少要字段:
Path NVARCHAR(450) PRIMARY KEY
Content BLOB
LastModified DATETIME2
Path
建唯一索引,否则
GetFileInfo
查太慢
GetDirectoryContents
不能用
LIKE 'prefix%'
模糊查——路径有层级,得按
/
分割后做前缀树或递归 CTE,简单做法是加一列
ParentPath
存上级目录
如果文件可能超 10MB,别把
Content
字段全 load 到内存再给
CreateReadStream
,改用
SqliteCommand
ExecuteReader(CommandBehavior.SequentialAccess)
流式读取

IFileProvider
替换进 ASP.NET Core 时为啥静态文件不生效

因为

UseStaticFiles()
默认只认
IWebHostEnvironment.WebRootFileProvider
,你 new 好的自定义 provider 得显式塞进去,不是注册到 DI 容器就完事。

正确姿势:
services.AddSingleton<ifileprovider>(sp => new DatabaseFileProvider(...));</ifileprovider>
注册,然后在
Configure
里:
app.UseStaticFiles(new StaticFileOptions { FileProvider = app.ApplicationServices.GetRequiredService<ifileprovider>() });</ifileprovider>
容易忽略:如果你还用了
UseSpa()
UseBlazorServer()
,它们内部也会创建自己的
FileProvider
,得看文档找对应选项覆盖,比如
spa.Options.SourcePath
是路径,不是 provider
调试技巧:在自定义
GetFileInfo
开头加
Console.WriteLine($"GetFileInfo({subpath})")
,启动后访问
/favicon.ico
,看有没有输出——没输出说明根本没走到你的 provider

最麻烦的其实是 Watch 机制:内存和数据库都没法高效监听变更,

ChangeToken.OnChange
那套得你自己用定时轮询或 DB 触发器模拟,生产环境慎用。

相关推荐