一、ASP.NET中的缓存分类介绍
ASP.NET中有两种缓存类型:输出缓存和数据缓存。
1、输出缓存:这是最简单的缓存类型,它是缓存整个页面,它保存发送到客户端的页面副本,当下一个客户端发送相同的页面请求时,此页面不会重新生成(在缓存有限期内),而是从缓存中获取该页面;当然由于缓存过期或被回收,这时页面会重新生成。
2、数据缓存
除此之外,还有两个特殊的缓存:片段缓存和数据源缓存。
1、片段缓存:这是一种特殊的输出缓存,它不是缓存整个页面,而是缓存部分页面;由于缓存整个页面(输出缓存)通常并不可行,因为页面的某些部分是针对用户定制的(例如用户登陆信息),但我们可以把应用程序中共享的部分进行缓存,这时我们可以考虑使用片段缓存和用户控件缓存(对页面中的某个控件进行缓存)。
2、数据源缓存:是建立在数据源控件的缓存,它包括SqlDataSource、ObjectDataSource和XmlDataSource控件。数据源缓存使用数据缓存方式,不同的是我们不需要通过显示方法处理缓存;我们只需设置相应的属性,然后数据源控件就能存储和检索数据。
二、输出缓存
输出缓存可以把最终呈现的页面缓存起来,当客户端再次请求同一页面时,控制对象不再重新创建(未过期的情况下),页面的生命周期不再启动,无需再次执行代码,通过在缓存中获取缓存的页面。
现在我们设计一个页面,每当用户发送页面请求时,就获取当前代码执行的时间,然后显示在页面上。
图1输出缓存
这是再简单不过的例子,每当用户发送页面请求都会更新页面显示的时间,这是由于每次请求都获取了一个新的页面,实际情况中,我们并不需要实时的响应用户每个页面请求,我们可以通过输出缓存把页面缓存起来每当用户发送同一页面请求时,而且在缓存有效期间,可以通过输出缓存把缓存的页面返回给用户。
我们要实现输出缓存,只需在页面中添加如下代码:
<%@ OutputCache Duration="23" VaryByParam="None" %>
它支持五个属性,其中两个属性Duration和VaryByParam是必填的
表1输出缓存属性
这里我们把输出缓存的有效期设置为23秒,也就是说,当缓存超过有效期就会被回收;当用户再次请求该页面时,就要重新创建页面。
二、客户端缓存(除Cookie、ViewState、Hidden...之外的客户端缓存)
另一种选择是客户端缓存,如果用户在浏览器中点击“后退”按钮或在地址栏中重新输入URL,那么在这种情况下,浏览器将从缓存获取页面;然而,如果用户点击“刷新”按钮,那么浏览器中缓存将失效,浏览器发送页面请求。
如果我们要使用客户端缓存,只需指定OutputCache中的属性Location=”Client”就OK了,具体代码如下所示:
<%@ OutputCache Duration="23" VaryByParam="None" Location="Client" %>
通过在OutputCache中添加Location属性,我们实现了客户端缓存,通过设置客户端缓存我们能够减少的客户端请求,也许有人会问:“每个用户第一次页面请求都需要服务器来完成,这不能很好的减少服务的压力”。的确是这样,相对于服务器缓存,客户端缓存并没有减少代码的执行和数据库的操作,但是当我们把包含个性化数据的页面缓存在服务器中,客户端请求页面时,由于不同的用户个性化数据不同,这将会导致请求出现错误,所以我们可以使用片段缓存把公用的部分缓存起来或客户端缓存把用户信息缓存起来。
三、Query String缓存
在前面的例子中,我们把OutputCache中的VaryByParam属性设置为None,ASP.NET程序在缓存有效期内只缓存一个页面副本;
如果页面请求包含参数,那么在缓存的有效期内,我们只可以查看到只是缓存结果,假设我们有个报表程序,它提供用户根据产品名称查询相关的产品信息。
首先我们创建两个页面:查询和结果页面,由于时间关系我们已经把页面设计好了,具体如下所示:
图2报表程序
首先我们提供查询页面,让用户根据成品名称(ProductName)查询相应的成品信息,具体的代码如下:
- protected void Page_Load(object sender, EventArgs e)
- {
- if (!Page.IsPostBack)
- {
- // 绑定Gridview1控件
- InitControl(GetProductId());
- }
- }
- /// <summary>
- /// 传递了一个参数到 Product.aspx页面
- /// </summary>
- protected void btnSubmit_Click(object sender, EventArgs e)
- {
- Response.Redirect(string.Format("Product.aspx?productname={0}", ddlProductName.SelectedValue));
- }
当用户点击Submit按钮后,跳转到Product.aspx页面并且在Url中传递参数——产品名称(ProducName)。
接下来,我们继续完成查询页面,由于在前一页面中传递了参数ProductName,那么我们将根据ProductName查询数据库获取相应的产品信息,具体代码如下所示:
- protected void Page_Load(object sender, EventArgs e)
- {
- //获取传递过来的参数:productname
- string productName = Request.QueryString["productname"];
- //根据传递过来的参数 绑定Gridview1控件
- InitControl(this.GetData(productName));
- }
- /// <summary>
- /// 绑定控件方法
- /// </summary>
- /// <param name="ds">The dataset.</param>
- private void InitControl(DataSet ds)
- {
- Gridview1.DataSource = ds;
- Gridview1.DataBind();
- }
- /// <summary>
- /// Gets the data.
- /// </summary>
- /// <param name="productName">Name of the product.</param>
- /// <returns>Returns dataset</returns>
- private DataSet GetData(string productName)
- {
- // The query sql base on product name.
- string sql =
- string.Format(
- "SELECT Name, ProductNumber, SafetyStockLevel, ReorderPoint, StandardCost, DaysToManufacture "
- + "FROM Production.Product WHERE ProductNumber='{0}'",
- productName);
- // Get data from table Production.Product.
- using (var con = new SqlConnection(ConfigurationManager.ConnectionStrings["SQLCONN"].ToString()))
- using (var com = new SqlCommand(sql, con))
- {
- com.Connection.Open();
- var ada = new SqlDataAdapter(com);
- var ds = new DataSet();
- ada.Fill(ds);
- return ds;
- }
- }
前面示例,我们通过Request的属性QueryString获取ProductName的值,然后根据ProductName查询数据库,最后把获取数据绑定到GridView1控件中(注:前面实例没有考虑SQL 注入问题)。
图3查询结果
现在我们在页面中添加输出缓存,如下代码:
<%@ OutputCache Duration="30" VaryByParam="None" %>
前面提到当输出缓存的属性VaryByParam=”None”时,ASP.NET程序在缓存有效期内只缓存一个页面副本;现在我们在缓存有效期内(30s)再发送请求。
图4查询结果
通过上图我们发现,现在查询参数ProductName=BK-M18B-40,但查询结果依然是ProductName=BB-9108的数据,这是由于ASP.NET程序在缓存有效期内只缓存一个页面副本。
通过上面的示例,我们发现只缓存一个页面是不适用包含有“参数”的页面输出缓存;
其实前面的示例我们只需稍稍改动就能符合查询参数的情况了,想必大家已经知道了,只需把VaryByParam属性设置为“*”就OK了,*表示可以为每个不同的“变量”创建不同的缓存,多个之间用“;”号隔开。
现在查询可以获取相应的结果,如果查询参数和前一个请求相同并且该页面缓存有效,那么缓存将被重用,否则,创建一个新的页面缓存。
由于ASP.NET给每个查询参数都添加了输出缓存,但我们要注意的是是否真的有必要缓存每个查询参数都缓存一个页面副本,假设查询Url中增加一个参数参数ProductId,那么现在Url中就有两个查询参数了(ProductName和ProductId)。
前面我们把VaryByParam设置为“*”,所为ASP.NET程序对ProductName和ProductId都创建页面缓存,如果我们只针对ProductName创建页面缓存,这时我们可以修改VaryByParam,具体如下所示:
<%@ OutputCache Duration="30" VaryByParam="productname" %>
四、自定义缓存控件
前面我们介绍了通过传递参数实现缓存一个(none)或多个页面(*),其实ASP.NET也允许我们自定义缓存方式来决定是否缓存页或重用现有的缓存,这时我们可以通过设置VaryByCustom属性来实现。
假设,现在我们要设计基于不同UserHostName的缓存,由于程序在执行过程中,首先调用GetVaryByCustomString()方法来确定是否缓存页面或重用现有的缓存,所以我们可以通过重写该方法实现基于UserHostName的缓存,具体实现如下:
- /// <summary>
- /// 确定是否缓存页面或重用现有的.
- /// </summary>
- /// <param name="context">Http context.</param>
- /// <param name="custom">自定义缓存控件名</param>
- /// <returns></returns>
- public override string GetVaryByCustomString(HttpContext context, string custom)
- {
- //如果传入的custom参数等于"UserHostName“
- if (string.Equals(custom, "UserHostName", StringComparison.OrdinalIgnoreCase))
- {
- // 则返回"UserHostName".
- return Context.Request.UserHostName;
- }
- //如果传入的custom参数不等于"UserHostName“,获取相应的缓存值
- return base.GetVaryByCustomString(context, custom);
- }
前面我们重写了GetVaryByCustomString()方法,使得UserHostName值不同时,获取相应的缓存值。
然后让程序基于"UserHostName"创建缓存,所以我们要在页面添加以下代码:
<%@ OutputCache Duration="30" VaryByParam="None" VaryByCustom="UserHostName" %>
我们通过自定义现在GetVaryByCustomString()方法,实现了Web程序根据UserHostName实施不同的缓存方式,其实,我们还可以实现更多种类缓存方案,例如:基于用户角色、时间和Url等等。
五、片段缓存
在某些情况下,我们不能缓存整个页面,但我们仍想缓存部分页面从而减轻系统的负担;其实,我们可以通过两种方法实现:片段缓存和数据缓存.
为了实现片段缓存,我们需要创建自定义控件缓存部分页面,然后我们把OutputCache指令添加到自定义控件中,这样整个页面将不会被缓存,而自定义缓存控件除外。
前面我们介绍了输出缓存的使用,只需在页面中添加OutputCache指令,假设我们要在几个页面中添加输出缓存这可能比较简单,但我们要在几十个页面中添加输出缓存功能,而且前面介绍的例子中Duration属性值都是直接在每个”页面头“设置的,如果我们需要修改Duration属性值,那么就必须修改每个页面了,ASP.NET还需要重新编译这些页面,这不利于我们的维护,最重要的是增加了我们的工作量。
其实,我们可以在web.config文件中定义一个outputCacheProfile(ProductCacheProfile),然后在页面中添加CacheProfile属性并且赋值为ProductCacheProfile,web.config文件设置如下:
<caching> <!-- 设置Duration缓存时间--> <outputCacheSettings> <outputCacheProfiles> <add name="ProductCacheProfile" duration="30"/> </outputCacheProfiles> </outputCacheSettings> </caching>
现在,我们在页面中添加CacheProfile属性,并且设置为ProductCacheProfile,如下所示:
<%@ OutputCache CacheProfile="ProductCacheProfile" VaryByParam="None" %>
六、数据缓存(asp.net缓存使用介绍一中介绍)
Cache对象是线程安全:这表示在添加、删除Cache对象中的元素的时候 无需显式实现锁定或解锁,Application在添加、删除的时候需要锁定和解锁,然而,在Cache对象中元素必须是线程安全的。例如,我们创建一个实体Product,而且存在多个客户端可能同时操作该对象的情况,这时我们必须为实体Product实现锁定和解锁操作(同步操作请参考《单例模式(Singleton)的6种实现》)。
Cache对象中的缓存项自动移除:当缓存过期,依赖项被修改或内存不足缓存ASP.NET会自动移除该缓存项。
缓存项支持依赖关系(CacheDependency):我们可以给缓存项添加文件、数据库表或其他资源类型的依赖关系。
七、SqlDataSource缓存
当我们在SqlDataSource控件中启用缓存,它缓存SelectCommand中的结果;如果SQL查询语句中带有参数时,SqlDataSource控件会缓存每一个参数值对应的结果。
这跟我们之前通过输出缓存实现报表程序缓存查询页面效果一样,所以我们将使用SqlDataSource缓存实现该效果。
假设我们要提供一个报表程序,让用户通过选择产品名称(ProductName),获取相应的产品信息。
首先,我们在页面中创建两个数据源控件:sourceProductName和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之前没有被缓存起来就会创建相应的缓存,而已经缓存起来的将被重用,查询结果如下: