ASP.NET Core 6框架揭秘实例演示[07]:文件系统
ASP.NET Core应用具有很多读取文件的场景,如读取配置文件、静态Web资源文件(如CSS、JavaScript和图片文件等)、MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件。这些文件的读取都需要使用一个IFileProvider对象。IFileProvider对象构建了一个抽象的文件系统,我们不仅可以利用该系统提供的统一API来读取各种类型的文件,还能及时监控目标文件的变化。(本篇提供的实例已经汇总到《ASP.NET Core 6框架揭秘-实例演示版》)
[S401] 输出文件系统目录结构(源代码)
[S402]读取物理文件内容(源代码)
[S403]读取内嵌文件内容(源代码)
[S404]监控文件的变更(源代码)
[401] 输出文件系统目录结构
文件系统下的文件以目录的形式进行组织,一个IFileProvider对象可以视为针对一个目录的映射。目录除了可以存放文件,还可以包含子目录,所以目录/文件在整体上呈现出树形层次化结构。接下来我们将一个IFileProvider对象映射到一个物理目录,并利用它将所在目录的结构呈现出来。我们创建一个控制台程序,并添加针对NuGet包“Microsoft.Extensions.FileProviders.Physical”的依赖,这个包提供了针对物理文件系统的实现。我们定义了如下一个这个IFileSystem接口,它的ShowStructure方法会将文件系统的整体结构输出到控制台上。该方法的Action<int, string>中的参数将文件系统的节点(目录或者文件)名称呈现出来,两个参数分别代表缩进的层级和目录/文件的名称。
public interface IFileSystem
{
void ShowStructure(Action<int, string> print);
}
如下这个FileSystem类型实现了IFileSystem接口,它利用只读_fileProvider字段表示的IFileProvider对象来提取目录结构。目标文件系统的整体结构通过Print方法以递归的方式呈现出来,其中涉及对IFileProvider对象的GetDirectoryContents方法的调用,该方法返回一个表示“目录内容” 的IDirectoryContents对象。如果对应的目录存在,我们遍历所有子目录和文件。目录和文件体现为一个IFileInfo对象,至于具体是目录还是文件由 IsDirectory属性决定。
public class FileSystem : IFileSystem
{
private readonly IFileProvider _fileProvider;
public FileSystem(IFileProvider fileProvider) => _fileProvider = fileProvider;
public void ShowStructure(Action<int, string> print)
{
int indent = -1;
Print(""); void Print(string subPath)
{
indent++;
foreach (var fileInfo in _fileProvider.GetDirectoryContents(subPath))
{
print(indent, fileInfo.Name);
if (fileInfo.IsDirectory)
{
Print($@"{subPath}\{fileInfo.Name}".TrimStart('\\'));
}
}
indent--;
}
}
}
我们接下来构建一个本地物理目录“c:\test\”,并在其下面创建如图1所示子目录和文件。我们将这个目录映射到一个IFileProvider对象上,并利用后者创建的FileSystem对象将目录结构呈现出来。

图1 FileProvider映射的物理目录结构
整个演示程序体现在如下所示的代码片段中。我们针对目录“c:\test\”创建了一个表示物理文件系统的PhysicalFileProvider对象,并将其注册到创建的ServiceCollection对象上,后者还添加了针对IFileSystem/FileSystem的服务注册。
using App;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; new ServiceCollection()
.AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:\test"))
.AddSingleton<IFileSystem, FileSystem>()
.BuildServiceProvider()
.GetRequiredService<IFileSystem>()
.ShowStructure(Print); static void Print(int layer, string name) => Console.WriteLine($"{new string(' ', layer * 4)}{name}");
我们最终利用ServiceCollection生成的IServiceProvider对象得到FileSystem对象,并调用它的ShowStructure方法将映射的目录结构呈现出来。运行该程序之后,映射物理目录的真实结构会以如图2所示形式输出到控制台上。

