ASP.NET 用户控件的相关缓存
启用用户控件之间的缓存依赖关系。
引言
ASP.NET 1.x 通过 `<@outputcach` 指令以及 `PartialCaching` 属性为用户控件的缓存提供了出色的支持。此外,它还提供了一些机制,可以在数据发生更改时从缓存中移除已缓存的用户控件。具体来说,如果依赖的文件已更改或依赖的缓存项已更改,则从缓存中移除已缓存的用户控件。(有关详细信息,请参阅 MSDN 和 ASP.NET 缓存)。
在实际应用中,用户控件的内容主要依赖于数据库数据,很少依赖文件,并且从不依赖缓存。因此,可以公平地说,ASP.NET 1.x 提供的缓存依赖关系对于用户控件的缓存移除来说并非开箱即用。开发人员必须想方设法扩展和增强此缓存依赖关系,就像 Dino Esposito 的文章中使用计时器轮询数据库,以及 Jeff Prosese 的文章中使用与文件系统交互的数据库扩展存储过程所做的那样。
本文采用了一种略有不同的方法:我们不使用计时器或文件系统更改通知来监视数据库表中的更改,而是监视用户控件的内部状态,该状态唯一地决定了要加载的数据。例如,假设我们登录到某个经纪账户并进行了一些交易,那么交易历史用户控件的内容将由会话 ID 和账户号决定,原因如下:
- 缓存的交易历史用户控件必须按会话 ID 进行区分,不允许将私有数据泄露给不同的登录用户。
- 如果用于匹配账户号的交易处理用户控件被加载使用,则缓存的交易历史用户控件将从缓存中移除。
本质上,我们需要将交易历史用户控件与交易处理用户控件相关联。因此,当用户尝试为某个账户进行交易时,我们将立即移除缓存的 `History` 用户控件。现在,让我解释如何使用“`VaryByCustom`”、`CacheDependency` 类和自定义属性来实现此缓存移除策略。
用户控件的多版本缓存
假设我们将 `History` 拖放到 IDE 中,只有一种方法可以正确访问已缓存的用户控件。
History h = (History) FindControl("History1");
if( h !=null) h.AccountNumber=this.tbAccountNumberRetrival.Text;
您可能会争辩说,我们可以使用以下代码加载已缓存的用户控件并更改其 `AccountNumber` 属性。
PartialCachingControl pcc= (PartialCachingControl) LoadControl("History.ascx");
this.TheLocation.Controls.Add(pcc);
if (pcc.CachedControl !=null)
((History) pcc.CachedControl).AccountNumber=this.tbAccountNumberRetrival.Text;
但是,正如本文包含的示例项目所示,`LoadControl` 方法不适用于已缓存的用户控件(请参考 WebForm1.aspx.cs 中 `Page_Load` 函数中的注释掉的代码片段)。因此,我们将继续使用 `FindControl` 方法和声明式加载(拖放)已缓存的用户控件,并通过编程方式设置其状态以更改内容。
不幸的是,上述方法对于多版本缓存来说不足够,因为在用户控件被缓存后,`FindControl` 将返回 `null
`,因此我们无法引用其 `AccountNumber` 属性。事实证明,ASP.NET 不允许以编程方式更改缓存内容,除非使用 `VaryByCustom` 或 `VaryByControl`。换句话说,当自定义字符串或控件值发生更改时,`FindControl` 方法将返回一个非空的用户控件引用,从而允许我们设置其属性以检索多版本内容。以下是利用 ASP.NET 中的 `VaryByCustom` 的相关代码片段:
<%@ OutputCache Duration="60" VaryByParam="None"
VaryByCustom="CachingMultiVersion" %>
在 Global.asax 中
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if ( custom.ToLower() =="cachingmultiversion")
{
HttpCookie cookie = context.Request.Cookies["CachingMultiVersion"];
if(cookie != null) return cookie.Value;
}
return base.GetVaryByCustomString (context, custom);
}
public static string CachingMultiVersion
{
set
{
HttpContext.Current.Response.Cookies["CachingMultiVersion"].Value=value;
}
}
在 WebForm1.aspx 中
Global.CachingMultiVersion=this.tbSessionID.Text +
this.tbAccountNumber.Text;
本质上,我们需要正确地变化 `CachingMultiVersion` 自定义字符串,以便 ASP.NET 给予我们一个使用 `FindControl` 方法的机会。这似乎是正确实现多版本用户控件缓存的唯一方法。
移除用户控件的缓存内容
现在我们知道如何为每个“账户号”进行用户控件的多版本缓存。接下来,让我们看看当另一个用户控件 `Processing.ascx` 以匹配的“账户号”加载时,如何移除已缓存的用户控件 History.ascx。答案在于 `CacheDependency` 类。事实上,ASP.NET 会生成 `StaticPartialCachingControl` 或 `PartialCachingControl`,它们都继承自 `BasePartialCachingControl`。因此,我们可以使用 `BasePartialCachingControl` 的 `Dependency` 属性与已缓存的项建立依赖关系。
pcc= Parent as PartialCachingControl;
spcc= Parent as StaticPartialCachingControl;
if (pcc !=null) pcc.Dependency=new CacheDependency(null,
new string[]{CachingKey});
if (spcc !=null) spcc.Dependency=new CacheDependency(null,
new string[]{CachingKey});
代码实际上位于 Base.ascx.cs 中,所有用户控件都将从中派生。因此,所有已缓存的用户控件都可以依赖于某些已缓存的项。因此,Process.ascx 只需包含以下代码即可移除相关已缓存项,从而移除已缓存的用户控件 History.ascx。
HttpRuntime.Cache.Remove(CachingKey);
最重要的是,我们需要通过关联 History.ascx 和 Processing.ascx 中的监控参数“`AccountNumber`”来构建 `CachingKey`,以便只移除匹配的缓存。以下是应用 `CorrelatedCaching` 属性以正确构建 `CachingKey` 的代码:
History.ascx.cs 中的代码
[PartialCaching(60)]
[CorrelatedCaching(CorrelatedCachingActionOption.Caching,"CorrCachingName1")]
public class History : JQD.BaseUserControl
{
......
string _AccountNumber;
[CorrelatedCaching(CorrelatedCachingActionOption.Monitoring,
"AccountNumber")]
protected System.Web.UI.WebControls.TextBox tbAccountNumber;
public string AccountNumber;
{
get { return _AccountNumber;}
set {
this.tbAccountNumber.Text=value;
_AccountNumber=value;
}
}
Processing.ascx 中的代码
[CorrelatedCaching(CorrelatedCachingActionOption.Monitoring,"AccountNumber")]
protected string _Prop1;
public string Prop1
{
get { return _Prop1;}
set{ _Prop1=value;}
}
理解应用属性就像将初始化数据传递给类一样非常重要。该类必须实现一个特殊函数来接收来自属性的数据。我实现了一个名为 `RegisterForCorrelatedCaching` 的函数来完成这项工作,以下是一些代码片段:
object[] obj =
this.GetType().GetCustomAttributes(typeof(CorrelatedCachingAttribute), true);
CorrelatedCachingAttribute corrCachingAttr = (CorrelatedCachingAttribute) obj[0];
MonitoringString="";
BuildMonitoringString();
CachingKey=corrCachingAttr.Name + MonitoringString;
private void BuildMonitoringString()
{
Type t = this.GetType();
FieldInfo[] allFields= t.GetFields( ...);
foreach (FieldInfo fi in allFields)
{
CorrelatedCachingAttribute[] attr =
(CorrelatedCachingAttribute[])
fi.GetCustomAttributes(typeof(CorrelatedCachingAttribute),true);
if (attr[0].Action ==CorrelatedCachingActionOption.Monitoring)
MonitoringString += attr[0].Name +"="+fi.GetValue(this).ToString()+";";
}
}
我们在这里所做的是遍历所有用户控件属性,并选择那些应用了 `CorreletedCaching` 属性且 `CorrelatedCachingActionOption` 为 `Monitoring` 的属性。然后,我们将使用反射获取这些属性的值,并构建 `MonitorString`。`CachingKey` 将是 `MonitorString` 和关联名称的连接,其中关联名称存在于应用于类 `History` 和类 `Processing` 的属性中。这使得用户控件可以通过几乎相同的 `CachingKey` 来实现关联。
如何运行示例项目
- 下载 zip 文件并将其解压到 C:\inetpub\wwwroot。
- 单击解决方案文件在 VS.NET 2003 中将其打开,然后构建解决方案。请注意,我们可能需要使用 IIS MMC 将目录 C:\inetpub\wwwroot\TestCorrelatedCaching 配置为应用程序,以便在调试模式下运行。
- 在调试模式下运行,您应该会看到以下屏幕:
- 设置 SessionID 和 AccountNumber,然后单击“CachingMultiVersion”,然后单击“Submit from page”。您应该会看到 `History` 用户控件的时间戳变化一次,然后保持不变。
- 现在更改 AccountNumber 并设置用于检索内容的 AccountNumber,然后单击“CachingMultiVersion”,然后单击“Submit from page”。您应该会看到时间戳变化一次,并且 AccountNumber 出现在 `History` 用户控件中,然后保持不变。
- 最后,在用于缓存移除的 Account Number 中输入匹配的 AccountNumber,然后单击“Submit from page”。您将看到时间戳不断变化,这表明 `History` 用户控件不再被缓存。但是,如果您输入不匹配的 AccountNumber,时间戳将不会改变。
结论
如果您熟悉使用属性和反射,那么相关缓存实际上是一个非常简单的概念。我打算在我的当前项目中使用它,希望您也会觉得它很有用。