前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ASP.NET MVC ETag & Cache等优化方法

ASP.NET MVC ETag & Cache等优化方法

作者头像
阿新
发布2021-12-24 09:49:27
7690
发布2021-12-24 09:49:27
举报
文章被收录于专栏:c#开发者c#开发者

背景

最近有一个项目是用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 例如:
代码语言:javascript
复制
[OutputCache(Duration = 360, VaryByParam = "none")]
public ActionResult Index() => this.View();
  • web.Config 添加对静态文件的缓存
代码语言:javascript
复制
<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 编译压缩还需要解决客户端更新的缓存的问题,我使用一下代码添加一个指纹标志

代码语言:javascript
复制
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;
        }
    }
代码语言:javascript
复制
<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>
代码语言:javascript
复制
<link rel="stylesheet" href="@Fingerprint.Tag("/content/site.css")" />

ETag

ETags 是用于 Web 缓存验证的工具,允许有条件的客户端请求。通过 ETags,浏览器可以判断某项资源是否被需要。如果不需要,浏览器就不会向 Web 服务器发送请求,从而最小化请求数量。配置方法

  • 全局方案,自定义一个HttpModule
代码语言:javascript
复制
  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
  }
代码语言:javascript
复制
<modules>
            <remove name="FormsAuthentication" />
            <!--<add type="WhitespaceModule" name="WhitespaceModule" />-->
           <add type="WebApp.ETagHttpModule" name="ETagHttpModule" />
</modules>
  • Action页面级
代码语言:javascript
复制
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");
      }
    }

   
  }
代码语言:javascript
复制
//[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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-03-27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 分析一下原因
  • 一般的优化手段
    • Cache
      • 压缩和合并资源文件
        • ETag
        • 总结
          • 问题
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档