图2 运行程序显示的目录结构
[402]读取物理文件内容
接下来我们来演示如何利用IFileProvider对象读取一个物理文件的内容。我们为IFileSystem接口定义如下一个ReadAllTextAsync方法以异步的方式读取指定文件内容,方法的参数表示文件的路径。如下代码片段所示,ReadAllTextAsync方法将指定的文件路径作为参数来调用IFileProvider对象的GetFileInfo方法,以得到一个描述目标文件的IFileInfo对象。我们进一步调用这个IFileInfo的CreateReadStream方法得到读取文件的输出流,进而得到文件的真实内容。
public interface IFileSystem
{
...
Task<string> ReadAllTextAsync(string path);
} public class FileSystem : IFileSystem
{
...
public async Task<string> ReadAllTextAsync(string path)
{
byte[] buffer;
using (var stream = _fileProvider.GetFileInfo(path).CreateReadStream())
{
buffer = new byte[stream.Length];
await stream.ReadAsync(buffer);
}
return Encoding.Default.GetString(buffer);
}
}
我们依然将IFileProvider对象映射为目录“c:\test\”,并该目录中创建一个名为data.txt的文本文件。下面的演示程序利用依赖注入容器的得到FileSystem对象,并调用其ReadAllTextAsync方法将该文件的文本内容读出来。
using App;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using System.Diagnostics; var content = await new ServiceCollection()
.AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:\test"))
.AddSingleton<IFileSystem, FileSystem>()
.BuildServiceProvider()
.GetRequiredService<IFileSystem>()
.ReadAllTextAsync("data.txt"); Debug.Assert(content == File.ReadAllText(@"c:\test\data.txt"));
[403]读取内嵌文件内容
我们一直强调IFileProvider接口代表一个抽象的文件系统,具体文件的提供方式取决于具体的实现类型。演示实例中定义的FileSystem并没有限定具体使用何种类型的IFileProvider,我们可以通过服务注册的方式指定任意实现类型。我们现在将data.txt文件直接以资源文件的形式编译到程序集中,并利用一个EmbeddedFileProvider对象来提取它的内容。EmbeddedFileProvider类型由NuGet包“Microsoft.Extensions.FileProviders.Embedded”提供,在添加了上述NuGet包的引用之后,我们直接将data.txt文件添加到控制台应用的项目根目录下。为了将该文件内嵌到编译生成的程序集中,我们可以在Visual Studio的解决方案窗口中右键选择这个文件,在打开的文件属性窗口中按照如图3所示的方式将Build Action属性设置为“Embedded resource”。

图3 设置文件的Build Action属性
上述针对内嵌文件的设置会改变项目文件(.csproj文件)的内容。具体来说,当文件的Build Action属性被设置为“Embedded resource”后,如下所示的<EmbeddedResource>节点会自动添加到项目文件中,所以我们也可以直接修改项目文件达到相同的目的。
<Project Sdk="Microsoft.NET.Sdk">
...
<ItemGroup>
<EmbeddedResource Include="data.txt"/>
</ItemGroup>
</Project>
在如下所示的演示程序中,我们根据入口程序集创建了一个EmbeddedFileProvider对象,并用它代替原来的PhysicalFileProvider对象的服务注册。我们采用完全一致的编程方式读取内嵌文件data.txt的内容。
using App;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using System.Diagnostics;
using System.Reflection;
using System.Text; var assembly = Assembly.GetEntryAssembly()!;
var content = await new ServiceCollection()
.AddSingleton<IFileProvider>(new EmbeddedFileProvider(assembly))
.AddSingleton<IFileSystem, FileSystem>()
.BuildServiceProvider()
.GetRequiredService<IFileSystem>()
.ReadAllTextAsync("data.txt"); var stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.data.txt");
var buffer = new byte[stream!.Length];
stream.Read(buffer, 0, buffer.Length); Debug.Assert(content == Encoding.Default.GetString(buffer));
[404]监控文件的变更
确定加载到内存中的数据与源文件的一致性并自动同步是一个很常见的需求。例如,我们将配置定义在一个JSON文件中,应用启动的时候会读取该文件并将其转换成对应的Options对象。如果能够检测到文件的变换,那么配置文件被修改了之后,程序就可以自动读取新的内容并将其绑定到Options对象上。对文件系统实施监控并在其发生改变时发送通知也是IFileProvider对象提供的核心功能之一。下面的程序演示如何使用PhysicalFileProvider对某个物理文件实施监控,并在目标文件被更新时重新读取新的内容。
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
using System.Text; using var fileProvider = new PhysicalFileProvider(@"c:\test");
string? original = null;
ChangeToken.OnChange(() => fileProvider.Watch("data.txt"), Callback);
while (true)
{
File.WriteAllText(@"c:\test\data.txt", DateTime.Now.ToString());
await Task.Delay(5000);
} async void Callback()
{
var stream = fileProvider.GetFileInfo("data.txt").CreateReadStream();
{
var buffer = new byte[stream.Length];
await stream.ReadAsync(buffer);
var current = Encoding.Default.GetString(buffer);
if (current != original)
{
Console.WriteLine(original = current);
}
}
}
如上面的代码片段所示,我们针对目录“c:\test”创建了一个PhysicalFileProvider对象,并调用其Watch方法对指定的data.txt文件实施监控。该方法会利用返回的IChangeToken对象发送文件更新的通知。我们调用ChangeToken的静态方法OnChange针对这个IChangeToken对象注册了一个自动读取并显示文件内容的回调。我们每隔5秒对data.txt文件进行一次修改,并将当前时间作为文件的内容。程序启动之后,作为文件内容的当前时间每隔5秒就会以图4所示的方式输出到控制台上。

