ASP.NET Core应用 具有很多读取文件的场景,比如配置文件、静态Web资源文件(比如CSS、JavaScript和图片文件等)以及MVC应用的View文件,甚至是直接编译到程序集中的内嵌资源文件。这些文件的读取都需要使用到一个IFileProvider对象。IFileProvider对象构建了一个抽象的文件系统,我们不仅可以利用它提供的统一API来读取各种类型的文件,还能及时监控目标文件的变化。

一、树形层次结构

IFileProvider对象为我们构建了一个具有层次化目录结构的文件系统。由于IFileProvider是一个接口,所以由它构建的是一个抽象化的文件系统,这里所谓的目录和文件都是一个抽象的概念。具体的文件可能对应一个物理文件,也可能保存在数据库中,或者来源于网络,甚至有可能根本就不存在,其内容需要在读取时动态生成。目录也仅仅是组织文件的逻辑容器。为了让读者朋友们对这个文件系统有一个大体认识,我们先来演示几个简单的实例。

文件系统管理的所有文件以目录的形式进行组织,一个IFileProvider对象可以视为针对一个根目录的映射。目录除了可以存放文件之外,还可以包含子目录,所以目录/文件在整体上呈现出树形化层次化结构。接下来我们将一个IFileProvider对象映射到一个物理目录,并利用它将所在目录的结构呈现出来。

我们演示实例是一个普通的控制台程序。我们在演示实例中定义了如下一个IFileManager接口,它利用一个唯一的ShowStructure方法将文件系统的整体结构显示出来。该方法具有一个类型为Action<int, string>的参数负责将文件系统的节点(目录或者文件)名称呈现出来。这个Action<int, string>对象的两个参数分别代表缩进的层级和目录/文件的名称。

public interface IFileManager
{
void ShowStructure(Action<int, string> render);
}

我们定义如下这个FileManager类作为对IFileManager接口的默认实现,它利用只读_fileProvider字段表示的IFileProvider对象来提取目录结构。目标文件系统的整体结构通过Render方法以递归的方式呈现出来,其中涉及到对IFileProvider对象的GetDirectoryContents方法的调用。该方法返回一个IDirectoryContents对象表示指定目录的内容,如果对应的目录存在,我们可以遍历该对象得到它的子目录和文件。目录和文件最终体现为一个IFileInfo对象来,至于IFileInfo对象对应的就是一个目录还是一个文件,则通过其IsDirectory属性来区分。

public class FileManager : IFileManager
{
private readonly IFileProvider _fileProvider;
public FileManager(IFileProvider fileProvider) => _fileProvider = fileProvider;
public void ShowStructure(Action<int, string> render)
{
int indent = -1;
Render("");
void Render(string subPath)
{
indent++;
foreach (var fileInfo in _fileProvider.GetDirectoryContents(subPath))
{
render(indent, fileInfo.Name);
if (fileInfo.IsDirectory)
{
Render($@"{subPath}\{fileInfo.Name}".TrimStart('\\'));
}
}
indent--;
}
}
}

接下来我们构建一个本地物理目录“c:\test\”,并按照如下图所示的结构在它下面创建相应的子目录和文件。我们会将这个目录映射到一个IFileProvider对象上,并进一步利用它创建出上面这个FileManager对象。我们最终调用这个FileManager对象的ShowStructure方法将目录结构呈现出来。

整个演示程序体现在如下的代码片段中。我们针对目录“c:\test\”创建了一个表示物理文件系统的PhysicalFileProvider对象,并将其注册到创建的ServiceCollection对象上。除此之外,ServiceCollection对象上还添加了针对IFileManager/FileManager的服务注册。

class Program
{
static void Main()
{
static void Print(int layer, string name) => Console.WriteLine($"{new string(' ', layer * 4)}{name}");
new ServiceCollection()
.AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:\test"))
.AddSingleton<IFileManager, FileManager>()
.BuildServiceProvider()
.GetRequiredService<IFileManager>()
.ShowStructure(Print);
}
}

我们最终利用ServiceCollection生成的IServiceProvider对象得到FileManager对象,并调用该对象的ShowStructure方法将PhysicalFileProvider对象映射的目录结构呈现出来。当我们运行该程序之后,控制台上将呈现出如下图所示的输出结果,该结果为我们展示了映射物理目录的真实结构。(S501)

二、读取文件内容

前面我们演示了如何利用IFileProvider对象将文件系统的结构完整地呈现出来,接下来我们来演示如何利用它来读取一个物理文件的内容。我们为IFileManager定义如下一个ReadAllTextAsync方法以异步的方式读取指定文件内容,方法的参数表示文件的路径。如下面的代码片段所示,ReadAllTextAsync方法将指定的文件路径作为参数调用IFileProvider对象的GetFileInfo方法得到一个IFileInfo对象。我们最终调用这个IFileInfo对象的CreateReadStream方法得到读取文件的输出流,进而得到文件的真实内容。

public interface IFileManager
{
...
Task<string> ReadAllTextAsync(string path);
} public class FileManager : IFileManager
{
...
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, 0, buffer.Length);
}
return Encoding.Default.GetString(buffer);
}
}

