ASP.NET頁面優化,提升載入速度[轉]

 

 

     ASP.NET頁面載入速度提升的一些作法:   1.採用 HTTP Module 控制頁面的生命週期。   2.自定義Response.Filter獲得輸出流stream生成動態頁面的靜態內容(磁盤緩存)。   3.頁面GZIP壓縮。   4.OutputCache 編程方式輸出頁面緩存。   5.刪除頁面空白字符串。(相似Google)   6.徹底刪除ViewState。   7.刪除服務器控件生成的垃圾NamingContainer。   8.使用計劃任務按時生成頁面。(本文不包含該作法的實現)   9.JS,CSS壓縮、合併、緩存,圖片緩存。(限於文章篇幅,本文不包含該作法的實現)   10.緩存破壞。(不包含第9作法的實現)    針對上述作法,咱們首先須要一個HTTP模塊,它是整個頁面流程的入口和核心。   1、自定義Response.Filter獲得輸出流stream生成動態頁面的靜態內容(磁盤緩存)   以下的代碼咱們能夠看出,咱們以 request.RawUrl 爲緩存基礎,由於它能夠包含任意的QueryString變量,而後咱們用MD5加密RawUrl 獲得服務器本地文件名的變量,再實例化一個FileInfo操做該文件,若是文件最後一次生成時間小於7天,咱們就使用.Net2.0新增的TransmitFile方法將存儲文件的靜態內容發送到瀏覽器。若是文件不存在,咱們就操做 response.Filter 獲得的 Stream 傳遞給 CommonFilter 類,並利用FileStream寫入動態頁面的內容到靜態文件中。html

複製代碼
namespace ASPNETCode.HttpModules
{
    public class CommonModule : IHttpModule
    {
        public void Init(HttpApplication application)
        {
            application.BeginRequest += Application_BeginRequest;
        }
        private void Application_BeginRequest(object sender, EventArgs e)
        {
            var context = HttpContext.Current;
            var request = context.Request;
            var url = request.RawUrl;
            var response = context.Response;
            var path = GetPath(url);
            var file = new FileInfo(path);
            if (DateTime.Now.Subtract(file.LastWriteTime).TotalDays < 7)
            {
                response.TransmitFile(path);
                response.End();
                return;
            }
            try
            {
                var stream = file.OpenWrite();
                response.Filter = new CommonFilter(response.Filter, stream);
            }
            catch (Exception)
            {
                //Log.Insert("");
            }
        }
        public void Dispose()
        {
        }
        private static string GetPath(string url)
        {
            var hash = Hash(url);
            string fold = HttpContext.Current.Server.MapPath("~/Temp/");
            return string.Concat(fold, hash);
        }
        private static string Hash(string url)
        {
            url = url.ToUpperInvariant();
            var md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
            var bs = md5.ComputeHash(Encoding.ASCII.GetBytes(url));
            var s = new StringBuilder();
            foreach (var b in bs)
            {
                s.Append(b.ToString("x2").ToLower());
            }
            return s.ToString();
        }
    }
}
複製代碼

2、頁面GZIP壓縮     對頁面GZIP壓縮幾乎是每篇講解高性能WEB程序的幾大作法之一,由於使用GZIP壓縮能夠下降服務器發送的字節數,能讓客戶感受到網頁的速度更快也減小了對帶寬的使用狀況。固然,這裏也存在客戶端的瀏覽器是否支持它。所以,咱們要作的是,若是客戶端支持GZIP,咱們就發送GZIP壓縮過的內容,若是不支持,咱們直接發送靜態文件的內容。幸運的是,現代瀏覽器IE6.7.8.0,火狐等都支持GZIP。 爲了實現這個功能,咱們須要改寫上面的 Application_BeginRequest 事件:編程

複製代碼
private void Application_BeginRequest(object sender, EventArgs e)
{
    var context = HttpContext.Current;
    var request = context.Request;
    var url = request.RawUrl;
    var response = context.Response;
    var path = GetPath(url);
    var file = new FileInfo(path);
    // 使用頁面壓縮
    ResponseCompressionType compressionType = this.GetCompressionMode(request);
    if (compressionType != ResponseCompressionType.None)
    {
        response.AppendHeader("Content-Encoding", compressionType.ToString().ToLower());
        if (compressionType == ResponseCompressionType.GZip)
        {
            response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
        }
        else
        {
            response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
        }
    }
    if (DateTime.Now.Subtract(file.LastWriteTime).TotalMinutes < 5)
    {
        response.TransmitFile(path);
        response.End();
        return;
    }
    try
    {
        var stream = file.OpenWrite();
        response.Filter = new CommonFilter(response.Filter, stream);
    }
    catch (Exception)
    {
        //Log.Insert("");
    }
}
private ResponseCompressionType GetCompressionMode(HttpRequest request)
{
    string acceptEncoding = request.Headers["Accept-Encoding"];
    if (string.IsNullOrEmpty(acceptEncoding))
        return ResponseCompressionType.None;
    acceptEncoding = acceptEncoding.ToUpperInvariant();
    if (acceptEncoding.Contains("GZIP"))
        return ResponseCompressionType.GZip;
    else if (acceptEncoding.Contains("DEFLATE"))
        return ResponseCompressionType.Deflate;
    else
        return ResponseCompressionType.None;
}
private enum ResponseCompressionType
{
    None,
    GZip,
    Deflate
}
複製代碼

