1. 程式人生 > >[ASP.NET Core 3框架揭祕] 檔案系統[3]:物理檔案系統

[ASP.NET Core 3框架揭祕] 檔案系統[3]:物理檔案系統

ASP.NET Core應用中使用得最多的還是具體的物理檔案,比如配置檔案、View檔案以及作為Web資源的靜態檔案。物理檔案系統由定義在NuGet包“Microsoft.Extensions.FileProviders.Physical”中的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();   
}

一、PhysicalFileInfo

一個PhysicalFileProvider物件總是對映到某個具體的物理目錄上,被對映的目錄所在的路徑通過建構函式的引數root來提供,該目錄將作為PhysicalFileProvider的根目錄。GetFileInfo方法返回的IFileInfo物件代表指定路徑對應的檔案,這是一個型別為PhysicalFileInfo的物件。一個物理檔案可以通過一個System.IO.FileInfo物件來表示,一個PhysicalFileInfo物件實際上就是對該物件的封裝,定義在PhysicalFileInfo的所有屬性都來源於這個FileInfo物件。對於建立讀取檔案輸出流的CreateReadStream方法來說,它返回的是一個根據物理檔案絕對路徑建立的FileStream物件。

public class PhysicalFileInfo : IFileInfo
{
    ...
    public PhysicalFileInfo(FileInfo info);    
}

對於PhysicalFileProvider的GetFileInfo方法來說,即使我們指定的路徑指向一個具體的物理檔案,它並不總是會返回一個PhysicalFileInfo物件。PhysicalFileProvider會將一些場景視為“目標檔案不存在”,並讓GetFileInfo方法返回一個NotFoundFileInfo物件。具體來說,PhysicalFileProvider的GetFileInfo方法在如下的場景中會返回一個NotFoundFileInfo物件:

  • 確實沒有一個物理檔案與指定的路徑相匹配。
  • 如果指定的是一個絕對路徑(比如“c:\foobar”),即Path.IsPathRooted方法返回True。
  • 如果指定的路徑指向一個隱藏檔案。

顧名思義,具有如下定義的NotFoundFileInfo型別表示一個“不存在”的檔案。NotFoundFileInfo物件的Exists屬性總是返回False,而其他的屬性則變得沒有任何意義。當我們呼叫它的CreateReadStream試圖讀取一個根本不存在的檔案內容時,會丟擲一個FileNotFoundException型別的異常。

public class NotFoundFileInfo : IFileInfo
{
    public bool Exists => false;   
    public long Length => throw new NotImplementedException();   
    public string PhysicalPath => null;  
    public string Name { get; }   
    public DateTimeOffset LastModified => DateTimeOffset.MinValue;
    public bool IsDirectory => false; 

    public NotFoundFileInfo(string name) => this.Name = name;
    public Stream CreateReadStream() => throw new FileNotFoundException($"The file {Name} does not exist.");
}

二、PhysicalDirectoryInfo

PhysicalFileProvider利用一個PhysicalFileInfo物件來描述某個具體的物理檔案,而一個物理目錄則通過一個PhysicalDirectoryInfo的物件來描述。既然PhysicalFileInfo是對一個FileInfo物件的封裝,那麼我們應該想得到PhysicalDirectoryInfo物件封裝的就是表示目錄的DirectoryInfo物件。如下面的程式碼片段所示,我們需要在建立一個PhysicalDirectoryInfo物件時提供這個DirectoryInfo物件,PhysicalDirectoryInfo實現的所有屬性的返回值都來源於這個DirectoryInfo物件。由於CreateReadStream方法的目的總是讀取檔案的內容,所以PhysicalDirectoryInfo型別的這個方法會丟擲一個InvalidOperationException型別的異常。

public class PhysicalDirectoryInfo : IFileInfo
{   
    ...
    public PhysicalDirectoryInfo(DirectoryInfo info);
}

三、PhysicalDirectoryContents

當我們呼叫PhysicalFileProvider的GetDirectoryContents方法時,如果指定的路徑指向一個具體的目錄,那麼該方法會返回一個型別為PhysicalDirectoryContents的物件。PhysicalDirectoryContents是一個IFileInfo物件的集合,該集合中包括所有描述子目錄的PhysicalDirectoryInfo物件和描述檔案的PhysicalFileInfo物件。PhysicalDirectoryContents的Exists屬性取決於指定的目錄是否存在。

