首先我们看图说话
这是在没有缓存的情况下,这个页面发送了很多静态资源的请求:
可以看到,静态资源占用了整个页面加载用时的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小时内与您取得联系。