通過HTTP請求獲取的Web資源很多都來源於儲存在伺服器磁碟上的靜態檔案。對於ASP.NET應用來說,如果將靜態檔案儲存到約定的目錄下,絕大部分檔案型別都是可以通過Web的形式對外發布的。「Microsoft.AspNetCore.StaticFiles」 這個NuGet包中提供了三個用來處理靜態檔案請求的中介軟體,我們可以用它們搭建一個檔案伺服器。(本篇提供的範例已經彙總到《ASP.NET Core 6框架揭祕-範例演示版》)
[1901]以Web形式釋出檔案(圖片)(原始碼)
[1902]以Web形式釋出檔案(PDF)(原始碼)
[1903]顯式檔案目錄結構(原始碼)
[1904]顯示目錄的預設頁面(原始碼)
[1905]客製化目錄的預設頁面(原始碼)
[1906]設定預設的媒體型別(原始碼)
[1907]對映副檔名的媒體型別(原始碼)
作為演示範例是ASP.NET應用具有如圖1所示的專案結構。在預設作為WebRoot的「wwwroot」目錄下,我們將JavaScript指令碼檔案、CSS樣式檔案和圖片檔案存放到對應的子目錄(js、css和img)下。該目錄下的所有檔案將自動釋出為Web資源,使用者端可以存取相應的URL來讀取對應它們的內容。
圖1 靜態檔案發布的專案結構
針對具體某個靜態檔案的請求是通過StaticFileMiddleware中介軟體來處理。如下所示的演示程式中呼叫IApplicationBuilder介面的UseStaticFiles擴充套件方法註冊的就是這個中介軟體。
var app = WebApplication.Create(); app.UseStaticFiles(); app.Run();
演示程式執行之後,就可以通過GET請求的方式來讀取對應檔案的內容,目標檔案相對於WebRoot目錄的路徑就是對應URL的路徑,如JPG圖片檔案「~/wwwroot/img/dolphin1.jpg」對應的URL路徑為「/img/dolphin1.jpg」。如果直接利用瀏覽器存取這個URL,目標圖片就會直接以圖2所示的形式顯示出來。
圖2 以Web形式請求釋出的圖片檔案
上面通過一個簡單的範例將WebRoot所在目錄下的所有靜態檔案發布為Web資源,如果需要釋出的靜態檔案儲存在其他目錄下呢?比如我們將上面演示的應用程式的一些檔案儲存在圖3所示的「~/doc/」目錄下,那麼對應的程式又該如何編寫呢?
圖3 釋出「~/doc/」和「~/wwwroot」目錄下的檔案
ASP.NET應用在大部分情況下都是利用一個IFileProvider物件來讀取檔案的,針對靜態檔案的讀取請求處理也不例外。StaticFileMiddleware中介軟體內部維護著一個IFileProvider物件和請求路徑的對映關係。如果呼叫UseStaticFiles方法沒有指定任何引數,那麼這個對映的路徑就是應用的基地址(PathBase),採用的IFileProvider物件就是指向WebRoot目錄的PhysicalFileProvider物件。上述需求可以通過客製化這個對映關係來實現。如下面的程式碼片段所示,我們在現有程式的基礎上額外新增了一次針對UseStaticFiles擴充套件方法的呼叫,並利用作為引數的StaticFileOptions設定選項新增請求路徑(「/documents」)與對應IFileProvider物件(針對路徑「~/doc/」的PhysicalFileProvider物件)之間的對映關係。
using Microsoft.Extensions.FileProviders; var path = Path.Combine(Directory.GetCurrentDirectory(), "doc"); var options = new StaticFileOptions { FileProvider = new PhysicalFileProvider(path), RequestPath = "/documents" }; var app = WebApplication.Create(); app .UseStaticFiles() .UseStaticFiles(options); app.Run();
按照上面這段程式指定的對映關係,對於儲存在「~/doc/」目錄下的這個PDF檔案(checklist.pdf),請求URL採用的路徑就應該是「/documents/checklist.pdf」。如果利用瀏覽器請求這個地址時,PDF檔案的內容就會按照圖4所示的形式顯示在瀏覽器上。
圖4 以Web形式請求釋出的PDF檔案
StaticFileMiddleware中介軟體只會處理針對具體的某個靜態檔案的請求,如果利用瀏覽器傳送一個針對目錄路徑的請求(比如「/img」),我們將得到狀態為「404 Not Found」的響應。如果希望瀏覽器呈現出目標目錄的結構,就可以註冊DirectoryBrowserMiddleware中介軟體。這個中介軟體會返回一個HTML頁面,請求目錄下的結構會以表格的形式顯示在這個頁面中。我們演示的程式按照如下方式呼叫IApplicationBuilder介面的UseDirectoryBrowser擴充套件方法註冊了這個中介軟體。
using Microsoft.Extensions.FileProviders; var path = Path.Combine(Directory.GetCurrentDirectory(), "doc"); var fileProvider = new PhysicalFileProvider(path); var fileOptions = new StaticFileOptions { FileProvider = fileProvider, RequestPath = "/documents" }; var diretoryOptions = new DirectoryBrowserOptions { FileProvider = fileProvider, RequestPath = "/documents" }; var app = WebApplication.Create(); app .UseStaticFiles() .UseStaticFiles(fileOptions) .UseDirectoryBrowser() .UseDirectoryBrowser(diretoryOptions); app.Run();
當上面的應用啟動之後,如果利用瀏覽器向針對某個目錄的URL(如「/」或者「/img」)發起請求,目標目錄的內容(包括子目錄和檔案)就會以圖5所示的形式顯示在一個表格中。可以看出在呈現的表格中,當前目錄的子目錄和檔案均會顯示為連結。
圖5 顯示目錄內容
UseDirectoryBrowser中介軟體會將整個目標目錄的結構和所有檔案全部暴露出來,所以這個中介軟體需要根據自身的安全策略謹慎使用。對於針對目錄的請求,更加常用的處理策略就是顯示一個儲存該目錄下的預設頁面。預設頁面檔案一般採用如下四種命名約定(default.htm、default.html、index.htm和index.html)。預設頁面的呈現實現DefaultFilesMiddleware中介軟體中,我們演示的這個應用可以按照如下方式呼叫IApplicationBuilder介面的UseDefaultFiles擴充套件方法來註冊這個中介軟體。
using Microsoft.Extensions.FileProviders; var path = Path.Combine(Directory.GetCurrentDirectory(), "doc"); var fileProvider = new PhysicalFileProvider(path); var fileOptions = new StaticFileOptions { FileProvider = fileProvider, RequestPath = "/documents" }; var diretoryOptions = new DirectoryBrowserOptions { FileProvider = fileProvider, RequestPath = "/documents" }; var defaultOptions = new DefaultFilesOptions { RequestPath = "/documents", FileProvider = fileProvider, }; var app = WebApplication.Create(); app .UseDefaultFiles() .UseDefaultFiles(defaultOptions) .UseStaticFiles() .UseStaticFiles(fileOptions) .UseDirectoryBrowser() .UseDirectoryBrowser(diretoryOptions); app.Run();
下面在「~/wwwroot/img/」和「~/doc」目錄下分別建立一個名為index.html的預設頁面,並且在該.html檔案的主體部分指定一段簡短的文字(This is an index page!)。我們在應用啟動之後利用瀏覽器存取這兩個目錄(「/img」和「/documents」),預設頁面就會以圖6的形式顯示出來。
圖6 顯示預設頁面
我們須將DefaultFilesMiddleware中介軟體放在StaticFileMiddleware和DirectoryBrowserMiddleware中介軟體之前。這是因為DirectoryBrowserMiddleware和DefaultFilesMiddleware中介軟體處理的均是針對目錄的請求,如果先註冊DirectoryBrowserMiddleware中介軟體,那麼顯示的總是目錄的結構。如果先註冊用於顯示預設頁面的DefaultFilesMiddleware中介軟體,那麼在預設頁面不存在的情況下它會將請求分發給後續中介軟體,此時DirectoryBrowserMiddleware中介軟體將當前目錄的結構呈現出來。要先於StaticFileMiddleware中介軟體之前註冊DefaultFilesMiddleware中介軟體是因為後者是通過採用URL重寫的方式實現的。這個中介軟體會將針對目錄的請求改寫成針對預設頁面的請求,而最終針對預設頁面的請求還需要依賴StaticFileMiddleware中介軟體來完成。
圖7 重新命名預設頁面
DefaultFilesMiddleware中介軟體在預設情況下總是以約定的名稱在當前請求的目錄下定位預設頁面。如果作為預設頁面的檔案沒有采用這樣的約定命名,比如我們如圖7所示的方式將預設頁面命名為readme.html,就需要按照如下方式顯式指定預設頁面的檔名(S1905)。
using Microsoft.Extensions.FileProviders; var path = Path.Combine(Directory.GetCurrentDirectory(), "doc"); var fileProvider = new PhysicalFileProvider(path); var fileOptions = new StaticFileOptions { FileProvider = fileProvider, RequestPath = "/documents" }; var diretoryOptions = new DirectoryBrowserOptions { FileProvider = fileProvider, RequestPath = "/documents" }; var defaultOptions1 = new DefaultFilesOptions(); var defaultOptions2 = new DefaultFilesOptions { RequestPath = "/documents", FileProvider = fileProvider, }; defaultOptions1.DefaultFileNames.Add("readme.html"); defaultOptions2.DefaultFileNames.Add("readme.html"); var app = WebApplication.Create(); app .UseDefaultFiles(defaultOptions1) .UseDefaultFiles(defaultOptions2) .UseStaticFiles() .UseStaticFiles(fileOptions) .UseDirectoryBrowser() .UseDirectoryBrowser(diretoryOptions); app.Run();
通過上面演示的範例可以看出,瀏覽器能夠準確地將請求的目標檔案的內容正常呈現出來。對HTTP協定具有基本瞭解的讀者應該都知道,響應檔案能夠在瀏覽器上被正常顯示的基本前提是響應報文通過Content-Type報頭攜帶的媒體型別必須與內容一致。我們的範例演示了針對兩種檔案型別的請求,一種是JPG檔案,另一種是PDF檔案,對應的媒體型別分別是「image/jpg」和「application/pdf」,那麼用來處理靜態檔案請求的StaticFileMiddleware中介軟體是如何解析出對應的媒體型別的呢?
StaticFileMiddleware中介軟體針對媒體型別的解析是通過一個IContentTypeProvider物件來完成的, FileExtensionContentTypeProvider是對該介面的預設實現。FileExtensionContentTypeProvider根據檔案的擴充套件命名來解析媒體型別。它在內部預定了數百種常用副檔名與對應媒體型別之間的對映關係,所以如果釋出的靜態檔案具有標準的擴充套件名,StaticFileMiddleware中介軟體就能為對應的響應賦予正確的媒體型別。
圖8 重新命名預設頁面
如果某個檔案的擴充套件名沒有在預定義的對映之中,或者需要某個預定義的擴充套件名匹配不同的媒體型別,那又應該如何解決呢?同樣是針對我們演示的這個範例,如果我們以圖8所示的方式將「~/wwwroot/img/ dolphin1.jpg」檔案的擴充套件名改成.img,那麼StaticFileMiddleware中介軟體將無法為針對該檔案的請求解析出正確的媒體型別。這個問題具有若干不同的解決方案,第一種方案就是按照如下方式讓StaticFileMiddleware中介軟體支援不能識別的檔案型別,併為設定一個預設的媒體型別。
var options = new StaticFileOptions { ServeUnknownFileTypes = true, DefaultContentType = "image/jpg" }; var app = WebApplication.Create(); app.UseStaticFiles(options); app.Run();
上述解決方案只能設定一種預設媒體型別,如果具有多種需要對映成不同媒體型別的檔案型別,這種方案就無能為力了,所以最根本的解決方案還是需要將不能識別的檔案型別和對應的媒體型別進行對映。由於StaticFileMiddleware中介軟體使用的IContentTypeProvider物件是可以客製化的,所以可以按照如下方式顯式地為該中介軟體指定一個FileExtensionContentTypeProvider物件,然後將缺失的對映新增到這個物件上即可。
using Microsoft.AspNetCore.StaticFiles; var contentTypeProvider = new FileExtensionContentTypeProvider(); contentTypeProvider.Mappings.Add(".img", "image/jpg"); var options = new StaticFileOptions { ContentTypeProvider = contentTypeProvider }; var app = WebApplication.Create(); app.UseStaticFiles(options); app.Run();