ASP.NET Core 具有很多针对文件读取的应用。比如我们倾向于采用JSON文件来定义配置,所以应用就会涉及针对配置文件读取。如果用户发送一个针对物理文件的HTTP请求,应用会根据指定的路径读取目标文件的内容并对请求予以响应。在一个ASP.NET Core MVC应用中,针对View的动态编译会涉及到根据预定义的路径映射关系来读取目标View。这些不同应用场景都会出现一个FileProvider对象的身影,以此对象为核心的文件系统提供了统一的API来读取文件的内容并监控内容的改变。 [ 本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、一个抽象的“文件系统”
二、呈现文件系统的结构
三、读取物理文件内容
四、读取内嵌于程序集中的文件内容
五、监控文件的变化

一、一个抽象的“文件系统”

本章所谓的“文件系统”有点名不副实,其实根本算不上一个系统,它仅仅是利用一个抽象化的FileProvider以统一的方式提供所需的文件而已。不过笔者实在想不到一个更为贴切的描述短语,所以还是姑且称之为文件系统吧(github上对应的项目名称就叫FileSystem)。作为文件系统的核心,FileProvider是对所有实现了IFileProvider接口的所有类型以及对应对象的统称。正式因为FileProvider自身是个抽象的对象,所以由它构建的也是一个抽象的文件系统。

这个文件系统采用目录的方式来组织和规划文件,但是这里所谓的目录和文件都是一个抽象的概念,并非对一个具体物理目录和文件的映射。文件系统的目录仅仅是文件的逻辑容器,而文件可能对应一个物理文件,也可能保存在数据库中,或者来源于网络,甚至有可能根本就不能存在,其内容需要在读取时动态生成。为了让读者朋友们能够对这个文件系统具有一个大体认识,我们先来演示几个简单的实例。

二、呈现文件系统的结构

文件系统中的文件以目录的形式进行组织,一个FileProvider可以视为针对一个根目录的映射。目录除了可以存放文件之外,还可以包含多个子目录,所以目录/文件在整体上呈现出树形层细化结构。接下来我们利用提供的FileProvider对象并将它映射到一个物理目录,最终将所在目录的整个结构呈现出来。

我们创建一个控制台应用,并添加相应的NuGet包。由于IFileProvider接口定义在“Microsoft.Extensions.FileProviders.Abstractions”这个NuGet包中,针对物理文件的FileProvider(PhysicalFileProvider)所在的NuGet包名为“Microsoft.Extensions.FileProviders.Physical”,所以我们只需要添加后者的依赖即可。除此之外,我们将采用针对依赖注入的编程方式,我们还添加了针对“Microsoft.Extensions.DependencyInjection”这个NuGet包的依赖。如下所示的是针对这两个NuGet包的依赖在project.json文件中的定义。

   1: {  

   2:   ...

   3:   "dependencies": {

   4:     ...

   5:     "Microsoft.Extensions.DependencyInjection"    : "1.0.0",

   6:     "Microsoft.Extensions.FileProviders.Physical"    : "1.0.0"

   7:   },

   8:   ...

   9: }

我们定义了如下一个IFileManager接口,它利用一个唯一的方式ShowStructure将文件系统的整体结构显示出来。该方法具有一个类型为Action<int, string>的参数,后者负责将文件系统的节点(目录或者文件)呈现出来。对于这个Action<int, string>委托对象的两个泛型参数,第一个整型参数代表缩进的层级,后一个代表需要显示的目录或者文件的名称。

   1: public interface IFileManager

   2: {

   3:     void ShowStructure(Action<int, string> render);

   4: }

如下所示的是实现了上面这个IFileManager接口的FileManager类型。构建文件系统的FileProvider对象对应着同名的只读属性,该属性在构造函数中通过对应的参数进行赋值。目标文件系统的整体结构最终是通过Render方法以递归的方式呈现出来的,这其中涉及到FileProvider的GetDirectoryContents方法的调用。该方法返回一个DirectoryContents对象表示由指定路径指向的目录内容,如果对应的目录存在,我们可以遍历该对象得到它的子目录和文件。目录和文件通过一个FileInfo对象来表示,至于究竟是目录还是文件,则通过其属性IsDirectory来区分。

   1: public class FileManager: IFileManager

   2: {

   3:     public IFileProvider FileProvider { get; private set; }

   4:  

   5:     public FileManager(IFileProvider fileProvider)

   6:     {

   7:         this.FileProvider = fileProvider;

   8:     }

   9:  

  10:     public void ShowStructure (Action<int, string> render)

  11:     {

  12:         int layer = -1;

  13:         Render("", ref layer, render);

  14:     }

  15:  

  16:     private void Render(string subPath, ref int layer, Action<int, string> render)

  17:     {

  18:         layer++;

  19:         foreach (var fileInfo in this.FileProvider.GetDirectoryContents(subPath))

  20:         {

  21:             render(layer, fileInfo.Name);

  22:             if (fileInfo.IsDirectory)

  23:             {

  24:                 Render($@"{subPath}\{fileInfo.Name}".TrimStart('\\'), ref layer, render);

  25:             }

  26:         }

  27:         layer--;

  28:     }

  29: }

接下来我们为演示的FileProvider构建一个映射的物理目录。将“C:\Test\”目录作为根目录,然后按照如下图所示的结构在它下面创建相应的子目录和文件。我们将利用映射为该目录的FileProvider创建上面定义的这个FileManager,那么调用它的ShowStructure方法应该呈现出与物理目录完全一致的结构。

我们在Main方法中编写了如下的演示程序。我们针对目录“C:\Test\”创建了一个PhysicalFileProvider对象,并采用服务接口类型IFileProvider注册到ServiceCollection对象上。除此之外,注册到同一个ServiceCollection对象上的还有IFileViwer和FileManager之间的映射。

   1: new ServiceCollection()

   2:     .AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:\test"))

   3:     .AddSingleton<IFileManager, FileManager>()

   4:     .BuildServiceProvider()

   5:     .GetService<IFileManager>()

   6:     .ShowStructure((layer, name) => Console.WriteLine("{0}{1}", new string('\t', layer), name));

我们最终利用ServiceCollection生成的ServiceProvider得到FileManager对象,并调用其ShowStructure方法将PhysicalFileProvider对象映射的目录结构呈现出来。当我们运行该程序之后,控制台上将呈现出如下所示的输出结果,该结果为我们展示了映射物理目录的真实结构。

三、读取物理文件内容

上面我们演示了如何利用FileProvider将文件系统的结构完整地呈现出来,接下来我们来演示如何利用它来读取一个具体文件的内容。我们为IFileManager定义如下一个ReadAllTextAsync方法以异步的方式读取指定路径对应的文件,并以字符串的形式返回读取的内容。FileManager依然利用一个FileProvider来完成针对文件的读取工作。具体来说,它将指定的文件路径作为参数调用其GetFileInfo方法并得到一个FileInfo对象。接下来,我们调用FileInfo的CreateReadStream得到读取文件的输出流,并利用后者得到文件的真实内容,最终采用最简单的ASCII码转换成返回的字符串。

   1: public interface IFileManager

   2: {

   3:     ...

   4:     Task<string> ReadAllTextAsync(string path);

   5: }

   6:  

   7: public class FileManager : IFileManager

   8: {

   9:     ...

  10:     public async Task<string> ReadAllTextAsync(string path)

  11:     {

  12:         byte[] buffer;

  13:         using (Stream readStream = this.FileProvider.GetFileInfo(path).CreateReadStream())

  14:         {

  15:             buffer = new byte[readStream.Length];

  16:             await readStream.ReadAsync(buffer, 0, buffer.Length);

  17:         }

  18:         return Encoding.ASCII.GetString(buffer);

  19:     }

  20: }

假设我们依然将FileManager使用的FileProvider映射为目录“C:\Test\”,现在我们该目录中创建一个名为data.txt的文本文件,并在该文件中任意写入一些内容。接下来我们在Main方法中编写了如下的程序利用依赖注入的方式得到FileManager对象,并读取文件data.txt的内容。最终的调试断言旨在确定通过FileProvider读取的确实就是目标文件的真实内容。

   1: string content = new ServiceCollection()

   2:     .AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:\test"))

   3:     .AddSingleton<IFileManager, FileManager>()

   4:     .BuildServiceProvider()

   5:     .GetService<IFileManager>()

   6:     .ReadAllTextAsync("data.txt").Result;

   7:  

   8: Debug.Assert(content == File.ReadAllText(@"c:\test\data.txt"));

四、读取内嵌于程序集中的文件内容

我们一直在强调由FileProvider构建的是一个抽象的具有目录结构的文件系统,具体文件的提供方式取决于具体FileProvider的实现。由于我们定义的FileManager并没有限定具体使用何种类型的FileProvider,后者是在应用中通过依赖注入的方式指定的。由于上面的应用程序注入的是一个PhysicalFileProvider对象,所以我们可以利用它来读取对应目录下的某个文件。假设现在我们将这个hello.txt直接以资源文件的形式编译到程序集中,我们就需要使用另一个名为EmbeddedFileProvider的FileProvider

现在我们直接将这个data.txt文件添加到控制台应用的项目根目录下。在默认的情况下,当我们编译项目的时候这样的文件并不能成为内嵌到目标程序集的资源文件,为此我们需要在project.json上作一些与编译相关的设置。具体来说,我们需要按照如下的方式将文件hello.txt的路径添加到通过配置节“buildOptions/embed”表示的内嵌文件列表中。除此之外,由于EmbeddedFileProvider定义在“Microsoft.Extensions.FileProviders.Embedded”这个NuGet包中,我们需要添加针对它的依赖。

   1: {

   2:   ...

   3:   "buildOptions": {

   4:     ...

   5:     "embed": ["data.txt"]

   6:   },

   7:   "dependencies": {

   8:     ...

   9:     "Microsoft.Extensions.DependencyInjection"       : "1.0.0",

  10:     "Microsoft.Extensions.FileProviders.Embedded"    : "1.0.0"

  11:   },

  12:   ...

  13: }

我们编写了如下的程序来演示针对内嵌于程序集中的资源文件的读取。我们首先得到当前入口程序集,并利用它创建了一个EmbeddedFileProvider,后者替换原来的PhysicalFileProvider对象被注册到ServiceCollection之上。我们接下来采用与上面完全一致的编程方式得到FileManager对象并利用它读取内嵌文件data.txt的内容。为了验证读取的目标文件准确无误,我们采用直接读取资源文件的方式得到了内嵌文件data.txt的内容,并利用一个调试断言确定两者的一致性。

   1: Assembly assembly = Assembly.GetEntryAssembly();

   2:  

   3: //利用EmbeddedFileProvider读取文件

   4: string content1 = new ServiceCollection()

   5:     .AddSingleton<IFileProvider>(new EmbeddedFileProvider(assembly))

   6:     .AddSingleton<IFileManager, FileManager>()

   7:     .BuildServiceProvider()

   8:     .GetService<IFileManager>()

   9:     .ReadAllTextAsync("data.txt").Result;

  10:  

  11: //直接读取内嵌资源文件

  12: Stream stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.data.txt");

  13: byte[] buffer = new byte[stream.Length];

  14: stream.Read(buffer, 0, buffer.Length);

  15: string content2 = Encoding.ASCII.GetString(buffer);

  16:  

  17: Debug.Assert(content1 == content2);

五、监控文件的变化

在文件读取场景中,应用数据与源文件的同步是一个很常见的需求。比如说我们将配置定义在一个JSON文件中,应用启动的时候会读取该文件并根据配置数据对应用作相应的设置。在很多情况下,如果我们改动了配置文件, 最新的配置数据只有在应用重启之后才能生效。如果我们能够以一种高效的方式对配置文件进行监控,并在其发生改变的情况下相应用发送通知,那么应用就能在不用重启的情况下重新读取配置文件,进而实现应用配置和原始配置文件的同步。

对文件系统试试监控并在发生改变时发送通知也是FileProvider对象的核心功能之一。接下来我们依然使用上面这个控制台文件来演示如何使用PhysicalFileProvider来对某个物理文件试试监控,并在目标文件的内容发生改变的时候重新读取新的内容。定义在Main方法上的整个程序代码如下所示。

   1: public class Program

   2: {

   3:     public static void Main(string[] args)

   4:     {

   5:         IFileProvider fileProvider = new PhysicalFileProvider(@"c:\test");

   6:         ChangeToken.OnChange(() => fileProvider.Watch("data.txt"), () => LoadFileAsync(fileProvider));

   7:         while (true)

   8:         {

   9:             File.WriteAllText(@"c:\test\data.txt", DateTime.Now.ToString());

  10:             Task.Delay(5000).Wait();

  11:         }

  12:     }

  13:  

  14:     public static async void LoadFileAsync(IFileProvider fileProvider)

  15:     {

  16:         Stream stream = fileProvider.GetFileInfo("data.txt").CreateReadStream();

  17:         {

  18:             byte[] buffer = new byte[stream.Length];

  19:             await stream.ReadAsync(buffer, 0, buffer.Length);

  20:             Console.WriteLine(Encoding.ASCII.GetString(buffer));

  21:         }

  22:     }

  23: }

如上面的代码片段所示,我们针对目录“c:\test”创建了一个PhysicalFileProvider,并调用Watch方法对指定的文件data.txt实施监控。该方法的返回类型为IChangeToken,我们正式利用这个对象接收文件改变的通知。我们调用ChangeToken的静态方法OnChange针对这个对象注册了一个回调,意味着当源文件发生改变的时候,注册的回调会自动执行,进而实现对源文件的重新读取和显示。在程序的末端,我们以每隔5秒的间隔对文件data.txt作一次修改,而文件的内容为当前时间。所以当我们的程序启动之后,每隔5秒钟当前时间就会以如下的方式呈现在控制台上。

.NET Core的文件系统[1]:读取并监控文件的变化的更多相关文章

  1. Storm监控文件夹变化 统计文件单词数量

    监控指定文件夹,读取文件(新文件动态读取)里的内容,统计单词的数量. FileSpout.java,监控文件夹,读取新文件内容 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...

  2. 监控文件内容变化,即时写入到新文件(tail)

    监控文件a,如有新内容写入,即时将新内容写入到新文件aa中: fw=open('e:\\aa.txt','ab') with open('e:\\a.txt','rb') as fo: while T ...

  3. C#监控文件夹变化

    当需要监控某一文件,FileSystemWatcher类提供了Created, Deleted,Rename等事件. 就拿FileSystemWatcher的Created事件来说,该事件类型是Fil ...

  4. .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文 ...

  5. .NET Core的文件系统[2]:FileProvider是个什么东西?

    在<读取并监控文件的变化>中,我们通过三个简单的实例演示从编程的角度对文件系统做了初步的体验,接下来我们继续从设计的角度来继续认识它.这个抽象的文件系统以目录的形式来组织文件,我们可以利用 ...

  6. .NET Core的文件系统[3]:由PhysicalFileProvider构建的物理文件系统

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

  7. 使用TheFolderSpy监控文件夹的变化-邮件通知

    一.概述 当我们的文档或者代码文件发布在公网.共享文件夹中,其他用户具备访问或修改的权限时,就存在文档被覆盖或删除的分享.另外一个典型的场景,发布在Web服务器上的网页文件,在网站版本不更新的时间,服 ...

  8. linux 监控文件变化

    介绍 有时候我们常需要当文件变化的时候便触发某些脚本操作,比如说有文件更新了就同步文件到远程机器.在实现这个操作上,主要用到两个工具,一个是rsync,一个是inotifywait .inotifyw ...

  9. .NET Core的文件系统[4]:由EmbeddedFileProvider构建的内嵌(资源)文件系统

    一个物理文件可以直接作为资源内嵌到编译生成的程序集中.借助于EmbeddedFileProvider,我们可以统一的编程方式来读取内嵌于某个程序集中的资源文件,不过在这之前我们必须知道如何将一个项目文 ...

随机推荐

  1. Asp.net Boilerplate之AbpSession扩展

    当前Abp版本1.2,项目类型为MVC5. 以属性的形式扩展AbpSession,并在"记住我"后,下次自动登录也能获取到扩展属性的值,版权归"角落的白板报"所 ...

  2. 【AR实验室】ARToolKit之概述篇

    0x00 - 前言 我从去年就开始对AR(Augmented Reality)技术比较关注,但是去年AR行业一直处于偶尔发声的状态,丝毫没有其"异姓同名"的兄弟VR(Virtual ...

  3. 以bank account 数据为例,认识elasticsearch query 和 filter

    Elasticsearch 查询语言(Query DSL)认识(一) 一.基本认识 查询子句的行为取决于 query context filter context 也就是执行的是查询(query)还是 ...

  4. Java定时任务的常用实现

    Java的定时任务有以下几种常用的实现方式: 1)Timer 2)ScheduledThreadPoolExecutor 3)Spring中集成Cron Quartz 接下来依次介绍这几类具体实现的方 ...

  5. C#日志

    参考页面: http://www.yuanjiaocheng.net/Entity/first.html http://www.yuanjiaocheng.net/Entity/jieshao.htm ...

  6. VB.NET设置控件和窗体的显示级别

    前言:在用VB.NET开发射频检测系统ADS时,当激活已存在的目标MDI子窗体时,被其他子窗体遮住了,导致目标MDI子窗体不能显示. 这个问题怎么解决呢?网上看到一篇帖子VB.NET设置控件和窗体的显 ...

  7. 张小龙宣布微信小程序1月9日发布,并回答了大家最关心的8个问题

    2016 年 12 月 28 日,张小龙在微信公开课 PRO 版的会场上,宣布了微信小程序的正式发布时间. 微信小程序将于 2017 年 1 月 9 号正式上线. 同时他解释称,小程序就像PC时代的网 ...

  8. BZOJ 1103: [POI2007]大都市meg [DFS序 树状数组]

    1103: [POI2007]大都市meg Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2221  Solved: 1179[Submit][Sta ...

  9. React单元测试——十八般兵器齐上阵,环境构建篇

    一个完整.优秀的项目往往离不开单元测试的环节,就 github 上的主流前端项目而言,基本都有相应的单元测试模块. 就 React 的项目来说,一套完整的单元测试能在在后续迭代更新中回归错误时候给与警 ...

  10. Quality 是什么?

    Quality 是什么? 通常,我们谈及 Quality(质量)时,最常见的问题就是:Quality 是什么? 有很多业界先驱和研究人员已经回答了这个问题,我在这里并不会再给出一个新的答案.在学习总结 ...