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构建的物理文件系统的更多相关文章

  1. 由PhysicalFileProvider构建的物理文件系统

    由PhysicalFileProvider构建的物理文件系统 ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件.View文件以及网页上的静态文件,物理文件系统的抽象通过Phy ...

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

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

  3. 基于 SquashFS 构建 Linux 可读写文件系统

    转载:http://www.oschina.net/question/129540_116839 在当前的嵌入式操作系统开发中,Linux 操作系统通常被压缩成 Image 后存放在 Flash 设备 ...

  4. .NET Core初体验 在window上构建第一个app

    ASP.NET Core 是一个跨平台,高性能的开源框架,用于构建现代化的,基于云的互联网应用程序.使用 ASP.NET Core ,您可以: 构建Web应用程序和服务,IoT应用程序和移动后端. 在 ...

  5. 构建MFS分布式文件系统

    ++++++++++++++构建MFS分布式文件系统++++++++++++++PB级别:企业存储空间达到PB级别,即100万GB空间.(1PB=1000TB,1TB=1000GB,1GB=1000M ...

  6. 构建Mogilefs分布式文件系统(配置篇)

    构建Mogilefs分布式文件系统:  当下互联网飞速发展,海量并发所产生的数据量以几何方式增长,随着信息链接方式日益多样化,数据存储的结构也发生了变化,在这样的压力下我们不得不重新审视大量数据的存储 ...

  7. Buildroot构建指南——根文件系统(Rootfs)【转】

    本文转载自; 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[+]   Buildroot构建指南——根文件系统(Rootfs) Buildroot的Rootfs构建流程有一个大 ...

  8. 在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务

    在 ASP.NET Core Web API中使用 Polly 构建弹性容错的微服务 https://procodeguide.com/programming/polly-in-aspnet-core ...

  9. CoreCRM 开发实录——Travis-CI 实现 .NET Core 程度在 macOS 上的构建和测试 [无水干货]

    上一篇文章我提到:为了使用"国货",我把 Linux 上的构建和测试委托给了 DaoCloud,而 Travis-CI 不能放着不用啊.还好,这货支持 macOS 系统.所以就把 ...

随机推荐

  1. Mapreduce的文件和hbase共同输入

    Mapreduce的文件和hbase共同输入 package duogemap;   import java.io.IOException;   import org.apache.hadoop.co ...

  2. 开源:Taurus.MVC 框架

    为什么要创造Taurus.MVC: 记得被上一家公司忽悠去负责公司电商平台的时候,情况是这样的: 项目原版是外包给第三方的,使用:WebForm+NHibernate,代码不堪入目,Bug无限,经常点 ...

  3. .NET Core系列 : 1、.NET Core 环境搭建和命令行CLI入门

    2016年6月27日.NET Core & ASP.NET Core 1.0在Redhat峰会上正式发布,社区里涌现了很多文章,我也计划写个系列文章,原因是.NET Core的入门门槛相当高, ...

  4. js复杂对象和简单对象的简单转化

    var course = { teacher :{ teacherId:001, teacherName:"王" }, course : { courseId : 120, cou ...

  5. Hyper-V 激活Windows系统重启后黑屏的解决方法 + 激活方法

    异常处理汇总-服 务 器 http://www.cnblogs.com/dunitian/p/4522983.html 服务器相关的知识点:http://www.cnblogs.com/dunitia ...

  6. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  7. 缓存工厂之Redis缓存

    这几天没有按照计划分享技术博文,主要是去医院了,这里一想到在医院经历的种种,我真的有话要说:医院里的医务人员曾经被吹捧为美丽+和蔼+可亲的天使,在经受5天左右相互接触后不得不让感慨:遇见的有些人员在挂 ...

  8. CRL快速开发框架系列教程十二(MongoDB支持)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  9. C#通过NPOI操作Excel

    参考页面: http://www.yuanjiaocheng.net/webapi/create-crud-api-1-post.html http://www.yuanjiaocheng.net/w ...

  10. C# 对象实例化 用json保存 泛型类 可以很方便的保存程序设置

    用于永久化对象,什么程序都行,依赖NewtonSoft.用于json序列化和反序列化. using Newtonsoft.Json; using System; using System.Collec ...