1. 程式人生 > >.NET Core的檔案系統[5]:擴充套件檔案系統構建一個簡易版“雲盤”

.NET Core的檔案系統[5]:擴充套件檔案系統構建一個簡易版“雲盤”

FileProvider構建了一個抽象檔案系統,作為它的兩個具體實現,PhysicalFileProvider和EmbeddedFileProvider則分別為我們構建了一個物理檔案系統和程式集內嵌檔案系統。總的來說,它們針對的都是“本地”檔案,接下來我們通過自定義FileProvider構建一個“遠端”檔案系統,我們可以將它視為一個只讀的“雲盤”。由於檔案系統的目錄結構和檔案內容都是通過HTTP請求的方式讀取的,所以我們將這個自定義的FileProvider命名為HttpFileProvider。[ 本文已經同步到《ASP.NET Core框架揭祕》之中]

7

上圖基本上體現了以HttpFileProvider的遠端檔案系統的設計和實現原理。真實的檔案儲存在檔案伺服器上,客戶端可以通過公佈出來的Web API得到指定路徑所在的目錄結構,以及目錄和檔案描述資訊,甚至可以讀取指定檔案的內容。檔案伺服器中的每一個目錄都對應著一個URL,客戶端可以指定相應的URL將某一個目錄作為本地檔案系統的根。如圖7所示,伺服器上的檔案系統實際是直接通過指向“c:\test”目錄的PhysicalFileProvider來表示的,這個根目錄通過“http://server/files/

