雖然ASP.NET Core是一款“動態”的Web服務端框架,但是在很多情況下都需要處理針對靜態文件的請求,最為常見的就是這對JavaScript腳本文件、CSS樣式文件和圖片文件的請求。針對不同格式的靜態文件請求的處理,ASP.NET Core為我們提供了三個中間件,它們將是本章論述的重點。不過在... ...
雖然ASP.NET Core是一款“動態”的Web服務端框架,但是在很多情況下都需要處理針對靜態文件的請求,最為常見的就是這對JavaScript腳本文件、CSS樣式文件和圖片文件的請求。針對不同格式的靜態文件請求的處理,ASP.NET Core為我們提供了三個中間件,它們將是本系列文章論述的重點。不過在針對對它們展開介紹之前,我們照理通過一些簡單的實例來體驗一下如何在一個ASP.NET Core應用中發佈靜態文件。[本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、以Web的形式讀取文件
二、瀏覽目錄內容
三、顯示預設頁面
四、映射媒體類型
一、以Web的形式讀取文件
我們創建的演示實例是一個簡單的ASP.NET Core控制台應用,它具有如下圖所示的項目結構。我們可以看到在預設作為WebRoot的目錄(wwwroot)下,我們將JavaScript腳本文件、CSS樣式文件和圖片文件存放到對應的子目錄(js、css和img)下,我們將把這個目錄的所有文件以Web的形式發佈出來,客戶端可以訪問相應的URL來獲取這些文件。
針對靜態文件的請求是通過一個名為StaticFileMiddleware的中間件來實現的,這個中間件類型定義在NuGet包“Microsoft.AspNetCore.StaticFiles”中,所以我們需要預先按照這個NuGet包。整個應用只包含如下所示的這幾行代碼,StaticFileMiddleware這個中間件的註冊是通過調用ApplicationBuilder的擴展方法UseStaticFiles來完成的。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseContentRoot(Directory.GetCurrentDirectory())
7: .UseKestrel()
8: .Configure(app => app.UseStaticFiles())
9: .Build()
10: .Run();
11: }
12: }
除了註冊必需的StaticFileMiddleware中間件之外,我們還調用了WebHostBuilder的UseContentRoot方法將當前項目的根目錄作為ContentRoot目錄。我們知道ASP.NET Core應用具有兩個重要的根目錄,它們分別是ContentRoot和WebRoot,後者也是對外發佈的靜態文件預設使用的根目錄。由於WebRoot目錄的預設路徑就是“{contentroot}/wwwroot”,所示上面這段程式就是將項目中的這個wwwroot目錄下的所有靜態文件發佈出來。
當這個程式運行之後,我們就可以通過向對應URL發送HTTP請求的方式來獲取某個的文件,這個URL由文件相當於wwwroot目錄的路徑來決定。比如JPG文件“~/wwwroot/img/dophin1.jpg”對應的URL為“http://
localhost:5000/img/dophin1.jpg”。我們直接利用瀏覽器訪問這個URL,目標圖片會直接顯示出來。
上面我們通過一個簡單的實例將WebRoot所在目錄下的所有靜態文件直接發佈出來。如果我們需要發佈的靜態文件存儲在其他目錄下呢?依舊是演示的這個應用,現在我們將一些文檔存儲在如下圖所示的“~/doc/”目錄下並以Web的形式發佈出來,我們的程式又該如何編寫呢?
我們知道ASP.NET Core應用大部分情況下都是利用一個FileProvider對象來讀取文件的,它在處理針對靜態文件的請求是也不例外。對於我們調用ApplicationBuilder的擴展方法UseStaticFiles方法註冊的這個類型為StaticFileMiddleware的中間件,其內部具有一個FileProvider和請求路徑的映射關係。如果調用UseStaticFiles方法沒有指定任何的參數,那麼這個映射關係的請求路徑就是應用的基地址(PathBase),而FileProvider自然就是指向WebRoot目錄的PhysicalFileProvider。
上述的這個需求可以通過顯式註冊這個映射的方式來實現,為此我們在現有程式的基礎上額外添加了一次針對UseStaticFiles方法的調用,並通過指定的參數(是一個StaticFileOptions對象)顯式指定了採用的FileProvider(針對“~/doc/”的PhysicalFileProvider)和請求路徑(“/documents”)。
1: public class Program
2: {
3: public static void Main()
4: {
5: string contentRoot = Directory.GetCurrentDirectory();
6: new WebHostBuilder()
7: .UseContentRoot(contentRoot)
8: .UseKestrel()
9: .Configure(app => app
10: .UseStaticFiles()
11: .UseStaticFiles(new StaticFileOptions {
12: FileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "doc")),
13: RequestPath = "/documents"
14: }))
15: .Build()
16: .Run();
17: }
18: }
按照上面這段程式指定的映射關係,對於存儲在“~/doc/”目錄下的這個PDF文件(“checklist.pdf”),發佈在Web上的URL為“http://localhost:5000/documents/checklist.pdf”。當我們在瀏覽器上請求這個地址時,該PDF文件的內容將會按照如下圖所示的形式顯示在瀏覽器上。
二、瀏覽目錄內容
註冊的StaticFileMiddleware中間件只會處理針對某個具體靜態文件的額請求,如果我們向針對某個目錄的URL發送HTTP請求(比如“http://localhost:5000/img/”),得到的將是一個狀態為404的響應。不過我們可以通過註冊另一個名為DirectoryBrowserMiddleware的中間件來顯示請求目錄的內容。具體來說,這個中間件會返回一個HTML頁面,請求目錄下的所有文件將以表格的形式包含在這個頁面中。對於我們演示的這個應用來說,我們可以按照如下的方式調用UseDirectoryBrowser方法來註冊這個DirectoryBrowserMiddleware中間件。
1: public class Program
2: {
3: public static void Main()
4: {
5: string contentRoot = Directory.GetCurrentDirectory();
6: IFileProvider fileProvider = new PhysicalFileProvider(
7: Path.Combine(contentRoot, "doc"));
8: new WebHostBuilder()
9: .UseContentRoot(contentRoot)
10: .UseKestrel()
11: .Configure(app => app
12: .UseStaticFiles()
13: .UseStaticFiles(new StaticFileOptions {
14: FileProvider = fileProvider,
15: RequestPath = "/documents"
16: })
17: .UseDirectoryBrowser()
18: .UseDirectoryBrowser(new DirectoryBrowserOptions {
19: FileProvider = fileProvider,
20: RequestPath = "/documents"
21: }))
22: .Build()
23: .Run();
24: }
25: }
當上面這個應用啟動之後,如果我們利用瀏覽器向針對某個目錄的URL(比如“http://localhost:5000/”或者“http://localhost:5000/img/”),目標目錄的內容(包括子目錄和文件)將會以下圖所示的形式顯示在一個表格中。不僅僅如此,子目錄和文件均會顯示為鏈接,指向目標目錄或者文件的URL。
三、顯示預設頁面
從安全的角度來講,利用註冊的UseDirectoryBrowser中間件顯示一個目錄瀏覽頁面會將整個目標目錄的介面和所有文件全部暴露出來,所以這個中間件需要根據自身的安全策略謹慎使用。對於針對目錄的請求,另一種更為常用的響應策略就是顯示一個保存在這個目錄下的預設頁面。按照約定,作為預設頁面的文件一般採用如下四種命名方式:default.htm、default.html、index.htm或者index.html。針對目標目錄下預設頁面的呈現實現在一個名為DefaultFilesMiddleware的中間件中,我們演示的這個應用可以按照如下的方式調用UseDefaultFiles方法來註冊這個中間件。
1: public class Program
2: {
3: public static void Main()
4: {
5: string contentRoot = Directory.GetCurrentDirectory();
6: IFileProvider fileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "doc"));
7:
8: new WebHostBuilder()
9: .UseContentRoot(contentRoot)
10: .UseKestrel()
11: .Configure(app => app
12: .UseDefaultFiles()
13: .UseDefaultFiles(new DefaultFilesOptions{
14: RequestPath = "/documents",
15: FileProvider = fileProvider,
16: })
17: .UseStaticFiles()
18: .UseStaticFiles(new StaticFileOptions
19: {
20: FileProvider = fileProvider,
21: RequestPath = "/documents"
22: })
23: .UseDirectoryBrowser()
24: .UseDirectoryBrowser(new DirectoryBrowserOptions
25: {
26: FileProvider = fileProvider,
27: RequestPath = "/documents"
28: }))
29: .Build()
30: .Run();
31: }
32: }
現在我們在“~/wwwroot/img/”目錄下創建一個名為index.htm的預設頁面,現在利用瀏覽器訪問這個目錄對應的URL(“http://localhost:5000/img/”),顯示就時這個頁面的內容。
我們必須在註冊StaticFileMiddleware和DirectoryBrowserMiddleware之前註冊DefaultFilesMiddleware,否則它起不了任何作用。由於DirectoryBrowserMiddleware和DefaultFilesMiddleware這兩個中間件處理的均是針對目錄的請求,如果DirectoryBrowserMiddleware先被註冊,那麼顯示的總是目錄的內容。若DefaultFilesMiddleware先被註冊,在預設頁面不存在情況下回顯示目錄的內容。至於為什麼要先於StaticFileMiddleware之前註冊DefaultFilesMiddleware,則是因為後者是通過採用URL重寫的方式實現的,也就是說這個中間件會將針對目錄的請求改寫成針對預設頁面的請求,而最終針對預設頁面的請求還得依賴StaticFileMiddleware完成。
DefaultFilesMiddleware中間件在預設情況下總是以約定的名稱(default.htm、default.html、index.htm或者index.html)在當前請求的目錄下定位預設頁面。如果我們希望作為預設頁面的文件不能按照這樣的約定命名(比如readme.htm),我們需要按照如下的方式顯式指定預設頁面的文件名。
1: public class Program
2: {
3: public static void Main()
4: {
5: string contentRoot = Directory.GetCurrentDirectory();
6: IFileProvider fileProvider = new PhysicalFileProvider(Path.Combine(contentRoot, "doc"));
7:
8: DefaultFilesOptions options1 = new DefaultFilesOptions();
9: DefaultFilesOptions options2 = new DefaultFilesOptions{
10: RequestPath = "/documents",
11: FileProvider = fileProvider
12: };
13: options1.DefaultFileNames.Add("readme.htm");
14: options2.DefaultFileNames.Add("readme.htm");