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:     
   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. 构建ubuntu根文件系统

    构建ubuntu根文件系统 象棋小子   1048272975 Ubuntu是一个广泛应用于个人电脑,云计算,以及智能物联网设备的开源操作系统.针对智能物联网,Ubuntu提供了一套更加安全,轻量级, ...

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

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

  3. 构建最小根文件系统lfs

    构建最小根文件系统linuxfromscratch 一.Archival Utilities 8.8. Xz-5.2.5 Contents of Xz Installed programs: lzca ...

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

    嵌入式 Linux 启动过程 本文所描述的的 Linux Image 由 BootLoader.kernel.initrd.rootfs 组成,它们共同存在于一个可以启动的存储设备中(本文以 USB ...

  5. windows kernel 可以直接读写文件系统资料吗_嵌入式杂谈之文件系统

    作者:于宙 , 经授权转载:编辑略作调整: 文件系统可以说是嵌入式中绕不开的一大块内容.之前我对文件系统认知一直停留在在U盘格式的理解上,直到接触了嵌入式Linux才发现这里面大有文章,以Linux启 ...

  6. 「底层原理」一层层剥开文件系统的面纱,彻底理解Linux文件系统

    概述 提到文件系统,Linux的老江湖们对这个概念当然不会陌生,然而刚接触Linux的新手们就会被文件系统这个概念弄得晕头转向,恰好我当年正好属于后者. 从windows下转到Linux的童鞋听到最多 ...

  7. 固态硬盘 linux 文件系统,SSD是否需要使用特别的文件系统?

    用认真的态度与专业的情怀倾注于存储,欢迎关注我,与我交流哦! 我们常用的电脑系统主要是Windows和Linux,其中,Windows系统中常用的文件系统主要是FAT和NFTS,FAT是旧Window ...

  8. 解析linux中的vfs文件系统机制,解析Linux中的VFS文件系统机制

    解析Linux中的VFS文件系统机制 本文阐述 Linux 中的文件系统部分,源代码来自基于 IA32 的 2.4.20 内核.总体上说Linux下的文件系统主要可分为三大块:一是上层的文件系统的系统 ...

  9. 多用户文件系统java实现_为什么要有文件系统?文件系统都有那些种类?

    王大拿在给新员工培训:"无论是使用过Windows操作系统还是Linux操作系统的同学,相信都知道什么是文件系统." 赵二狗插嘴到:"什么是文件系统?" 好吧, ...

最新文章

  1. codeforces364D
  2. Android开发--SharedPreferences初步介绍
  3. python编程入门书籍推荐-Python编程启蒙书籍推荐
  4. Synchronize读脏
  5. Android 沉浸式状态栏 实现方式一
  6. chrome中Google插件导出导入
  7. python将图片存入数据库_使用python把图片存入数据库
  8. 第48次《中国互联网络发展状况统计报告》
  9. 内置指令-cloak // 内置指令-once // 内置指令-pre
  10. ros发布者退出后重新发布, 订阅者无法收到消息的解决办法
  11. php语法------04 php内置函数之与页面表单请求相关的内置函数对象
  12. 0基础学单片机要知道51单片机CPU的一些相关构成以及作用
  13. 刘永行:领导者的得与失
  14. excel数据分析常用图表制作案例
  15. Win10系统优化工具
  16. Reflex WMS入门系列七:收货(Receipt)
  17. Windows server 2008 R2 微软官方下载地址
  18. JVM监控Jconsole
  19. win7下crtl+scroll会触发蓝屏重启(滑稽脸)
  20. 优家益购——JavaWeb项目(Jsp+Servlet+MySQL+tomcat)

热门文章

  1. html5 滤色,深入理解CSS mix-blend-mode滤色screen混合模式
  2. gdb 版本和gcc版本的对应关系_GNU发布GDB新版本 10.1和 mtools 4.0.25
  3. html 地址坐标图标,浏览器地址栏中显示自定义小图标
  4. java代码上传到私服,java生成 java代码 上传maven私服
  5. 关于vue项目中输入框无法正常输入的问题
  6. html5和响应式,35个响应式HTML5和CSS3模版
  7. android+多米音乐+自动播放,android 高仿多米音乐播放器
  8. SpringMVC学习06之SSM整合(一)
  9. java 时间l_java中获取系统时间并格式化输出
  10. java 类与对象题_Java 类与对象 编程练习题