时间:2021-07-01 10:21:17 帮助过:7人阅读
<!-- Adds OutputCache directive -->
<%@ OutputCache Duration="23" VaryByParam="None" %>
它支持五个属性,其中两个属性Duration和VaryByParam是必填的
Duration |
必需属性。页面应该被缓存的时间,以秒为单位。必须是正整数。 |
Location |
指定应该对输出进行缓存的位置。如果要指定该参数,则必须是下列选项之一:Any、Client、Downstream、None、Server 或 ServerAndClient。 |
VaryByParam |
必需属性。Request 中变量的名称,这些变量名应该产生单独的缓存条目。"none" 表示没有变动。"*" 可用于为每个不同的变量数组创建新的缓存条目。变量之间用 ";" 进行分隔。 |
VaryByHeader |
基于指定的标头中的变动改变缓存条目。 |
VaryByCustom |
允许在 global.asax 中指定自定义变动(例如,"Browser")。 |
输出缓存,但我们要注意的是是否真的有必要缓存每个查询参数都缓存一个页面副本,假设查询Url中增加一个参数参数ProductId,那么现在Url中就有两个查询参数了(ProductName和ProductId)。
<!-- Sets client OutputCache -->
<%@ OutputCache Duration="23" VaryByParam="None" Location="Client" %>
通过在OutputCache中添加Location属性,我们实现了客户端缓存,通过设置客户端缓存我们能够减少的客户端请求,也许有人会问:“每个用户第一次页面请求都需要服务器来完成,这不能很好的减少服务的压力”。的确是这样,相对于服务器缓存,客户端缓存并没有减少代码的执行和数据库的操作,但是当我们把包含个性化数据的页面缓存在服务器中,客户端请求页面时,由于不同的用户个性化数据不同,这将会导致请求出现错误,所以我们可以使用片段缓存把公用的部分缓存起来或客户端缓存把用户信息缓存起来。
Query String缓存
在前面的例子中,我们把OutputCache中的VaryByParam属性设置为None,那么ASP.NET程序只缓存一个页面副本;如果页面请求包含查询参数,那么在缓存的有效期内,我们只可以查看到只是缓存结果,假设我们有个报表程序,它提供用户根据产品名称查询相关的产品信息。
首先我们创建两个页面:查询和结果页面,由于时间关系我们已经把页面设计好了,具体如下所示:
图2报表程序
首先我们提供查询页面,让用户根据成品名称(ProductName)查询相应的成品信息,具体的代码如下:
代码如下:输出缓存;其实前面的示例我们只需稍稍改动就能符合查询参数的情况了,想必大家已经知道了,只需把VaryByParam属性设置为“*”就OK了。
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
// Gets product id from table Production.Product.
// Then binding data to drop down list control.
InitControl(GetProductId());
}
}
/// <summary>
/// Handles the Click event of the btnSubmit control.
/// Redirects to relative product information page.
/// </summary>
protected void btnSubmit_Click(object sender, EventArgs e)
{
Response.Redirect(string.Format("Product.aspx?productname={0}", ddlProductName.SelectedValue));
}
当用户点击Submit按钮后,跳转到Product页面并且在Url中传递查询参数——产品名称(ProducName)。
接下来,我们继续完成查询页面,由于在前一页面中传递了查询参数ProductName,那么我们将根据ProductName查询数据库获取相应的产品信息,具体代码如下所示:
代码如下:输出缓存,如下代码:
protected void Page_Load(object sender, EventArgs e)
{
// Get product name.
string productName = Request.QueryString["productname"];
// Binding data to data grid view control.
InitControl(this.GetData(productName));
}
/// <summary>
/// Inits the control.
/// </summary>
/// <param name="ds">The dataset.</param>
private void InitControl(DataSet ds)
{
dgvProduct.DataSource = ds;
dgvProduct.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();
////gdvData.DataSource = com.ExecuteReader();
////gdvData.DataBind();
var ada = new SqlDataAdapter(com);
var ds = new DataSet();
ada.Fill(ds);
return ds;
}
}
前面示例,我们通过Request的属性QueryString获取ProductName的值,然后根据ProductName查询数据库,最后把获取数据绑定到Datagridview控件中(注:前面实例没有考虑SQL Injection问题)。
图3查询结果
现在我们在页面中添加
代码如下:输出缓存的属性VaryByParam=”None”时,ASP.NET程序在缓存有效期内只缓存一个页面副本;现在我们在缓存有效期内(30s)再发送请求。
<!-- Adds OutputCache directive -->
<%@ OutputCache Duration="30" VaryByParam="None" %>
前面提到当
图4查询结果
通过上图我们发现,现在查询参数ProductName=BK-M18B-40,但查询结果依然是ProductName=BB-9108的数据,这是由于ASP.NET程序在缓存有效期内只缓存一个页面副本。
通过上面的示例,我们发现只缓存一个页面是不适用包含查询参数的页面
图5查询结果
现在查询可以获取相应的结果,如果查询参数和前一个请求相同并且该页面缓存有效,那么缓存将被重用,否则,创建一个新的页面缓存。
由于ASP.NET给每个查询参数都添加了
输出缓存功能,而且前面介绍的例子中Duration属性值都是直接Hard code到每个页面中,如果我们需要修改Duration属性值,那么就必须修改每个页面了,ASP.NET还需要重新编译这些页面,这不利于我们的维护,最重要的是增加了我们的工作量。
<!-- Sets VaryByParam property-->
<%@ OutputCache Duration="30" VaryByParam="productname" %>
自定义缓存控件
前面我们介绍了通过查询参数实现缓存一个或多个页面,其实ASP.NET也允许我们自定义缓存方式来决定是否缓存页或重用现有的,这时我们可以通过设置VaryByCustom属性来实现。
假设,现在我们要设计基于不同UserHostName的缓存,由于程序在执行过程中,首先调用全局方法GetVaryByCustomString()来确定是否缓存页面或重用现有的,所以我们可以通过重写GetVaryByCustomString()方法实现基于UserHostName的缓存,首先我们创建一个Global.asax文件然后重新全局方法GetVaryByCustomString()具体实现如下:
代码如下:输出缓存这可能比较简单,但我们要在几十个页面中添加
/// <summary>
/// Gets vary cache based on custom string value.
/// </summary>
/// <param name="context">Http context.</param>
/// <param name="custom">custom string</param>
/// <returns></returns>
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (string.Equals(custom, "UserHostName", StringComparison.OrdinalIgnoreCase))
{
// Indicates that the cache should be vary on user host name.
return Context.Request.UserHostName;
}
return base.GetVaryByCustomString(context, custom);
}
前面我们重写了GetVaryByCustomString()方法,使得UserHostName值不同时,获取相应的缓存值。
然后让程序基于UserHostName创建缓存,所以我们要在页面添加以下代码:
代码如下:输出缓存的使用,只需在页面中添加OutputCache指令,假设我们要在几个页面中添加
<!-- set vary cache based on custom string value -->
<%@ OutputCache Duration="30" VaryByParam="None" VaryByCustom="UserHostName" %>
我们通过自定义现在GetVaryByCustomString()方法,实现了Web程序根据UserHostName实施不同的缓存方式,其实,我们还可以实现更多种类缓存方案,例如:基于用户角色、时间和Url等等。
片段缓存
在某些情况下,我们不能缓存整个页面,但我们仍想缓存部分页面从而减轻系统的负担;其实,我们可以通过两种方法实现:片段缓存和数据缓存.
为了实现片段缓存,我们需要创建自定义控件缓存部分页面,然后我们把OutputCache指令添加到自定义控件中,这样整个页面将不会被缓存,而自定义缓存控件除外。
前面我们介绍了
<caching>
<!-- Sets out put cache profile-->
<outputCacheSettings>
<outputCacheProfiles>
<add name="ProductCacheProfile" duration="30"/>
</outputCacheProfiles>
</outputCacheSettings>
</caching>现在,我们在页面中添加CacheProfile属性,并且设置为ProductCacheProfile,如下所示:
代码如下:输出缓存实现报表程序缓存查询页面效果一样,所以我们将使用SqlDataSource缓存实现该效果。
<!-- set CacheProfile property -->
<%@ OutputCache CacheProfile="ProductCacheProfile" VaryByParam="None" %>
数据缓存
Cache对象是线程安全:这表示无需显式实现锁定或解锁,在添删Cache对象中的元素,然而,在Cache对象中元素必须是线程安全的。例如,我们创建一个实体Product,而且存在多个客户端可能同时操作该对象的情况,这时我们必须为实体Product实现锁定和解锁操作(同步操作请参考《单例模式(Singleton)的6种实现》)。
Cache对象中的缓存项自动移除:当缓存过期,依赖项被修改或内存不足缓存ASP.NET会自动移除该缓存项。
缓存项支持依赖关系:我们可以给缓存项添加文件、数据库表或其他资源类型的依赖关系。
SqlDataSource缓存
当我们在SqlDataSource控件中启用缓存,它缓存SelectCommand中的结果;如果SQL查询语句中带有参数时,SqlDataSource控件会缓存每一个参数值对应的结果。
这跟我们之前通过
假设我们要提供一个报表程序,让用户通过选择产品名称(ProductName),获取相应的产品信息。
首先,我们在页面中创建两个数据源控件:sourceProductName和sourceProduct,接着把数据源分别绑定到Dropdownlist和Gridview中,具体实现如下:
代码如下:
<!-- The product number datasource START -->
<asp:SqlDataSource ID="sourceProductName" runat="server" ProviderName="System.Data.SqlClient"
EnableCaching="True" CacheDuration="3600" ConnectionString="<%$ ConnectionStrings:SQLCONN %>"
SelectCommand="SELECT ProductNumber FROM Production.Product"></asp:SqlDataSource>
<!-- The product number datasource END -->
<!-- The product datasource START -->
<asp:SqlDataSource ID="sourceProduct" runat="server" ProviderName="System.Data.SqlClient"
EnableCaching="True" CacheDuration="3600" ConnectionString="<%$ ConnectionStrings:SQLCONN %>"
SelectCommand="SELECT Name, ProductNumber, SafetyStockLevel, ReorderPoint, StandardCost, DaysToManufacture
FROM Production.Product WHERE ProductNumber=@ProductNumber">
<SelectParameters>
<asp:ControlParameter ControlID="ddlProductNumber" Name="ProductNumber" PropertyName="SelectedValue" />
</SelectParameters>
</asp:SqlDataSource>
<!-- The product number datasource END -->
<!-- Binding the product number to gridview control -->
<!-- NOTE: Due to search and result in the same page, so need to set AutoPostBack is True-->
<asp:DropDownList ID="ddlProductNumber" AutoPostBack="True" DataSourceID="sourceProductName"
DataTextField="ProductNumber" runat="server">
</asp:DropDownList>
<!-- Binding the product datasource to gridview control -->
<asp:GridView ID="gvProduct" runat="server" DataSourceID="sourceProduct" CssClass="Product">
</asp:GridView>
现在我们对报表程序进行查询,如果ProudctName之前没有被缓存起来就会创建相应的缓存,而已经缓存起来的将被重用,查询结果如下:
图6查询结果
缓存的依赖关系
缓存项之间的依赖
ASP.NET Cache允许我们建立缓存之间的依赖关系,即一个缓存项依赖于另一个缓存项;以下示例代码创建了二个缓存项,并且它们之间建立依赖关系。具体实现如下:
代码如下:
// Creates cache object Key1.
Cache["Key1"] = "Cache Item 1";
// Makes Cache["Key2"] dependent on Cache["Key1"].
string[] dependencyKey = new string[1];
dependencyKey[0] = "Key1";
// Creates a CacheDependency object.
CacheDependency dependency = new CacheDependency(null, dependencyKey);
// Establishs dependency between cache Key1 and Key2.
Cache.Insert("Key2", "Cache Item 2", dependency);
现在,当Key1缓存项更新或从缓存中删除,Key2缓存项就会自动从缓存删除。
文件依赖
前面我们介绍了缓存项之间的依赖关系,ASP.NET Cache还提供缓存项与文件之间的依赖关系,当文件被更新或删除对应的缓存项也将失效。
在上篇博文《Ajax与JSON的一些总结》的最后介绍的一个DEMO——Weibo Feed中,我们通过实时方式向新浪微博API发送请求获取相应的数据,但在一定时间内请求的次数是有限制的,一旦超出了限制次数就不再接受请求了(具体请参考Rate-limiting)。所以可以通过Cache的方式把数据缓存起来,当客户端请求时,如果缓存数据已经存在那么直接返回数据,否则重新想微博API请求数据。
首先,我们创建一个HttpHandler,它负责向微博API发送请求并且把数据保存的文件中,最后把数据返回的客户端。
图7 请求流程
接下来,我们定义CacheData()方法把微博数据保存到文本文件中并且建立缓存与数据文件的依赖关系。
代码如下:
/// <summary>
/// Caches the data into text file.
/// </summary>
/// <param name="context">The http context</param>
private void CacheData(HttpContext context)
{
// Weibo API.
string uri = context.Request.QueryString["api"] + "?" +
"source=" + context.Request.QueryString["source"] + "&" +
"count=" + context.Request.QueryString["count"];
HttpWebResponse response = this.GetWeibos(uri);
if (null == response)
{
throw new ArgumentNullException("Response is null");
}
string jsonData;
// Writes the reponse data into text file.
using (var reader = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding(response.CharacterSet)))
{
jsonData = reader.ReadToEnd();
}
string dataPath = context.Server.MapPath("weibo.json");
using (var writer = new StreamWriter(dataPath, false, Encoding.GetEncoding(response.CharacterSet)))
{
writer.Write(jsonData);
}
// Establishs dependency between cache weibo and text file.
// Sets cache expires after 2 minuntes.
HttpRuntime.Cache.Insert("weibo", jsonData, Dep, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(2));
}
现在我们把数据保存到文本文件中并且建立了缓存weibo与数据文件的依赖关系,接下来我们要把JSON格式数据返回给客户端。
代码如下:
/// <summary>
/// Responses the weibo data.
/// </summary>
/// <param name="context">The http contex.</param>
private void ResponseWeibo(HttpContext context)
{
// Gets the weibo cache data.
byte[] buf = Encoding.UTF8.GetBytes(HttpRuntime.Cache["weibo"].ToString());
// Writes the data into output stream.
context.Response.OutputStream.Write(buf, 0, buf.Length);
context.Response.OutputStream.Flush();
////context.Response.Close();
}
上面我们把JSON格式字符串转换为Byte数值,然后写入到OutputStream中,最后把数据返回给客户端。
代码如下:输出缓存和数据缓存的应用场合和区别。
// The function to get weibo data.
loadWeibo: function() {
$.ajax({
// Weibo API.
url: "WeiboHandler.ashx",
type: "GET",
// NOTE: We get the data from same domain,
// dataType is json.
dataType: "json",
data: {
source: JQWeibo.appKey,
count: JQWeibo.numWeibo
},
// When the requet completed, then invokes success function.
success: function(data, textStatus, xhr) {
// Sets html structure.
var html =
'<div class="weibo">' +
'<a href="http://weibo.com/DOMAIN" target="_blank">USER</a>' +
':WEIBO_TEXT<div class="time">AGO</div>';
// Appends weibos into html page.
for (var i = 0; i < data.length; i++) {
$(JQWeibo.appendTo).append(
html.replace('WEIBO_TEXT', JQWeibo.ify.clean(data[i].text))
// Uses regex and declare DOMAIN as global, if found replace all.
.replace(/DOMAIN/g, data[i].user.domain)
.replace(/USER/g, data[i].user.screen_name)
.replace('AGO', JQWeibo.timeAgo(data[i].created_at))
);
}
}
})
}
图8请求结果
1.1.3 总结
缓存可以使应用程序的性能得到很大的提高,因此在设计应用程序应该予以考虑,本博文主要介绍了ASP.NET中
页面缓存适用于生成的页面通常都相同或改变时间比较固定情况,例如:数据在每小时都会更新,那么我们可以设置duration为3600s。
数据缓存适用生成的页面总是在变化情况。
http://www.codeproject.com/Articles/29899/Exploring-Caching-in-ASP-NET
http://msdn.microsoft.com/zh-cn/library/aa478965.aspx#XSLTsection129121120120
http://www.amazon.com/Beginning-ASP-NET-3-5-2008-Professional/dp/1590598911