background
A recent project is to use SmartAdmin + Jquery + EasyUI to create an ASP Net mvc5 project, there has always been a performance problem. The loading speed is relatively slow. The first loading (without cache) takes 4-5 seconds to complete all loading
The following figure shows the test results with Chrome PageSpeed
There are several very important indicators
First contentfu paint: it takes 4 seconds to draw the page for the first time. The first 4 seconds are white, which is really a little long
First meaningfull paint: it takes 8.6 seconds to draw meaningful content for the first time before the visible operation page appears
Elite render blocking resources: blocking the loading of resource files because jquery and css are loaded in the head of your project because some code must be executed first
Remove unused css: there are a lot of useless css style definitions, which is also difficult to avoid

Analyze the reason
The main reason for the above problems is that the size of the page itself and all resources add up to more than 3.2m. JQuery easyUI's JS+css is close to 3M. In addition, several partialviews are embedded in the page, and the time to execute js. EasyUI DataGrid needs to grab data from the background and generate complex Dom structure, which takes time
General optimization means
Cache
The first thing I think of is to use cache. It can only solve the speed problem of the second access. It is rarely used. I usually do this. The setting methods are
- Add outputcache, for example:
[OutputCache(Duration = 360, VaryByParam = "none")] public ActionResult Index() => this.View();
- web.Config add cache for static files
<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>
Compress and merge resource files
Minimize the size of the resource file and the number of requests. The usual approach is to use bundleconfig CS merges and compresses JS and CSS files I now use bundleconfig JSON configuration instead of system Web. Optimization. The configuration should be flexible. If you use bundleconfig JSON compilation compression also needs to solve the problem of client-side update cache. I use the following code to add a fingerprint flag
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 is a tool for Web cache validation that allows conditional client requests. Through ETags, the browser can judge whether a resource is needed. If not, the browser will not send requests to the web server, minimizing the number of requests. Configuration method
- Global scheme, customize an 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 page level
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();
In the rendering, the number of bytes sent back has been greatly reduced, and the single response time is almost the same, which is not very obvious

summary
There are many optimization schemes, but the effect is not ideal. To achieve the ultimate user experience, you may really have to abandon Jquery,EasyUI and other large and complex class libraries
problem
In addition, do you have a very easy and simple way to solve the problem of initial loading white screen? I tried to use js preloading layer animation, but the effect is still not ideal But I've seen some websites and apps, and the results are very good. I don't know how to implement them. In ASP Net MVC environment can be used
Reference articles
(ten ways to speed up ASP.NET MVC applications) http://blog.oneapm.com/apm-tech/679.html