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

    18年下班年用的vue + echarts,现在才想起来总结,着实不敬业 线上的项目叫股往(http://rich.xchol.com/#/) 好了,进入正题: 首先,需要新建一个vue的项目,在vu ...

  2. python学习-函数和lambda表达式(五)

    5.2函数参数 位置参数:根据位置传入参数 关键字参数:根据参数名来传入参数 def girth(width, height): print("width:", width) pr ...

  3. 介绍Webflux

    介绍Webflux 关于WebFlux 我们知道传统的Web框架,比如说:struts2,springmvc等都是基于Servlet API与Servlet容器基础之上运行的,在Servlet3.1之 ...

  4. canvas模拟中国铁路运行图

    原理说明 1.在知道canvas画布尺寸的情况下,需要将地理经纬度信息转换为canvas画布x,y坐标,因为中国地图地理经纬度坐标取值范围为73.33-135.05(经度)37-50(维度),所以第一 ...

  5. 用FILE*指针对象读文件的方式。

      先直接上代码: ]; ]; char* ptr1; ]; FILE* fh; QString strpath = getenv("GCDIR"); QString str_in ...

  6. 设计模式(十九)State模式

    在面向对象编程中,是用类表示对象的.也就是说,程序的设计者需要考虑用类来表示什么东西.类对应的东西可能存在于真实世界中,也可能不存在于真实世界中.对于后者,可能有人看到代码后会感到吃惊:这些东西居然也 ...

  7. Python调试工具

    1. 日志 通过日志或者print来打印变量.必要时可以打印locals()和globals() 建议使用logging.debug()来代替print,这样到了正式环境,就可以统一删除这些日志. 2 ...

  8. background-origin背景图片定位

    语法 background-origin: padding-box|border-box|content-box; background-Origin属性指定background-position属性 ...

  9. .NET如何写正确的“抽奖”——打乱数组算法

    .NET如何写正确的"抽奖"--数组乱序算法 数组乱序算法常用于抽奖等生成临时数据操作.就拿年会抽奖来说,如果你的算法有任何瑕疵,造成了任何不公平,在年会现场code review ...

  10. web.xml 配置文件 超详细说明!!!

    一.web.xml是什么? 首先 web.xml 是java web 项目的一个重要的配置文件,但是web.xml文件并不是Java web工程必须的. web.xml文件是用来配置:欢迎页.serv ...