public class PhysicalDirectoryContents : IDirectoryContents
{
    public bool Exists { get; }
    public PhysicalDirectoryContents(string directory);
    public IEnumerator<IFileInfo> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

四、NotFoundDirectoryContents

如果指定的路徑並不指向一個存在的目錄,或者指定的是一個絕對路徑,GetDirectoryContents方法都會返回一個Exsits為False的NotFoundDirectoryContents物件。如下所示的程式碼片段展示了NotFoundDirectoryContents型別的定義,如果我們需要使用到這麼一個型別,可以直接利用靜態屬性Singleton得到對應的單例物件。

public class NotFoundDirectoryContents : IDirectoryContents
{    
    public static NotFoundDirectoryContents Singleton { get; }  = new NotFoundDirectoryContents();
    public bool Exists => false;
    public IEnumerator<IFileInfo> GetEnumerator()  => Enumerable.Empty<IFileInfo>().GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

五、PhysicalFilesWatcher

我們接著來談談PhysicalFileProvider的Watch方法。當我們呼叫該方法的時候,PhysicalFileProvider會通過解析我們提供的Globbing Pattern表示式來確定我們期望監控的檔案或者目錄,並最終利用FileSystemWatcher物件來對這些檔案實施監控。這些檔案或者目錄的變化(建立、修改、重新命名和刪除等)都會實時地反映到Watch方法返回的IChangeToken上。

PhysicalFileProvider的Watch方法中指定的Globbing Pattern表示式必須是針對當前根目錄的相對路徑,我們可以使用“/”或者“./”字首,也可以不採用任何字首。一旦我們使用了絕對路徑(比如“c:\test\*.txt”)或者“../”字首(比如“../test/*.txt”),不論解析出來的檔案是否存在於PhysicalFileProvider的根目錄下,這些檔案都不會被監控。除此之外,如果我們沒有指定Globbing Pattern表示式,PhysicalFileProvider也不會有任何的檔案會被監控。

PhysicalFileProvider針對物理檔案系統變化的監控是通過如下這個PhysicalFilesWatcher物件實現的,其Watch方法內部會直接呼叫PhysicalFileProvider的CreateFileChangeToken方法,並返回得到的IChangeToken物件。這是一個公共型別,如果我們具有監控物理檔案系統變化的需要,可以直接使用這個型別。

public class PhysicalFilesWatcher: IDisposable
{
    public PhysicalFilesWatcher(string root, FileSystemWatcher fileSystemWatcher, bool pollForChanges);
    public IChangeToken CreateFileChangeToken(string filter);
    public void Dispose();
}

從PhysicalFilesWatcher建構函式的定義我們不難看出,它最終是利用一個FileSystemWatcher物件(對應於fileSystemWatcher引數)來完成針對指定根目錄下(對應於root引數)所有子目錄和檔案的監控。FileSystemWatcher的CreateFileChangeToken方法返回的IChangeToken物件會幫助我們感知到子目錄或者檔案的新增、刪除、修改和重新命名,但是它會忽略隱藏的目錄和檔案。最後需要提醒的是,當我們不再需要對指定目錄實施監控的時候,記得呼叫PhysicalFileProvider的Dispose方法,該方法會負責將FileSystemWatcher物件關閉。

六、小結

我們藉助下圖所示的UML來對由PhysicalFileProvider構建物理檔案系統的整體設計做一個簡單的總結。首先,該檔案系統使用PhysicalDirectoryInfo和PhysicalFileInfo對型別來描述目錄和檔案,它們分別是對DirectoryInfo和FileInfo(System.IO.FileInfo)物件的封裝。

PhysicalFileProvider的GetDirectoryContents方法返回一個PhysicalDirectoryContents 物件(如果指定的目錄存在),組成該物件的分別是根據其所有子目錄和檔案建立的PhysicalDirectoryInfo和PhysicalFileInfo物件。當我們呼叫PhysicalFileProvider的GetFileInfo方法時,如果指定的檔案存在,返回的是描述該檔案的PhysicalFileInfo物件。至於PhysicalFileProvider的Watch方法,它最終利用了FileSystemWatcher來監控指定檔案或者目錄的變化。

[ASP.NET Core 3框架揭祕] 檔案系統[1]:抽象的“檔案系統”
[ASP.NET Core 3框架揭祕] 檔案系統[2]:總體設計
[ASP.NET Core 3框架揭祕] 檔案系統[3]:物理檔案系統
[ASP.NET Core 3框架揭祕] 檔案系統[4]:程式集內嵌檔案系統