ASP.NET MVC ETag & Cache等优化方法
背景
最近有一个项目是用SmartAdmin + Jquery + EasyUI 一个ASP.NET MVC5的项目,一直存在一个性能问题,加载速度比较慢,第一次加载需要(在没有cache的情况下)需要4-5秒完成全部的加载.
如下图就是用Chrome PageSpeed 测试的结果
有几个非常重要的指标
Fist Contentfu Paint:第一次绘出页面,需要4秒,前4秒都是白屏,确实有点长
Fist Meaningfull Paint:第一次绘出有意义的内容,需要8.6秒,才出现可见的操作页面.
Eliminate render-blocking resources:阻塞加载资源文件,因为的项目在head中加载了jquery和css,因为有些代码必须先执行导致的
Remove unused css:存在大量的没用的css样式定义,这也很难避免.

分析一下原因
出现上述问题的主要原因,页面本身的大小,所有资源加起来超过3.2M,Jquery EasyUI的JS+css 就接近3M,另外页面里有嵌入了好几个PartialView,还有就是执行js的时间,EasyUI DataGrid需要从后台抓起数据并生成复杂的Dom结构这都需要时间.
一般的优化手段
Cache
第一想到的就是使用cache,单只能解决第二次访问的速度问题,对少有点用,我一般会这样做,设置的方法有
- 添加outputcache 例如:
[OutputCache(Duration = 360, VaryByParam = "none")]
public ActionResult Index() => this.View();
- web.Config 添加对静态文件的缓存
<system.webServer>
<staticContent>
<remove fileExtension=".js" />
<mimeMap fileExtension=".js" mimeType="text/javascript" />
<remove fileExtension=".ico" />
<mimeMap fileExtension=".ico" mimeType="image/x-icon" />
<remove fileExtension=".eot" />
<mimeMap fileExtension=".eot" mimeType="application/vnd.ms-fontobject" />
<remove fileExtension=".woff" />
<mimeMap fileExtension=".woff" mimeType="application/x-font-woff" />
<remove fileExtension=".woff2" />
<mimeMap fileExtension=".woff2" mimeType="application/x-font-woff2" />
<remove fileExtension=".svg" />
<mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
<remove fileExtension=".ttf" />
<mimeMap fileExtension=".ttf" mimeType="application/x-font-ttf" />
<clientCache cacheControlMode="UseMaxAge" httpExpires="365.00:00:00" cacheControlMaxAge="365.00:00:00" />
</staticContent>
</system.webServer>
压缩和合并资源文件
尽量减少资源文件的大小和请求次数,通常的做法就是使用BundleConfig.cs合并和压缩js,css文件.我现在使用bundleconfig.json配置代替System.Web.Optimization.配置灵活一点,如果使用bundleconfig.json 编译压缩还需要解决客户端更新的缓存的问题,我使用一下代码添加一个指纹标志
public class Fingerprint
{
public static string Tag(string rootRelativePath)
{
if (HttpRuntime.Cache[rootRelativePath] == null)
{
string absolute = HostingEnvironment.MapPath("~" + rootRelativePath); DateTime date = File.GetLastWriteTime(absolute);
int index = rootRelativePath.LastIndexOf('/'); string result = rootRelativePath.Insert(index, "/v-" + date.Ticks);
HttpRuntime.Cache.Insert(rootRelativePath, result, new CacheDependency(absolute));
} return HttpRuntime.Cache[rootRelativePath] as string;
}
}
<system.webServer>
<urlCompression doStaticCompression="true" doDynamicCompression="true" dynamicCompressionBeforeCache="false" />
<rewrite>
<rules>
<rule name="fingerprint">
<match url="([\S]+)(/v-[0-9]+/)([\S]+)" />
<action type="Rewrite" url="{R:1}/{R:3}" />
</rule>
</rules>
</rewrite>
</system.webServer>
<link rel="stylesheet" href="@Fingerprint.Tag("/content/site.css")" />
ETag
ETags 是用于 Web 缓存验证的工具,允许有条件的客户端请求。通过 ETags,浏览器可以判断某项资源是否被需要。如果不需要,浏览器就不会向 Web 服务器发送请求,从而最小化请求数量。配置方法
- 全局方案,自定义一个HttpModule
public class ETagHttpModule : IHttpModule
{
#region IHttpModule Members
void IHttpModule.Dispose()
{
// Nothing to dispose;
}
void IHttpModule.Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
WebPageHttpHandler.DisableWebPagesResponseHeader = true;
}
#endregion
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication; //if (app.Request.CurrentExecutionFilePath.EndsWith("/") || app.Request.CurrentExecutionFilePath.EndsWith(".cshtml"))
//{
app.Response.Filter = new ETagStream(app.Response, app.Request);
//}
}
#region Stream filter
public class ETagStream : MemoryStream
{
private HttpResponse _response = null;
private HttpRequest _request;
private Stream _filter = null;
public ETagStream(HttpResponse response, HttpRequest request)
{
_response = response;
_request = request;
_filter = response.Filter;
}
private string GetToken(Stream stream)
{
var checksum = new byte[0];
checksum = MD5.Create().ComputeHash(stream);
return Convert.ToBase64String(checksum, 0, checksum.Length);
}
public override void Write(byte[] buffer, int offset, int count)
{
var data = new byte[count];
Buffer.BlockCopy(buffer, offset, data, 0, count);
var token = GetToken(new MemoryStream(data));
var clientToken = _request.Headers["If-None-Match"];
if (token != clientToken)
{
_response.AddHeader("ETag", token);
_filter.Write(data, 0, count);
}
else
{
_response.SuppressContent = true;
_response.StatusCode = 304;
_response.StatusDescription = "Not Modified";
_response.AddHeader("Content-Length", "0");
}
}
}
#endregion
}
<modules>
<remove name="FormsAuthentication" />
<!--<add type="WhitespaceModule" name="WhitespaceModule" />-->
<add type="WebApp.ETagHttpModule" name="ETagHttpModule" />
</modules>
- Action页面级
public class ETagAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext) => filterContext.HttpContext.Response.Filter = new ETagFilter(filterContext.HttpContext.Response, filterContext.RequestContext.HttpContext.Request);
} public class ETagFilter : MemoryStream
{
private HttpResponseBase _response = null;
private HttpRequestBase _request;
private Stream _filter = null; public ETagFilter(HttpResponseBase response, HttpRequestBase request)
{
_response = response;
_request = request;
_filter = response.Filter;
} private string GetToken(Stream stream)
{
var checksum = new byte[0];
checksum = MD5.Create().ComputeHash(stream);
return Convert.ToBase64String(checksum, 0, checksum.Length);
} public override void Write(byte[] buffer, int offset, int count)
{
var data = new byte[count]; Buffer.BlockCopy(buffer, offset, data, 0, count); var token = GetToken(new MemoryStream(data));
var clientToken = _request.Headers["If-None-Match"]; if (token != clientToken)
{
_response.AddHeader("ETag", token);
_filter.Write(data, 0, count);
}
else
{
_response.SuppressContent = true;
_response.StatusCode = 304;
_response.StatusDescription = "Not Modified";
_response.AddHeader("Content-Length", "0");
}
} }
//[OutputCache(Duration = 360, VaryByParam = "none")]
[ETag]
public ActionResult Index() => this.View();
效果图,回发的字节数确实减少了很多,单响应时间差不多,不是很明显.

