前言
如今前后端分離開發模式如火如荼,開發職責更加分明(當然前后端一起搞的模式也沒有完全褪去);而對于每個公司產品實施來說,部署模式會稍有差別,有的會單獨將前端文件部署為一個站點,有的會將前端文件和后端站點整合一起部署;通常當項目規模比較大的時候,分開站點部署是不錯的選擇,管理和維護清晰,而對于一些小型項目,整合在一起部署為一個站點就顯得相對比較方便,畢竟有時候開發是你、部署是你、維護也是你;如果選擇整合部署,或者是項目包含靜態文件(如圖片)的訪問,接下來的內容就有用武之地了~~~
正文
Asp.NetCore的請求管道是根據需求通過注冊中間件進行構造的(構造過程參考:跟我一起學.NetCore之中間件(Middleware)簡介和解析請求管道構建),而通過模板創建出來的項目,請求管道中默認只有關鍵的幾個中間件,如果有其他需要,可以自己添加注冊。其中靜態文件中間件默認就沒有,如下案例:
如上例運行結果,是訪問不到添加的index.html,可能有小伙伴會說,那是因為沒有加目錄,然而并不是這個原因;?現在注冊上靜態文件中間件試試:
為什么要創建wwwroot目錄呢?其他目錄不行嗎?
當注冊靜態文件中間件時,通過構造函數可以看出(看下面靜態文件中間件的構造函數截圖),可以指定對應的靜態文件目錄,當沒有指定目錄時,默認就會使用IHostingEnvironment中的WebRootFileProvider,而WebRootFileProvider默認就指定了wwwroot:
在IHostingEnvironment的擴展方法Initialize中指定;
這里就不一一去扒代碼了,如果有興趣的小伙伴,可以按照以下思路去扒:
那如何指定目錄,在扒代碼的過程中應該會看到,注冊中間件的時候可以傳參進行指定,如下:
根據需求可以注冊多個靜態文件中間件,如上所示,請求到請求管道時,會先到wwwroot目錄中去找匹配文件,如果找不到繼續下一個中間件,去指定的myFile目錄中去匹配文件。
往往在開發過程中,會對相關靜態文件進行分類,同時Url地址也要不同,通常會通過注冊中間件時,將對應靜態文件目錄映射到指定Url目錄,如下:
搞過IIS的小伙伴應該都知道設置默認文件的配置吧,通過現成的中間件也能實現,如下:
注冊中間件實現,能減少配置當然也是不錯的選擇:
到這,小伙伴們應該嘗試一下,將wwwroot目錄下的index.html的名字改改,再運行一下,同樣的訪問Url地址肯定訪問不了的,如果能,那估計是存在緩存,可以清清緩存再試;?那為什么呢?定位很精確,肯定是默認文件這個中間件再搞事情,來,看看里面咋實現的:
// 定義默認文件中間件
public class DefaultFilesMiddleware
{// 選項配置private readonly DefaultFilesOptions _options;private readonly PathString _matchUrl;private readonly RequestDelegate _next;// 靜態文件目錄讀取Provider,默認目錄是wwwrootprivate 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的構造函數有對應的默認列表,現在是不是豁然開朗了~~~;那如果一定要指定其他文件怎么辦呢?老規矩,注冊中間件時傳參:
是不是很簡單,再來個需求,比如想做一個在線文件管理系統,那肯定得訪問目錄吧,現在肯定不能訪問的,小伙伴們可以試試;?
通過注冊中間又可以實現,是不是覺得中間件很是靈活,而且還很強大:
這里對于參數的設置就不一一舉例了,用法和UseStaticFiles參數差不多一致,小伙伴感興趣可私下試試。
其實微軟早就想到一會要這么干,一會要那么干了,所以直接提供了一個全功能的中間件,直接UseFileServer即可,可以針對上面說到的每一項進行配置,如下:
其實內部就是整合以上說到的中間件,如下源碼:
詳細配置這里就不一一配置測試了,使用和單獨注冊中間件時一致,這里只是整合在一起而已。
總結
說好的偏應用,還是沒忍住扒代碼,但是感覺適當的扒扒能說的更清楚一些;下一節說說路由的最佳實踐。
總結
以上是生活随笔 為你收集整理的跟我一起学.NetCore之静态文件处理的那些事 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。