在《讀取并監控文件的變化》中,我們通過三個簡單的實例演示從編程的角度對文件系統做了初步的體驗,接下來我們繼續從設計的角度來繼續認識它。這個抽象的文件系統以目錄的形式來組織文件,我們可以利用它讀取某個文件的內容,還可以對目標文件試試監控并捕捉它的變化。這些基本的功能均由相應的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(string subpath);
5: IChangeToken Watch(string filter);
6: }
二、FileInfo & GetFileInfo方法
雖然文件系統采用目錄來組織文件,但是不論是目錄還是文件都通過具有如下定義的IFileInfo接口來表示,我們將實現了該接口的類型以及對應對象統稱為FileInfo。我們可以通讀屬性Exists判斷指定的目錄或者文件是否真實存在,它的另一個屬性IsDirectory總是返回False。至于另外兩個屬性Name和PhysicalPath,它們分別表示文件或者目錄的名稱和物理路徑。屬性LastModified返回一個時間戳,表示目錄或者文件最終一次被修改的時間。對于一個表示具體文件的FileInfo,我們可以利用屬性Length得到文件內容的字節長度。如果我們希望讀取文件的內容,可以借助于通過CreateReadStream方法返回的Stream對象來完成。
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展示了涉及的這些接口以及它們之間的關系。
文章列表