总结
优化方案有很多,但是感觉效果都不是很理想,要做到极致的用户体验,可能真的要抛弃Jquery,EasyUI,这类肥大又复杂的类库.
问题
另外大家有没有非常好用又简单的方法解决初始加载白屏的问题,我试过用js preloading图层动画,但是效果还是不理想.但看过一些网址和APP做的效果非常好,不知道具体是如何实现的,在Asp.net mvc环境下能不能用
参考文章
(ASP.NET MVC 应用提速的十种方法)http://blog.oneapm.com/apm-tech/679.html
ASP.NET MVC ETag & Cache等优化方法的更多相关文章
- ASP.NET MVC 应用提速的十种方法
[编者按]本文作者为 DZone 社区的最具价值博主(MVB) Jonathan Danylko,主要介绍为 ASP.NET MVC 应用提速的十种方法.由国内 ITOM 管理平台 OneAPM 编译 ...
- ASP.NET MVC URL重写与优化(1)-使用Global路由表定制URL
ASP.NET MVC URL重写与优化(1)-使用Global路由表定制URL 引言--- 在现今搜索引擎制霸天下的时代,我们不得不做一些东西来讨好爬虫,进而提示网站的排名来博得一个看得过去的流量. ...
- ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase
原文地址:http://www.51csharp.com/MVC/882.html ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL 引言-- 在初级篇中,我们 ...
- ASP.NET MVC URL重写与优化(初级篇)-使用Global路由表定制URL
ASP.NET MVC URL重写与优化(初级篇)-使用Global路由表定制URL 引言--- 在现今搜索引擎制霸天下的时代,我们不得不做一些东西来讨好爬虫,进而提示网站的排名来博得一个看得过去的流 ...
- ASP.NET MVC学前篇之扩展方法、链式编程
ASP.NET MVC学前篇之扩展方法.链式编程 前言 目的没有别的,就是介绍几点在ASP.NETMVC 用到C#语言特性,还有一些其他琐碎的知识点,强行的划分一个范围的话,只能说都跟MVC有关,有的 ...
- 在Asp.Net MVC中用Ajax回调后台方法
在Asp.Net MVC中用Ajax回调后台方法基本格式: var operData = ...; //传递的参数(action中定义的) var type = ...; //传递的参数(action ...
- Asp.Net MVC以 JSON传值扩展方法
Asp.Net在客户端和服务器端,以JSON形式相互传值,可写扩展方法,用到的类型如下: DataContractJsonSerializer类: 该类在System.Runtime.Serializ ...
- ASP.net MVC 文件下载的几种方法
ASP.net MVC 文件下载的几种方法(欢迎讨论) 在ASP.net MVC 中有几种下载文件的方法前提:要下载的文件必须是在服务器目录中的,至于不在web项目server目录中的文件下载我不 ...
- Asp.Net MVC以JSON传值扩展方法
Asp.Net在客户端和服务器端,以JSON形式相互传值,可写扩展方法,用到的类型如下: DataContractJsonSerializer类: 该类在System.Runtime.Serializ ...
随机推荐
- 机器学习——KNN算法(k近邻算法)
一 KNN算法 1. KNN算法简介 KNN(K-Nearest Neighbor)工作原理:存在一个样本数据集合,也称为训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分 ...
- entOS7查看开放端口命令
CentOS7的开放关闭查看端口都是用防火墙来控制的,具体命令如下: 查看已经开放的端口: firewall-cmd --list-ports 开启端口 firewall-cmd --zone=/tc ...
- S7 Connection 通讯
参考两个链接: http://www.ad.siemens.com.cn/service/answer/solution.aspx?Q_ID=74626&cid=1029 https://su ...
- 在Codeblocks下配置GoogleTest单元测试工具
开发工具 我和我的组员的都是使用的是大一老师推荐的codeblocks,所以,就愉快的决定了工具统一为codeblocks,语言C++. 测试单元 老师推荐的是JUnit和VSTS工具,但同学们从大一 ...
- linux android开发环境搭建
android开发环境搭建的一些有用链接:1.sdk manager的国内服务器http://www.cnblogs.com/huangjacky/p/4077982.html2.常见问题的解决htt ...
- js的一些function
/** * * 根据秒数返回 一个日期范围 * timerFilter(10) */ function timerFilter(n) { let days = 31; // 一月多少天 const o ...
- 32 ArcToolBox学习系列之数据管理工具箱——属性域(Domains)的两种创建及使用方式
属性域分为两类,一种是范围域,一种是编码的值,下面将两个一起介绍,其中涉及到的编码,名称,只是试验,并非真实情况. 一.首先新建一个文件型地理数据库,将数据导入或者是新建要素类都可以 二.打开ArcT ...
- sqlmap Windows 安装教程
第一步:下载 python :https://www.python.org/downloads/ (这里有python各种版本,但是一般建议安装3和2.7) sqlmap:https://git ...
- C语言复习1_变量与数据类型
变量命名规则: 1.变量名的首字母或下划线(不能是其他特殊符号) 2.变量名的其他字母包含下划线.数字 和字母 3.不能使用关键字 基本数据类型 分为数值型和非数值型,其中数值型分为整型和非整型 整型 ...
- H5项目常见问题及注意事项,视频全屏,定位,屏幕旋转和触摸,偏页面重构向 来源joacycode的github
Meta基础知识: H5页面窗口自动调整到设备宽度,并禁止用户缩放页面 //一.HTML页面结构 <meta name="viewport" content="wi ...