5.1 抽象的文件系统
ASP.NET Core应用 具有很多读取文件的场景,比如配置文件、静态Web资源文件(比如CSS、JavaScript和图片文件等)以及MVC应用的View文件,甚至是直接编译到程序集中的内嵌资源文件。这些文件的读取都需要使用到一个IFileProvider对象。IFileProvider对象构建了一个抽象的文件系统,我们不仅可以利用它提供的统一API来读取各种类型的文件,还能及时监控目标文件的变化。
1、树形层次结构
IFileProvider对象的GetDirectoryContents方法的调用。该方法返回一个IDirectoryContents对象表示指定目录的内容,如果对应的目录存在,我们可以遍历该对象得到它的子目录和文件。目录和文件最终体现为一个IFileInfo对象来,至于IFileInfo对象对应的就是一个目录还是一个文件,则通过其IsDirectory属性来区分。
2、读取文件内容
IFileProvider对象的GetFileInfo方法得到一个IFileInfo对象。我们最终调用这个IFileInfo对象的CreateReadStream方法得到读取文件的输出流,进而得到文件的真实内容。
3、内嵌资源的读取
由IFileProvider结构构建的是一个抽象的具有目录结构的文件系统,具体文件的提供方式取决于对具体的IFileProvider对象是怎样一个类型。该对象是在应用中通过依赖注入的方式指定的。由于上面的应用程序注入的是一个PhysicalFileProvider对象,所以我们可以利用它来读取对应物理目录下的某个文件。假设现在将这个data.txt直接以资源文件的形式编译到程序集中,我们就需要使用另一个名为EmbeddedFileProvider的实现类型。
嵌入的资源的设置,(会编译到项目目录中)
4、监控文件的改变
在文件读取场景中,确定加载到内存中的数据与源文件的一致性并自动同步是一个很常见的需求。
比如说我们将配置定义在一个JSON文件中,应用启动的时候会读取该文件并将其转换成对应的Options对象。在很多情况下,如果我们改动了配置文件, 最新的配置数据只有在应用重启之后才能生效。如果我们能够以一种高效的方式对配置文件进行监控,并在其发生改变的情况下向应用发送通知,那么应用就能在不用重启的情况下重新读取配置文件,进而实现Options对象承载的内容和原始配置文件完全同步。
PhysicalFileProvider对象,并调用其Watch方法对指定的文件data.txt实施监控。该方法的返回一个IChangeToken对象,我们正是利用这个对象接收文件改变的通知。我们调用ChangeToken的静态方法OnChange针对这个对象注册了一个回调实现对源文件的重新读取和显示,当源文件发生改变的时候,注册的回调会自动执行。【模拟文件改变】我们以每隔5秒的间隔对文件data.txt作一次修改,而文件的内容为当前时间。所以当我们的程序启动之后,每隔5秒钟当前时间就会以如下图的方式呈现在控制台上。
示例:读取本地文件/文件夹
5.2 总体设计
public interface IFileProvider { IFileInfo GetFileInfo(string subpath); IDirectoryContents GetDirectoryContents(string subpath); IChangeToken Watch(string filter); }
1、 物理文件系统
ASP.NET Core应用中使用得最多的还是具体的物理文件,比如配置文件、View文件以及作为Web资源的静态文件。
1、PhysicalFileProvider对象
我们知道System.IO命名空间下定义了一整套针操作物理目录和文件的API,实际上PhysicalFileProvider最终也是通过调用这些API来完成相关的IO操作。
public class PhysicalFileProvider : IFileProvider, IDisposable { public PhysicalFileProvider(string root); public IFileInfo GetFileInfo(string subpath); public IDirectoryContents GetDirectoryContents(string subpath); public IChangeToken Watch(string filter); public void Dispose(); }
2、PhysicalFileInfo
一个PhysicalFileProvider对象总是映射到某个具体的物理目录上,被映射的目录所在的路径通过构造函数的参数root来提供,该目录将作为PhysicalFileProvider的根目录。GetFileInfo方法返回的IFileInfo对象代表指定路径对应的文件,这是一个类型为PhysicalFileInfo的对象。一个物理文件可以通过一个System.IO.FileInfo对象来表示,一个PhysicalFileInfo对象实际上就是对该对象的封装,定义在PhysicalFileInfo的所有属性都来源于这个FileInfo对象。对于创建读取文件输出流的CreateReadStream方法来说,它返回的是一个根据物理文件绝对路径创建的FileStream对象。
PhysicalFileProvider会将一些场景视为“目标文件不存在”,并让GetFileInfo方法返回一个NotFoundFileInfo对象。具体来说,PhysicalFileProvider的GetFileInfo方法在如下的场景中会返回一个NotFoundFileInfo对象:
- 确实没有一个物理文件与指定的路径相匹配。
- 如果指定的是一个绝对路径(比如“c:foobar”),即Path.IsPathRooted方法返回True。
- 如果指定的路径指向一个隐藏文件。
3、PhysicalFilesWatcher
我们接着来谈谈PhysicalFileProvider的Watch方法。当我们调用该方法的时候,PhysicalFileProvider会通过解析我们提供的Globbing Pattern表达式来确定我们期望监控的文件或者目录,并最终利用FileSystemWatcher对象来对这些文件实施监控。这些文件或者目录的变化(创建、修改、重命名和删除等)都会实时地反映到Watch方法返回的IChangeToken上。
4、小结:
该文件系统使用PhysicalDirectoryInfo和PhysicalFileInfo对类型来描述目录和文件,它们分别是对DirectoryInfo和FileInfo(System.IO.FileInfo)对象的封装。
PhysicalFileProvider的GetDirectoryContents方法返回一个PhysicalDirectoryContents 对象(如果指定的目录存在),组成该对象的分别是根据其所有子目录和文件创建的PhysicalDirectoryInfo和PhysicalFileInfo对象。
当我们调用PhysicalFileProvider的GetFileInfo方法时,如果指定的文件存在,返回的是描述该文件的PhysicalFileInfo对象。
至于PhysicalFileProvider的Watch方法,它最终利用了FileSystemWatcher来监控指定文件或者目录的变化。
2、 内嵌文件系统
1、直接可以修改.csproj文件 来将项目文件变成内嵌资源,也可以可视化方式(在vs中对文件的属性设置)
<Project Sdk="Microsoft.NET.Sdk"> ... <ItemGroup> <EmbeddedResource Include="root/**" Exclude="root/dir1/baz.txt"></EmbeddedResource> </ItemGroup> </Project>
2、读取资源文件
虽然文件在原始的项目中具有层次化的目录结构,但是当它们成功转移到编译生成的程序集中之后,目录结构将不复存在,所有的内嵌文件将统一存放在同一个容器中。如果我们通过Reflector打开程序集,资源文件的扁平化存储将会一目了然。为了避免命名冲突,编译器将会根据原始文件所在的路径来对资源文件重新命名,具体的规则是“{BaseNamespace}.{Path}”,目录分隔符将统一转换成“.”。值得强调的是资源文件名称的前缀不是程序集的名称,而是我们为项目设置的基础命名空间的名称。
表示程序集的Assembly对象定义了如下几个方法来提取内嵌资源的文件的相关信息和读取指定资源文件的内容。GetManifestResourceNames方法帮助我们获取记录在程序集清单文件中的资源文件名,而另一个方法GetManifestResourceInfo则用于获取指定资源文件的描述信息。如果我们需要读取某个资源文件的内容,我们可以将资源文件名称作为参数调用GetManifestResourceStream方法,该方法会返回一个读取文件内容的Stream对象。
public abstract class Assembly { public virtual string[] GetManifestResourceNames(); public virtual ManifestResourceInfo GetManifestResourceInfo(string resourceName); public virtual Stream GetManifestResourceStream(string name); }
5.3 远程文件系统
FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为我们构建了一个物理文件系统和程序集内嵌文件系统。总的来说,它们针对的都是“本地”文件。
接下来我们通过自定义FileProvider构建一个“远程”文件系统,我们可以将它视为一个只读的“云盘”。由于文件系统的目录结构和文件内容都是通过HTTP请求的方式读取的,所以我们将这个自定义的FileProvider命名为HttpFileProvider。