在《抽象的“文件系统”》中,我们通过几个简单的实例演示从编程的角度对文件系统做了初步的体验,接下来我们继续从设计的角度来进一步认识它。这个抽象的文件系统以目录的形式来组织文件,我们可以利用它读取某个文件的内容,还可以对目录或者文件实施监控并及时得到变化的通知。由于IFileProvider对象提供了针对文件系统变换的监控功能,在.NET Core下里类似的功能大都利用一个IChangeToken对象来实现,所以我们在对IFileProvider进行深入介绍之前有必要先来了解一下IChangeToken。

一、IChangeToken

从字面上理解的IChangeToken对象就是一个与某组监控数据关联的“令牌(Token)”,它能够在检测到数据改变的时候及时地对外发出一个通知。如果IChangeToken关联的数据发生改变,它的HasChanged属性将变成True。我们可以调用其RegisterChangeCallback方法注册一个在数据发生改变时可以自动执行的回调,该方法会返回一个IDisposable对象,我们通过用其Dispose方法解除注册的回调。至于IChangeToken接口的另一个属性ActiveChangeCallbacks,它表示当数据发生变化时是否需要主动执行注册的回调操作。

public interface IChangeToken
{
bool HasChanged { get; }
bool ActiveChangeCallbacks { get; }
IDisposable RegisterChangeCallback(Action<object> callback, object state);
}

.NET Core提供了若干原生的IChangeToken实现类型,我们最常使用的是一个名为CancellationChangeToken的实现。CancellationChangeToken的实现原理很简单,它基本上就是按照如下的形式借助我们熟悉的CancellationToken对象来发送通知。

public class CancellationChangeToken : IChangeToken
{
private readonly CancellationToken _token;
public CancellationChangeToken(CancellationToken token) => _token = token;
public bool HasChanged => _token.IsCancellationRequested;
public bool ActiveChangeCallbacks => true;
public IDisposable RegisterChangeCallback(Action<object> callback, object state) => _token.Register(callback, state);
}

除了CancellationChangeToken,有时也我们也会使用到一个名为CompositeChangeToken的实现。顾名思义,CompositeChangeToken代表由多个IChangeToken组合而成的复合型IChangeToken对象。如下面的代码片段所示,我们在调用构造函数创建一个CompositeChangeToken对象的时候,需要提供这些IChangeToken对象。对于一个CompositeChangeToken对象来说,只要组成它的任何一个IChangeToken发生改变,其HasChanged属性将会变成True,而注册的回调自然会被执行。至于ActiveChangeCallbacks属性,只要任何一个IChangeToken的同名属性返回True,该属性就会返回True。

public class CompositeChangeToken : IChangeToken
{
public bool ActiveChangeCallbacks { get; }
public IReadOnlyList<IChangeToken> ChangeTokens { get; }
public bool HasChanged { get; } public CompositeChangeToken(IReadOnlyList<IChangeToken> changeTokens);
public IDisposable RegisterChangeCallback(Action<object> callback, object state);
}

我们可以直接调用IChangeToken提供的RegisterChangeCallback方法来注册在接收到数据变化通知后的回调操作,但是更常用的方式则是直接调用静态类型ChangeToken提供的如下两个OnChange方法重载来进行回调注册,这两个方法的第一个参数需要被指定为一个用来提供IChangeToken对象的Func<IChangeToken>委托。

public static class ChangeToken
{
public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer) ;
public static IDisposable OnChange<TState>(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state) ;
}

二、IFileProvider

在了解了IChangeToken是怎样一个对象之后,我们将关注转移到文件系统的核心接口IFileProvider上,该接口定义在NuGet包“Microsoft.Extensions.FileProviders.Abstractions”中。我们在《抽象的“文件系统”》做了几个简单的实例演示,它们实际上体现了文件系统承载的三个基本功能,而这三个基本功能分别体现在IFileProvider接口如下所示的三个方法中。