假设我们依然将FileManager使用的IFileProvider映射为目录“c:\test\”,现在我们在该目录中创建一个名为data.txt的文本文件,并在该文件中任意写入一些内容。接下来我们在Main方法中编写了如下的程序利用依赖注入的方式得到FileManager对象,并读取文件data.txt的内容。最终的调试断言旨在确定通过IFileProvider读取的确实就是目标文件的真实内容。(S502)

class Program
{
static async Task Main()
{
var content = await new ServiceCollection()
.AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:\test"))
.AddSingleton<IFileManager, FileManager>()
.BuildServiceProvider()
.GetRequiredService<IFileManager>()
.ReadAllTextAsync("data.txt"); Debug.Assert(content == File.ReadAllText(@"c:\test\data.txt"));
}
}

三、内嵌文件系统

我们一直在强调由IFileProvider结构构建的是一个抽象的具有目录结构的文件系统,具体文件的提供方式取决于对具体的IFileProvider对象是怎样一个类型。我们演示实例定义的FileManager并没有限定具体使用何种类型的IFileProvider,该对象是在应用中通过依赖注入的方式指定的。由于上面的应用程序注入的是一个PhysicalFileProvider对象,所以我们可以利用它来读取对应物理目录下的某个文件。假设现在将这个data.txt直接以资源文件的形式编译到程序集中,我们就需要使用另一个名为EmbeddedFileProvider的实现类型。现在我们直接将这个data.txt文件添加到控制台应用的项目根目录下。在默认的情况下,当我们编译项目的时候这样的文件并不能成为内嵌到目标程序集的资源文件,我们需要利用VS将该文件的“Build Action”属性按照如下所示的方式设置为“Embedded resource”。

上图所示的设置将会体现在项目文件(.csproj文件)上。具体来说,项目文件会以如下的形式添加一个<EmbeddedResource>元素将文件data.txt设置为内嵌到编译后生成的程序集的内嵌资源文件。

<Project Sdk="Microsoft.NET.Sdk">
...
<ItemGroup>
<EmbeddedResource Include="data.txt"/>
</ItemGroup>
</Project>

我们编写了如下的程序来演示针对内嵌于程序集中的资源文件的读取。我们首先得到当前入口程序集,并利用它创建了一个EmbeddedFileProvider对象,它代替原来的PhysicalFileProvider对象被注册到ServiceCollection之中。我们接下来采用了完全一致的编程方式得到FileManager对象并利用它读取内嵌文件data.txt的内容。为了验证读取的目标文件准确无误,我们采用直接读取资源文件的方式得到了内嵌文件data.txt的内容,并利用一个调试断言确定两者的一致性。(S503)

class Program
{
static async Task Main()
{
var assembly = Assembly.GetEntryAssembly(); var content1 = await new ServiceCollection()
.AddSingleton<IFileProvider>(new EmbeddedFileProvider(assembly))
.AddSingleton<IFileManager, FileManager>()
.BuildServiceProvider()
.GetRequiredService<IFileManager>()
.ReadAllTextAsync("data.txt"); var stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.data.txt");
var buffer = new byte[stream.Length];
stream.Read(buffer, 0, buffer.Length);
var content2 = Encoding.Default.GetString(buffer); Debug.Assert(content1 == content2);
}
}

四、监控文件的变化

在文件读取场景中,确定加载到内存中的数据与源文件的一致性并自动同步是一个很常见的需求。比如说我们将配置定义在一个JSON文件中,应用启动的时候会读取该文件并将其转换成对应的Options对象。在很多情况下,如果我们改动了配置文件, 最新的配置数据只有在应用重启之后才能生效。如果我们能够以一种高效的方式对配置文件进行监控,并在其发生改变的情况下向应用发送通知,那么应用就能在不用重启的情况下重新读取配置文件,进而实现Options对象承载的内容和原始配置文件完全同步。

对文件系统实施监控并在其发生改变时发送通知也是IFileProvider对象提供的核心功能之一。接下来我们依然使用前面这个程序来演示如何使用PhysicalFileProvider对某个物理文件实施监控,并在目标文件的内容发生改变的时候重新读取新的内容。

class Program
{
static async Task Main()
{
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, 0, buffer.Length);
string current = Encoding.Default.GetString(buffer);
if (current != original)
{
Console.WriteLine(original = current);
}
}
}
}
}
}

如上面的代码片段所示,我们针对目录“c:\test”创建了一个PhysicalFileProvider对象,并调用其Watch方法对指定的文件data.txt实施监控。该方法的返回一个IChangeToken对象,我们正是利用这个对象接收文件改变的通知。我们调用ChangeToken的静态方法OnChange针对这个对象注册了一个回调实现对源文件的重新读取和显示,当源文件发生改变的时候,注册的回调会自动执行。我们以每隔5秒的间隔对文件data.txt作一次修改,而文件的内容为当前时间。所以当我们的程序启动之后,每隔5秒钟当前时间就会以如下图的方式呈现在控制台上。

