在《抽象的“文件系统”》中,我们通过几个简单的实例演示从编程的角度对文件系统做了初步的体验,接下来我们继续从设计的角度来进一步认识它。这个抽象的文件系统以目录的形式来组织文件,我们可以利用它读取某个文件的内容,还可以对目录或者文件实施监控并及时得到变化的通知。由于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. Python3字符串常见方法

    目录 字符串的进阶使用 格式化输出字符串 当然除了上述方法外,还可以你使用format方法 format方法第二种用法: Python字符串与二进制的转换 字母大写 计字符a出现的次数 输出50个字符 ...

  2. SpringBoot系列:Spring Boot集成Spring Cache

    一.关于Spring Cache 缓存在现在的应用中越来越重要, Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework. ...

  3. [CF431C]k-Tree

    题目描述 Quite recently a creative student Lesha had a lecture on trees. After the lecture Lesha was ins ...

  4. opencv实践::对象提取与测量

    问题描述 照片是来自太空望远镜的星云图像,科学家想知道它的面 积与周长. 解决思路 方法一: 通过二值分割+图像形态学+轮廓提取 #include <opencv2/opencv.hpp> ...

  5. 第三方软件 Serv-u提权

    Serv-U FTP Server,是一种被广泛运用的FTP服务器端软件,支持3x/9x/ME/NT/2K等全Windows系列.可以设定多个FTP服务器.限定登录用户的权限.登录主目录及空间大小等  ...

  6. Feign服务调用请求方式及参数总结

    前言 最近做微服务架构的项目,在用feign来进行服务间的调用.在互调的过程中,难免出现问题,根据错误总结了一下,主要是请求方式的错误和接参数的错误造成的.在此进行一下总结记录.以下通过分为三种情况说 ...

  7. FileZilla Server超详细配置

    FileZilla Server下载安装完成后,必须启动软件进行设置,由于此软件是英文,本来就是一款陌生的软件,再加上英文(注:本站提供中文版本,请点击下载),配置难度可想而知,站长从网上找到一篇非常 ...

  8. day10作业(函数实现注册''')

    在猜年龄的基础上编写登录.注册方法,并且把猜年龄游戏分函数处理,如 登录函数 注册函数 猜年龄函数 选择奖品函数 '''在猜年龄的基础上编写登录.注册方法,并且把猜年龄游戏分函数处理,如 2. 登录函 ...

  9. bash_history文件怎么删除

    Bash shell在“~/.bash_history”(“~/”表示用户目录)文件中保存了500条使用过的命令,这样可以使你输入使用过的长命令变得容易.每个在系统中拥有账号的用户在他的目录下都有一个 ...

  10. Golang的安装和编译

    一.下载安装(Ubuntu16.04) 1.下载地址:https://golang.google.cn/dl/ 2.下载Linux版本的安装包go1.10.3.linux-amd64.tar.gz并复 ...