public interface IFileProvider
{
IFileInfo GetFileInfo(string subpath);
IDirectoryContents GetDirectoryContents(string subpath);
IChangeToken Watch(string filter);
}

三、IFileInfo

虽然文件系统采用目录来组织文件,但是不论是目录还是文件都通过一个IFileInfo对象来表示,至于具体是目录还是文件则通过IFileInfo的IsDirectory属性来确定。对于一个IFileInfo对象,我们可以通过只读属性Exists判断指定的目录或者文件是否真实存在。至于另外两个属性Name和PhysicalPath,它们分别表示文件或者目录的名称和物理路径。属性LastModified返回一个时间戳,表示目录或者文件最终一次被修改的时间。对于一个表示具体文件的IFileInfo对象来说,我们可以利用属性Length得到文件内容的字节长度。如果我们希望读取文件的内容,可以借助于CreateReadStream方法返回的Stream对象来完成。

public interface IFileInfo
{
bool Exists { get; }
bool IsDirectory { get; }
string Name { get; }
string PhysicalPath { get; }
DateTimeOffset LastModified { get; }
long Length { get; } Stream CreateReadStream();
}

IFileProvider接口的GetFileInfo方法会根据指定的路径得到表示所在文件的IFileInfo对象。换句话说,虽然一个IFileInfo对象可以用于描述目录和文件,但是GetFileInfo方法的目的在于得到指定路径返回的文件而不是目录(我个人不太认同这种令人产生歧义的API设计)。一般来说,不论指定的文件是否存在,该方法总会返回一个具体的IFileInfo对象,因为目标文件的存在与否是由该对象的Exists属性来确定的。

四、IDirectoryContents

如果希望得到某个目录的内容,比如需要查看多少文件或者子目录包含在这个目录下,我们可以调用IFileProvider对象的GetDirectoryContents方法并将所在目录的路径作为参数。目录内容通过该方法返回的IDirectoryContents对象来表示。如下面的代码片段所示,一个IDirectoryContents对象实际上是一组IFileInfo对象的集合,组成这个集合的所有IFileInfo自然就是对包含在这个目录下的所有文件和子目录的描述。和GetFileInfo方法一样,不论指定的目录是否存在,GetDirectoryContents方法总是会返回一个具体的IDirectoryContents对象,它的Exists属性会帮助我们确定指定目录是否存在。

public interface IDirectoryContents : IEnumerable<IFileInfo>
{
bool Exists { get; }
}

五、监控目录或者文件更新

如果我们希望监控IFileProvider所在目录或者文件的变化,我们可以调用它的Watch方法,当然前提是对应的IFileProvider对象提供了这样的监控功能。这个方法接受一个字符串类型的参数filter,我们可以利用这个参数指定一个针对“文件匹配模式(File Globing Pattern)”表达式(以下简称Globing Pattern表达式)来筛选需要监控的目标目录或文件。

Globing Pattern表达式比正则表达式简单多了,它只包含“*”一种“通配符”,如果硬说它包含两种通配符的话,那么另一个通配符是“**”。Globing Pattern表达式体现为一个文件路径,其中“*”代表所有不包括路径分隔符(“/”或者“\”)的所有字符,而“**”则代表包含路径分隔符在内的所有字符。下表给出了几个典型的Globing Pattern表达式和它们代码的文件匹配语义。

Globing
Pattern表达式

匹配的文件

src/foobar/foo/settings.*

子目录“src/foobar/foo/”(不含其子目录)下名为“settings”的所有文件,比如settings.json、settings.xml和settings.ini等。

src/foobar/foo/*.cs

子目录“src/foobar/foo/”(不含其子目录)下的所有.cs文件。

src/foobar/foo/*.*

子目录“src/foobar/foo/”(不含其子目录)下所有文件。

src/**/*.cs

子目录“src”(含其子目录)下的所有.cs文件。

