[ASP.NET Core 3框架揭秘] 文件系统[2]:总体设计
在《抽象的“文件系统”》中,我们通过几个简单的实例演示从编程的角度对文件系统做了初步的体验,接下来我们继续从设计的角度来进一步认识它。这个抽象的文件系统以目录的形式来组织文件,我们可以利用它读取某个文件的内容,还可以对目录或者文件实施监控并及时得到变化的通知。由于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 |
匹配的文件 |
|
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]:总体设计的更多相关文章
- [ASP.NET Core 3框架揭秘] 文件系统[1]:抽象的“文件系统”
ASP.NET Core应用 具有很多读取文件的场景,比如配置文件.静态Web资源文件(比如CSS.JavaScript和图片文件等)以及MVC应用的View文件,甚至是直接编译到程序集中的内嵌资源文 ...
- [ASP.NET Core 3框架揭秘] 文件系统[4]:程序集内嵌文件系统
一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以采用统一的编程方式来读取内嵌的资源文件,该类型定义在 "Microsoft.Ex ...
- [ASP.NET Core 3框架揭秘] 文件系统[3]:物理文件系统
ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件.View文件以及作为Web资源的静态文件.物理文件系统由定义在NuGet包"Microsoft.Extension ...
- ASP.NET Core 6框架揭秘实例演示[07]:文件系统
ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...
- [ASP.NET Core 3框架揭秘] 依赖注入:控制反转
ASP.NET Core框架建立在一些核心的基础框架之上,这些基础框架包括依赖注入.文件系统.配置选项和诊断日志等.这些框架不仅仅是支撑ASP.NET Core框架的基础,我们在进行应用开发的时候同样 ...
- [ASP.NET Core 3框架揭秘] 配置[7]:多样化的配置源[中篇]
物理文件是我们最常用到的原始配置载体,而最佳的配置文件格式主要有三种,它们分别是JSON.XML和INI,对应的配置源类型分别是JsonConfigurationSource.XmlConfigura ...
- [ASP.NET Core 3框架揭秘] 跨平台开发体验: Linux
如果想体验Linux环境下开发.NET Core应用,我们有多种选择.一种就是在一台物理机上安装原生的Linux,我们可以根据自身的喜好选择某种Linux Distribution,目前来说像RHEL ...
- 《ASP.NET Core 3框架揭秘》读者群,欢迎加入
作为一个17年的.NET开发者,我对一件事特别不能理解:我们的计算机图书市场充斥着一系列介绍ASP.NET Web Forms.ASP.NET MVC.ASP.NET Web API的书籍,但是却找不 ...
- 《ASP.NET Core 3框架揭秘》博文汇总
在过去一段时间内,写了一系列关于ASP.NET Core 3相关的文章,其中绝大部分来源于即将出版的<ASP.NET Core 3框架揭秘>(博文只能算是"初稿",与书 ...
随机推荐
- Lombok中关于@Data的使用
当你在使用 Lombok 的 @Data 注解时,其实会有一些坑需要关注,今天就让我们来见识一下. Lombok 先来简单介绍一下 Lombok ,其官方介绍如下: Project Lombok ma ...
- PHP array_product
1.函数的作用:计算数组元素的乘积 2.函数的参数: @params array 3.例子: <?php $input = [false,true]; print_r(array_product ...
- Spring Cloud Alibaba(二) 配置中心多项目、多配置文件、分目录实现
介绍 之前Spring Cloud Config基础篇这篇文章介绍了Spring Cloud Config 配置中心基础的实现,今天继续聊下Spring Cloud Config 并结合nacos做服 ...
- < 配置jupyer notebook遇到的问题 - 500 : Internal Server Error >
< anaconda配置jupyer notebook遇到的问题 - 500 : Internal Server Error > 问题描述: 我的jupyer notebook是在anac ...
- webpack 4 移除 CommonsChunkPlugin,取而代之的是两个新的配置项(optimization.splitChunks 和 optimization.runtimeChunk
默认方式 webpack模式模式现在已经做了一些通用性优化,适用于多数使用者. 需要注意的是:默认模式只影响按需(on-demand)加载的代码块(chunk),因为改变初始代码块会影响声明在HTML ...
- IDEA+docker,进行远程漏洞调试(weblogic)
用于学习java漏洞debug. 以weblogic为例. cve-2017-10271 weblogic:10.3.6 环境搭建 拿docker为例 https://github.com/vulhu ...
- 基于MVC的RESTful风格的实现
基于MVC的RESTful风格的实现 1.RESTful风格阐述 REST服务是一种ROA(Resource-Oriented Architecture,面向资源的架构)应用.主要特点是方法信息存在于 ...
- 2018.8.9 python中的动态传参与命名空间
主要内容: 1.函数参数 ----动态传参 2.名称空间与作用域 3.函数的嵌套 4.global,nonlocal关键字 一.函数参数 ------动态传参 形参的第三种:动态传参 动态传参分为两种 ...
- 关于ReentrantLock 中的lockInterruptibly方法的简单探究
今天在看Lock,都知道相比于synchronized,多了公平锁,可中断等优秀性能. 但是说到可中断这个特点,看到很多博客是这么描述的: “与synchronized关键字不同,获取到锁的线程能够响 ...
- 学习笔记之javascript编写简单计算器
感觉自己的的实力真的是有待提高,在编写计算器的过程中,出现了各种各样的问题,暴露了自己的基础不扎实,逻辑思维能力不够,学得知识不能运用到自己的demo中区.先介绍一些这个这个计算器的整体思路.大致 ...