追求艺术的脚步
Be the change you want to see in the world.Things are always as hard as you think but always as easy as you do.

IFileProvider 对象构建了一个抽象的文件系统,我们不仅可以利用该系统提供的统一 API 读取各种类型的文件,还能及时监控目标文件的变化。

 

4.1 抽象的文件系统

IFileProvider 对象用于构建一个具有层次化目录结构的抽象文件系统,该系统中的目录和文件都是一个抽象的概念。

 

4.1.1 树形层次结构

一个 IFileProvider 对象可以视为针对一个目录的映射。

增加 Microsoft.Extensions.FileProviders.Physical  NuGet 包:dotnet add package Microsoft.Extensions.FileProviders.Physical,这个包提供了物理文件系统的实现。

IFileProvider#GetDirectoryContents 方法返回一个表示“目录内容”的 IDirectoryContents 对象。如果对应的目录存在,可遍历所有子目录和文件,目录和文件体现为一个 IFileInfo 对象,通过 IsDirectory 属性判断是目录还是文件。

GetDirectoryContents 返回一个 IDirectoryContents 对象,具体定义如下:

 1    //
 2     // Summary:
 3     //     Represents a directory's content in the file provider.
 4     public interface IDirectoryContents : IEnumerable<IFileInfo>, IEnumerable
 5     {
 6         //
 7         // Summary:
 8         //     True if a directory was located at the given path.
 9         bool Exists { get; }
10     }

从定义看实现了 IEnumerable<IFileInfo> 接口,因此可以用 froeach 循环。

 

4.1.2 读取文件内容

IFileProvider#GetFileInfo 方法可以得到一个描述目标文件的 IFileInfo 对象,再调用 IFileInfo#CreateReadStream 得到输出流。

 1   static async Task MainAsync(){
 2             IServiceProvider provider = new ServiceCollection()
 3                     .AddSingleton<IFileProvider>(new PhysicalFileProvider(@"D:\repository\Core\FirstCore"))  //
 4                     .AddSingleton<IFileSystem, FileSystem.FileSystem>()
 5                     .BuildServiceProvider();
 6 
 7             var content = await provider.GetRequiredService<IFileSystem>().ReadAllTextAsync(@"cities.txt");
 8             Debug.Assert(content == File.ReadAllText(@"D:\repository\Core\FirstCore\cities.txt"));
 9     }
10 
11   static void Print(int layer, string name)
12                 => Console.WriteLine($"{new string(' ', layer * 4)}{name}");

 

4.1.3 监控文件的变化

对文件系统实施监控并在其发生改变时发送通知也是 IFileProvider 提供的核心功能之一。

 1   var fileProvider = new PhysicalFileProvider(@"d:\");
 2             string? original = null;
 3 
 4             ChangeToken.OnChange(() => fileProvider.Watch("test.txt"), Callback);
 5 
 6             while(true){
 7                 File.WriteAllText(@"d:\test.txt", DateTime.Now.ToString());
 8                 await Task.Delay(5000);
 9             }
10 
11             async void Callback(){
12                 var stream = fileProvider.GetFileInfo("test.txt").CreateReadStream();
13                 {
14                     var buffer = new byte[stream.Length];
15                     await stream.ReadAsync(buffer);
16 
17                     var current = Encoding.Default.GetString(buffer);
18                     if(current != original){
19                         Console.WriteLine(original = current);
20                     }
21                 }
22             }

上面代码中,使用 IFileProvider 对象的 Watch 方法监控指定的文件,该方法会利用返回的  IChangeToken 对象发送文件更新的通知。调用 ChangeToken 的静态方法 OnChange 给 IChangeToken 对象注册了一个回调。

后面有讲到 IChangeToken 只能使用一次,这里为什么可以一直监控?

 

 

4.2 文件与目录

接下来从设计的角度对文件系统进行系统的介绍。

文件系统更新的通知是由一个 IChangeToken 对象发出来的。

 

4.2.1 IChangeToken 

IChangeToken 传播已发生更改的通知。 IChangeToken 驻留在 Microsoft.Extensions.Primitives 命名空间中。

IChangeToken 具有以下两个属性:

  1)ActiveChangeCallbacks,当数据发生变化时指示令牌是否主动引发回调。

  2)HasChanged,接收一个指示是否发生更改的值。

IChangeToken 接口具有 RegisterChangeCallback(Action<Object>, Object) 方法,用于注册在令牌更改时调用的回调。该方法会返回一个 IDisposable 对象,可以用其 Dispose 方法解除回调。

