CachePanel的构建与使用 其实CachePanel很简单,相信已经有一些朋友能够想象出这个组件的大致逻辑了。根据老赵的习惯,我们还是使用“用例驱动开发”的方式来进行组件开发。例如老赵期望的使用方式是这样的:
<jeffz:CachePanel runat="server"
Duration="00:15:00"
CopyCount="10"
CacheKey="RandomUsers"
ResolveCacheKey="CachePanel_ResolveCacheKey"
>
<jeffz:RandomUsers runat="server" UserIDs="1, 2, 3, 4, 5" Count="3" />
</asp:CachePanel>
以下是CachePanel的各种成员描述与代码:
public class CachePanel : Control
{
public TimeSpan Duration { get; set; }
public int CopyCount { get; set; }
public string CacheKey { get; set; }
public EventHandler ResolveCacheKey { get; set; }
...
}
Duration属性:每份缓存副本的有效时间长度。这里使用TimeSpan的字符串表示法,老赵认为相较传统的秒数,这样能够更直接地设定和读取一段时间长度。
CopyCount属性:每个缓存版本的副本数量,输出时将任意选择一个副本输出。在上例中,我们通过生成10个副本让用户看起来的确是在输出随机的结果,而其实我们只是缓存了10个副本而已。
CacheKey属性:不同的CacheKey决定了不同的缓存版本。请注意这个CacheKey是全局的,因此不同页面中的CachePanel如果CacheKey相同,将会得到相同的缓存结果(排除CopyCount属性的影响)。
ResolveCacheKey事件:提供了一个动态指定缓存版本的可能。开发人员可以响应该事件,根据上下文环境的不同(例如QueryString,Form或Header的不同)对CacheKey进行改变。
可以看到,其实只是这简单的四个成员就能满足上文的要求(而且事实上,在理论上CopyCount也能够省略,因为我们有ResolveCacheKey事件,不是吗?)。
至于与缓存相关的具体逻辑,其实非常简单。首先是在OnInit事件中检查是否命中缓存:
- 执行ResolveCacheKey事件以确认CacheKey。
- 随机选取副本编号。
- 根据CacheKey和副本编号确认被缓存的数据所使用的key(如果CacheKey为空,则使用默认的CacheKey,它保证了同一页面中的位置相同的CachePanel实例共享缓存版本)。
- 如果缓存命中,则清除CachePanel内的所有子控件。
代码如下:
public class CachePanel : Control
{
...
private static Random s_random = new Random(DateTime.Now.Millisecond);
public bool CacheHit { get; private set; }
private string m_cacheKey;
private string m_cachedContent;
protected override void OnInit(EventArgs e)
{
var resolveCacheKey = this.ResolveCacheKey;
if (resolveCacheKey != null)
{
resolveCacheKey(this, EventArgs.Empty);
}
int copyIndex = s_random.Next(this.CopyCount);
this.m_cacheKey = this.GetCacheKey(copyIndex);
this.m_cachedContent = this.Context.Cache.Get(this.m_cacheKey) as string;
this.CacheHit = (this.m_cachedContent != null);
if (this.CacheHit) this.Controls.Clear();
base.OnInit(e);
}
private string GetCacheKey(int copyIndex)
{
var cacheKeyBase = this.CacheKey ?? this.GetDefaultCacheKeyBase();
return "$CachePanel$" + cacheKeyBase + "_" + copyIndex;
}
private string GetDefaultCacheKeyBase()
{
return this.Context.Request.AppRelativeCurrentExecutionFilePath + "_" + this.UniqueID;
}
...
}
由于内容被清空,然后到了生成内容阶段,事情就好办了——简单的缓存子控件生成的HTML内容即可:
public partial class CachePanel : Control
{
...
protected override void RenderChildren(HtmlTextWriter writer)
{
if (this.m_cachedContent == null)
{
StringBuilder sb = new StringBuilder();
HtmlTextWriter innerWriter = new HtmlTextWriter(new StringWriter(sb));
base.RenderChildren(innerWriter);
this.m_cachedContent = sb.ToString();
this.Context.Cache.Insert(this.m_cacheKey, this.m_cachedContent, null,
DateTime.Now.Add(this.Duration), Cache.NoSlidingExpiration);
}
writer.Write(this.m_cachedContent);
}
}
至此,CachePanel就制作完成了,其实只是短短的几十行代码而已。到这里老赵不禁又要发一句感慨:只要了解了框架的运行规则,开发出各种扩展又有多少难度呢?一切都只是看您有多少想象力而已。
不过大家在使用CachePane时可能还需要注意以下几点:
- CacheKey的作用域是整个ASP.NET应用程序,因此如果您要指定CacheKey的话,请给出清晰而明确的值。
- CachePanel能够缓存页面中任意部分的内容,不过在使用时可能就需要您根据CacheHit属性的值来判断是否需要为控件填充数据,否则可能就会无法达到缓存的目的。
- CachePanel将会在缓存命中时清空所有子控件,因此在操作时也请注意这一点,以免出现不可预知(Unpredictable)的结果。