3、OutputCache 編程方式輸出頁面緩存   ASP.NET內置的 OutputCache 緩存能夠將內容緩存在三個地方:Web服務器、代理服務器和瀏覽器。當用戶訪問一個被設置爲 OutputCache的頁面時,ASP.NET在MSIL以後,先將結果寫入output cache緩存,而後在發送到瀏覽器,當用戶訪問同一路徑的頁面時,ASP.NET將直接發送被Cache的內容,而不通過.aspx編譯以及執行MSIL的過程,因此,雖然程序的自己效率沒有提高,可是頁面載入速度卻獲得了提高。 爲了實現這個功能,咱們繼續改寫上面的 Application_BeginRequest 事件,咱們在 TransmitFile 後,將這個路徑的頁面以OutputCache編程的方式緩存起來:瀏覽器

複製代碼
private void Application_BeginRequest(object sender, EventArgs e)
{
    if (DateTime.Now.Subtract(file.LastWriteTime).TotalMinutes < 5)
    {
        response.TransmitFile(path);
        // 添加 OutputCache 緩存頭,並緩存在客戶端
        response.Cache.SetExpires(DateTime.Now.AddMinutes(5));
        response.Cache.SetCacheability(HttpCacheability.Public);
        response.End();
        return;
    }
}
複製代碼

4、實現CommonFilter類過濾ViewState、過濾NamingContainer、空白字符串,以及生成磁盤的緩存文件   咱們傳入response.Filter的Stream對象給CommonFilter類:   首先,咱們用先Stream的Write方法實現生成磁盤的緩存文件,代碼以下,在這些代碼中,只有初始化構造函數,Write方法,Close方式是有用的,其中FileStream字段是生成靜態文件的操做對象:緩存

複製代碼
namespace ASPNETCode.HttpModules
{
    public class CommonFilter : Stream
    {
        private readonly Stream _responseStream;
        private readonly FileStream _cacheStream;

        public override bool CanRead
        {
            get
            {
                return false;
            }
        }
        public override bool CanSeek
        {
            get
            {
                return false;
            }
        }
        public override bool CanWrite
        {
            get
            {
                return _responseStream.CanWrite;
            }
        }
        public override long Length
        {
            get
            {
                throw new NotSupportedException();
            }
        }
        public override long Position
        {
            get
            {
                throw new NotSupportedException();
            }
            set
            {
                throw new NotSupportedException();
            }
        }

        public CommonFilter(Stream responseStream, FileStream stream)
        {
            _responseStream = responseStream;
            _cacheStream = stream;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }
        public override void SetLength(long length)
        {
            throw new NotSupportedException();
        }
        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }
        public override void Flush()
        {
            _responseStream.Flush();
            _cacheStream.Flush();
        }
        public override void Write(byte[] buffer, int offset, int count)
        {
            _cacheStream.Write(buffer, offset, count);
            _responseStream.Write(buffer, offset, count);
        }
        public override void Close()
        {
            _responseStream.Close();
            _cacheStream.Close();
        }
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _responseStream.Dispose();
                _cacheStream.Dispose();
            }
        }
    }
}
複製代碼

而後咱們利用正則徹底刪除ViewState:服務器

複製代碼
// 過濾ViewState
private string ViewStateFilter(string strHTML)
{
    string matchString1 = "type=\"hidden\" name=\"__VIEWSTATE\" id=\"__VIEWSTATE\"";
    string matchString2 = "type=\"hidden\" name=\"__EVENTVALIDATION\" id=\"__EVENTVALIDATION\"";
    string matchString3 = "type=\"hidden\" name=\"__EVENTTARGET\" id=\"__EVENTTARGET\"";
    string matchString4 = "type=\"hidden\" name=\"__EVENTARGUMENT\" id=\"__EVENTARGUMENT\"";

    string positiveLookahead1 = "(?=.*(" + Regex.Escape(matchString1) + "))";
    string positiveLookahead2 = "(?=.*(" + Regex.Escape(matchString2) + "))";
    string positiveLookahead3 = "(?=.*(" + Regex.Escape(matchString3) + "))";
    string positiveLookahead4 = "(?=.*(" + Regex.Escape(matchString4) + "))";

    RegexOptions opt = RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant | RegexOptions.Compiled;

    Regex[] arrRe = new Regex[] {
        new Regex("\\s*<div>" + positiveLookahead1 + "(.*?)</div>\\s*", opt),
        new Regex("\\s*<div>" + positiveLookahead2 + "(.*?)</div>\\s*", opt),
        new Regex("\\s*<div>" + positiveLookahead3 + "(.*?)</div>\\s*", opt),
        new Regex("\\s*<div>" + positiveLookahead3 + "(.*?)</div>\\s*", opt),
        new Regex("\\s*<div>" + positiveLookahead4 + "(.*?)</div>\\s*", opt)
    };

    foreach (Regex re in arrRe)
    {
        strHTML = re.Replace(strHTML, "");
    }
    return strHTML;
}
複製代碼

