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]:文件系统的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  2. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

  3. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  4. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  5. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  6. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

  7. ASP.NET Core 6框架揭秘实例演示[14]:日志的进阶用法

    为了对各种日志框架进行整合,微软创建了一个用来提供统一的日志编程模式的日志框架.<日志的基本编程模式>以实例演示的方式介绍了日志的基本编程模式,现在我们来补充几种"进阶" ...

  8. ASP.NET Core 6框架揭秘实例演示[15]:针对控制台的日志输出

    针对控制台的ILogger实现类型为ConsoleLogger,对应的ILoggerProvider实现类型为ConsoleLoggerProvider,这两个类型都定义在 NuGet包"M ...

  9. ASP.NET Core 6框架揭秘实例演示[16]:内存缓存与分布式缓存的使用

    .NET提供了两个独立的缓存框架,一个是针对本地内存的缓存,另一个是针对分布式存储的缓存.前者可以在不经过序列化的情况下直接将对象存储在应用程序进程的内存中,后者则需要将对象序列化成字节数组并存储到一 ...

随机推荐

  1. 计算机视觉--CV技术指南文章汇总

    前言  本文汇总了过去本公众号原创的.国外博客翻译的.从其它公众号转载的.从知乎转载的等一些比较重要的文章,并按照论文分享.技术总结三个方面进行了一个简单分类.点击每篇文章标题可阅读详细内容 欢迎关注 ...

  2. 一篇文章带你搞懂DEX文件的结构

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 DEX文件就是Android Dalvik虚拟机运行的程序,关于DEX文件的结构的重要性我就不多说了.下面,开练! 建议:不要只看 ...

  3. Android官方文档翻译 十四 3.2Supporting Different Screens

    Supporting Different Screens 支持不同的屏幕 This lesson teaches you to 这节课教给你 Create Different Layouts 创建不同 ...

  4. Android官方文档翻译 十一 2.4Overlaying the Action Bar

    Overlaying the Action Bar 叠加菜单栏 This lesson teaches you to 这节课教给你: Enable Overlay Mode 启用叠加模式 For An ...

  5. linux目录作用

    / 根目录 /bin 命令保存目录(普通用户就可以使用的命令) /sbin 命令保存目录(超级用户才能使用的命令) /boot 启动目录,启动相关文件 /dev 设备文件保存目录 /etc 配置文件保 ...

  6. 2月3日 体温APP开发记录

    1.阅读构建之法 现代软件工程(第三版) 2.观看Android开发视频教程最新版 Android Studio开发 3.回返地址学习,下载导入相关jar包

  7. 【刷题-LeetCode】148 Sort List

    Sort List Sort a linked list in O(n log n) time using constant space complexity. Example 1: Input: 4 ...

  8. JDK原子操作类

    在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型.原子更新数组.原子更新引用和原子更新属性(字段).Atomic包里的类基本都是使用Unsafe实现的包装类. ...

  9. vue学习2-bind属性绑定

    需要加上 v-html才能显示网页

  10. cobbler最小化安装centos8

    centos8 已经发布了GA版本,迫不及待的想尝鲜了,然后现实总是那么残酷,一直安装失败,具体安装步骤如下: 假设cobbler已配置完成. 1.下载centos8 iso镜像 wget http: ...