自定义缓存控件

前面我们介绍了通过查询参数实现缓存一个或多个页面,其实ASP.NET也允许我们自定义缓存方式来决定是否缓存页或重用现有的,这时我们可以通过设置VaryByCustom属性来实现。

假设,现在我们要设计基于不同UserHostName的缓存,由于程序在执行过程中,首先调用GetVaryByCustomString()方法来确定是否缓存页面或重用现有的,所以我们可以通过重写该方法实现基于UserHostName的缓存,具体实现如下:

 
  1. /// <summary> 
  2. /// Gets vary cache based on custom string value.  
  3. /// </summary> 
  4. /// <param name="context">Http context.</param> 
  5. /// <param name="custom">custom string</param> 
  6. /// <returns></returns> 
  7. public override string GetVaryByCustomString(HttpContext context, string custom)  
  8. {  
  9.     if (string.Equals(custom, "UserHostName", StringComparison.OrdinalIgnoreCase))  
  10.     {  
  11.         // Indicates that the cache should be vary on user host name.  
  12.         return Context.Request.UserHostName;  
  13.     }  
  14.     return base.GetVaryByCustomString(context, custom);  

前面我们重写了GetVaryByCustomString()方法,使得UserHostName值不同时,获取相应的缓存值。

然后让程序基于UserHostName创建缓存,所以我们要在页面添加以下代码:

 
  1. <!-- set vary cache based on custom string value --> 
  2. <%@ OutputCache Duration="30" VaryByParam="None" VaryByCustom="UserHostName" %> 

我们通过自定义现在GetVaryByCustomString()方法,实现了Web程序根据UserHostName实施不同的缓存方式,其实,我们还可以实现更多种类缓存方案,例如:基于用户角色、时间和Url等等。

片段缓存

在某些情况下,我们不能缓存整个页面,但我们仍想缓存部分页面从而减轻系统的负担;其实,我们可以通过两种方法实现:片段缓存数据缓存.

为了实现片段缓存,我们需要创建自定义控件缓存部分页面,然后我们把OutputCache指令添加到自定义控件中,这样整个页面将不会被缓存,而自定义缓存控件除外。

前面我们介绍了输出缓存的使用,只需在页面中添加OutputCache指令,假设我们要在几个页面中添加输出缓存这可能比较简单,但我们要在几十个页面中添加输出缓存功能,而且前面介绍的例子中Duration属性值都是直接Hard code到每个页面中,如果我们需要修改Duration属性值,那么就必须修改每个页面了,ASP.NET还需要重新编译这些页面,这不利于我们的维护,最重要的是增加了我们的工作量。

其实,我们可以在web.config文件中定义一个outputCacheProfile(ProductCacheProfile),然后在页面中添加CacheProfile属性并且赋值为ProductCacheProfile,web.config文件设置如下:

 
  1. <caching> 
  2.   <!-- Sets out put cache profile--> 
  3.   <outputCacheSettings> 
  4.     <outputCacheProfiles> 
  5.       <add name="ProductCacheProfile" duration="30"/> 
  6.     </outputCacheProfiles> 
  7.   </outputCacheSettings> 
  8. </caching> 

现在,我们在页面中添加CacheProfile属性,并且设置为ProductCacheProfile,如下所示:

 
  1. <!-- set CacheProfile property --> 
  2. <%@ OutputCache CacheProfile="ProductCacheProfile" VaryByParam="None" %> 
  3. 数据缓存

    Cache对象是线程安全:这表示无需显式实现锁定或解锁,在添删Cache对象中的元素,然而,在Cache对象中元素必须是线程安全的。例如,我们创建一个实体Product,而且存在多个客户端可能同时操作该对象的情况,这时我们必须为实体Product实现锁定和解锁操作(同步操作请参考《》)。

    Cache对象中的缓存项自动移除:当缓存过期,依赖项被修改或内存不足缓存ASP.NET会自动移除该缓存项。

    缓存项支持依赖关系:我们可以给缓存项添加文件、数据库表或其他资源类型的依赖关系。

    SqlDataSource缓存

    当我们在SqlDataSource控件中启用缓存,它缓存SelectCommand中的结果;如果SQL查询语句中带有参数时,SqlDataSource控件会缓存每一个参数值对应的结果。

    这跟我们之前通过输出缓存实现报表程序缓存查询页面效果一样,所以我们将使用SqlDataSource缓存实现该效果。

    假设我们要提供一个报表程序,让用户通过选择产品名称(ProductName),获取相应的产品信息。

    首先,我们在页面中创建两个数据源控件:和sourceProduct,接着把数据源分别绑定到Dropdownlist和Gridview中,具体实现如下:

     
    1. <!-- The product number datasource START -->  
    2. <asp:SqlDataSource ID="sourceProductName" runat="server" ProviderName="System.Data.SqlClient" 
    3.     EnableCaching="True" CacheDuration="3600" ConnectionString="<%$ ConnectionStrings:SQLCONN %>" 
    4.     SelectCommand="SELECT ProductNumber FROM Production.Product"></asp:SqlDataSource>  
    5. <!-- The product number datasource END -->  
    6.  
    7. <!-- The product datasource START -->  
    8. <asp:SqlDataSource ID="sourceProduct" runat="server" ProviderName="System.Data.SqlClient" 
    9.     EnableCaching="True" CacheDuration="3600" ConnectionString="<%$ ConnectionStrings:SQLCONN %>" 
    10.     SelectCommand="SELECT Name, ProductNumber, SafetyStockLevel, ReorderPoint, StandardCost, DaysToManufacture  
    11.      FROM Production.Product WHERE ProductNumber=@ProductNumber">  
    12.     <SelectParameters>  
    13.         <asp:ControlParameter ControlID="ddlProductNumber" Name="ProductNumber" PropertyName="SelectedValue" />  
    14.     </SelectParameters>  
    15. </asp:SqlDataSource>  
    16. <!-- The product number datasource END -->  
    17.  
    18. <!-- Binding the product number to gridview control -->  
    19. <!-- NOTE: Due to search and result in the same page, so need to set AutoPostBack is True-->  
    20. <asp:DropDownList ID="ddlProductNumber" AutoPostBack="True" DataSourceID="sourceProductName" 
    21.     DataTextField="ProductNumber" runat="server">  
    22. </asp:DropDownList>  
    23.  
    24. <!-- Binding the product datasource to gridview control -->  
    25. <asp:GridView ID="gvProduct" runat="server" DataSourceID="sourceProduct" CssClass="Product">  
    26. </asp:GridView>  

    现在我们对报表程序进行查询,如果ProudctName之前没有被缓存起来就会创建相应的缓存,而已经缓存起来的将被重用,查询结果如下:

     图6查询结果

  4. 缓存的依赖关系

    缓存项之间的依赖

    ASP.NET Cache允许我们建立缓存之间的依赖关系,即一个缓存项依赖于另一个缓存项;以下示例代码创建了二个缓存项,并且它们之间建立依赖关系。具体实现如下:

     
    1. // Creates cache object Key1.  
    2. Cache["Key1"] = "Cache Item 1";  
    3.  
    4. // Makes Cache["Key2"] dependent on Cache["Key1"].  
    5. string[] dependencyKey = new string[1];  
    6. dependencyKey[0] = "Key1";  
    7.  
    8. // Creates a CacheDependency object.  
    9. CacheDependency dependency = new CacheDependency(null, dependencyKey);  
    10.  
    11. // Establishs dependency between cache Key1 and Key2.  
    12. Cache.Insert("Key2""Cache Item 2", dependency); 

    现在,当Key1缓存项更新或从缓存中删除,Key2缓存项就会自动从缓存删除。

    文件依赖

    前面我们介绍了缓存项之间的依赖关系,ASP.NET Cache还提供缓存项与文件之间的依赖关系,当文件被更新或删除对应的缓存项也将失效。

    在上篇博文《》的最后介绍的一个DEMO——Weibo Feed中,我们通过实时方式向新浪微博API发送请求获取相应的数据,但在一定时间内请求的次数是有限制的,一旦超出了限制次数就不再接受请求了(具体请参考)。所以可以通过Cache的方式把数据缓存起来,当客户端请求时,如果缓存数据已经存在那么直接返回数据,否则重新想微博API请求数据。

    首先,我们创建一个HttpHandler,它负责向微博API发送请求并且把数据保存的文件中,最后把数据返回的客户端。

    图7 请求流程

    接下来,我们定义CacheData()方法把微博数据保存到文本文件中并且建立缓存与数据文件的依赖关系。

     
    1. /// <summary>  
    2. /// Caches the data into text file.  
    3. /// </summary>  
    4. /// <param name="context">The http context</param>  
    5. private void CacheData(HttpContext context)  
    6. {  
    7.     // Weibo API.  
    8.     string uri = context.Request.QueryString["api"] + "?" +  
    9.         "source=" + context.Request.QueryString["source"] + "&" +  
    10.           "count=" + context.Request.QueryString["count"];  
    11.       
    12.     HttpWebResponse response = this.GetWeibos(uri);  
    13.  
    14.     if (null == response)  
    15.     {  
    16.         throw new ArgumentNullException("Response is null");  
    17.     }  
    18.  
    19.     string jsonData;  
    20.     // Writes the reponse data into text file.  
    21.     using (var reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding(response.CharacterSet)))  
    22.     {  
    23.         jsonData = reader.ReadToEnd();  
    24.     }  
    25.  
    26.     string dataPath = context.Server.MapPath("weibo.json");  
    27.     using (var writer = new StreamWriter(dataPath, false, Encoding.GetEncoding(response.CharacterSet)))  
    28.     {  
    29.         writer.Write(jsonData);  
    30.     }  
    31.       
    32.     // Establishs dependency between cache weibo and text file.  
    33.     // Sets cache expires after 2 minuntes.  
    34.     HttpRuntime.Cache.Insert("weibo", jsonData, Dep, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(2));  
    35.  

    现在我们把数据保存到文本文件中并且建立了缓存weibo与数据文件的依赖关系,接下来我们要把JSON格式数据返回给客户端。

     
    1. /// <summary>  
    2. /// Responses the weibo data.  
    3. /// </summary>  
    4. /// <param name="context">The http contex.</param>  
    5. private void ResponseWeibo(HttpContext context)  
    6. {  
    7.     // Gets the weibo cache data.  
    8.     byte[] buf = Encoding.UTF8.GetBytes(HttpRuntime.Cache["weibo"].ToString());  
    9.       
    10.     // Writes the data into output stream.  
    11.     context.Response.OutputStream.Write(buf, 0, buf.Length);  
    12.     context.Response.OutputStream.Flush();  
    13.     context.Response.Close();  

    上面我们把JSON格式字符串转换为Byte数值,然后写入到OutputStream中,最后把数据返回给客户端。

     
    1. // The function to get weibo data.  
    2. loadWeibo: function() {  
    3.     $.ajax({  
    4.         // Weibo API.  
    5.     url: "WeiboHandler.ashx",  
    6.         type: "GET",  
    7.         // NOTE: We get the data from same domain,  
    8.         // dataType is json.  
    9.         dataType: "json",             
    10.         data: {  
    11.             source: JQWeibo.appKey,  
    12.             count: JQWeibo.numWeibo  
    13.         },  
    14.  
    15.         // When the requet completed, then invokes success function.  
    16.         success: function(data, textStatus, xhr) {  
    17.  
    18.             // Sets html structure.  
    19.             var html =  
    20.         '<div class="weibo">' +  
    21.         '<a href="http://weibo.com/DOMAIN" target="_blank">USER</a>' +  
    22.         ':WEIBO_TEXT<div class="time">AGO</div>';  
    23.  
    24.             // Appends weibos into html page.  
    25.             for (var i = 0; i < data.length; i++) {  
    26.                 $(JQWeibo.appendTo).append(  
    27.             html.replace('WEIBO_TEXT', JQWeibo.ify.clean(data[i].text))  
    28.  
    29.                 // Uses regex and declare DOMAIN as global, if found replace all.  
    30.                 .replace(/DOMAIN/g, data[i].user.domain)  
    31.                 .replace(/USER/g, data[i].user.screen_name)  
    32.                 .replace('AGO', JQWeibo.timeAgo(data[i].created_at))  
    33.         );  
    34.             }  
    35.         }  
    36.     })  
    图8请求结果
    总结

    缓存可以使应用程序的性能得到很大的提高,因此在设计应用程序应该予以考虑,本博文主要介绍了ASP.NET中输出缓存和数据缓存的应用场合和区别。

    页面缓存适用于生成的页面通常都相同或改变时间比较固定情况,例如:数据在每小时都会更新,那么我们可以设置duration为3600s。

    数据缓存适用生成的页面总是在变化情况。

    原文链接: