深入解析 .NET 运行时中的 Microsoft.Extensions.FileProviders.Abstractions:文件提供程序的核心抽象层
2026/9/20 21:01:17 网站建设 项目流程
  • 语言运行时
  • 标准库
  • 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)核心抽象的程序集。它通过IFileProviderIFileInfoIDirectoryContents三个接口,把"从不同来源获取文件"这一能力抽象为统一的只读契约,并随附NullFileProviderNotFoundFileInfo等空实现作为基础工具。本文将以该程序集的 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.md:NuGet 包文档;
  • 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 name="subpath">The relative path that identifies the file.</param> /// <returns>The file information. Caller must check Exists property.</returns> IFileInfo GetFileInfo(string subpath); /// <summary> /// Enumerates a directory at the given path, if any. /// </summary> /// <param name="subpath">The relative path that identifies the directory.</param> /// <returns>The contents of the directory.</returns> IDirectoryContents GetDirectoryContents(string subpath); /// <summary> /// Creates an <see cref="IChangeToken"/> for the specified <paramref name="filter"/>. /// </summary> /// <param name="filter">A filter string used to determine what files or folders to monitor. Examples: **/*.cs, *.*, subFolder/**/*.cshtml.</param> /// <returns>An <see cref="IChangeToken"/> that is notified when a file matching <paramref name="filter"/> is added, modified, or deleted.</returns> IChangeToken Watch(string filter); } }

三个方法的职责分工非常清晰:

方法作用关键注意点
GetFileInfo(string subpath)定位指定相对路径下的文件返回值不代表文件一定存在,调用方必须检查IFileInfo.Exists
GetDirectoryContents(string subpath)枚举指定相对路径下的目录内容路径不存在时返回的IDirectoryContents.Existsfalse,且可正常遍历(空集合)
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 { /// <summary>Gets a value that indicates if the resource exists in the underlying storage system.</summary> bool Exists { get; } /// <summary>Gets the length of the file in bytes, or -1 for a directory or nonexistent file.</summary> long Length { get; } /// <summary>Gets the path to the file, including the file name. Returns <see langword="null"/> if the file is not directly accessible.</summary> string? PhysicalPath { get; } /// <summary>Gets the name of the file or directory, not including any path.</summary> string Name { get; } /// <summary>Gets the time when the file was last modified.</summary> DateTimeOffset LastModified { get; } /// <summary>Gets a value that indicates whether <c>TryGetDirectoryContents</c> has enumerated a subdirectory.</summary> bool IsDirectory { get; } /// <summary>Returns file contents as a read-only stream.</summary> /// <returns>The file stream.</returns> /// <remarks>The caller should dispose the stream when complete.</remarks> Stream CreateReadStream(); } }

各成员语义要点:

  • Exists:资源在底层存储系统中是否存在;
  • Length:文件字节数;对目录或不存在的文件返回-1
  • PhysicalPath:文件路径(含文件名);若文件无法直接访问(例如嵌入式资源),返回null
  • Name:仅文件名或目录名,不含路径部分;
  • LastModified:最后修改时间;
  • IsDirectory:是否为目录(由底层TryGetDirectoryContents枚举判定);
  • CreateReadStream():以只读流返回文件内容,调用方负责在读取完毕后释放流。

2.3 IDirectoryContents:目录内容的可枚举集合

IDirectoryContents表示文件提供程序中的目录内容,本质是IFileInfo的枚举集合:

namespace Microsoft.Extensions.FileProviders { public interface IDirectoryContents : IEnumerable<IFileInfo> { /// <summary> /// True if a directory was located at the given path. /// </summary> bool Exists { get; } } }

它继承自IEnumerable<IFileInfo>,因此可以用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恒为false
  • IsDirectory恒为false
  • LastModified恒为DateTimeOffset.MinValue
  • Length恒为-1
  • PhysicalPath恒为null
  • CreateReadStream()永远抛出FileNotFoundException(消息通过SR.Format(SR.FileNotExists, Name)本地化资源生成,资源定义见 Resources/Strings.resx)。

它的设计价值在于:GetFileInfo对不存在的路径返回一个"存在但不存在"的占位对象,调用方只需检查Exists即可统一分支,而不会遭遇空引用或异常。

3.3 NotFoundDirectoryContents:标准化的"目录不存在"

NotFoundDirectoryContents.cs 对应目录场景:Exists恒为falseGetEnumerator()返回Enumerable.Empty<IFileInfo>()的空枚举。同时提供了Singleton静态实例,避免重复分配:

public class NotFoundDirectoryContents : IDirectoryContents { public static NotFoundDirectoryContents Singleton { get; } = new(); public bool Exists => false; public IEnumerator<IFileInfo> GetEnumerator() => Enumerable.Empty<IFileInfo>().GetEnumerator(); }

3.4 NullChangeToken:不触发任何回调的变更令牌

NullChangeToken.cs 实现Microsoft.Extensions.Primitives.IChangeToken(来自依赖程序集Microsoft.Extensions.Primitives):

  • HasChanged恒为false
  • ActiveChangeCallbacks恒为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(Action<object?> callback, object? state) { return EmptyDisposable.Instance; } }

EmptyDisposable由程序集从公共源码目录$(CommonPath)Extensions\EmptyDisposable.cs引入(见 csproj 中的 Compile Include 项),用于让"注册回调"这一动作本身也有安全的返回值。

3.5 完整公开 API 一览

程序集的公共 API 面可以在 ref/Microsoft.Extensions.FileProviders.Abstractions.cs 中确认,包括:IFileProviderIFileInfoIDirectoryContents三个接口,以及NullFileProviderNotFoundFileInfoNotFoundDirectoryContentsNullChangeToken四个类。该 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 Dictionary<string, byte[]> _files; // 以 "/" 开头的相对路径为键 public InMemoryFileProvider(Dictionary<string, 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)即可。这一示例展示了抽象层的核心用法:

  1. 文件不存在时返回NotFoundFileInfo,而不是抛异常;
  2. 目录不存在时返回NotFoundDirectoryContents.Singleton
  3. 不需要变更通知时返回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 生态;
  • 可打包IsPackabletrue,以 NuGet 包Microsoft.Extensions.FileProviders.Abstractions形式发布;
  • 包描述Abstractions of files and directories.,并列出常用类型IDirectoryContentsIFileInfoIFileProvider
  • 根命名空间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 analyzers)PR。

相关规则详见 src/libraries/README.md。这意味着该抽象层保持活跃演进,但所有公开 API 变更都必须经过 API 评审并同步更新 ref 文件。

在生态协作上,该程序集是文件提供程序体系的地基

  • 抽象接口在此定义(本程序集);
  • 物理文件实现见Microsoft.Extensions.FileProviders.Physical
  • 组合式实现见Microsoft.Extensions.FileProviders.Composite
  • 嵌入式资源实现见Microsoft.Extensions.FileProviders.Embedded(NuGet 包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
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询