2026/9/20 21:01:23

深入解析 .NET 运行时中的 Microsoft.Extensions.FileProviders.Abstractions:文件提供程序的核心抽象层

深入解析 .NET 运行时中的 Microsoft.Extensions.FileProviders.Abstractions:文件提供程序的核心抽象层 语言运行时标准库JIT编译编译器【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址https://gitcode.com/GitHub_Trending/runtime6/runtime点击查看免费下载导读Microsoft.Extensions.FileProviders.Abstractions是 .NET 运行时仓库中负责定义文件提供程序File Provider核心抽象的程序集。它通过IFileProvider、IFileInfo、IDirectoryContents三个接口把从不同来源获取文件这一能力抽象为统一的只读契约并随附NullFileProvider、NotFoundFileInfo等空实现作为基础工具。本文将以该程序集的 README 为主线结合仓库源码逐层拆解其接口定义、内置实现与依赖关系并演示如何基于抽象层编写自定义文件提供程序。读完本文你将能够理解 ASP.NET Core 静态文件、嵌入式资源、物理磁盘等多来源文件访问的统一模型并具备独立实现自定义IFileProvider的能力。一、程序集定位一套抽象多种文件来源程序集 README 开宗明义该程序集为文件提供程序提供核心抽象。一个文件提供程序可以从完全不同的来源取文件.NET 官方为物理文件系统与组合式文件提供程序提供了实现ASP.NET Core 则基于该抽象提供了嵌入式资源文件提供程序Microsoft.Extensions.FileProviders.Embedded的实现。这意味着无论文件是存放在磁盘目录、嵌入到程序集资源还是分布在多个根目录中使用者面对的始终是同一套 API——IFileProvider。这正是该抽象层存在的价值把读取文件与文件来自哪里彻底解耦。从仓库目录结构可以直观看到该程序集的组成src/libraries/Microsoft.Extensions.FileProviders.Abstractions/src接口与辅助类型的实现源码src/libraries/Microsoft.Extensions.FileProviders.Abstractions/ref/Microsoft.Extensions.FileProviders.Abstractions.cs公开 API 的参考ref面用于 API 兼容性校验src/libraries/Microsoft.Extensions.FileProviders.Abstractions/src/PACKAGE.mdNuGet 包文档src/libraries/Microsoft.Extensions.FileProviders.Abstractions/src/Microsoft.Extensions.FileProviders.Abstractions.csproj项目文件。二、三大核心接口文件提供程序的最小契约整个抽象层由三个接口组成它们在源码目录中一一对应IFileProvider.cs、IFileInfo.cs、IDirectoryContents.cs。2.1 IFileProvider入口抽象IFileProvider是文件提供程序的根接口定义了对文件系统的全部只读操作能力。其完整定义如下namespace Microsoft.Extensions.FileProviders { /// summary /// A read-only file provider abstraction. /// /summary public interface IFileProvider { /// summary /// Locates a file at the given path. /// /summary /// param namesubpathThe relative path that identifies the file./param /// returnsThe file information. Caller must check Exists property./returns IFileInfo GetFileInfo(string subpath); /// summary /// Enumerates a directory at the given path, if any. /// /summary /// param namesubpathThe relative path that identifies the directory./param /// returnsThe contents of the directory./returns IDirectoryContents GetDirectoryContents(string subpath); /// summary /// Creates an see crefIChangeToken/ for the specified paramref namefilter/. /// /summary /// param namefilterA filter string used to determine what files or folders to monitor. Examples: **/*.cs, *.*, subFolder/**/*.cshtml./param /// returnsAn see crefIChangeToken/ that is notified when a file matching paramref namefilter/ is added, modified, or deleted./returns IChangeToken Watch(string filter); } }三个方法的职责分工非常清晰方法作用关键注意点GetFileInfo(string subpath)定位指定相对路径下的文件返回值不代表文件一定存在调用方必须检查IFileInfo.ExistsGetDirectoryContents(string subpath)枚举指定相对路径下的目录内容路径不存在时返回的IDirectoryContents.Exists为false且可正常遍历空集合Watch(string filter)为指定过滤器创建变更令牌IChangeToken当匹配过滤器如**/*.cs、*.*、subFolder/**/*.cshtml的文件被新增、修改或删除时令牌得到通知Watch的过滤器语法是接入 ASP.NET Core 热重载、IOptionsMonitor、配置重载等机制的关键。其中**/*.cs表示递归匹配任意层级下的.cs文件*.*表示匹配当前层级所有文件subFolder/**/*.cshtml表示subFolder目录下递归的所有.cshtml文件。2.2 IFileInfo单个文件或目录的描述IFileInfo描述文件提供程序中的单个文件或目录其成员完整列出如下namespace Microsoft.Extensions.FileProviders { public interface IFileInfo { /// summaryGets a value that indicates if the resource exists in the underlying storage system./summary bool Exists { get; } /// summaryGets the length of the file in bytes, or -1 for a directory or nonexistent file./summary long Length { get; } /// summaryGets the path to the file, including the file name. Returns see langwordnull/ if the file is not directly accessible./summary string? PhysicalPath { get; } /// summaryGets the name of the file or directory, not including any path./summary string Name { get; } /// summaryGets the time when the file was last modified./summary DateTimeOffset LastModified { get; } /// summaryGets a value that indicates whether cTryGetDirectoryContents/c has enumerated a subdirectory./summary bool IsDirectory { get; } /// summaryReturns file contents as a read-only stream./summary /// returnsThe file stream./returns /// remarksThe caller should dispose the stream when complete./remarks Stream CreateReadStream(); } }各成员语义要点Exists资源在底层存储系统中是否存在Length文件字节数对目录或不存在的文件返回-1PhysicalPath文件路径含文件名若文件无法直接访问例如嵌入式资源返回nullName仅文件名或目录名不含路径部分LastModified最后修改时间IsDirectory是否为目录由底层TryGetDirectoryContents枚举判定CreateReadStream()以只读流返回文件内容调用方负责在读取完毕后释放流。2.3 IDirectoryContents目录内容的可枚举集合IDirectoryContents表示文件提供程序中的目录内容本质是IFileInfo的枚举集合namespace Microsoft.Extensions.FileProviders { public interface IDirectoryContents : IEnumerableIFileInfo { /// summary /// True if a directory was located at the given path. /// /summary bool Exists { get; } } }它继承自IEnumerableIFileInfo因此可以用foreach或 LINQ 直接遍历目录中的每个条目Exists属性用于判断给定路径下是否真的存在目录。这一设计让目录不存在也能被安全表达返回一个Exists false且为空的可枚举对象调用方无需判空即可遍历。三、内置辅助类型优雅处理空与不存在除了三个接口程序集还提供了四个面向空/不存在场景的辅助类型。它们让抽象层在边界情况下依然保持类型安全、免于空引用异常。3.1 NullFileProvider什么都不提供的提供程序NullFileProvider.cs 是一个空文件提供程序其三个方法全部返回空实现public class NullFileProvider : IFileProvider { public IDirectoryContents GetDirectoryContents(string subpath) NotFoundDirectoryContents.Singleton; public IFileInfo GetFileInfo(string subpath) new NotFoundFileInfo(subpath); public IChangeToken Watch(string filter) NullChangeToken.Singleton; }从实现可见其语义任何文件都找不到、任何目录都不存在、任何监视都不会触发回调。它常用于依赖注入默认值、单元测试桩stub或作为需要IFileProvider但又不想真正访问文件系统时的占位实现。3.2 NotFoundFileInfo标准化的文件不存在NotFoundFileInfo.cs 是IFileInfo的不存在实现各属性的固定取值非常明确Exists恒为falseIsDirectory恒为falseLastModified恒为DateTimeOffset.MinValueLength恒为-1PhysicalPath恒为nullCreateReadStream()永远抛出FileNotFoundException消息通过SR.Format(SR.FileNotExists, Name)本地化资源生成资源定义见 Resources/Strings.resx。它的设计价值在于GetFileInfo对不存在的路径返回一个存在但不存在的占位对象调用方只需检查Exists即可统一分支而不会遭遇空引用或异常。3.3 NotFoundDirectoryContents标准化的目录不存在NotFoundDirectoryContents.cs 对应目录场景Exists恒为falseGetEnumerator()返回Enumerable.EmptyIFileInfo()的空枚举。同时提供了Singleton静态实例避免重复分配public class NotFoundDirectoryContents : IDirectoryContents { public static NotFoundDirectoryContents Singleton { get; } new(); public bool Exists false; public IEnumeratorIFileInfo GetEnumerator() Enumerable.EmptyIFileInfo().GetEnumerator(); }3.4 NullChangeToken不触发任何回调的变更令牌NullChangeToken.cs 实现Microsoft.Extensions.Primitives.IChangeToken来自依赖程序集Microsoft.Extensions.PrimitivesHasChanged恒为falseActiveChangeCallbacks恒为false表示该令牌不会主动回调RegisterChangeCallback返回EmptyDisposable.Instance一个空释放对象注册的回调永远不会被调用。public sealed class NullChangeToken : IChangeToken { public static NullChangeToken Singleton { get; } new NullChangeToken(); public bool HasChanged false; public bool ActiveChangeCallbacks false; public IDisposable RegisterChangeCallback(Actionobject? callback, object? state) { return EmptyDisposable.Instance; } }EmptyDisposable由程序集从公共源码目录$(CommonPath)Extensions\EmptyDisposable.cs引入见 csproj 中的 Compile Include 项用于让注册回调这一动作本身也有安全的返回值。3.5 完整公开 API 一览程序集的公共 API 面可以在 ref/Microsoft.Extensions.FileProviders.Abstractions.cs 中确认包括IFileProvider、IFileInfo、IDirectoryContents三个接口以及NullFileProvider、NotFoundFileInfo、NotFoundDirectoryContents、NullChangeToken四个类。该 ref 文件是 API 评审流程aka.ms/api-review的一部分任何公开 API 变更都必须同步更新这是 .NET 仓库维持 API 兼容性的基础设施。四、如何基于抽象层编写自定义文件提供程序程序集本身只提供抽象实际使用总是配合一个具体实现。以仓库中两个官方实现为例Microsoft.Extensions.FileProviders.Physical从物理磁盘目录读取文件Microsoft.Extensions.FileProviders.Composite将多个文件提供程序组合为一个整体按顺序查找文件Microsoft.Extensions.FileProviders.Embedded从程序集嵌入资源读取文件ASP.NET Core 提供实现。基于该抽象实现自定义提供程序时只需要继承IFileProvider并实现三个方法。例如一个最简单的内存文件提供程序骨架using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; public sealed class InMemoryFileProvider : IFileProvider { private readonly Dictionarystring, byte[] _files; // 以 / 开头的相对路径为键 public InMemoryFileProvider(Dictionarystring, byte[] files) { _files files; } public IFileInfo GetFileInfo(string subpath) { if (_files.TryGetValue(subpath, out byte[]? content)) { return new MemoryFileInfo(subpath, content); } // 复用抽象层自带的不存在实现保证调用方无需判空 return new NotFoundFileInfo(subpath); } public IDirectoryContents GetDirectoryContents(string subpath) NotFoundDirectoryContents.Singleton; public IChangeToken Watch(string filter) NullChangeToken.Singleton; // 内存提供程序默认不监视变更 }其中MemoryFileInfo只需实现IFileInfo的七个成员CreateReadStream()返回new MemoryStream(content)即可。这一示例展示了抽象层的核心用法文件不存在时返回NotFoundFileInfo而不是抛异常目录不存在时返回NotFoundDirectoryContents.Singleton不需要变更通知时返回NullChangeToken.Singleton。这样实现的提供程序在任何消费方ASP.NET Core 静态文件中间件、配置系统等中都能安全工作。五、部署形态与依赖关系5.1 多目标框架与打包从 Microsoft.Extensions.FileProviders.Abstractions.csproj 可以看到目标框架$(NetCoreAppCurrent)、$(NetCoreAppPrevious)、$(NetCoreAppMinimum)、netstandard2.0、$(NetFrameworkMinimum)即覆盖当前及历史 .NET 版本同时通过netstandard2.0支持旧版 .NET Framework 与 .NET Standard 生态可打包IsPackable为true以 NuGet 包Microsoft.Extensions.FileProviders.Abstractions形式发布包描述Abstractions of files and directories.并列出常用类型IDirectoryContents、IFileInfo、IFileProvider根命名空间Microsoft.Extensions.FileProviders。5.2 依赖关系该程序集只有一个核心项目依赖——Microsoft.Extensions.Primitives提供IChangeToken接口这在 csproj 中有明确声明ItemGroup ProjectReference Include$(LibrariesProjectRoot)Microsoft.Extensions.Primitives\src\Microsoft.Extensions.Primitives.csproj / /ItemGroup而在当前 .NET 版本目标下额外引用System.Linq供NotFoundDirectoryContents使用Enumerable.Empty与System.Runtime。这种轻量依赖设计保证了抽象层可以被任何文件提供程序实现独立引用不会引入重量级运行时负担。六、贡献门槛与生态协作README 的 Contribution Bar 部分明确了该库的演进策略主要门槛Primary Bar接受面向该库的新功能、新 API 与性能改进次要门槛Secondary Bar接受针对该库的新源码分析器source code analyzersPR。相关规则详见 src/libraries/README.md。这意味着该抽象层保持活跃演进但所有公开 API 变更都必须经过 API 评审并同步更新 ref 文件。在生态协作上该程序集是文件提供程序体系的地基抽象接口在此定义本程序集物理文件实现见Microsoft.Extensions.FileProviders.Physical组合式实现见Microsoft.Extensions.FileProviders.Composite嵌入式资源实现见Microsoft.Extensions.FileProviders.EmbeddedNuGet 包Microsoft.Extensions.FileProviders.Embedded。上层如静态文件中间件、Razor 视图引擎、配置与本地化系统都建立在这套抽象之上。理解了IFileProvider的三个方法就等于拿到了理解整个 .NET 文件访问生态的钥匙。结语Microsoft.Extensions.FileProviders.Abstractions以极小的 API 面3 个接口 4 个辅助类型定义了 .NET 中统一的只读文件访问模型。其设计精髓在于用Exists属性与NotFound*占位对象优雅表达文件/目录不存在用IChangeToken将文件变更通知与具体存储解耦再用NullChangeToken为不支持监视的提供程序提供安全默认值。无论是阅读 ASP.NET Core 源码、编写自定义配置源还是构建自己的资源加载框架这套抽象都是不可或缺的基础设施。赞分享语言运行时标准库JIT编译编译器【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址https://gitcode.com/GitHub_Trending/runtime6/runtime点击查看免费下载相关推荐Turso.Data.Native 深度解析Turso .NET 提供程序的原生运行时打包与加载机制Turso.Data.Native 深度解析Turso .NET 提供程序的原生运行时打包与加载机制 Turso.Data.Native 是 Turso .N数据库嵌入式数据库关系型数据库深入理解 GPUI 四层核心抽象Window、App、Context 与 Entity深入理解 GPUI 四层核心抽象Window、App、Context 与 Entity 本指南以 gpui kit 仓库中 website/docs/cont桌面应用UI组件前端ArduPilot核心库架构解析AP_HAL硬件抽象层设计ArduPilot核心库架构解析AP_HAL硬件抽象层设计 AP_HALHardware Abstraction Layer是ArduPilot项目的核心嵌入式无人机自动驾驶机器人固件创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考