常使用的 IChangeToken 的实现类型是 CancellationChangeToken,它是对一个 CancellationToken 对象的封装。

还有 CompositeChangeToken,它表示由多个 IChangeToken 组合而成的复合型 IChangeToken 对象。只要组成它的任何一个 IChangeToken 发生改变,其 HasChanged 就为 True,注册的回调随之被执行。只要任何一个 IChangeToken 的同名属性返回 True,ActiveChangeCallbacks 就为 True。

由于 IChangeToken 对象没有状态“复位”功能,所以变更一旦发生,它的使命就完成了,下次变更检测将交给新创建的 IChangeToken 对象。

 

4.2.2 IFileProvider

IFileProvider 有 3 个核心方法。

 1    //
 2     // Summary:
 3     //     A read-only file provider abstraction.
 4     public interface IFileProvider
 5     {
 6         //
 7         // Summary:
 8         //     Enumerate a directory at the given path, if any.
 9         //
10         // Parameters:
11         //   subpath:
12         //     Relative path that identifies the directory.
13         //
14         // Returns:
15         //     Returns the contents of the directory.
16         IDirectoryContents GetDirectoryContents(string subpath);
17         //
18         // Summary:
19         //     Locate a file at the given path.
20         //
21         // Parameters:
22         //   subpath:
23         //     Relative path that identifies the file.
24         //
25         // Returns:
26         //     The file information. Caller must check Exists property.
27         IFileInfo GetFileInfo(string subpath);
28         //
29         // Summary:
30         //     Creates a Microsoft.Extensions.Primitives.IChangeToken for the specified filter.
31         //
32         // Parameters:
33         //   filter:
34         //     Filter string used to determine what files or folders to monitor. Example: **/*.cs,
35         //     *.*, subFolder/**/*.cshtml.
36         //
37         // Returns:
38         //     An Microsoft.Extensions.Primitives.IChangeToken that is notified when a file
39         //     matching filter is added, modified or deleted.
40         IChangeToken Watch(string filter);
41     }

IFileInfo 对象可以用于描述目录和文件,但是 GetFileInfo 方法只为得到指定路径的文件而不是目录,无论指定的目录或者文件是否存在,GetFileInfo 和 GetDirectoryContents 一样,都会返回一个具体的 IFileInfo 或 IDirectoryContents,通过 Exists 属性确定是否存在。

 1    public interface IFileInfo
 2     {
 3         //
 4         // Summary:
 5         //     True if resource exists in the underlying storage system.
 6         bool Exists { get; }
 7         //
 8         // Summary:
 9         //     The length of the file in bytes, or -1 for a directory or non-existing files.
10         long Length { get; }
11         //
12         // Summary:
13         //     The path to the file, including the file name. Return null if the file is not
14         //     directly accessible.
15         string? PhysicalPath { get; }
16         //
17         // Summary:
18         //     The name of the file or directory, not including any path.
19         string Name { get; }
20         //
21         // Summary:
22         //     When the file was last modified
23         DateTimeOffset LastModified { get; }
24         //
25         // Summary:
26         //     True for the case TryGetDirectoryContents has enumerated a sub-directory
27         bool IsDirectory { get; }
28 
29         //
30         // Summary:
31         //     Return file contents as readonly stream. Caller should dispose stream when complete.
32         //
33         // Returns:
34         //     The file stream
35         Stream CreateReadStream();
36     }

IFileProvider#Watch 方法接收一个字符串类型的参数 filter,可以使用该参数指定一个“文件匹配模式”(File Globbing Pattern)表达式。这种表达式类似于正则表达式但比正则简单很多,它只包含“*”一种通配符。“*”表示路径分隔符之间的单个分段内的所有字符;“**”表示可以跨越多个路径分段的所有字符。

一般来说,调用 IFileProvider 的方法时所指定的目标文件或目录的路径,提供的是根目录的相对路径,指定的这个路径可以采用“/”作为前缀,但这个不是必需的。

 

4.2.3 两个特殊的文件系统

1. NullFileProvider

NullFileProvider 表示一个不包含任何目录和文件的文件系统,定义在 “Microsoft.Extensions.FileProviders” 命名空间中。

它的 GetDirectoryContents 和 GetFileInfo 分别返回 NotFoundDirectoryContents 和 NotFoundFileInfo 对象。空的文件系统不存在所谓的目录和文件变化,因此 Watch 方法返回一个 NullChangeToken 对象。

 

2. CompositeFileProvider

需要导入包:dotnet add package Microsoft.Extensions.FileProviders.Composite

