ASP.NET Core 6框架揭祕實例演示[29]:搭建文件服務器

通過HTTP請求獲取的Web資源很多都來源於存儲在服務器磁盤上的靜態文件。對於ASP.NET應用來說,如果將靜態文件存儲到約定的目錄下,絕大部分文件類型都是可以通過Web的形式對外發布的。“Microsoft.AspNetCore.StaticFiles” 這個NuGet包中提供了三個用來處理靜態文件請求的中間件,我們可以用它們搭建一個文件服務器。(本篇提供的實例已經彙總到《ASP.NET Core 6框架揭祕-實例演示版》)

[1901]以Web形式發佈文件(圖片)(源代碼
[1902]以Web形式發佈文件(PDF)(源代碼
[1903]顯式文件目錄結構(源代碼
[1904]顯示目錄的默認頁面(源代碼
[1905]定製目錄的默認頁面(源代碼
[1906]設置默認的媒體類型(源代碼
[1907]映射文件擴展名的媒體類型(源代碼

[1901]以Web形式發佈文件(圖片)

作爲演示實例是ASP.NET應用具有如圖1所示的項目結構。在默認作爲WebRoot的“wwwroot”目錄下,我們將JavaScript腳本文件、CSS樣式文件和圖片文件存放到對應的子目錄(js、css和img)下。該目錄下的所有文件將自動發佈爲Web資源,客戶端可以訪問相應的URL來讀取對應它們的內容。

image

圖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所示的形式顯示出來。

image

圖2 以Web形式請求發佈的圖片文件

[1902]以Web形式發佈文件(PDF)

上面通過一個簡單的實例將WebRoot所在目錄下的所有靜態文件發佈爲Web資源,如果需要發佈的靜態文件存儲在其他目錄下呢?比如我們將上面演示的應用程序的一些文檔存儲在圖3所示的“~/doc/”目錄下,那麼對應的程序又該如何編寫呢?

image

圖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所示的形式顯示在瀏覽器上。

image

圖4 以Web形式請求發佈的PDF文件

[1903]顯式文件目錄結構

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所示的形式顯示在一個表格中。可以看出在呈現的表格中,當前目錄的子目錄和文件均會顯示爲鏈接。

image

圖5 顯示目錄內容

[1904]顯示目錄的默認頁面

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的形式顯示出來。

image

圖6 顯示默認頁面

[1905]定製目錄的默認頁面

我們須將DefaultFilesMiddleware中間件放在StaticFileMiddleware和DirectoryBrowserMiddleware中間件之前。這是因爲DirectoryBrowserMiddleware和DefaultFilesMiddleware中間件處理的均是針對目錄的請求,如果先註冊DirectoryBrowserMiddleware中間件,那麼顯示的總是目錄的結構。如果先註冊用於顯示默認頁面的DefaultFilesMiddleware中間件,那麼在默認頁面不存在的情況下它會將請求分發給後續中間件,此時DirectoryBrowserMiddleware中間件將當前目錄的結構呈現出來。要先於StaticFileMiddleware中間件之前註冊DefaultFilesMiddleware中間件是因爲後者是通過採用URL重寫的方式實現的。這個中間件會將針對目錄的請求改寫成針對默認頁面的請求,而最終針對默認頁面的請求還需要依賴StaticFileMiddleware中間件來完成。

image

圖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();

[1906]設置默認的媒體類型

通過上面演示的實例可以看出,瀏覽器能夠準確地將請求的目標文件的內容正常呈現出來。對HTTP協議具有基本瞭解的讀者應該都知道,響應文件能夠在瀏覽器上被正常顯示的基本前提是響應報文通過Content-Type報頭攜帶的媒體類型必須與內容一致。我們的實例演示了針對兩種文件類型的請求,一種是JPG文件,另一種是PDF文件,對應的媒體類型分別是“image/jpg”和“application/pdf”,那麼用來處理靜態文件請求的StaticFileMiddleware中間件是如何解析出對應的媒體類型的呢?

StaticFileMiddleware中間件針對媒體類型的解析是通過一個IContentTypeProvider對象來完成的, FileExtensionContentTypeProvider是對該接口的默認實現。FileExtensionContentTypeProvider根據文件的擴展命名來解析媒體類型。它在內部預定了數百種常用文件擴展名與對應媒體類型之間的映射關係,所以如果發佈的靜態文件具有標準的擴展名,StaticFileMiddleware中間件就能爲對應的響應賦予正確的媒體類型。

image

圖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();

[1907]映射文件擴展名的媒體類型

上述解決方案只能設置一種默認媒體類型,如果具有多種需要映射成不同媒體類型的文件類型,這種方案就無能爲力了,所以最根本的解決方案還是需要將不能識別的文件類型和對應的媒體類型進行映射。由於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();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章