.NET Core的文件系统[3]:由PhysicalFileProvider构建的物理文件系统
ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件、View文件以及网页上的静态文件,物理文件系统的抽象通过PhysicalFileProvider这个FileProvider来实现,该类型定义在NuGet包“Microsoft.Extensions.FileProviders.Physical”中。我们知道System.IO命名空间下定义了一整套针操作物理目录和文件的API,实际上PhysicalFileProvider最终也是通过调用这些API来完成相关的IO操作的。[ 本文已经同步到《ASP.NET Core框架揭秘》之中]
目录
一、PhysicalFileProvider
二、PhysicalFileInfo
三、PhysicalDirectoryInfo
四、针对物理文件的监控
五、总结
一、PhysicalFileProvider
如下所示的代码片段展示PhysicalFileProvider类型的定义。
1: public class PhysicalFileProvider : IFileProvider, IDisposable
2: {
3: public PhysicalFileProvider(string root);
4:
5: public IFileInfo GetFileInfo(string subpath);
6: public IDirectoryContents GetDirectoryContents(string subpath);
7: public IChangeToken Watch(string filter);
8:
9: public void Dispose();
10: }
二、PhysicalFileInfo
一个PhysicalFileProvider对象总是映射到某个具体的物理目录下,被映射的目录所在的路径通过构造函数的参数root来提供,该目录将作为PhysicalFileProvider的根目录。GetFileInfo方法返回的FileInfo对象代表指定路径对应的文件,这是一个类型为PhysicalFileInfo的对象,如下所示的代码片段展示了该类型的完整定义。一个物理文件可以通过一个System.IO.FileInfo对象来表示,一个PhysicalFileInfo对象实际上就是对这个一个FileInfo对象的封装,定义在PhysicalFileInfo的所有属性都来源于这个FileInfo对象。对于创建读取文件输出流的CreateReadStream方法来说,它返回的是一个根据物理文件绝对路径创建的FileStream对象。
1: public class PhysicalFileInfo : IFileInfo
2: {
3: ...
4: public PhysicalFileInfo(FileInfo info);
5: }
对于PhysicalFileProvider的GetFile方法来说,即使我们指定的路径指向一个具体的物理文件,它并不总是会返回一个PhysicalFileInfo对象。具体来说,PhysicalFileProvider会将如下几种场景视为“目标文件不存在”,并让GetFile返回一个NotFoundFileInfo对象。顾名思义,NotFoundFileInfo表示的正式一个“不存在”的文件,即它的Exists属性总是返回False,而其他的属性则变得没有任何意义。当我们调用它的CreateReadStream试图读取一个根本不存在的文件内容时,会抛出一个FileNotFoundException类型的异常。
- 确实没有一个物理文件与指定的路径相匹配。
- 如果指定的是一个绝对路径(比如“c:\foobar”),即Path.IsPathRooted返回返回True。
- 如果指定的路径指向一个隐藏文件。
三、PhysicalDirectoryInfo
对于PhysicalFileProvider来说,它利用PhysicalFileInfo对象来描述某个具体的物理文件,针对目录的描述则通过一个类型为PhysicalDirectoryInfo的对象。既然PhysicalFileInfo是对一个System.IO.FileInfo对象的封装,那么我们应该想得到PhysicalDirectoryInfo封装的自然就是表示目录的DirectoryInfo对象。如下面的代码片段所示,我们需要在创建一个PhysicalDirectoryInfo对象时提供这个DirectoryInfo对象,PhysicalDirectoryInfo实现的所有属性的返回值都来源于这个DirectoryInfo对象。由于CreateReadStream方法的目的是读取文件的内容,所以当我们调用一个PhysicalDirectoryInfo对象的这个方法的时候,会抛出一个InvalidOperationException类型的异常。
1: public class PhysicalDirectoryInfo : IFileInfo
2: {
3: ...
4: public PhysicalDirectoryInfo(DirectoryInfo info);
5: }
当我们调用PhysicalFileProvider的GetDirectoryContents方法时,如果指定的路径指向一个具体的目录,那么该方法会返回一个类型为EnumerableDirectoryContents的对象,不过EnumerableDirectoryContents仅仅是一个在编程过程中不可见的内部类型。EnumerableDirectoryContents是一个FileInfo对象的集合,该集合中会包括所有描述子目录的PhysicalDirectoryInfo对象和描述文件的PhysicalFileInfo对象。至于EnumerableDirectoryContents的Exists属性,它总是返回True。如果指定的路径并不指向一个存在目录,或者指定的是一个绝对路径,这个方法都会返回一个Exsits属性总是返回False的NotFoundDirectoryContents对象。
四、针对物理文件的监控
我们接着来谈谈PhysicalFileProvider的Watch方法。当我们调用该方法的时候,PhysicalFileProvider会通过解析我们提供的筛选表达式确定我们期望监控的文件,然后利用FileSystemWatcher对象来对这些文件试试监控。针对这些文件的变化(创建、修改、重命名和删除)都会实时地反映到Watch方法返回的ChangeToken上。 值得一提的是,FileSystemWatcher类型实现IDisposable接口,PhysicalFileProvider也实现了相同的接口,PhysicalFileProvider的Dispose方法的唯一使命就是释放这个FileSystemWatcher对象。
Watch方法中指定的筛选表达式必须是针对当前PhysicalFileProvider根目录的相对路径,可以使用“/”或者“./”前缀,也可以不采用任何前缀。一旦我们使用了绝对路径(比如“c:\test\*.txt”)或者“../”前缀(比如“../test/*.txt”),不论解析出来的文件是否存在于PhysicalFileProvider的根目录下,这些文件都不会被监控。除此之外,如果我们没有指定任何筛选条件,也不会有任何的文件会被监控。
监控文件变化的真正目的在于让应用程序能够及时感知到数据源的改变,进而自动执行某些预先注册的回掉操作。回调的注册可以直接通过调用ChangeToken的RegisterChangeCallback方法来完成,注册的回调通过一个类型为Action<object>的委托对象来表示。对于在第一节演示的文件监控的实例,相应的程序“照理说”可以改写成如下的形式。
1: IFileProvider fileProvider = new PhysicalFileProvider(@"c:\test");
2: fileProvider.Watch("data.txt").RegisterChangeCallback(_ = >LoadFileAsync(fileProvider), null);
3: while (true)
4: {
5: File.WriteAllText(@"c:\test\data.txt", DateTime.Now.ToString());
6: Task.Delay(5000).Wait();
7: }
8:
9: public static async void LoadFileAsync(IFileProvider fileProvider)
10: {
11: Stream stream = fileProvider.GetFileInfo("data.txt").CreateReadStream();
12: {
13: byte[] buffer = new byte[stream.Length];
14: await stream.ReadAsync(buffer, 0, buffer.Length);
15: Console.WriteLine(Encoding.ASCII.GetString(buffer));
16: }
17: }
如果执行上面这段程序,我们会发现只有第一个针对文件的更新能够被感知,后续的文件更新操作将自动被忽略。导致这个问题的根源在于,单个ChangeToken对象的使命在于当绑定的数据源第一次发生变换时对外发送相应的信号,而不具有持续发送数据变换的能力。其实这一点从IChangeToken接口的定义就可以看出来,我们知道它具有一个HasChanged属性表示数据是否已经发生变化,而并没有提供一个让这个属性“复位”的方法。所以当我们需要对某个文件进行持续监控的时候,我们需要在注册的回调中重新调用FileProvider的Watch方法,并利用生成ChangeToken再次注册回调。除此之外,考虑到ChangeToken的RegisterChangeCallback方法以一个IDisposable对象的形式返回回调注册对象,我们应该在对回调实施二次注册时调用第一次返回的回调注册对象的Dispose方法将其释放掉。如下所示的程序才能达到对文件试试持续监控的目的。
1: IFileProvider fileProvider = new PhysicalFileProvider(@"c:\test");
2: Action<object> callback = null;
3: IDisposable regiser = null;
4: callback = _ =>
5: {
6: regiser.Dispose();
7: LoadFileAsync(fileProvider);
8: fileProvider.Watch("data.txt").RegisterChangeCallback(callback, null);
9: };
10:
11: regiser = fileProvider.Watch("data.txt").RegisterChangeCallback(callback, null);
不过这样的编程方式不但看起来比较繁琐,很多对ChangeToken缺乏认识的人甚至对这样的编程方式无法理解。为了解决这个问题,我们可以使用定义在ChangeToken类型中如下两个方法OnChange方法来注册数据发生改变时自动执行的回调。这两个方法具有两个参数,前者是一个用于创建ChangeToken对象的Func<IChangeToken>对象,后者则是代表回调操作的Action<object>/Action<TState>对象。实际上第一节的实例演示中我们就是调用的这个OnChange方法。
1: public static class ChangeToken
2: {
3: public static IDisposable OnChange(Func<IChangeToken> changeTokenProducer, Action changeTokenConsumer)
4: {
5: Action<object> callback = null;
6: callback = delegate (object s) {
7: changeTokenConsumer();
8: changeTokenProducer().RegisterChangeCallback(callback, null);
9: };
10: return changeTokenProducer().RegisterChangeCallback(callback, null);
11: }
12:
13: public static IDisposable OnChange<TState>(Func<IChangeToken> changeTokenProducer, Action<TState> changeTokenConsumer, TState state)
14: {
15: Action<object> callback = null;
16: callback = delegate (object s) {
17: changeTokenConsumer((TState) s);
18: changeTokenProducer().RegisterChangeCallback(callback, s);
19: };
20: return changeTokenProducer().RegisterChangeCallback(callback, state);
21: }
22: }
如果改用这个OnChange方法来替换掉原来手工调用ChangeToken的RegisterChangeCallback方法进行回调注册的方式,原本显得相对繁琐的程序可以通过如下两句代码来替换。实际上在《读取并监控文件的变化》中,我们调用的正是这个OnChange方法。
1: IFileProvider fileProvider = new PhysicalFileProvider(@"c:\test");
2: ChangeToken.OnChange(() => fileProvider.Watch("data.txt"), () => LoadFileAsync(fileProvider));
五、总结
我们借助下图所示的UML来对由PhysicalFileProvider构建物理文件系统的整体设计做一个简单的总结。首先,该文件系统下用于描述目录和文件的分别是一个PhysicalDirectoryInfo和PhysicalFileInfo对象,它们分别是对一个DirectoryInfo和FileInfo(System.IO.FileInfo)对象的封装。PhysicalFileProvider的GetDirectoryContents方法返回一个EnumerableDirectoryContents对象(如果指定的目录存在),组成该对象的分别是根据其所有子目录和文件创建的PhysicalDirectoryInfo和PhysicalFileInfo对象。当我们调用PhysicalFileProvider的GetFileInfo方法时,如果指定的文件存在,返回的是描述该文件的PhysicalFileInfo对象。至于PhysicalFileProvider的Watch方法,它最终利用了FileSystemWatcher来监控指定文件的变化。

.NET Core的文件系统[3]:由PhysicalFileProvider构建的物理文件系统的更多相关文章
- 由PhysicalFileProvider构建的物理文件系统
由PhysicalFileProvider构建的物理文件系统 ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件.View文件以及网页上的静态文件,物理文件系统的抽象通过Phy ...
- [ASP.NET Core 3框架揭秘] 文件系统[3]:物理文件系统
ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件.View文件以及作为Web资源的静态文件.物理文件系统由定义在NuGet包"Microsoft.Extension ...
- 基于 SquashFS 构建 Linux 可读写文件系统
转载:http://www.oschina.net/question/129540_116839 在当前的嵌入式操作系统开发中,Linux 操作系统通常被压缩成 Image 后存放在 Flash 设备 ...
- .NET Core初体验 在window上构建第一个app
ASP.NET Core 是一个跨平台,高性能的开源框架,用于构建现代化的,基于云的互联网应用程序.使用 ASP.NET Core ,您可以: 构建Web应用程序和服务,IoT应用程序和移动后端. 在 ...
- 构建MFS分布式文件系统
++++++++++++++构建MFS分布式文件系统++++++++++++++PB级别:企业存储空间达到PB级别,即100万GB空间.(1PB=1000TB,1TB=1000GB,1GB=1000M ...
- 构建Mogilefs分布式文件系统(配置篇)
构建Mogilefs分布式文件系统: 当下互联网飞速发展,海量并发所产生的数据量以几何方式增长,随着信息链接方式日益多样化,数据存储的结构也发生了变化,在这样的压力下我们不得不重新审视大量数据的存储 ...
- Buildroot构建指南——根文件系统(Rootfs)【转】
本文转载自; 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] Buildroot构建指南——根文件系统(Rootfs) Buildroot的Rootfs构建流程有一个大 ...
- 在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务
在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务 https://procodeguide.com/programming/polly-in-aspnet-core ...
- CoreCRM 开发实录——Travis-CI 实现 .NET Core 程度在 macOS 上的构建和测试 [无水干货]
上一篇文章我提到:为了使用"国货",我把 Linux 上的构建和测试委托给了 DaoCloud,而 Travis-CI 不能放着不用啊.还好,这货支持 macOS 系统.所以就把 ...
随机推荐
- 在Openfire上弄一个简单的推送系统
推送系统 说是推送系统有点大,其实就是一个消息广播功能吧.作用其实也就是由服务端接收到消息然后推送到订阅的客户端. 思路 对于推送最关键的是服务端向客户端发送数据,客户端向服务端订阅自己想要的消息.这 ...
- DDD 领域驱动设计-看我如何应对业务需求变化,愚蠢的应对?
写在前面 阅读目录: 具体业务场景 业务需求变化 "愚蠢"的应对 消息列表实现 消息详情页实现 消息发送.回复.销毁等实现 回到原点的一些思考 业务需求变化,领域模型变化了吗? 对 ...
- 移动端1px边框
问题:移动端1px边框,看起来总是2倍的边框大小,为了解决这个问题试用过很多方法,用图片,用js判断dpr等,都不太满意, 最后找到一个还算好用的方法:伪类 + transform 原理是把原先元素的 ...
- app引导页(背景图片切换加各个页面动画效果)
前言:不知不觉中又加班到了10点半,整个启动页面做了一天多的时间,一共有三个页面,每个页面都有动画效果,动画效果调试起来麻烦,既要跟ios统一,又要匹配各种不同的手机,然后产品经理还有可能在中途改需求 ...
- C#多线程之线程池篇2
在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...
- .net 分布式架构之配置中心
开源QQ群: .net 开源基础服务 238543768 开源地址: http://git.oschina.net/chejiangyi/Dyd.BaseService.ConfigManager ...
- PHP之时间和日期函数
// 时间日期函数 Time <?php date_default_timezone_set('UTC'); // 获取当前时间的时间戳 $time0 = mktime(); $time1 = ...
- 声音分贝的概念,dBSPL.dBm,dBu,dBV,dBFS
需要做个音频的PPM表,看着一堆的音频术语真是懵了,苦苦在网上扒了几天的文档,终于有了点收获,下面关于声音的分贝做个总结. 分贝 Decibel 分贝(dB)是一个对数单位(logarithmic u ...
- 我理解的MVC
前言 前一阶段对MVC模式及其衍生模式做了一番比较深入的研究和实践,这篇文章也算是一个阶段性的回顾和总结. 经典MVC模式 经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的 ...
- 【干货分享】流程DEMO-出差申请单
流程名: 出差申请 业务描述: 员工出差前发起流程申请,流程发起时,会检查预算,如果预算不够,将不允许发起费用申请,如果预算够用,将发起流程,同时占用相应金额的预算,但撤销流程会释放相应金额的预算. ...