如下是刪除頁面空白的方法:app

複製代碼
// 刪除空白
private Regex tabsRe = new Regex("\\t", RegexOptions.Compiled | RegexOptions.Multiline);
private Regex carriageReturnRe = new Regex(">\\r\\n<", RegexOptions.Compiled | RegexOptions.Multiline);
private Regex carriageReturnSafeRe = new Regex("\\r\\n", RegexOptions.Compiled | RegexOptions.Multiline);
private Regex multipleSpaces = new Regex("  ", RegexOptions.Compiled | RegexOptions.Multiline);
private Regex spaceBetweenTags = new Regex(">\\s<", RegexOptions.Compiled | RegexOptions.Multiline);
private string WhitespaceFilter(string html)
{
    html = tabsRe.Replace(html, string.Empty);
    html = carriageReturnRe.Replace(html, "><");
    html = carriageReturnSafeRe.Replace(html, " ");
    while (multipleSpaces.IsMatch(html))
        html = multipleSpaces.Replace(html, " ");
    html = spaceBetweenTags.Replace(html, "><");
    html = html.Replace("//<![CDATA[", "");
    html = html.Replace("//]]>", "");
    return html;
}
複製代碼

如下是刪除ASP.NET控件的垃圾UniqueID名稱方法:ide

複製代碼
// 過濾NamingContainer
private string NamingContainerFilter(string html)
{
    RegexOptions opt =
        RegexOptions.IgnoreCase |
        RegexOptions.Singleline |
        RegexOptions.CultureInvariant |
        RegexOptions.Compiled;
    Regex re = new Regex("( name=\")(?=.*(" + Regex.Escape("$") + "))([^\"]+?)(\")", opt);
    html = re.Replace(html, new MatchEvaluator(delegate(Match m)
    {
        int lastDollarSignIndex = m.Value.LastIndexOf('$');
        if (lastDollarSignIndex >= 0)
        {
            return m.Groups[1].Value + m.Value.Substring(lastDollarSignIndex + 1);
        }
        else
        {
            return m.Value;
        }
    }));
    return html;
}
複製代碼

最後,咱們把以上過濾方法整合到CommonFilter類的Write方法:函數

複製代碼
public override void Write(byte[] buffer, int offset, int count)
{
    // 轉換buffer爲字符串
    byte[] data = new byte[count];
    Buffer.BlockCopy(buffer, offset, data, 0, count);
    string html = System.Text.Encoding.UTF8.GetString(buffer);

    // 如下整合過濾方法
    html = NamingContainerFilter(html);
    html = ViewStateFilter(html);
    html = WhitespaceFilter(html);
    byte[] outdata = System.Text.Encoding.UTF8.GetBytes(html);

    // 寫入磁盤
    _cacheStream.Write(outdata, 0, outdata.GetLength(0));
    _responseStream.Write(outdata, 0, outdata.GetLength(0));
}
複製代碼

5、緩存破壞   通過以上程序的實現,網頁已經被高速緩存在客戶端了,若是果用戶訪問網站被緩存過的頁面,則頁面會以0請求的速度加載頁面。可是,若是後臺更新了某些數據,前臺用戶則不能及時看到最新的數據,所以要改變這種狀況,咱們必須破壞緩存。根據咱們如上的程序,咱們破壞緩存只須要作2步:更新服務器上的臨時文件,刪除OutputCache過的頁面。   更新服務器上的文件咱們只需刪除這個文件便可,當某一用戶第一次訪問該頁面時會自動生成,固然,你也能夠用程序先刪除後生成:post

// 更新文件
foreach ( var file in Directory.GetFiles( HttpRuntime.AppDomainAppPath + "Temp" ) ) {
    File.Delete( file );
}

要刪除OutputCache關聯的緩存項,代碼以下,咱們只須要保證該方法的參數,指頁面的絕對路徑是正確的,路徑不能使用../這樣的相對路徑:性能

// 刪除緩存
HttpResponse.RemoveOutputCacheItem( "/Default.aspx" );

優化並未結束,戰鬥還在繼續……

 

感謝  五度蒼穹  分享

相關文章
相關標籤/搜索