CompositeFileProvider 表示一个由多个 IFileProvider 构建的复合式的文件系统,定义在 “Microsoft.Extensions.FileProviders” 命名空间中。

CompositeFileProvider 由多个 IFileProvider 对象构成,当调用 GetFileInfo 方法时,会遍历这些 IFileProvider 对象,直到找到一个存在的文件。如果都没有,那么返回一个 NoteFoundFileInfo 对象。遍历的顺序取决于构建 CompositeFileProvider 时提供的顺序,因为如果有优先级的要求,把优先级高的放在前面。

某个目录的内容是由所有这些内部 IFileProvider 对象共同提供的,GetDirectoryContents 返回的是 CompositeDirectoryContents。

 1       IFileProvider provider1 = new PhysicalFileProvider(@"d:\work\1");
 2         IFileProvider provider2 = new PhysicalFileProvider(@"d:\work\2");
 3         IFileProvider compositeProvider = new CompositeFileProvider(provider1, provider2);
 4         IDirectoryContents contents = compositeProvider.GetDirectoryContents("a");
 5 
 6         StringBuilder sb = new StringBuilder();
 7         
 8         foreach(var fileInfo in contents){
 9             sb.Append($"{fileInfo.PhysicalPath}\r\n");
10         }
11 
12         return sb.ToString();

如果多个 IFileProvider 对象存在一个具有相同路径的文件,则从优先提供的 IFileProvider 中提取。

 

 

4.3 物理文件系统

4.3.1 PhysicalFileInfo

一个 PhysicalFileProvider 总是映射到某个物理目录上,被映射的目录所在的路径通过构造函数的参数 root 提供。它的 GetFileInfo 方法返回的是 PhysicalFileInfo 对象表示指定路径的文件,PhysicalFileInfo 实际上是 FileInfo 的一个封装。

以下场景 GetFileInfo 方法返回 NotFoundFileInfo 对象:

  1)不存在与指定路径相匹配的物理文件;

  2)指定的是文件的绝对路径;

  3)目标文件为隐藏文件。

 

4.3.2 PhysicalDirectoryInfo

物理目录,是对 System.IO.DirectoryInfo 的封装。

Length 属性返回 -1.

 

4.3.3 PhsicalDirectoryContents

PhysicalFileProvider#GetDirectoryContents ,如果指定的路径指向一个具体的目录,返回 PhysicalDirectoryContents,后者是一组 IFileInfo 的集合,封装的 IFileInfo 要么是描述子目录的 PhysicalDirectoryInfo 对象,要么是描述物理文件的 PhysicalFileInfo 对象。

 

4.3.4 NotFoundDirectoryContents

如果指定的路径并不存在或者是一个绝对路径,则返回一个 Exists 属性为 false 的 NotFoundDirectoryContents 对象。

可以使用静态属性 Singleton 得到对应的单例对象。

 

4.3.5 PhysicalFilesWatcher

监控文件或者目录的变化都会实时反映到 Watch 方法返回的 IChangeToken 对象上。它是利用一个 FileSystemWatcher 对象来完成对指定根目录下子目录和文件的监控。

在 Watch 方法中指定的 Globbing Pattern 表达式必须是当前根目录的相对路径。

 

 

4.4 内嵌文件系统

EmbeddedFileProvider 可以使用统一的编程方式来读取内嵌的资源文件。该类型定义在“Microsoft.Extensions.FileProvider.Embedded” NuGet 包中。

 

4.4.1 将项目文件变成内嵌资源

要将静态文件作为目标程序集的内嵌文件,需要修改当前多项目的 .csproj 文件,在文件中添加<ItemGroup>/<EmbeddedResource>元素,并利用 Include 属性显式地把资源文件包含进来。

<EmbeddedResource> 节点的 Include 属性可以设置多个以分号分隔的路径,也可以使用通配符将一组文件包含进来。排除不符合要求的文件使用 Exclude 属性。

 

4.4.2 读取资源文件

一个程序集主要由两种类型的文件构成,即承载 IL 代码的托管模块文件和编译时内嵌的资源文件。程序集使用清单(Manifest)记录组成程序集的所有文件成员。

资源文件在程序集中是扁平化存储的,编译器会根据原始文件所在的路径对资源文件重新命名,具体规则是“{BaseNamespce}.{Path}”。

Assembly#GetManifestResourceNames:返回所有的资源文件名称。

 

posted on 2022-12-12 17:57  小笨笨  阅读(102)  评论(0编辑  收藏  举报