PhysicalFileProvider 构造时路径必须是绝对路径
传入相对路径会直接抛出
ArgumentException,错误信息里明确写着“root must be an absolute path”。它不帮你做
Directory.GetCurrentDirectory()或
AppContext.BaseDirectory拼接,这点和
IWebHostEnvironment.WebRootPath的行为完全不同。
常见错误现象:本地开发时用
"wwwroot"或
"./files"直接传给
new PhysicalFileProvider(...),运行就崩;部署到 IIS 或 Linux 容器后路径更不可控。 正确做法:用
Path.GetFullPath("wwwroot") 或显式拼接 Path.Combine(AppContext.BaseDirectory, "data")推荐封装成静态方法,避免多处重复写
Path.GetFullPath如果路径来自配置(如 JSON),务必在读取后调用
Path.IsPathRooted()校验,不是绝对路径就拒绝初始化
GetFileInfo 返回 null 不代表文件不存在
GetFileInfo()对于不存在的路径,返回的是一个
IFileInfo实例,其
Exists属性为
false,而不是
null。这是设计使然——它要统一提供元数据接口,哪怕文件没找到。
容易踩的坑:写成
if (provider.GetFileInfo("x.txt") == null) 判断缺失,结果永远进不去分支,后续读取时才爆 NullReferenceException或空流。 必须检查
fileInfo.Exists,不是判空
fileInfo.Length在
Exists == false时返回 0,不能靠长度判断 若需区分“路径无效”和“文件不存在”,可先用
File.Exists(path)快速探路,但注意并发场景下仍有竞态
Watch() 在容器或网络路径上基本不可靠
PhysicalFileProvider.Watch()底层依赖
FileSystemWatcher,而后者在 Docker 容器(尤其 Linux)、UNC 路径、某些 NAS 设备上频繁失灵:事件丢失、触发延迟、甚至完全静默。它不是轮询,而是靠 OS 文件系统通知,一旦底层不支持就彻底失效。
使用场景:仅适合开发机本地目录热重载,或 Windows 服务中托管的稳定本地磁盘。生产环境用它监听用户上传目录或日志目录,等于埋雷。
替代方案:用定时轮询 +GetDirectoryContents()+ 时间戳/哈希比对,简单但可控 若必须用 Watch,请加 fallback 机制——比如每 30 秒兜底扫描一次 Linux 容器中启用 inotify 限制(
fs.inotify.max_user_watches)可能需宿主机调高,但这不属于应用层能控制的范畴
GetDirectoryContents() 不递归,也不过滤隐藏文件
GetDirectoryContents("logs") 只返回 logs下一级的条目,不会深入子目录;而且
.gitignore、
.DS_Store这类隐藏文件默认照常返回,
IFileInfo.IsDirectory是唯一可靠分类依据。
性能影响:如果目录下有几万个小文件,
GetDirectoryContents()会一次性枚举全部并构建
IFileInfo对象,内存和耗时都明显。它不像
Directory.EnumerateFiles()那样支持延迟执行。 需要递归?自己用
Directory.GetFiles(path, "*", SearchOption.AllDirectories)再包装成
IFileInfo要跳过隐藏文件?遍历后过滤
fileInfo.Name.StartsWith(".") || (fileInfo.Attributes & FileAttributes.Hidden) != 0
大数据量场景下,别在请求中直接调用它,考虑缓存结果或改用分页式扫描
物理路径的不确定性比想象中更顽固——它藏在部署方式、容器挂载点、权限模型背后,而
PhysicalFileProvider只负责“照本宣科”,不替你兜底。