图4 实时显示监控文件的内容
ASP.NET Core 6框架揭秘实例演示[07]:文件系统的更多相关文章
- ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式
.NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...
- ASP.NET Core 6框架揭秘实例演示[09]:配置绑定
我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...
- ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式
依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...
- ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式
在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...
- ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法
一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...
- ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]
<诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...
- ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法
为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...
- ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出
针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...
- ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用
.NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...
随机推荐
- 信不信由你!iPhone6屏幕宽度不一定是375px,iPhone6 Plus屏幕宽度不一定是414px
看到这个题目你可能不信,引出这个问题的缘由是几次项目中Chrome模拟器和iPhone6真机预览效果不一致. 为什么在Chrome Emulation模拟手机页面和真机预览效果不一致? 以前觉得不外乎 ...
- HDU 2084 数塔 (动态规划DP)
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=2084 题目分析:此题采用动态规划自底向上计算,如果我们要知道所走之和最大,那么最后一步肯定是走最后一排 ...
- Git 的基本命令的使用
1.获得Git仓库(克隆一份代码到本地仓库) git clone url 2.更新本地的代码 git pull 3.查看本地修改的文件 git status 4.将本地的修改加到stage中 git ...
- day 11 算法的时间空间复杂度
(1).有以下程序: 求输入的n值(除1和n)之外的所有因子之和. 分析:这里函数内的循环体i初值不能为零.%是表示"取余",0除以任何数都不会存在余数的,所有是余数为0. (2) ...
- 《剑指offer》面试题11. 旋转数组的最小数字
问题描述 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的 ...
- JS调用堆栈
调用栈 JavaScript 是一门单线程的语言,这意味着它只有一个调用栈,因此,它同一时间只能做一件事.如果我们运行到一个函数,它就会将其放置到栈顶.当从这个函数返回的时候,就会将这个函数从栈顶弹出 ...
- 【C++】自定义数据类型
自定义数据类型 标签:c++ 目录 自定义数据类型 一.结构体 定义方法: 特点: 成员访问方式: 初始化: 结构数组 指针和动态内存分配: 结构变量作为函数参数: 二.联合 定义方法: 特点: 举例 ...
- python文档2-unittest单元测试之mock.patch
介绍mock里面另一种实现方式,patch装饰器的使用,patch() 作为函数装饰器,为您创建模拟并将其传递到装饰函数 patch简介 1.unittest.mock.patch(target,ne ...
- 0,NULL和nullpter
#include <iostream> using namespace std; void f(int) { cout<<"f(int)"<<e ...
- 沁恒CH32F103C8T6(三): PlatformIO DAPLink和WCHLink下载配置
目录 沁恒CH32F103C8T6(一): Keil5环境配置,示例运行和烧录 沁恒CH32F103C8T6(二): Linux PlatformIO环境配置, 示例运行和烧录 沁恒CH32F103C ...