一般来说,不论是调用IFileProvider对象的GetFileInfo或GetDirectoryContents方法所指定的目标文件或目录的路径,还是调用Watch方法指定的筛选表达式,都是一个针对当前IFileProvider对象映射根目录的相对路径。指定的这个路径可以采用“/”字符作为前缀,但是这个前缀是不必要的。换句话说,如下所示的这两组程序是完全等效的。

路径不包含前缀“/”

var dirContents = fileProvider.GetDirectoryContents("foobar");
var fileInfo = fileProvider.GetFileInfo("foobar/foobar.txt");
var changeToken = fileProvider.Watch("foobar/*.txt");

路径包含前缀“/”

var dirContents = fileProvider.GetDirectoryContents("/foobar");
var fileInfo = fileProvider.GetFileInfo("/foobar/foobar.txt");
var changeToken = fileProvider.Watch("/foobar/*.txt");

总的来说,以IFileProvider对象为核心的文件系统在设计上看是非常简单的。除了IFileProvider接口之外,文件系统还涉及到其他一些对象,比如IDirectoryContents、IFileInfo和IChangeToken等,下图所示的UML展示了这些接口以及它们之间的关系。

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

[ASP.NET Core 3框架揭秘] 文件系统[2]:总体设计的更多相关文章

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

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

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

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

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

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

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

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

  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. Web安全之url跳转漏洞及bypass总结

    0x01 成因 对于URL跳转的实现一般会有几种实现方式: META标签内跳转 javascript跳转 header头跳转 通过以GET或者POST的方式接收将要跳转的URL,然后通过上面的几种方式 ...

  2. ArrayList源码解析[一]

    ArrayList源码解析[一] 欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 在工作中集合list集合用的相对来 ...

  3. 【Dubbo】Zookeeper+Dubbo项目demo搭建

    一.Dubbo的注解配置 在Dubbo 2.6.3及以上版本提供支持. 1.@Service(全路径@org.apache.dubbo.config.annotation.Service) 配置服务提 ...

  4. Mac系统 安装Photoshop CC 2018破解版

    应用场景 本人从事前端行业,但是工作中有时也需要会点PS技能,之前一直使用window系统,突然换了Mac其他软件基本都差不多安装完了,就剩下比较难搞的PS.刚开始按照网上乱七八槽的教程下载过好多次都 ...

  5. eclipse提交代码到GitHub

    1.配置git 2.右键项目--> Team--> Share Project... 3.右键项目--> Team--> Commit...

  6. spring在IoC容器中装配Bean详解

    1.Spring配置概述 1.1.概述 Spring容器从xml配置.java注解.spring注解中读取bean配置信息,形成bean定义注册表: 根据bean定义注册表实例化bean: 将bean ...

  7. iOS cocoapods导入项目 出现 "___gxx_personality_v0", referenced from: 或者 clang: error: linker command failed with exit code 1 (use -v to see invocation) 错误

    今天想导入PNChart 编译的时候出现了  "___gxx_personality_v0", referenced from:  和 clang: error: linker c ...

  8. 使用JRebel插件实现SpringBoot应用代码热加载

    前言 在实际的开发过程中,我们经常修改代码之后,手动的重启项目,查看修改效果.那么有没有一种方式能够快速的.自动的帮我们将修改代码自动更新,避免手动重启,从而提高开发效率呢?是有的,在我之前的文章里面 ...

  9. electron快捷键

    我们分为在主进程中注册快捷键和在渲染进程中注册快捷键 在主进程中我们有两种方式 一 利用[Menu]来模拟快捷键,只有app获得焦点时才生效,很少使用 const { Menu, MenuItem } ...

  10. vue 代码迁移的坑

    由于开发需要,开发过程中总会遇到由于代码调试.svn/git上传等过程中,总会出现代码迁移文件的需求,很多时候,迁移过后总会出现一些大大小小的问题, 首先,需要迁移文件内有没有系统自动隐藏的文件(例如 ...