ASP.NET Core 3 框架揭秘(上下册)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

5.3 远程文件系统

IFileProvider 构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider 和EmbeddedFileProvider 分别构建了一个物理文件系统与程序集内嵌文件系统。总的来说,它们针对的都是“本地”文件,下面通过自定义 IFileProvider 实现类型构建一个“远程”文件系统,我们可以将它视为一个只读的“云盘”。由于文件系统的目录结构和文件内容都是通过 HTTP请求的方式读取的,所以可以将这个自定义的 IFileProvider 实现命名为 HttpFileProvider。读者可以将本节作为选读内容,跳过本节不会影响后续章节的阅读。

图 5-8基本上体现了以 HttpFileProvider为核心的远程文件系统的设计和实现原理。我们将真实的文件保存在文件服务器上,客户端可以通过公布的 Web API 得到指定路径所在的目录结构和描述信息,以及读取指定文件的内容。文件服务器中的每个目录都对应一个 URL,客户端可以通过指定相应的URL将某个目录作为本地文件系统的根目录。

图5-8 远程文件系统设计和实现原理

如图 5-8 所示,服务器上的文件系统实际上是直接通过指向“c:\test”目录的 Physical FileProvider 对象构建的,根目录对应的 URL 表示为“http://server/files/”。两个客户端采用HttpFileProvider 将远程文件系统“挂载”到本地,采用的 URL 分别为“http://server/files/dir1”和“http://server/files/dir1/foobar”,所以它们分别映射为文件服务器上的目录“c:\dir1”和“c:\dir1\foobar”。

5.3.1 HttpFileInfo与HttpDirectoryContents

在以 HttpFileProvider 为核心的文件系统中,我们通过 HttpFileInfo 来表示目录和文件,包含子目录和文件的目录内容则通过HttpDirectoryContents类型来表示。在给出这两个类型的定义之前,我们需要介绍两个对应的描述类型:描述文件和目录的 HttpFileDescriptor,描述目录内容的 HttpDirectoryContentsDescriptor。如下面的代码片段所示,HttpFileDescriptor的属性成员基本上是根据 IFileInfo 接口定义的。由于真实的目录或者文件存在于文件服务器上,所以HttpFileDescriptor的 PhysicalPath属性表示的路径实际上是对应的 URL,该 URL在构造时通过指定的Func<string,string>委托计算出来。

用于描述文件和目录的 HttpFileDescriptor对象是对一个 IFileInfo对象的封装,与之类似,用来描述目录内容的 HttpDirectoryContentsDescriptor对象则是对一个 IDirectoryContents 对象的封装。如下面的代码片段所示,HttpDirectoryContentsDescriptor的 FileDescriptors属性返回一组HttpFileDescriptor 对象的集合,集合中的每个 HttpFileDescriptor 对象对应当前目录下的某个子目录或者文件。

从前面的代码片段可以看出,HttpFileDescriptor类型具有一个ToFileInfo方法,可以将自己转换成一个 HttpFileInfo对象。由于 HttpFileInfo是通过一个 HttpFileDescriptor对象创建的,所以它的所有属性最初都来源于这个对象。除了提供目录或者文件的描述信息,HttpFileInfo 还可以通过自身的 CreateReadStream 方法承载读取文件内容的职责。由于真正的文件保存在服务器上,所以我们需要利用构建时提供的 HttpClient对象向目标文件所在的 URL发送 HTTP请求来读取文件内容。

与 HttpFileInfo对象类似,表示目录内容的 HttpDirectoryContents对象依然是根据对应的描述对象(一个HttpDirectoryContentsDescriptor对象)创建的。一个HttpDirectoryContents对象本质上就是一个 IFileInfo对象的集合,集合中的每个元素都是一个根据 HttpFileDescriptor对象创建的HttpFileInfo对象。

5.3.2 HttpFileProvider

下面介绍 HttpFileProvider 这个核心类型的实现。IFileProvider 承载了 3 项职责:通过GetDirectoryContents方法得到指定目录的内容,通过GetFileInfo方法得到指定目录或者文件的描述,通过Watch方法监控目录或者文件的变化。虽然可以采用某种技术手段从服务端向客户端发送通知,但是针对远程文件的监控意义不大,所以HttpFileProvider只提供前面两项基本功能。