[ASP.NET Core 3框架揭秘] 文件系统[1]:抽象的“文件系统”
[ASP.NET Core 3框架揭秘] 文件系统[2]:总体设计
[ASP.NET Core 3框架揭秘] 文件系统[3]:物理文件系统
[ASP.NET Core 3框架揭秘] 文件系统[4]:程序集内嵌文件系统

[ASP.NET Core 3框架揭秘] 文件系统[1]:抽象的“文件系统”的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

  2. [ASP.NET Core 3框架揭秘] 文件系统[2]:总体设计

    在<抽象的"文件系统">中,我们通过几个简单的实例演示从编程的角度对文件系统做了初步的体验,接下来我们继续从设计的角度来进一步认识它.这个抽象的文件系统以目录的形式来组 ...

  3. [ASP.NET Core 3框架揭秘] 文件系统[4]:程序集内嵌文件系统

    一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以采用统一的编程方式来读取内嵌的资源文件,该类型定义在 "Microsoft.Ex ...

  4. [ASP.NET Core 3框架揭秘] 文件系统[3]:物理文件系统

    ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件.View文件以及作为Web资源的静态文件.物理文件系统由定义在NuGet包"Microsoft.Extension ...

  5. [ASP.NET Core 3框架揭秘] 依赖注入:控制反转

    ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...

  6. [ASP.NET Core 3框架揭秘] 配置[7]:多样化的配置源[中篇]

    物理文件是我们最常用到的原始配置载体,而最佳的配置文件格式主要有三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...

  7. [ASP.NET Core 3框架揭秘] 跨平台开发体验: Linux

    如果想体验Linux环境下开发.NET Core应用,我们有多种选择.一种就是在一台物理机上安装原生的Linux,我们可以根据自身的喜好选择某种Linux Distribution,目前来说像RHEL ...

  8. 《ASP.NET Core 3框架揭秘》读者群,欢迎加入

    作为一个17年的.NET开发者,我对一件事特别不能理解:我们的计算机图书市场充斥着一系列介绍ASP.NET Web Forms.ASP.NET MVC.ASP.NET Web API的书籍,但是却找不 ...

  9. 《ASP.NET Core 3框架揭秘》博文汇总

    在过去一段时间内,写了一系列关于ASP.NET Core 3相关的文章,其中绝大部分来源于即将出版的<ASP.NET Core 3框架揭秘>(博文只能算是"初稿",与书 ...

随机推荐

  1. std::multiset

    Set.multiset都是集合类, 差别在与set中不允许有重复元素, multiset中允许有重复元素. sets和multiset内部以平衡二叉树实现.   multiset 多重集合容器是一个 ...

  2. 定制的print()输出格式

    #定制print时的显示内容 #烤地瓜案例:主要显示格式digua("cd1","cd2","cd3""),而不是就直接在一个列表 ...

  3. 打python&adb组合拳,实现微信读书永久免费读

    用过“微信读书”的朋友都知道,如果我们想阅读全本的付费书籍,除了购买整本(使用书币)外,还可以使用无限卡.可无论是购买全书还是无限卡,归根结底都是要花银子的. 除此之外,还有一种方式——用阅读时长兑换 ...

  4. 在react项目中使用redux or mobx?

    主要比较参数: 库体积,打包项目体积 开发体验 性能对比 在对比参数前首先分析一下redux和mobx的设计模式,redux和mobx都没有使用传统的mvc/mvvm形式,而且他们使用flux结构也略 ...

  5. volatile关键字使用

    1.volatile 使用场景(多线程情况下): 适合使用在 一写多读 的情况下: 2.volatile 理解分析: 使用 volatile 关键字修饰的变量,值在改变时会直接刷新到 主内存 中,而不 ...

  6. Java基础(三十一)JDBC(1)常用类和接口

    1.Driver接口 每种数据库的驱动程序都应该提供一个实现java.sql.Driver接口的类.在加载某一驱动程序的Driver类时,它应该创建自己的实例并向java.sql.DriverMana ...

  7. Codeforces Round #595 (Div. 3)B2 简单的dfs

    原题 https://codeforces.com/contest/1249/problem/B2 这道题一开始给的数组相当于地图的路标,我们只需对每个没走过的点进行dfs即可 #include &l ...

  8. 浅谈Retinex

    Retinex是上个世纪七十年代由Land提出的色彩理论.我认为其核心思想基于俩点 (1)在颜色感知时,人眼对局部相对光强敏感程度要优于绝对光强. (2)反射分量R(x,y)储存有无光源物体的真实模样 ...

  9. H5 + WebGL 实现的楼宇自控 3D 可视化监控

    前言 智慧楼宇和人们的生活息息相关,楼宇智能化程度的提高,会极大程度的改善人们的生活品质,在当前工业互联网大背景下受到很大关注.目前智慧楼宇可视化监控的主要优点包括: 智慧化 -- 智慧楼宇是一个生态 ...

  10. C语言I博客作业04

    这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/SE2019-1/homework/9773 我在这个课程的目标 ...