和StaticFileMiddleware中間件一樣,DirectoryBrowserMiddleware中間本質上還是定義了一個請求地址與某個物理目錄之間的映射關系,而目標目錄體現為一個FileProvider對象。當這個中間件接收到匹配的請求后,會根據請求地址解析出對應目錄的相對路徑,并利用這個FileProvider獲取目錄的內容。目錄的內容最終會以一個HTML文檔的形式被定義,而此HTML最終會被這個中間件作為響應的內容,“目錄瀏覽器”的實現原理就這么簡單。 [本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、DirectoryBrowserMiddleware
二、DirectoryFormatter
三、具體請求處理邏輯
四、自定義DirectoryFormatter
一、DirectoryBrowserMiddleware
接下來我們來看看DirectoryBrowserMiddleware的定義。如下面的代碼片段所示,DirectoryBrowserMiddleware的第二個構造函數具有四個參數,其中第二個參數是代表當前執行環境的HostingEnvironment。作為第三個參數的是一個HtmlEncoder對象,當目標目錄被呈現為一個HTML文檔的時候,它被用于實現針對HTML的編碼,如果沒有顯式指定(調用第一個構造函數),默認的HtmlEncoder(HtmlEncoder.Default)會被使用。至于第四個類型為IOptions<DirectoryBrowserOptions>的參數,則承載了針對DirectoryBrowserMiddleware的配置選項,DirectoryBrowserOptions與前面介紹的StaticFileOptions一樣,它們都是SharedOptionsBase的子類。
1: public class DirectoryBrowserMiddleware
2: {
3: public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment env, IOptions<DirectoryBrowserOptions> options)
4: public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment hostingEnv, HtmlEncoder encoder, IOptions<DirectoryBrowserOptions> options);
5: public Task Invoke(HttpContext context);
6: }
7:
8: public class DirectoryBrowserOptions : SharedOptionsBase
9: {
10: public IDirectoryFormatter Formatter { get; set; }
11:
12: public DirectoryBrowserOptions();
13: public DirectoryBrowserOptions(SharedOptions sharedOptions);
14: }
二、DirectoryFormatter
DirectoryBrowserMiddleware中間件的目的很明確,就是將目錄下的內容(文件和子目錄)格式化成一種可讀的形式響應給客戶端,針對目錄內容的響應最終實現在一個DirectoryFormatter對象上。DirectoryFormatter是我們對所有實現了IDirectoryFormatter接口的類型與對應對象的統稱,DirectoryBrowserOptions的Formatter屬性設置和返回的就是這個一個對象。
如下面的代碼片段所示,IDirectoryFormatter接口僅僅包含一個GenerateContentAsync方法。當實現這個方法的時候,我們可以利用第一個類型為HttpContext的參數獲取當前請求上下文的信息。該方法的另一個參數返回一組FileInfo的集合,每個FileInfo代表目標下的某個以文件或者子目錄。
1: public interface IDirectoryFormatter
2: {
3: Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents);
4: }
我們知道默認情況下請求目錄的內容在頁面上是以一個表格的形式被呈現的,包含這個表格的HTML文檔是默認使用的DirectoryFormatter生成的,它是一個類型為HtmlDirectoryFormatter的對象。如下面的代碼片段所示,我們在構造一個HtmlDirectoryFormatter對象的時候需要指定一個HtmlEncoder對象,該對象最初來源于構造DirectoryBrowserMiddleware時指定的那個HtmlEncoder對象。
1: public class HtmlDirectoryFormatter : IDirectoryFormatter
2: {
3: public HtmlDirectoryFormatter(HtmlEncoder encoder);
4: public virtual Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents);
5: }
三、具體請求處理邏輯
既然最復雜的工作(呈現目錄內容)都已經交給DirectoryFormatter來完成了,DirectoryBrowserMiddleware自身的工作其實就沒有多少了。為了更好的說明這個中間件在處理請求是具體做了些什么,我們采用一種比較好理解的方式對DirectoryBrowserMiddleware類型進行了重新定義,具體的實現體現在如下所示的代碼片段中。
1: public class DirectoryBrowserMiddleware
2: {
3: private RequestDelegate _next;
4: private DirectoryBrowserOptions _options;
5:
6: public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment env, IOptions<DirectoryBrowserOptions> options) : this(next, env, HtmlEncoder.Default,options)
7: { }
8:
9: public DirectoryBrowserMiddleware(RequestDelegate next, IHostingEnvironment env, HtmlEncoder encoder, IOptions<DirectoryBrowserOptions> options)
10: {
11: _next = next;
12: _options = options.Value;
13: _options.FileProvider = _options.FileProvider ?? env.WebRootFileProvider;
14: _options.Formatter = _options.Formatter ?? new HtmlDirectoryFormatter(encoder);
15: }
16:
17: public async Task Invoke(HttpContext context)
18: {
19: //只處理GET和HEAD請求
20: if (!new string[] { "GET", "HEAD" }.Contains(context.Request.Method, StringComparer.OrdinalIgnoreCase))
21: {
22: await _next(context);
23: return;
24: }
25:
26: //檢驗當前路徑是否與注冊的請求路徑相匹配
27: PathString path = new PathString(context.Request.Path.Value.TrimEnd('/') + "/");
28: PathString subpath;
29: if (!path.StartsWithSegments(_options.RequestPath, out subpath))
30: {
31: await _next(context);
32: return;
33: }
34:
35: //檢驗目標目錄是否存在
36: IDirectoryContents directoryContents = _options.FileProvider.GetDirectoryContents(subpath);
37: if (!directoryContents.Exists)
38: {
39: await _next(context);
40: return;
41: }
42:
43: //如果當前路徑不以"/"作為后綴,會響應一個針對“標準”URL的重定向
44: if (!context.Request.Path.Value.EndsWith("/"))
45: {
46: context.Response.StatusCode = 302;
47: context.Response.GetTypedHeaders().Location = new Uri(path.Value + context.Request.QueryString);
48: return;
49: }
50:
51: //利用DirectoryFormatter響應目錄內容
52: await _options.Formatter.GenerateContentAsync(context, directoryContents);
53: }
54: }
如上面的代碼片段所示,當DirectoryBrowserMiddleware最終利用注冊的DirectoryFormatter來響應目標目錄的內容之前,它會做一系列的前期工作。比如它會驗證當前請求是否是GET或者HEAD請求,以及當前的URL是否與注冊的請求路徑相匹配,在匹配的情況下還需要驗證目標目錄是否存在。除此之外,這個中間件要求訪問目錄的請求路勁必須以字符“/”作為后綴,否則會在目前的路徑上添加這個后綴并針對最終的路徑發送一個重定向。所以我們利用瀏覽器發送針對某個目錄的請求的時候,URL明明沒有指定“/”作為后綴,這個后綴會自動給我們加上,這就是重定向的作用。
四、自定義DirectoryFormatter
由于目錄的內容在瀏覽器中的呈現方式完全由DirectoryFormatter完成,如果實現在HtmlDirectoryFormatter的默認呈現方式不能滿足需求(比如我們需要這個頁面與現有網站保持相同的風格),這可以通過注冊一個自定義的DirectoryFormatter來完成。接下來我們通過一個簡單的實例來演示如何定義這么一個DirectoryFormatter。我們將自定義的DirectoryFormatter命名為ListDirectoryFormatter,應為它僅僅將所有文件或者子目錄顯示為一個簡單的列表。
1: public class ListDirectoryFormatter : IDirectoryFormatter
2: {
3: public async Task GenerateContentAsync(HttpContext context, IEnumerable<IFileInfo> contents)
4: {
5: context.Response.ContentType = "text/html";
6: await context.Response.WriteAsync("<html><head><title>Index</title><body><ul>");
7: foreach (var file in contents)
8: {
9: string href = $"{context.Request.Path.Value.TrimEnd('/')}/{file.Name}";
10: await context.Response.WriteAsync($"<li><a href='{href}'>{file.Name}</a></li>");
11: }
12: await context.Response.WriteAsync("</ul></body></html>");
13: }
14: }
15:
16: public class Program
17: {
18: public static void Main()
19: {
20: new WebHostBuilder()
21: .UseContentRoot(Directory.GetCurrentDirectory())
22: .UseKestrel()
23: .Configure(app => app.UseDirectoryBrowser(new DirectoryBrowserOptions {Formatter = new ListDirectoryFormatter()}))
24: .Build()
25: .Run();
26: }
27: }
如上面的代碼片段,ListDirectoryFormatter最終響應的是一個完整的HTML文檔,它的主體部分只包含一個通過<ul>…</ul>表示的無序列表。列表元素(<li>)是一個針對文件或者子目錄的鏈接。在調用擴展方法UseDirectoryBrowser注冊DirectoryBrowserMiddleware中間件的時候,我們為將一個ListDirectoryFormatter對象設置為DirectoryBrowserOptions的Formatter屬性。目錄內容最終將會采用如圖9所示的形式呈現在瀏覽器上。
ASP.NET Core應用針對靜態文件請求的處理[1]: 以Web的形式發布靜態文件
ASP.NET Core應用針對靜態文件請求的處理[2]: 條件請求與區間請求
ASP.NET Core應用針對靜態文件請求的處理[3]: StaticFileMiddleware中間件如何處理針對文件請求
ASP.NET Core應用針對靜態文件請求的處理[4]: DirectoryBrowserMiddleware中間件如何呈現目錄結構
ASP.NET Core應用針對靜態文件請求的處理[5]: DefaultFilesMiddleware中間件如何顯示默認頁面
文章列表