由于文件系统由服务器托管,目录内容和目录与文件的描述信息都只能通过发送 HTTP 请求的形式来获取。HttpFileProvider 利用一个 HttpClient 对象来获取这些远程资源。HttpFileProvider 建立的本地文件系统的根目录可以指向文件服务器上的任意一个目录,我们将指向这个目录的 URL称为基地址,该地址通过字段_baseAddress表示。对于任何一个目录或者文件来说,它对应的URL通过这个基地址和相对地址合并而成。

不论是GetFileInfo方法还是GetDirectoryContents方法,HttpFileProvider发送HTTP请求的地址都是所在目录或者文件对应的 URL,但是它们返回的内容是不同的。前者返回的是目录或者文件的描述信息,后者返回的是目录内容的描述信息。因此,可以采用相应的查询字符串来区分这两种具有相同路径的 HTTP 请求,它们采用的查询字符串的名称分别是“?file-meta”和“?dir-meta”。

对于HttpFileProvider实现的GetDirectoryContents方法和GetFileInfo方法来说,它们会根据指定的相对路径解析出对应的 URL,然后利用 HttpClient针对这个地址发送 HTTP请求。响应的内容利用 JsonConvert 反序列化成一个 HttpDirectoryContentsDescriptor 对象或者HttpFileDescriptor 对象,然后据此创建并返回一个 HttpDirectoryContents 对象或者 HttpFileInfo对象。

5.3.3 FileProviderMiddleware

文件服务器是一个简单的ASP.NET Core应用,HttpFileProvider调用的Web API是通过一个类型为FileProviderMiddleware的中间件实现的。这个自定义的FileProviderMiddleware需要处理如下3种类型的HTTP请求。

读取文件内容:请求地址指向目标文件,不含任何查询字符串,如“/files/dir1/foobar/foo.txt”。

读取文件或者目录的描述:请求地址指向目标文件或者目录,将“?file-meta”作为查询字符串,如“/files/dir1/foobar?file-meta”或者“/files/dir1/foobar/foo.txt?file-meta”。

读取目录内容:请求地址指向目标目录,将“?dir-meta”作为查询字符串,如“/files/dir1/foobar?dir-meta”。

如下所示的代码片段体现了 FileProviderMiddleware 这个中间件的完整定义。可以看出,它直接使用一个 PhysicalFileProvider 来构建本地文件系统,对应的根目录直接在构造函数中指定。针对上述 3种 HTTP请求的处理在 InvokeAsync方法中实现,具体的实现逻辑其实很简单:如果请求地址携带查询字符串“dir-meta”,中间件会根据请求目标目录创建一个HttpDirectoryContentsDescriptor 对象,然后利用 JsonConvert 将其序列化后写入响应报文。如果请求地址携带查询字符串“file-meta”,则根据请求的目录或者文件创建一个 HttpFileDescriptor对象,并采用相同的方式序列化后写入响应报文。如果请求地址没有包含上面的两个查询字符串,则直接读取目标文件的内容并写入响应报文的主体。

5.3.4 远程文件系统的应用

整个文件系统由FileProviderMiddleware对象和HttpFileProvider对象组成,我们可以利用前者创建一个ASP.NET Core应用来作为文件服务器,客户端则利用后者在本地建立一个虚拟的文件系统。下面演示如何在一个具体的应用程序中使用这两个对象。首先创建一个ASP.NET Core应用来承载文件服务器,具体的代码如下所示。

FileProviderMiddleware 中间件直接通过调用 IWebHostBuilder 的 Configure 扩展方法进行注册,并在注册的同时指定根目录的路径。下面直接利用在本章开篇创建的实例来演示如何利用 HttpFileProvider 来展示指定的目录结构和远程读取文件内容,并且对之前的程序进行了如下改写。

上面的代码片段创建并注册了一个 HttpFileProvider 对象,它采用的根目录的 URL 为“http://localhost:5000/files/dir1”。由于文件服务器和客户端处于同一台主机,所以通过HttpFileProvider 建立的本地文件系统的根目录实际上指向目录“c:\test\dir1”。调用 FileManager的 ShowStructure 方法之后,控制台上会以图 5-9 所示的形式呈现出本地文件系统的虚拟结构。(S505)

图5-9 通过HttpFileProvider构建的文件系统结构

我们依然可以直接调用FileManager的ReadAllTextAsync方法远程读取某个文件的内容。如下面的代码片段所示,调用这个方法读取的文件路径为“foobar/foo.txt”,由于 HttpFileProvider采用的基地址为“/files/dir1”,所以读取的这个文件在本地的路径为“c:\test\dir1\foobar\foo.txt”。如下所示的调试断言表明,利用HttpFileProvider读取的文件就是这个物理文件。(S506)