[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]:程式集內嵌檔案系統