.NET Core的文件系统[1]:读取并监控文件的变化
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: },1)'> 8: ... 9: }
我们定义了如下一个IFileManager接口,它利用一个唯一的方式ShowStructure将文件系统的整体结构显示出来。该方法具有一个类型为Action<int,string>的参数,后者负责将文件系统的节点(目录或者文件)呈现出来。对于这个Action<int,string>委托对象的两个泛型参数,第一个整型参数代表缩进的层级,后一个代表需要显示的目录或者文件的名称。 2: {
4: } 如下所示的是实现了上面这个IFileManager接口的FileManager类型。构建文件系统的FileProvider对象对应着同名的只读属性,该属性在构造函数中通过对应的参数进行赋值。目标文件系统的整体结构最终是通过Render方法以递归的方式呈现出来的,这其中涉及到FileProvider的GetDirectoryContents方法的调用。该方法返回一个DirectoryContents对象表示由指定路径指向的目录内容,如果对应的目录存在,我们可以遍历该对象得到它的子目录和文件。目录和文件通过一个FileInfo对象来表示,至于究竟是目录还是文件,则通过其属性IsDirectory来区分。 public IFileProvider FileProvider { get; private set; }
public FileManager(IFileProvider fileProvider) 7: this.FileProvider = fileProvider;
9:? 10: void ShowStructure (Action<string> render) 11: {
12: int layer = -1; 13: Render("",1)">ref layer,render); 14: }
15:?
16: private void Render(string subPath,1)">ref int layer,Action< 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(''),1)'> 25: } 26: }
27: layer--;
28: }
29: }
接下来我们为演示的FileProvider构建一个映射的物理目录。将“C:Test”目录作为根目录,然后按照如下图所示的结构在它下面创建相应的子目录和文件。我们将利用映射为该目录的FileProvider创建上面定义的这个FileManager,那么调用它的ShowStructure方法应该呈现出与物理目录完全一致的结构。 ? 我们在Main方法中编写了如下的演示程序。我们针对目录“C:Test”创建了一个PhysicalFileProvider对象,并采用服务接口类型IFileProvider注册到ServiceCollection对象上。除此之外,注册到同一个ServiceCollection对象上的还有IFileViwer和FileManager之间的映射。 2: .AddSingleton<IFileProvider>(new PhysicalFileProvider(@"c:test"))
4: .BuildServiceProvider() 6: .ShowStructure((layer,name) => Console.WriteLine("{0}{1}",1)">new string('t',layer),name)); 我们最终利用ServiceCollection生成的ServiceProvider得到FileManager对象,并调用其ShowStructure方法将PhysicalFileProvider对象映射的目录结构呈现出来。当我们运行该程序之后,控制台上将呈现出如下所示的输出结果,该结果为我们展示了映射物理目录的真实结构。 三、读取物理文件内容上面我们演示了如何利用FileProvider将文件系统的结构完整地呈现出来,接下来我们来演示如何利用它来读取一个具体文件的内容。我们为IFileManager定义如下一个ReadAllTextAsync方法以异步的方式读取指定路径对应的文件,并以字符串的形式返回读取的内容。FileManager依然利用一个FileProvider来完成针对文件的读取工作。具体来说,它将指定的文件路径作为参数调用其GetFileInfo方法并得到一个FileInfo对象。接下来,我们调用FileInfo的CreateReadStream得到读取文件的输出流,并利用后者得到文件的真实内容,最终采用最简单的ASCII码转换成返回的字符串。 4: Task<string> ReadAllTextAsync(string path);
6:? 8: { public async Task<string path)
13: using (Stream readStream = this.FileProvider.GetFileInfo(path).CreateReadStream()) 15: buffer = byte[readStream.Length];
17: } 19: } string content = 6: .ReadAllTextAsync("data.txt").Result; 8: Debug.Assert(content == File.ReadAllText(@"c:testdata.txt"));
四、读取内嵌于程序集中的文件内容我们一直在强调由FileProvider构建的是一个抽象的具有目录结构的文件系统,具体文件的提供方式取决于具体FileProvider的实现。由于我们定义的FileManager并没有限定具体使用何种类型的FileProvider,后者是在应用中通过依赖注入的方式指定的。由于上面的应用程序注入的是一个PhysicalFileProvider对象,所以我们可以利用它来读取对应目录下的某个文件。假设现在我们将这个hello.txt直接以资源文件的形式编译到程序集中,我们就需要使用另一个名为EmbeddedFileProvider的FileProvider 现在我们直接将这个data.txt文件添加到控制台应用的项目根目录下。在默认的情况下,当我们编译项目的时候这样的文件并不能成为内嵌到目标程序集的资源文件,为此我们需要在project.json上作一些与编译相关的设置。具体来说,我们需要按照如下的方式将文件hello.txt的路径添加到通过配置节“buildOptions/embed”表示的内嵌文件列表中。除此之外,由于EmbeddedFileProvider定义在“Microsoft.Extensions.FileProviders.Embedded”这个NuGet包中,我们需要添加针对它的依赖。 "buildOptions": {
6: },1)"> 7: 8: ...
13: } 我们编写了如下的程序来演示针对内嵌于程序集中的资源文件的读取。我们首先得到当前入口程序集,并利用它创建了一个EmbeddedFileProvider,后者替换原来的PhysicalFileProvider对象被注册到ServiceCollection之上。我们接下来采用与上面完全一致的编程方式得到FileManager对象并利用它读取内嵌文件data.txt的内容。为了验证读取的目标文件准确无误,我们采用直接读取资源文件的方式得到了内嵌文件data.txt的内容,并利用一个调试断言确定两者的一致性。 2:?
4: string content1 = 5: .AddSingleton<IFileProvider>(new EmbeddedFileProvider(assembly)) 8: .GetService<IFileManager>() 11: //直接读取内嵌资源文件
13: byte[] buffer = byte[stream.Length]; 16:? class Program 4: { 6: ChangeToken.OnChange(() => fileProvider.Watch("data.txt"),() => LoadFileAsync(fileProvider));
8: { 10: Task.Delay(5000).Wait(); 12: } 14: static async void LoadFileAsync(IFileProvider fileProvider) 16: Stream stream = fileProvider.GetFileInfo("data.txt").CreateReadStream();
18: 19: await stream.ReadAsync(buffer,1)"> 20: Console.WriteLine(Encoding.ASCII.GetString(buffer));
22: } 相关内容
推荐文章
站长推荐
热点阅读
|