.NET Core的檔案系統[2]:FileProvider是個什麼東西?
在《讀取並監控檔案的變化》中,我們通過三個簡單的例項演示從程式設計的角度對檔案系統做了初步的體驗,接下來我們繼續從設計的角度來繼續認識它。這個抽象的檔案系統以目錄的形式來組織檔案,我們可以利用它讀取某個檔案的內容,還可以對目標檔案試試監控並捕捉它的變化。這些基本的功能均由相應的FileProvider來提供,從某種意義上講FileProvider代表了整個檔案系統。[ 本文已經同步到《ASP.NET Core框架揭祕》之中]
目錄
一、FileProvider
二、FileInfo & GetFileInfo方法
三、DirectoryContents & GetDirectoryContents方法
四、ChangeToken & Watch方法
五、關於路徑字首“/”
六、總結
一、FileProvider
FileProvider是我們對所有實現了IFileProvider介面的所有型別以及對應物件的統稱。我們在《讀取並監控檔案的變化》三個簡單的例項演示,它們實際上體現了檔案系統承載的三個基本功能,而這個三個基本功能分別體現在IFileProvider介面如下所示的三個方法中。
1: public interface IFileProvider
2: {
3: IFileInfo GetFileInfo(string subpath);
4: IDirectoryContents GetDirectoryContents(stringsubpath);
5: IChangeToken Watch(string filter);
6: }
二、FileInfo & GetFileInfo方法
雖然檔案系統採用目錄來組織檔案,但是不論是目錄還是檔案都通過具有如下定義的IFileInfo介面來表示,我們將實現了該介面的型別以及對應物件統稱為FileInfo。我們可以通讀屬性Exists判斷指定的目錄或者檔案是否真實存在,它的另一個屬性IsDirectory總是返回False。至於另外兩個屬性Name和PhysicalPath,它們分別表示檔案或者目錄的名稱和物理路徑。屬性LastModified返回一個時間戳,表示目錄或者檔案最終一次被修改的時間。對於一個表示具體檔案的FileInfo,我們可以利用屬性Length得到檔案內容的位元組長度。如果我們希望讀取檔案的內容,可以藉助於通過CreateReadStream
1: public interface IFileInfo
2: {
3: bool Exists { get; }
4: bool IsDirectory { get; }
5: string Name { get; }
6: string PhysicalPath { get; }
7: DateTimeOffset LastModified { get; }
8: long Length { get; }
9:
10: Stream CreateReadStream();
11: }
IFileProvider的GetFileInfo方法根據指定的路徑得到表示所在檔案的FileInfo物件,一般來說,這個路徑應該是相對應當前FileProvider的相對路徑。換句話說,雖然FileInfo可以用於描述目錄和檔案,但是GetFileInfo方法的目的在於得到指定路徑返回的檔案而不是目錄。當我們呼叫這個方法的時候,不論我們指定的路徑是否存在,該方法總是返回一個具體的FileInfo物件。即使我們指定的路徑對應著一個具體的目錄,這個FileInfo物件的IsDirectory也總是返回False(它的Exists屬性也返回False)。
三、DirectoryContents & GetDirectoryContents方法
如果我們希望得到某個目錄的內容,即多少檔案或者子目錄包含在這個目錄下,我們可以呼叫指定所在目錄的路徑作為引數呼叫FileProvider的GetDirectoryContents,目錄內容通過該方法返回的DirectoryContents物件來表示。DirectoryContents是對所有實現了具有如下定義的IDirectoryContents介面的所有型別以及對應物件的統稱。一個DirectoryContents物件實際上表示一個FileInfo的集合,組成這個集合的所有FileInfo自然就是對所有檔案和子目錄的描述。和GetFileInfo方法一樣,不論指定的目錄是否存在,GetDirectoryContents方法總是會返回一個具體的DirectoryContents物件,它的Exists屬性會幫助我們確定指定目錄是否存在。
1: public interface IDirectoryContents : IEnumerable<IFileInfo>
2: {
3: bool Exists { get; }
4: }
四、ChangeToken & Watch方法
如果我們希望監控FileProvider所在目錄或者檔案的變化,我們可以呼叫它的Watch方法,當時前提是對應的FileProvider提供了這樣的監控功能。這個方法接受一個字串型別的引數filter,我們可以利用這個引數指定一個表示式來篩選需要監控的目標目錄或檔案。就目前預定義的這幾個FileProvider來說,只有PhysicalFileProvider提供針對檔案的監控功能。對於PhysicalFileProvider來說,它會委託一個FileSystemWatcher物件來完成最終的檔案監控任務。在指定刪選表示式的時候,我們可以指定需要被監控的某個具體目錄或者檔案路徑,也可以採用下表所示的萬用字元“*”。
Filter |
Description |
foobar/data.txt |
儲存在目錄foobar下的檔案data.txt。 |
foobar/*.txt |
儲存在目錄foobar下的所有.txt檔案。 |
foobar/*.* |
儲存在目錄foobar下的所有檔案。 |
foobar//*.* |
儲存在目錄foobar的所有子目錄下的所有檔案。 |
Watch方法的返回型別為具有如下定義的IChangeToken介面,我們將實現了該介面的所有型別以及對應物件統稱外ChangeToken。ChangeToken可以視為一個與某個資料進行關聯,並在資料發生變化對外發送通知的令牌。如果關聯的資料發生改變,它的HasChanged屬性將變成True。我們可以呼叫它的RegisterChangeCallback方法註冊一個在資料發生改變時可以自動執行的回撥。值得一提的是,該方法會以一個IDisposable物件的形式返回註冊物件,原則上講我們應該在適當的時機呼叫其Dispose方法解除註冊的回掉,以免出現記憶體洩漏的問題。至於IChangeToken介面的另一個屬性ActiveChangeCallbacks,它表示當資料發生變化時是否需要主動執行註冊的回撥操作。
1: public interface IChangeToken
2: {
3: bool HasChanged { get; }
4: bool ActiveChangeCallbacks { get; }
5:
6: IDisposable RegisterChangeCallback(Action<object> callback, object state);
7: }
五、關於路徑字首“/”
一般來說,不論是呼叫GetFileInfo和GetDirectoryContents方法所指定的目標檔案和目錄的路徑,還是在呼叫Watch方法指定的篩選表示式,都是一個針對當前FileProvider根目錄的相對路徑。指定的這個路徑可以採用“/”字元作為字首,但是這個字首是不必要的。換句話說,如下所示的這兩組程式是完全等效的。
1: //路徑不包含字首“/”
2: IFileProvider fileProvider = GetFileProvider();
3: IDirectoryContents dirContents = fileProvider.GetDirectoryContents("foobar");
4: IFileInfo fileInfo = fileProvider.GetFileInfo("foobar/foobar.txt");
5: IChangeToken changeToken = fileProvider.Watch("foobar/*.txt");
6:
7: //路徑包含字首“/”
8: IFileProvider fileProvider = GetFileProvider();
9: IDirectoryContents dirContents = fileProvider.GetDirectoryContents("/foobar");
10: IFileInfo fileInfo = fileProvider.GetFileInfo("/foobar/foobar.txt");
11: IChangeToken changeToken = fileProvider.Watch("/foobar/*.txt");
六、總結
總的來說,以FileProvider為核心的檔案系統在設計上看是非常簡單的。除了FileProvider,檔案系統還涉及到其他一些物件,比如DirectoryContents、FileInfo和ChangeToken。這些物件都具有對應的介面定義,下圖所示的UML展示了涉及的這些介面以及它們之間的關係。