”表示。對於兩個客戶端的“本地檔案系統來說”,它們的根分別指向檔案伺服器上的目錄“c:\dir1”和“c:\dir1\foobar”(對應的URL分別是“http://server/files/dir1”和“ http://server/files/dir1/foobar”)。

目錄
一、HttpFileInfo與HttpDirectoryContents
二、HttpFileProvider
三、FileProviderMiddleware
四、遠端檔案系統的應用

一、HttpFileInfo與HttpDirectoryContents

在以HttpFileProvider為核心的檔案系統中,我們通過HttpFileInfo來表示目錄和檔案,包含子目錄和檔案的目錄內容則通過另一個HttpDirectoryContents型別來表示。不過在這之前,我們需要介紹兩個對應的描述型別,它們分別是描述檔案和目錄的HttpFileDescriptor和描述目錄內容的HttpDirectoryContentsDescriptor。

如下面的程式碼片段所示,HttpFileDescriptor的屬性成員基本上是根據IFileInfo這個介面來定義的,並且這些屬性的值本身就來源於在構造時指定的FileInfo物件。由於真實的目錄或檔案存在於檔案伺服器上,所以HttpFileDescriptor的PhysicalPath屬性表示的實際上是對應的URL,這個URL是通過構造時指定的委託物件計算出來的。

   1: public class HttpFileDescriptor
   2: {
   3:     public bool               Exists { get;  set; }
   4:     public
bool IsDirectory { get; set; }
   5:     public DateTimeOffset     LastModified { get;  set; }
   6:     public long               Length { get;  set; }
   7:     public string             Name { get;  set; }
   8:     public string             PhysicalPath { get;  set; }
   9:  
  10:     public HttpFileDescriptor()
  11:     { }
  12:  
  13:     public HttpFileDescriptor(IFileInfo fileInfo, Func<string, string> physicalPathResolver)
  14:     {
  15:         this.Exists           = fileInfo.Exists;
  16:         this.IsDirectory      = fileInfo.IsDirectory;
  17:         this.LastModified     = fileInfo.LastModified;
  18:         this.Length           = fileInfo.Length;
  19:         this.Name             = fileInfo.Name;
  20:         this.PhysicalPath     = physicalPathResolver(fileInfo.Name);
  21:     }
  22:  
  23:     public IFileInfo ToFileInfo(HttpClient httpClient)
  24:     {
  25:         return this.Exists 
  26:             ? new HttpFileInfo(this, httpClient)
  27:             : (IFileInfo)new NotFoundFileInfo(this.Name);
  28:     }
  29: }

用於描述檔案或者目錄HttpFileDescriptor物件實際上可以視為是對一個FileInfo物件的封裝,而用來描述目錄內容的HttpDirectoryContentsDescriptor則是對一個DirectoryContents物件的封裝。如下面的程式碼片段所示,HttpDirectoryContentsDescriptor具有一個名為FileDescriptors的屬性返回一組HttpFileDescriptor物件的集合,集合中的每個HttpFileDescriptor物件對應著當前目錄下的某個子目錄或者檔案。

   1: public class HttpDirectoryContentsDescriptor
   2: {
   3:     public bool                                 Exists { get;  set; }
   4:     public IEnumerable<HttpFileDescriptor>      FileDescriptors { get;  set; }
   5:  
   6:     public HttpDirectoryContentsDescriptor()
   7:     {
   8:         this.FileDescriptors = new HttpFileDescriptor[0];
   9:     }
  10:  
  11:     public HttpDirectoryContentsDescriptor(IDirectoryContents directoryContents, Func<string, string> physicalPathResolver)
  12:     {
  13:         this.Exists = directoryContents.Exists;
  14:         this.FileDescriptors = directoryContents.Select(_ => new HttpFileDescriptor(_, physicalPathResolver));
  15:     }
  16: }

從前面的程式碼片段可以看到HttpFileDescriptor具有一個ToFileInfo方法將自己轉換成一個FileInfo物件,這個物件的型別就是我們上面提到過的HttpFileInfo。由於HttpFileInfo是通過一個HttpFileDescriptor物件創建出來的,所以它的所有屬性最初都來源於這個物件。由於FileInfo除了提供目錄或者檔案的描述資訊之外,它還通過自身的CreateReadStream方法承載著讀取檔案內容的職責。由於真正的檔案儲存在伺服器上,所以我們需要利用構建時提供的HttpClient物件向目標檔案所在的URL傳送HTTP請求的方式來讀取檔案內容,

   1: public class HttpFileInfo: IFileInfo
   2: {
   3:     private HttpClient _httpClient;
   4:  
   5:     public bool               Exists { get; private set; }
   6:     public bool               IsDirectory { get; private set; }
   7:     public DateTimeOffset     LastModified { get; private set; }
   8:     public long               Length { get; private set; }
   9:     public string             Name { get; private set; }
  10:     public string             PhysicalPath { get; private set; }
  11:  
  12:     public HttpFileInfo(HttpFileDescriptor descriptor, HttpClient httpClient)
  13:     {
  14:         this.Exists           = descriptor.Exists;
  15:         this.IsDirectory      = descriptor.IsDirectory;
  16:         this.LastModified     = descriptor.LastModified;
  17:         this.Length           = descriptor.Length;
  18:         this.Name             = descriptor.Name;
  19:         this.PhysicalPath     = descriptor.PhysicalPath;
  20:         _httpClient           = httpClient;
  21:     }
  22:  
  23:     public Stream CreateReadStream()
  24:     {
  25:         HttpResponseMessage message =  _httpClient.GetAsync(this.PhysicalPath).Result;
  26:         return message.Content.ReadAsStreamAsync().Result;
  27:     }
  28: }

表示目錄內容的HttpDirectoryContents具有如下的定義。與HttpFileInfo類似,HttpDirectoryContents物件依然是根據對應的描述物件(一個HttpDirectoryContentsDescriptor物件)建立的。HttpDirectoryContents本質上就是一個FileInfo物件的集合,集合中的每個元素都是一個根據HttpFileDescriptor物件建立的HttpFileInfo物件。

   1: public class HttpDirectoryContents : IDirectoryContents
   2: {
   3:     private IEnumerable<IFileInfo> _fileInfos;
   4:     public bool Exists { get; private set; }
   5:  
   6:     public HttpDirectoryContents(HttpDirectoryContentsDescriptor descriptor, HttpClient httpClient)
   7:     {
   8:         this.Exists     = descriptor.Exists;
   9:         _fileInfos     = descriptor.FileDescriptors.Select(file => file.ToFileInfo(httpClient));
  10:     }
  11:  
  12:     public IEnumerator<IFileInfo> GetEnumerator() => _fileInfos.GetEnumerator();
  13:     IEnumerator IEnumerable.GetEnumerator() => _fileInfos.GetEnumerator();
  14: }

二、HttpFileProvider

接下來我們來介紹作為核心的HttpFileProvider型別的實現。我們知道FileProvider承載著三項職責,即通過GetDirectoryContents方法得到指定目錄的內容,通過GetFileInfo得到指定目錄或者檔案的描述,以及通過Watch方法監控目錄或者檔案的變化。雖然我們可以採用某種技術手段實現從服務端向客戶端傳送通知,但是針對遠端檔案的監控意義不大,所以HttpFileProvider只提供前面兩種基本的功能。

   1: public class HttpFileProvider : IFileProvider
   2: {
   3:     private readonly string _baseAddress;
   4:     private HttpClient      _httpClient;
   5:  
   6:     public HttpFileProvider(string baseAddress)
   7:     {
   8:         _baseAddress = baseAddress.TrimEnd('/');
   9:         _httpClient     = new HttpClient();
  10:     }
  11:  
  12:     public IDirectoryContents GetDirectoryContents(string subpath)
  13:     {
  14:         string url = $"{_baseAddress}/{subpath.TrimStart('/')}?dir-meta";
  15:         string content = _httpClient.GetStringAsync(url).Result;
  16:         HttpDirectoryContentsDescriptor descriptor = JsonConvert.DeserializeObject<HttpDirectoryContentsDescriptor>(content);
  17:         return new HttpDirectoryContents(descriptor, _httpClient);
  18:     }
  19:  
  20:     public IFileInfo GetFileInfo(string subpath)
  21:     {
  22:         string url = $"{_baseAddress}/{subpath.TrimStart('/')}?file-meta";
  23:         string conten