轉自博客:https://www.cnblogs.com/CreateMyself/p/9235968.htmlhtml
最近發表的EF Core貌似有點多,可別誤覺得我只專攻EF Core哦,私下有時間也是一直在看ASP.NET Core的內容,因此後續會穿插講EF Core和ASP.NET Core,別認爲你會用ASP.NET Core就自認爲你很瞭解ASP.NET Core,雖然說是基礎系列但也是也有你不知道的ASP.NET Core。瀏覽器
當咱們建立默認.NET Core Web應用程序時,.NET Core默認爲咱們注入了StaticFiles從而可以使用wwwroot目錄下的靜態文件,請注意這裏注入StaticFiles是基於wwwroot目錄下的靜態文件,此時咱們以下經過使用UseDefaultFiles啓用默認靜態文件。緩存
app.UseDefaultFiles();
app.UseStaticFiles();
在此以前呢,咱們在wwwroot目錄下建立了四個靜態HTML文件,以下:服務器
...mvc
根據官方文檔說明,咱們建立如上四個靜態html,同時也會根據如上順序在wwwroot目錄下查找靜態html,查找到了default.htm,因此此時如上顯示對應內容,若咱們刪除第一個html,則會查找default.html,以此類推。要是咱們將注入順序顛倒會這樣呢?以下:app
app.UseStaticFiles();
app.UseDefaultFiles();
此時會出現頁面404找不到頁面,這是爲什麼呢?官方文檔強調必須將注入默認文件放在注入靜態文件前面,主要是由於注入默認文件只是進行URL重寫,告訴路由我要到wwwroot目錄下查找靜態文件,可是實際上提供靜態文件的是StaticFiles,因此這也是爲何必須將注入默認文件放在注入靜態文件前面。可是若是咱們非要將注入默認文件放在注入靜態文件前面,咱們該如何作呢?接下來經過使用UseFileServer,UseFileServer是UseDefaultFiles和UseStaticFiles的組合體,既然是組合體,咱們將UseFileServer放在第一位不就這個問題了嗎,咱們來試試,以下:ide
app.UseFileServer();
app.UseStaticFiles();
app.UseDefaultFiles();
結果將會呈現默認靜態html,這裏我就再也不演示了,有興趣的童鞋可自行研究。接下來咱們再來看看啓用目錄瀏覽,啓用目錄瀏覽和咱們在IIS上啓用目錄瀏覽同樣,以下:函數
app.UseDirectoryBrowser();
app.UseFileServer();
app.UseStaticFiles();
app.UseDefaultFiles();
這裏就不用我再多說,那麼問題來了:要是咱們將啓用目錄瀏覽放到使用MVC路由後面會怎樣呢?此時啓用目錄瀏覽會覆蓋MVC路由?不會,可自行驗證。this
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); app.UseDirectoryBrowser();
關於修改默認文件名稱等基礎,官方文檔有詳細說明,這裏就再也不演示,浪費篇幅,接下來咱們來重點講解不同的。好比默認啓用靜態文件,是放在wwwroot根目錄下,要是咱們想將靜態文件放在所給第一張圖中dist文件夾下呢?此時咱們應該如何作呢?由於.NET Core默認將wwwroot目錄做爲靜態文件目錄,因此此時咱們須要改變其目錄到wwwroot目錄下的dist目錄,經過使用UseWebRoot方法,將Web靜態目錄更改到wwwroot下的dist目錄,固然同時啓用默認文件(UseDefaultFiles)以下:spa
.UseWebRoot(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "dist"))
那麼問題又來了,此時假設我想將默認靜態文件放在外部即項目根目錄,此時咱們應該如何作呢?好比訪問以下靜態html文件。
此時咱們可利用UseDefaultFiles方法的重載,將目錄更換到項目根目錄下的OutDefaultHtml目錄,以下:
var fileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath,"OutDefaultHtml")); app.UseDefaultFiles(new DefaultFilesOptions() { FileProvider = fileProvider, DefaultFileNames = new [] { "OutDefault.html" } });
由於咱們更換了查找靜態html的目錄,同時最終提供默認文件的是UseStaticFiles,因此咱們也須要經過UseStaticFiles方法的重載切換目錄再也不是wwwroot,以下:
app.UseStaticFiles(new StaticFileOptions() { FileProvider = fileProvider });
除了上述經過聯合使用UseDefaultFiles和UseStaticFiles以外,是否還有更簡潔的方式呢?固然是有的,當默認靜態文件放在wwwroot目錄下再也不知足咱們的需求時,咱們須要自定義默認靜態文件所放置目錄時,推薦使用兩者的聯合體即UseFileServer。上述咱們可修改爲以下:
var fileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath,"OutDefaultHtml")); var fileServerOptions = new FileServerOptions(); fileServerOptions.DefaultFilesOptions.DefaultFileNames = new[] { "OutDefault.html"}; fileServerOptions.FileProvider = fileProvider; app.UseFileServer(fileServerOptions);
在大部分狀況下,咱們都將靜態文件放在wwwroot目錄下,可是有那麼百分之十的狀況下會將靜態文件放在項目根目錄,那麼此時使用默認注入的UseStaticFiles方法就再也不適用,此時咱們須要用到其重載方法。好比咱們要訪問以下圖中的mvc_course.gif,咱們該如何作呢?
上面已經講過,須要使用UseStaticFiles方法的重載,第一個參數將目錄切換到靜態文件所在目錄,第二個參數是虛擬路徑用來訪問靜態文件,爲了避免對外暴露實際物理路徑,以下:
app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "OutStaticFiles")),RequestPath = "/outfiles" }); //或者 //app.UseStaticFiles(new StaticFileOptions() //{ // FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "OutStaticFiles")), // RequestPath = new PathString("/outfiles") //});
該重載方法還有一個委託參數OnPrepareResponse,這個主要用來緩存靜態文件,接下來咱們來重點講講,其實本文都是重點,哈哈,簡單的你們直接去看官網吧。
app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "OutStaticFiles")),RequestPath = "/outfiles", OnPrepareResponse = ctx => { const int cacheControll = 60; ctx.Context.Response.Headers["Cache-Control"] = "public,max-age=" + cacheControll; } });
在官方文檔上是進行如上設置,但實際上官方文檔APi已通過時,對於請求頭的設置直接有HeaderNames這樣一個枚舉來進行設置,再也不經過字符串的形式來設置,這樣不容易出錯且方便,上述對於請求頭中緩存控制的設置有以下兩種方式皆可。
app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "OutStaticFiles")),RequestPath = "/outfiles", OnPrepareResponse = ctx => { const int cacheControll = 60; ctx.Context.Response.Headers[HeaderNames.CacheControl] = "public,max-age=" + cacheControll; } }); 或者 app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(Path.Combine(env.ContentRootPath, "OutStaticFiles")),RequestPath = "/outfiles", OnPrepareResponse = ctx => { const int cacheControll = 60; var headers = ctx.Context.Response.GetTypedHeaders(); headers.CacheControl = new CacheControlHeaderValue() { MaxAge = TimeSpan.FromSeconds(cacheControll) }; } });
在響應頭中添加緩存控制有什麼實際做用?此時就要談到緩存控制的原理了。上述緩存控制設置的過時時間爲60秒。當第一次請求時返回200,在此間隙即60秒內反覆刷新都會是200,同時從瀏覽器緩存中讀取,一旦過了60秒,再刷新此時會再去讀取服務器上的圖片,發現圖片未發生改變返回304未修改。那麼問題來了,若是咱們在此間隙內修改了圖片的內容,而後再刷新圖片的內容是否會發生改變呢?答案是:不會,只要在緩存間隙時間內,即便咱們修改了圖片的內容,再刷新仍是顯示原來的圖片(除非進行ctrl+F5強制刷新才行)。好了講了這麼多,咱們繼續拓展一下,再來看看ASP.NET Core中TagHelper特性:asp-append-version特性。該特性和緩存控制原理是同樣的麼,接下來咱們來談談asp-append-version以及其原理。
咱們以wwwroot目錄下images文件下的圖片爲例,而後在頁面上訪問圖片加上asp-append-version看看,以下:
img src="~/images/mvc_course.gif" asp-append-version="true" />
此時響應返回連接地址爲:http://localhost:63277/images/mvc_course.gif?v=y3F-lvD7XoqGqLIWq_WsuFN9POPSjit1Au6_0iRrgwE,咱們從如上圖也可看到,此時在圖片後面相似加了一個版本號v,咱們反覆刷新版本號後面的字符串一直未變,那麼這個相似於哈希碼的值是怎麼得來的呢?基於請求URL和圖片內容計算出哈希碼即版本號。也就說只要咱們更改了圖片的內容,當刷新或者再次訪問此頁面時內容相應會進行對應更新,這也就是咱們所說的緩存擊穿,相對於緩存控制而言,只要在緩存間隙時間內修改了圖片內容,除非進行強制刷新,不然圖片依然顯示舊的圖片,而asp-append-version特性則是你變,我變,你不變,我一成不變。是否是就這麼簡單呢?接下來咱們訪問一下項目根目錄下的圖片看看,經過UseStaticFiles重載訪問外部圖片,同時加上asp-append-version特性。
<img src="/outfiles/mvc_course.gif" asp-append-version="true" />
WOW,看到了什麼沒有,發現了什麼沒有,至此咱們能夠得出結論:asp-append-version特性實現圖片緩存只是針對於WebRoot目錄下的靜態文件,而外部靜態文件則無效。
那麼既然問題已經很凸出了,asp-append-version主要是針對於WebRoot目錄下的靜態文件,而WebRoot裏面只有wwwroot,因此咱們能夠稱之爲只對wwwroot目錄下的靜態文件才生效,因此咱們是否能夠嘗試將外部文件目錄也置於WebRoot目錄呢?從而實現對外部靜態文件的緩存呢?咱們下面來作嘗試,在Startup.cs中默認注入UseStaticFiles,咱們擱置不變,這樣對默認針對wwwroot下的樣式、腳本、文件都不會發生任何改變,咱們只是再來注入一個UseStaticFiles而已,以下:
var compositeProvider = new CompositeFileProvider ( env.WebRootFileProvider, new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "OutStaticFiles")) ); env.WebRootFileProvider = compositeProvider; app.UseStaticFiles(new StaticFileOptions() { FileProvider = compositeProvider, RequestPath = "/outfiles" });
如上針對默認的WebRoot即wwwroot保持不變,咱們在此基礎上添加外部目錄從而做爲複合FileProvider做爲WebRoot,這樣一切都未變。咱們再來進行以下訪問。
<img src="/mvc_course.gif" asp-append-version="true" />
如上是針對OutStaticFiles做爲WebRoot目錄訪問其靜態文件,斷不可加上outfiles虛擬路徑,這樣就當作是外部靜態文件,從而不會有版本號出現,結果以下:
咱們如何自定義實現對外部文件也添加相似於asp-append-version特性版本號的效果呢? 上述咱們已經明確講解到asp-append-version本質原理則是基於請求URL和請求圖片內容來計算版本號從而實現緩存,關於緩存咱們大可藉助IMemoryCache接口來進行緩存,請求的路徑咱們能夠經過請求上下文獲取到,同時也可經過環境變量拿到請求靜態文件所在目錄,因此接下來咱們只須要實現視圖的擴展方法便可。
視圖擴展方法經過指向IRazorPage接口,而後參數則是咱們的文件路徑,ASP.NET Core有了依賴注入讓咱們甚爲歡喜,咱們經過視圖中的視圖上下文拿到請求上下文。而後拿到已經注入的IMemoryCache和IHostingEnviroment接口,關於文件版本號,ASP.NET Core給咱們提供了FileVersionProvider類,以下:
咱們將參數傳遞到FileVersionProvider構造函數中去,最後將獲得的文件版本號添加到咱們請求的文件路徑尾巴上,代碼以下:
public static class RazorPageExtension { public static string AddAppendVersion(this IRazorPage page, string path) { var context = page.ViewContext.HttpContext; var memoryCache = context.RequestServices.GetService(typeof(IMemoryCache)) as IMemoryCache; var hostingEnviroment = context.RequestServices.GetService(typeof(IHostingEnvironment)) as IHostingEnvironment; var fileversionProvider = new FileVersionProvider(hostingEnviroment.WebRootFileProvider, memoryCache, context.Request.Path); return fileversionProvider.AddFileVersionToPath(path); } }
咱們利用上述自定義實現的Razor視圖擴展方法來訪問圖片從而獲得版本號試試,以下:
<img src="@this.AddAppendVersion("/mvc_course.gif")"
本文詳細講解了ASP.NET Core MVC中靜態文件以及緩存控制、asp-append-version本質原理,同時講解了緩存控制和asp-append-version區別所在。默認狀況下,asp-append-version只針對wwwroot有效,由於在WebRoot裏面只存在wwwroot,要想對外部文件有效,可將外部文件所在目錄也做爲WebRoot來使用。