ASP.NET Core应用 具有很多读取文件的场景,比如配置文件、静态Web资源文件(比如CSS、JavaScript和图片文件等)以及MVC应用的View文件,甚至是直接编译到程序集中的内嵌资源文件。这些文件的读取都需要使用到一个IFileProvider对象。IFileProvider对象构建了一个抽象的文件系统,我们不仅可以利用它提供的统一API来读取各种类型的文件,还能及时监控目标文件的变化。 一、树形层次结构 IFileProvider对象为我们构建了一个具有层次化目录结构的文件系统。由于IFileProvider是一个接口,所以由它构建的是一个抽象化的文件系统,这里所谓的目录和文件都是一个抽象的概念。具体的文件可能对应一个物理文件,也可能保存在数据库中,或者来源于网络,甚至有可能根本就不存在,其内容需要在读取时动态生成。目录也仅仅是组织文件的逻辑容器。为了让读者朋友们对这个文件系统有一个大体认识,我们先来演示几个简单的实例。 文件系统管理的所有文件以目录的形式进行组织,一个IFileProvider对象可以视为针对一个根目录的映射。目录除了可以存放文件之外,还可以包含子目录,所以目录/文件在整体上呈现出树形化层次化结构。接下来我们将一个IFileProvider对象映射到一个物理目录,并利用它将所在目录的结构呈现出来。 https://www.cnblogs.com/artech/p/inside-asp-net-core-04-01.html 我们演示实例是一个普通的控制台程序。我们在演示实例中定义了如下一个IFileManager接口,它利用一个唯一的ShowStructure方法将文件系统的整体结构显示出来。该方法具有一个类型为Action的参数负责将文件系统的节点(目录或者文件)名称呈现出来。这个Action对象的两个参数分别代表缩进的层级和目录/文件的名称。 public interface IFileManager { void ShowStructure(Action render); } 我们定义如下这个FileManager类作为对IFileManager接口的默认实现,它利用只读_fileProvider字段表示的IFileProvider对象来提取目录结构。目标文件系统的整体结构通过Render方法以递归的方式呈现出来,其中涉及到对IFileProvider对象的GetDirectoryContents方法的调用。该方法返回一个IDirectoryContents对象表示指定目录的内容,如果对应的目录存在,我们可以遍历该对象得到它的子目录和文件。目录和文件最终体现为一个IFileInfo对象来,至于IFileInfo对象对应的就是一个目录还是一个文件,则通过其IsDirectory属性来区分。 public class FileManager : IFileManager { private readonly IFileProvider _fileProvider; public FileManager(IFileProvider fileProvider) => _fileProvider = fileProvider; public void ShowStructure(Action render) { int indent = -1; Render(""); void Render(string subPath) { indent++; foreach (var fileInfo in _fileProvider.GetDirectoryContents(subPath)) { render(indent, fileInfo.Name); if (fileInfo.IsDirectory) { Render($@"{subPath}\{fileInfo.Name}".TrimStart('\\')); } } indent--; } } } 接下来我们构建一个本地物理目录“c:\test\”,并按照如下图所示的结构在它下面创建相应的子目录和文件。我们会将这个目录映射到一个IFileProvider对象上,并进一步利用它创建出上面这个FileManager对象。我们最终调用这个FileManager对象的ShowStructure方法将目录结构呈现出来。 5-1 整个演示程序体现在如下的代码片段中。我们针对目录“c:\test\”创建了一个表示物理文件系统的PhysicalFileProvider对象,并将其注册到创建的ServiceCollection对象上。除此之外,ServiceCollection对象上还添加了针对IFileManager/FileManager的服务注册。 class Program { static void Main() { static void Print(int layer, string name) => Console.WriteLine($"{new string(' ', layer * 4)}{name}"); new ServiceCollection() .AddSingleton(new PhysicalFileProvider(@"c:\test")) .AddSingleton() .BuildServiceProvider() .GetRequiredService() .ShowStructure(Print); } } 我们最终利用ServiceCollection生成的IServiceProvider对象得到FileManager对象,并调用该对象的ShowStructure方法将PhysicalFileProvider对象映射的目录结构呈现出来。当我们运行该程序之后,控制台上将呈现出如下图所示的输出结果,该结果为我们展示了映射物理目录的真实结构。(S501) 5-2 二、读取文件内容 前面我们演示了如何利用IFileProvider对象将文件系统的结构完整地呈现出来,接下来我们来演示如何利用它来读取一个物理文件的内容。我们为IFileManager定义如下一个ReadAllTextAsync方法以异步的方式读取指定文件内容,方法的参数表示文件的路径。如下面的代码片段所示,ReadAllTextAsync方法将指定的文件路径作为参数调用IFileProvider对象的GetFileInfo方法得到一个IFileInfo对象。我们最终调用这个IFileInfo对象的CreateReadStream方法得到读取文件的输出流,进而得到文件的真实内容。 public interface IFileManager { ... Task ReadAllTextAsync(string path); } public class FileManager : IFileManager { ... public async Task ReadAllTextAsync(string path) { byte[] buffer; using (var stream = _fileProvider.GetFileInfo(path).CreateReadStream()) { buffer = new byte[stream.Length]; await stream.ReadAsync(buffer, 0, buffer.Length); } return Encoding.Default.GetString(buffer); } } 假设我们依然将FileManager使用的IFileProvider映射为目录“c:\test\”,现在我们在该目录中创建一个名为data.txt的文本文件,并在该文件中任意写入一些内容。接下来我们在Main方法中编写了如下的程序利用依赖注入的方式得到FileManager对象,并读取文件data.txt的内容。最终的调试断言旨在确定通过IFileProvider读取的确实就是目标文件的真实内容。(S502) class Program { static async Task Main() { var content = await new ServiceCollection() .AddSingleton(new PhysicalFileProvider(@"c:\test")) .AddSingleton() .BuildServiceProvider() .GetRequiredService() .ReadAllTextAsync("data.txt"); Debug.Assert(content == File.ReadAllText(@"c:\test\data.txt")); } } 三、内嵌文件系统 我们一直在强调由IFileProvider结构构建的是一个抽象的具有目录结构的文件系统,具体文件的提供方式取决于对具体的IFileProvider对象是怎样一个类型。我们演示实例定义的FileManager并没有限定具体使用何种类型的IFileProvider,该对象是在应用中通过依赖注入的方式指定的。由于上面的应用程序注入的是一个PhysicalFileProvider对象,所以我们可以利用它来读取对应物理目录下的某个文件。假设现在将这个data.txt直接以资源文件的形式编译到程序集中,我们就需要使用另一个名为EmbeddedFileProvider的实现类型。现在我们直接将这个data.txt文件添加到控制台应用的项目根目录下。在默认的情况下,当我们编译项目的时候这样的文件并不能成为内嵌到目标程序集的资源文件,我们需要利用VS将该文件的“Build Action”属性按照如下所示的方式设置为“Embedded resource”。 image 上图所示的设置将会体现在项目文件(.csproj文件)上。具体来说,项目文件会以如下的形式添加一个元素将文件data.txt设置为内嵌到编译后生成的程序集的内嵌资源文件。 ... 我们编写了如下的程序来演示针对内嵌于程序集中的资源文件的读取。我们首先得到当前入口程序集,并利用它创建了一个EmbeddedFileProvider对象,它代替原来的PhysicalFileProvider对象被注册到ServiceCollection之中。我们接下来采用了完全一致的编程方式得到FileManager对象并利用它读取内嵌文件data.txt的内容。为了验证读取的目标文件准确无误,我们采用直接读取资源文件的方式得到了内嵌文件data.txt的内容,并利用一个调试断言确定两者的一致性。(S503) class Program { static async Task Main() { var assembly = Assembly.GetEntryAssembly(); var content1 = await new ServiceCollection() .AddSingleton(new EmbeddedFileProvider(assembly)) .AddSingleton() .BuildServiceProvider() .GetRequiredService() .ReadAllTextAsync("data.txt"); var stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.data.txt"); var buffer = new byte[stream.Length]; stream.Read(buffer, 0, buffer.Length); var content2 = Encoding.Default.GetString(buffer); Debug.Assert(content1 == content2); } } 四、监控文件的变化 在文件读取场景中,确定加载到内存中的数据与源文件的一致性并自动同步是一个很常见的需求。比如说我们将配置定义在一个JSON文件中,应用启动的时候会读取该文件并将其转换成对应的Options对象。在很多情况下,如果我们改动了配置文件, 最新的配置数据只有在应用重启之后才能生效。如果我们能够以一种高效的方式对配置文件进行监控,并在其发生改变的情况下向应用发送通知,那么应用就能在不用重启的情况下重新读取配置文件,进而实现Options对象承载的内容和原始配置文件完全同步。 对文件系统实施监控并在其发生改变时发送通知也是IFileProvider对象提供的核心功能之一。接下来我们依然使用前面这个程序来演示如何使用PhysicalFileProvider对某个物理文件实施监控,并在目标文件的内容发生改变的时候重新读取新的内容。 class Program { static async Task Main() { using (var fileProvider = new PhysicalFileProvider(@"c:\test")) { string original = null; ChangeToken.OnChange(() => fileProvider.Watch("data.txt"), Callback); while (true) { File.WriteAllText(@"c:\test\data.txt", DateTime.Now.ToString()); await Task.Delay(5000); } async void Callback() { var stream = fileProvider.GetFileInfo("data.txt").CreateReadStream(); { var buffer = new byte[stream.Length]; await stream.ReadAsync(buffer, 0, buffer.Length); string current = Encoding.Default.GetString(buffer); if (current != original) { Console.WriteLine(original = current); } } } } } } 如上面的代码片段所示,我们针对目录“c:\test”创建了一个PhysicalFileProvider对象,并调用其Watch方法对指定的文件data.txt实施监控。该方法的返回一个IChangeToken对象,我们正是利用这个对象接收文件改变的通知。我们调用ChangeToken的静态方法OnChange针对这个对象注册了一个回调实现对源文件的重新读取和显示,当源文件发生改变的时候,注册的回调会自动执行。我们以每隔5秒的间隔对文件data.txt作一次修改,而文件的内容为当前时间。所以当我们的程序启动之后,每隔5秒钟当前时间就会以如下图的方式呈现在控制台上。 5-3 [ASP.NET Core 3框架揭秘] 文件系统[1]:抽象的“文件系统” [ASP.NET Core 3框架揭秘] 文件系统[2]:总体设计 [ASP.NET Core 3框架揭秘] 文件系统[3]:物理文件系统 [ASP.NET Core 3框架揭秘] 文件系统[4]:程序集内嵌文件系统 作者:蒋金楠 微信公众账号:大内老A 微博:www.weibo.com/artech 如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号)。 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 标签: ASP.NET Core 3 框架揭秘, .NET Core, ASP.NET Core, IFileProvider, PhysicalFileProvider, EmbeddedFileProvider 好文要顶 关注我 收藏该文 Artech 关注 - 52 粉丝 - 9151 推荐博客 +加关注 25 1 « 上一篇: [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配 » 下一篇: [ASP.NET Core 3框架揭秘] 文件系统[2]:总体设计 posted @ 2019-11-19 09:00 Artech 阅读(2974) 评论(14) 编辑 收藏 评论列表 #1楼 2019-11-19 09:09 johnhorse 大佬 书到哪个地步了? 还在等着呢 支持(0) 反对(0) #2楼 [楼主] 2019-11-19 09:49 Artech @ johnhorse 引用 大佬 书到哪个地步了? 还在等着呢 出版社编辑中 支持(5) 反对(0) #3楼 2019-11-19 09:56 angtianqiang 这个文件系统好像只能读取,不能更新,比如提供了读取配置文件的API,但我要是在运行时改一下配置文件,好像没有提供支持。 支持(0) 反对(0) #4楼 [楼主] 2019-11-19 09:58 Artech @ angtianqiang 引用 这个文件系统好像只能读取,不能更新,比如提供了读取配置文件的API,但我要是在运行时改一下配置文件,好像没有提供支持。 是的! 支持(0) 反对(0) #5楼 2019-11-19 17:15 angtianqiang @ Artech 这样,为啥还要用MS的文件系统,还不如自己用JSON库来读写 支持(0) 反对(0) #6楼 2019-11-19 19:40 lindexi @ angtianqiang 如果你自己解析就会遇到如内存压力,请求释放,以及很多 IO 性能问题 支持(0) 反对(0) #7楼 [楼主] 2019-11-19 20:43 Artech @ angtianqiang 引用 @Artech 这样,为啥还要用MS的文件系统,还不如自己用JSON库来读写 它的目的就是抽象出一个只读文件系统来解决ASP.NET Core框架中针对文件存储的需求(比如配置文件、比如视图文件、比如静态Web资源文件) 支持(0) 反对(0) #8楼 2019-11-20 09:27 HK_killer 光速支持 支持(0) 反对(0) #9楼 2019-11-20 09:42 荆棘人 感觉这样的代码写着难度好大啊,只会抄不会写………… 支持(0) 反对(0) #10楼 2019-11-20 09:48 放逐人 大佬好,首先是支持你的行为的,但之前买过你一本 asp.net web api ,感觉不行,同教材似的,照本宣科。一点也没有凸显出立意,仅只叙述一些类和方法,同msdn一个样。所有是否反思一下怎么写书。 支持(2) 反对(0) #11楼 [楼主] 2019-11-20 10:30 Artech @ 放逐人 引用 大佬好,首先是支持你的行为的,但之前买过你一本 asp.net web api ,感觉不行,同教材似的,照本宣科。一点也没有凸显出立意,仅只叙述一些类和方法,同msdn一个样。所有是否反思一下怎么写书。 实在是水平有限! 支持(0) 反对(0) #12楼 2019-11-20 11:56 angtianqiang @ Artech 引用 @angtianqiang 引用 引用@Artech 这样,为啥还要用MS的文件系统,还不如自己用JSON库来读写 它的目的就是抽象出一个只读文件系统来解决ASP.NET Core框架中针对文件存储的需求(比如配置文件、比如视图文件、比如静态Web资源文件) 存储的需求?都不能改,应该是读取需求 支持(0) 反对(0) #13楼 [楼主] 2019-11-20 13:15 Artech @ angtianqiang 引用 @Artech 引用 引用@angtianqiang 引用引用 @Artech 这样,为啥还要用MS的文件系统,还不如自己用JSON库来读写 它的目的就是抽象出一个只读文件系统来解决ASP.NET Core框架中针对文件存储的需求(比如配置文件、比如视图文件、比如静态Web资源文件) 存储的需求?都不能改,应该是读取需求 你把存储当做动词,我把它当名词! 支持(0) 反对(0) #14楼 2019-11-20 16:24 jionsoft @放逐人 我最开始看mvc4框架揭秘的时候跟你有意义的感觉,后来发现我错了。 推荐你一种方式: 先按作者的的顺序把每个类看一遍,大概老子里有个印象, 最后回过头按流程把每个类套进去思考。 反复多看几遍你会有大收获。 看老A的文章不仅仅是学一个框架或知识点。 是跟着他一起学习微软的开发思路,类与类各自独立又相互协作 最后完成一个复杂的任务。在变化点地方会出现接口、抽象类来隔离变化,留出扩展点 之前看老a的书入门了这种学习方式,后来自己去看了Katana和Identity源码,这种方式很好 支持(1) 反对(0) 刷新评论刷新页面返回顶部 注册用户登录后才能发表评论,请 登录 或 注册, 访问 网站首页。 【推荐】腾讯云海外1核2G云服务器低至2折,半价续费券限量免费领取! 【活动】京东云服务器_云主机低于1折,低价高性能产品备战双11 【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库 【培训】马士兵老师一对一在线指导!帮你从月薪3000到日薪3000 【推荐】天翼云双十一翼降到底,云主机11.11元起,抽奖送大礼 【提升】Java程序员年薪40W,他1年走了别人5年的学习之路 【推荐】流程自动化专家UiBot,体系化教程成就高薪RPA工程师 相关博文: · .NET Core的文件系统[1]:读取并监控文件的变化 · ASP.NET Core框架揭秘(持续更新中…) · 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[上]:采用管道处理请求 · ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求 · .NET Core采用的全新配置系统[1]: 读取配置数据 » 更多推荐... 最新 IT 新闻: · 王思聪被北京二院再发限制消费令:此前上海禁令刚取消 · 私有云服务商Gravitational获2500万美元A轮融资 · 比特币矿业巨头嘉楠在美IPO融资9000万美元 · 虚拟女友亦能摸!《头号玩家》场景实现且不插电 · 5G和数据中心推动高端FPGA芯片市场需求显著增加 » 更多新闻... 微信公众