跟我一起學.NetCore之靜態檔案處理的那些事

Code綜藝圈發表於2020-09-17

前言

如今前後端分離開發模式如火如荼,開發職責更加分明(當然前後端一起搞的模式也沒有完全褪去);而對於每個公司產品實施來說,部署模式會稍有差別,有的會單獨將前端檔案部署為一個站點,有的會將前端檔案和後端站點整合一起部署;通常當專案規模比較大的時候,分開站點部署是不錯的選擇,管理和維護清晰,而對於一些小型專案,整合在一起部署為一個站點就顯得相對比較方便,畢竟有時候開發是你、部署是你、維護也是你;如果選擇整合部署,或者是專案包含靜態檔案(如圖片)的訪問,接下來的內容就有用武之地了~~~

正文

Asp.NetCore的請求管道是根據需求通過註冊中介軟體進行構造的(構造過程參考:跟我一起學.NetCore之中介軟體(Middleware)簡介和解析請求管道構建),而通過模板建立出來的專案,請求管道中預設只有關鍵的幾個中介軟體,如果有其他需要,可以自己新增註冊。其中靜態檔案中介軟體預設就沒有,如下案例:

img

如上例執行結果,是訪問不到新增的index.html,可能有小夥伴會說,那是因為沒有加目錄,然而並不是這個原因; 現在註冊上靜態檔案中介軟體試試:

img

為什麼要建立wwwroot目錄呢?其他目錄不行嗎?

當註冊靜態檔案中介軟體時,通過建構函式可以看出(看下面靜態檔案中介軟體的建構函式截圖),可以指定對應的靜態檔案目錄,當沒有指定目錄時,預設就會使用IHostingEnvironment中的WebRootFileProvider,而WebRootFileProvider預設就指定了wwwroot:

img

在IHostingEnvironment的擴充套件方法Initialize中指定;

img

這裡就不一一去扒程式碼了,如果有興趣的小夥伴,可以按照以下思路去扒:

img

那如何指定目錄,在扒程式碼的過程中應該會看到,註冊中介軟體的時候可以傳參進行指定,如下:

img

根據需求可以註冊多個靜態檔案中介軟體,如上所示,請求到請求管道時,會先到wwwroot目錄中去找匹配檔案,如果找不到繼續下一個中介軟體,去指定的myFile目錄中去匹配檔案。

往往在開發過程中,會對相關靜態檔案進行分類,同時Url地址也要不同,通常會通過註冊中介軟體時,將對應靜態檔案目錄對映到指定Url目錄,如下:

img

搞過IIS的小夥伴應該都知道設定預設檔案的配置吧,通過現成的中介軟體也能實現,如下:

img

註冊中介軟體實現,能減少配置當然也是不錯的選擇:

img

到這,小夥伴們應該嘗試一下,將wwwroot目錄下的index.html的名字改改,再執行一下,同樣的訪問Url地址肯定訪問不了的,如果能,那估計是存在快取,可以清清快取再試; 那為什麼呢?定位很精確,肯定是預設檔案這個中介軟體再搞事情,來,看看裡面咋實現的:

