physicalfileprovider构造必须用绝对路径,否则抛argumentexception;getfileinfo返回exists为false的ifileinfo而非null;watch在容器或网络路径不可靠;getdirectorycontents不递归且不过滤隐藏文件。

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 只负责“照本宣科”,不替你兜底。










