zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    20155229 2016-2017-2 《Java程序设计》第九周学习总结
    20155229实验二 《Java面向对象程序设计》实验报告
    10.11课后练习——MyOD系统调用版本
    2017-2018-1 20155223 《信息安全系统设计基础》第5周学习总结
    课堂实践及课后练习9.27@20155223
    2017-2018-1 20155223 《信息安全系统设计基础》第3周学习总结
    20155223 2016-2017-2《Java程序设计》课程总结
    Java第五次实验报告
    课堂代码练习补交
    第四次实验报告
  • 原文地址:https://www.cnblogs.com/neozhu/p/10605929.html
Copyright © 2011-2022 走看看