// 定義預設檔案中介軟體
public class DefaultFilesMiddleware
{
    // 選項配置
    private readonly DefaultFilesOptions _options;
    private readonly PathString _matchUrl;
    private readonly RequestDelegate _next;
    // 靜態檔案目錄讀取Provider,預設目錄是wwwroot
    private readonly IFileProvider _fileProvider;
    // 建構函式,用於初始化對應的變數
    public DefaultFilesMiddleware(RequestDelegate next, IWebHostEnvironment hostingEnv, IOptions<DefaultFilesOptions> options)
    {
        // 校驗引數
        if (next == null)
        {
            throw new ArgumentNullException(nameof(next));
        }
        if (hostingEnv == null)
        {
            throw new ArgumentNullException(nameof(hostingEnv));
        }
        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }
        _next = next;
        // 初始化配置資訊
        _options = options.Value;
        // 如果沒有指定對應的IFileProvider,就用IWebHostEnvironment的WebRootFileProvider,預設目錄就wwwroot
        _fileProvider = _options.FileProvider ?? Helpers.ResolveFileProvider(hostingEnv);
        _matchUrl = _options.RequestPath;
    }
    // 預設檔案中介軟體的關鍵方法
    public Task Invoke(HttpContext context)
    {
        if (context.GetEndpoint() == null &&
            Helpers.IsGetOrHeadMethod(context.Request.Method)
            && Helpers.TryMatchPath(context, _matchUrl, forDirectory: true, subpath: out var subpath))
        {
            var dirContents = _fileProvider.GetDirectoryContents(subpath.Value);
            if (dirContents.Exists)
            {
                // 依次遍歷預設檔案,檢查對應檔案是否在指定目錄中存在,這裡是關鍵
                for (int matchIndex = 0; matchIndex < _options.DefaultFileNames.Count; matchIndex++)
                {
                    string defaultFile = _options.DefaultFileNames[matchIndex];
                    var file = _fileProvider.GetFileInfo(subpath.Value + defaultFile);
                    // TryMatchPath will make sure subpath always ends with a "/" by adding it if needed.
                    if (file.Exists)
                    {
                        // 如果路徑與目錄匹配,但沒有以斜槓結尾,則重定向以新增斜槓.
                        // This prevents relative links from breaking.
                        if (!Helpers.PathEndsInSlash(context.Request.Path))
                        {
                            context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
                            var request = context.Request;
                            var redirect = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path + "/", request.QueryString);
                            context.Response.Headers[HeaderNames.Location] = redirect;
                            return Task.CompletedTask;
                        }

                        // 如果匹配找到,就重寫請求地址,由下一個中介軟體處理,所以在箇中介軟體的註冊一定要在UseStaticFiles前面,否則會報錯
                        context.Request.Path = new PathString(context.Request.Path.Value + defaultFile);
                        break;
                    }
                }
            }
        }
        // 執行下一個中介軟體
        return _next(context);
    }
}

在中介軟體Invoke方法中,遍歷_options.DefaultFileNames進行匹配,但我們並沒有指定,猜想應該是有預設設定,去看看對應的DefaultFilesOptions:

public DefaultFilesOptions(SharedOptions sharedOptions)
           : base(sharedOptions)
{
    // 果然,在建構函式中指定了預設列表
    DefaultFileNames = new List<string>
            {
                "default.htm",
                "default.html",
                "index.htm",
                "index.html",
            };
}

果然在DefaultFilesOptions的建構函式有對應的預設列表,現在是不是豁然開朗了~~~;那如果一定要指定其他檔案怎麼辦呢?老規矩,註冊中介軟體時傳參:

img

是不是很簡單,再來個需求,比如想做一個線上檔案管理系統,那肯定得訪問目錄吧,現在肯定不能訪問的,小夥伴們可以試試;

通過註冊中間又可以實現,是不是覺得中介軟體很是靈活,而且還很強大:

img

這裡對於引數的設定就不一一舉例了,用法和UseStaticFiles引數差不多一致,小夥伴感興趣可私下試試。

其實微軟早就想到一會要這麼幹,一會要那麼幹了,所以直接提供了一個全功能的中介軟體,直接UseFileServer即可,可以針對上面說到的每一項進行配置,如下:

img

其實內部就是整合以上說到的中介軟體,如下原始碼:

img

詳細配置這裡就不一一配置測試了,使用和單獨註冊中介軟體時一致,這裡只是整合在一起而已。

總結

說好的偏應用,還是沒忍住扒程式碼,但是感覺適當的扒扒能說的更清楚一些;下一節說說路由的最佳實踐。

------------------------------------------------

CSDN:Code綜藝圈

知乎:Code綜藝圈

掘金:Code綜藝圈

部落格園:Code綜藝圈

bilibili:Code綜藝圈

------------------------------------------------

一個被程式搞醜的帥小夥,關注"Code綜藝圈",識別關注跟我一起學~~~

擼文不易,莫要白瞟,三連走起~~~~

相關文章