为什么 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 触发器模拟,生产环境慎用。
