.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 系统.所以就把 ...
随机推荐
- 关于DOM的操作以及性能优化问题-重绘重排
写在前面: 大家都知道DOM的操作很昂贵. 然后贵在什么地方呢? 一.访问DOM元素 二.修改DOM引起的重绘重排 一.访问DOM 像书上的比喻:把DOM和JavaScript(这里指ECMScri ...
- Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系
祝各位2017年事业辉煌!开年第一篇博客,继续探索Xamarin.Forms… 为什么我做Xamarin开发的时候中意于Prism.Forms框架?本章为你揭晓. 实例代码地址:https://git ...
- ASP.NET Core应用的错误处理[2]:DeveloperExceptionPageMiddleware中间件如何呈现“开发者异常页面”
在<ASP.NET Core应用的错误处理[1]:三种呈现错误页面的方式>中,我们通过几个简单的实例演示了如何呈现一个错误页面,这些错误页面的呈现分别由三个对应的中间件来完成,接下来我们将 ...
- HTTP常用状态码分析
不管是面试还是工作中,经常会碰到需要通过HTTP状态码去判断问题的情况,比如对于后台RD,给到前端FE的一个接口,出现502或者504 error错误,FE就会说接口存在问题,如果没有知识储备,那就只 ...
- OpenCV模板匹配算法详解
1 理论介绍 模板匹配是在一幅图像中寻找一个特定目标的方法之一,这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否“相似”,当相似度足够高时,就认为找到了我们的目标.OpenCV ...
- CRL快速开发框架系列教程九(导入/导出数据)
本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...
- PHP获取上个月最后一天的一个容易忽略的问题
正常来说,PHP是有一个很方便的函数可以获取上个月时间的 strtotime (PHP 4, PHP 5, PHP 7) strtotime - 将任何英文文本的日期时间描述解析为 Unix 时间戳 ...
- python10作业思路及源码:类Fabric主机管理程序开发(仅供参考)
类Fabric主机管理程序开发 一,作业要求 1, 运行程序列出主机组或者主机列表(已完成) 2,选择指定主机或主机组(已完成) 3,选择主机或主机组传送文件(上传/下载)(已完成) 4,充分使用多线 ...
- oracle常用函数及示例
学习oracle也有一段时间了,发现oracle中的函数好多,对于做后台的程序猿来说,大把大把的时间还要学习很多其他的新东西,再把这些函数也都记住是不太现实的,所以总结了一下oracle中的一些常用函 ...
- firebug不能加载JS文件 ,无法进行JS脚本调试
提示: 本页面不包含 Javascript 如果 <script> 标签有 "type" 属性,其值应为 "text/javascript" 或者& ...