首先我們看圖說話
這是在沒有緩存的情況下,這個頁面發送了很多靜態資源的請求:
可以看到,靜態資源占用了整個頁面加載用時的90%以上,而且這個靜態資源還是已經在我使用了nginx配置壓縮以后的大小,如果沒有對這些靜態資源壓縮的話,那么靜態資源加載應該會占用這個頁面展示99%以上的時間。聽起來是不是已經被嚇到了,但是數據已經擺在這里了,這可不是危言聳聽。
然后再看看使用了nginx緩存之后的效果圖:
看到沒有,朋友們,整個頁面只有請求接口的時間和從本地磁盤加載css的時間。頁面加載速度直接提升10倍以上!并且由于我這個頁面沒有采用前后端分離的方式,所以html沒有緩存下來,如果采用了前后端分離架構的話,就連html都可以直接緩存,那提升的速度可想而知。當然由于瀏覽器或者手機端對頁面加載的優化我們并不能很直觀的感受到10倍的提升,實際上以肉眼觀察的話,差不多減少了一半的時間,并且由于并沒有向后端服務器請求這些靜態資源,也相當于對后端服務器做了一層保護措施。
首先在http模塊加配置:
# 開啟gzip gzip on; # 啟用gzip壓縮的最小文件,小于設置值的文件將不會壓縮 gzip_min_length 1k; # gzip 壓縮級別,1-10,數字越大壓縮的越好,也越占用CPU時間。一般設置1和2 gzip_comp_level 2; # 進行壓縮的文件類型。javascript有多種形式。其中的值可以在 mime.types 文件中找到。 gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; # 是否在http header中添加Vary: Accept-Encoding,建議開啟 gzip_vary on; # 禁用IE 6 gzip gzip_disable "MSIE [1-6]\."; # 設置緩存路徑并且使用一塊最大100M的共享內存,用于硬盤上的文件索引,包括文件名和請求次數,每個文件在1天內若不活躍(無請求)則從硬盤上淘汰,硬盤緩存最大10G,滿了則根據LRU算法自動清除緩存。 proxy_cache_path /var/cache/nginx/cache levels=1:2 keys_zone=imgcache:100m inactive=1d max_size=10g;
關于模塊以及nginx配置信息在上一篇文章有說明。
可以看到在http模塊中主要是使用gzip壓縮,最后一個配置才是和緩存有關的配置。
然后是server中加上location配置
location ~* ^.+\.(css|js|ico|gif|jpg|jpeg|png)$ { log_not_found off; # 關閉日志 access_log off; # 緩存時間7天 expires 7d; # 源服務器 proxy_pass http://localhost:8888; # 指定上面設置的緩存區域 proxy_cache imgcache; # 緩存過期管理 proxy_cache_valid 200 302 1d; proxy_cache_valid 404 10m; proxy_cache_valid any 1h; proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504; } ?
加上這兩塊配置之后,就能享受到緩存給你帶來的快樂了。當然系統優化之路還是相當漫長的,nginx緩存只是其中的一塊而已,想要把系統達到完美還需要在很多地方下功夫,比如這些靜態資源完全可以直接在客戶端緩存,這樣連請求都不會往服務端發了,更大的減輕服務器的壓力。
態資源上線問題。
有學員出去面試,面試官問了一個問題:如果你真的沒有做過前端的資源的上線發布,還真的不好回答。
正常前端要上線的時候,打完包之后會生成一個dist的文件夾,dist文件夾里面會扔到服務器上。這時候上完線之后,比如去請求頁面的時候,基本上有js,對于用戶來說,這時候有新的東西要更新。
可以看一下,有些資源是走的緩存的,比如js資源,包括圖片資源,看走的緩存。所以對用戶來說,如果不去強制刷新頁面,不清緩存,訪問其實還是舊的業務邏輯。
打完包之后會有一個index.html,但是這里面的東西通常index.html文件本身很少去做緩存的,所以它里面index.html去上線,上完線之后放到靜態服務器上,就是它。上完之后,這里面外鏈的js是這個版本的。
比如這次更新完了之后要上線,有可能js變了,后綴名也變了,這時候要去上線。要去上線之后,用戶訪問網頁的時候,加載的index.html里面還是舊的js文件。對于舊的js文件來說,有可能對于遠端的復習上把舊的文件刪掉了,還有可能會導致前面出錯,因為請求不了js了。
因為上完線之后,js已經刪掉了。這時候一般的方案就是要不就是index.html不做緩存,每次都請求最新的。像首頁沒有緩存,每次刷新頁面都是請求新的,看到沒有?但這種方式不是特別好。
這種方式怎么做?一般在index.html里面可以加上這樣的請求頭,加上標簽,就是強制不讓它緩存。再一個就是服務是部署在nginx,在nginx里面也可以加上不讓它緩存。但是這種方法還不是最好的。
一般不管是js這種資源,還是圖片,CSS還是index.html,基本上都會做緩存的。
比如靜態資源,index.html還是圖片,都會上到cdn服務上,每一次發包的時候,它會把原來的上一個版本的靜態資源留著,也不會去刪除。像會做一個備份,根據日期做一個備份。
對于用戶來說,這個網站如果用戶沒有清緩存,訪問index.html,那么它里面那種東西還是舊的內容,js文件名也是舊的,但是在這個服務器上,我給它保留備份。
如果這時候用戶清緩存,或者打包之后,index.html的過期時間已經到了,過期時間到了,它會去請求新的,請求新的就去請求新的js文件,也就是在上線的時候,舊的靜態資源,比如在這個里面的js,都會做備份的。
但是這樣也有一個弊端,備份的內容會非常多,從二零二年的到現在的二零二四年了,都還在備份當中,因為不知道用戶什么時候去刷新頁面,有可能訪問的是舊的,一般都設置緩存時間的,index.html可能緩存個30天。
如果面試官在問你的時候,可以以這種方式給面試官聊。
在之前的4篇的內容里,我們較為詳細的介紹了路由以及控制器還有視圖之間的關系。也就是說,系統如何從用戶的HTTP請求解析到控制器里,然后在控制器里處理數據,并返回給視圖,在視圖中顯示出來。這一篇我將為大家介紹基礎的最后一部分,布局頁和靜態資源引入。
在控制器和視圖那一篇,我們了解到_ViewStart 里設置了一個Layout屬性的值,這個值正是用來設置布局頁的。所謂的布局頁,就是視圖的公用代碼。在實際開發中,布局頁通常存放我們為整個系統定義的頁面框架,視圖里寫每個視圖的頁面。
回顧一下,默認的_ViewStart里的內容是:
@{
Layout = "_Layout";
}
默認的布局頁指定的是名為_Layout的布局頁,在本系列第三篇中,我們得知這個視圖應當在Shared文件夾下,那我們進去看一下這個視圖有什么內容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - MvcWeb</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">MvcWeb</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
? 2020 - MvcWeb - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@RenderSection("Scripts", required: false)
</body>
</html>
這是默認的布局頁內容,看著挺多的,但是除了一些html代碼,里面還有一些關鍵的地方需要注意。
RenderSection 分部渲染,在頁面中創建一個標記,表示這個頁面塊將在子視圖(或者是路由的實際渲染視圖)中添加內容。
來,我們看一下微軟官方給的注釋:
In layout pages, renders the content of the section named name.
意思就是在布局頁中,渲染名稱為name的分部內容。
新創建一個分布頁,名稱為_Layout1:
<html>
<head>
<title>Render 測試</title>
</head>
<body>
@RenderSection("SectionDemo")
</body>
</html>
這個布局頁里什么都沒有,只有一個RenderSection。現在我們新建一個控制器:
using Microsoft.AspNetCore.Mvc;
namespace MvcWeb.Controllers
{
public class RenderTestController : Controller
{
public IActionResult Index()
{
return View();
}
}
}
創建對應的視圖:
Views / RenderTest/Index.cshtml
先設置布局頁為_Layout1:
@{
Layout = "_Layout1";
}
先試試啟動應用,訪問:
http://localhost:5006/RenderTest/Index
正常情況下,你應該能看到這個頁面:
仔細看一下信息,意思是在 RenderTest/Index.cshtml 視圖中沒有找到 SectionDemo 的分部內容。
那么,如何在視圖中設置分部內容呢?
@{
Layout = "_Layout1";
}
@section SectionDemo{
<h1>你好</h1>
}
使用 @section <Section 名稱> 后面跟一對大括號,在大括號中間的內容就是這個section(分部)的內容。
重啟應用,然后刷新頁面,你能看到這樣的頁面:
如果不做特殊要求的話,定義在布局頁中的分部塊,視圖必須實現。當然,RenderSection還有一個參數,可以用來設置分部不是必須的:
public HtmlString RenderSection(string name, bool required);
先看下微軟給的官方注釋:
In a Razor layout page, renders the portion of a content page that is not within a named section.
簡單講,如果在布局頁中設置了@RenderBody,那么在使用了這個布局頁的視圖里所有沒被分部塊包裹的代碼都會渲染到布局頁中聲明了@RenderBody的地方。
修改_Layout1.cshtml:
<html>
<head>
<title>Render 測試</title>
</head>
<body>
<h1>RenderBody 測試 -之前</h1>
@RenderBody()
<h1>RenderBody 測試 -之后</h1>
</body>
</html>
修改RenderTest/Index.cshtml:
@{
Layout = "_Layout1";
}
RenderBody測試
<h1>我是視圖的內容!</h1>
重啟應用,刷新剛剛訪問的頁面:
可以看出,RenderBody渲染的位置。
通常情況下,靜態資源的引入與HTML引用js和css等資源是一致的,但是對于我們在編寫系統時自己創建的腳本和樣式表,asp.net core提供了不同的處理方式。那就是服務器端壓縮功能。
asp.net core 3.0 的mvc 默認項目是不啟動這個功能的,需要我們額外的開啟支持。
先引入 BuildBundleMinifier
cd MvcWeb # 切換目錄到MvcWeb項目下
dotnet add package BuildBundleMinifier
創建 bundleconfig.json
[
{
"outputFileName": "wwwroot/css/site.min.css",
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
"minify": {
"enabled": true,
"renameLocals": true
},
"sourceMap": false
}
]
每個節點允許設置項:
正常情況下在布局頁中,把壓縮后的文件路徑引入即可。不過在開發中,通常按照以下方式引用:
<environment exclude="Development">
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
<environment include="Development">
<link rel="stylesheet" href="~/css/site.css" />
</environment>
注: asp-append-version 表示在引用路徑追加一個版本號,這是針對html靜態資源緩存的問題的一個解決方案,這一步是由程序決定的。
environment表示環境,現在大家知道這個寫法就行,在接下來的篇幅會講。
我們知道到目前為止,我們的靜態資源都是在wwwroot目錄下。那么我們是否可以修改或者添加別的目錄作為靜態資源目錄呢?
在Startup.cs文件內的Configure方法下有這樣一行代碼:
app.UseStaticFiles();
這行代碼的意思就是啟用靜態文件,程序自動從 wwwroot尋找資源。那么,我們就可以從這個方法入手,設置我們自己的靜態資源:
public static IApplicationBuilder UseStaticFiles(this IApplicationBuilder app, StaticFileOptions options);
我們找到了這個方法的另一個重載版本,里面有一個參數類:
public class StaticFileOptions : SharedOptionsBase
{
public StaticFileOptions();
public StaticFileOptions(SharedOptions sharedOptions);
public IContentTypeProvider ContentTypeProvider { get; set; }
public string DefaultContentType { get; set; }
public HttpsCompressionMode HttpsCompression { get; set; }
public Action<StaticFileResponseContext> OnPrepareResponse { get; set; }
public bool ServeUnknownFileTypes { get; set; }
}
并沒有發現我們想要的,先別慌,它還有個父類。我們再去它的父類里看看:
public abstract class SharedOptionsBase
{
protected SharedOptionsBase(SharedOptions sharedOptions);
public IFileProvider FileProvider { get; set; }
public PathString RequestPath { get; set; }
protected SharedOptions SharedOptions { get; }
}
這下就比較明了了,需要我們提供一個文件提供器,那么我們來找一個合適的IFileProvider實現類吧:
public class PhysicalFileProvider : IFileProvider, IDisposable
這個類可以滿足我們的要求,它位于命名空間:
namespace Microsoft.Extensions.FileProviders
那么,添加一組我們自己的配置吧:
using Microsoft.Extensions.FileProviders;
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 省略其他代碼,僅添加以下代碼
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),"OtherStatic")),
});
}
在項目的根目錄創建名為OtherStatic的文件夾,然后在里面創建個文件夾,例如: files,并在這個文件夾里隨便添加一個文件。
然后啟動應用訪問:
http://localhost:5006/files/<你添加的文件(包括后綴名)>
然后能在瀏覽器中看到這個文件被正確響應。
當然,這里存在一個問題,如果在 OtherStatic中的文件在wwwroot也有相同目錄結構的文件存在,這樣訪問就會出現問題。這時候,可以為我們新加的這個配置設置一個請求前綴:
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(),"OtherStatic")),
RequestPath = "/other"
});
重啟程序,然后訪問:
http://localhost:5006/other/files/<你添加的文件(包括后綴名)>
然后就能看到剛才響應的文件,重新訪問之前的路徑,發現瀏覽器提示404。
在這一篇,我們講解了布局頁的內容,靜態資源的壓縮綁定以及添加一個新的靜態資源目錄。通過這幾篇內容,讓我們對asp.net core mvc有了一個基本的認知。下一篇,我們將重新創建一個項目,并結合之前的內容,以實戰為背景,帶領大家完成一個功能完備的web系統。
求關注,求點贊,求轉發~~有啥可以評論喲
*請認真填寫需求信息,我們會在24小時內與您取得聯系。