【发布时间】:2012-03-27 13:26:11
【问题描述】:
我需要缓存自定义 WebControls 的生成内容。由于控制集合层次结构的构建非常昂贵,因此数据库结果的简单缓存是不够的。缓存整个页面是不可行的,因为页面内部还有其他动态部分。
我的问题:是否有解决此问题的最佳实践方法?我发现很多缓存整个页面或静态用户控件的解决方案,但没有适合我的。我最终得到了自己的解决方案,但我很怀疑这是否可行。
应缓存的自定义 WebControl 可能如下所示:
public class ReportControl : WebControl
{
public string ReportViewModel { get; set; }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Fake expensive control hierarchy build up
System.Threading.Thread.Sleep(10000);
this.Controls.Add(new LiteralControl(ReportViewModel));
}
}
包含内容控件的 aspx 页面可能如下所示:
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// Fake authenticated UserID
int userID = 1;
// Parse ReportID
int reportID = int.Parse(Request.QueryString["ReportID"]);
// Validate if current user is allowed to view report
if (!UserCanAccessReport(userID, reportID))
{
form1.Controls.Add(new LiteralControl("You're not allowed to view this report."));
return;
}
// Get ReportContent from Repository
string reportContent = GetReport(reportID);
// This controls needs to be cached
form1.Controls.Add(new ReportControl() { ReportViewModel = reportContent });
}
private bool UserCanAccessReport(int userID, int reportID)
{
return true;
}
protected string GetReport(int reportID)
{
return "This is Report #" + reportID;
}
}
我最终编写了两个包装器控件,一个用于捕获生成的 html,另一个用于缓存内容 - 用于简单缓存功能的代码非常多(见下文)。
用于捕获输出的包装器控件会覆盖函数 Render,如下所示:
public class CaptureOutputControlWrapper : Control
{
public event EventHandler OutputGenerated = (sender, e) => { };
public string CapturedOutput { get; set; }
public Control ControlToWrap { get; set; }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.Controls.Add(ControlToWrap);
}
protected override void Render(HtmlTextWriter writer)
{
StringWriter stringWriter = new StringWriter();
HtmlTextWriter htmlTextWriter = new HtmlTextWriter(stringWriter);
base.RenderChildren(htmlTextWriter);
CapturedOutput = stringWriter.ToString();
OutputGenerated(this, EventArgs.Empty);
writer.Write(CapturedOutput);
}
}
用于缓存此生成的输出的包装控件如下所示:
public class CachingControlWrapper : WebControl
{
public CreateControlDelegate CreateControl;
public string CachingKey { get; set; }
public delegate Control CreateControlDelegate();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
string content = HttpRuntime.Cache.Get(CachingKey) as string;
if (content != null)
{
// Content is cached, display
this.Controls.Add(new LiteralControl(content));
}
else
{
// Content is not cached, create specified content control and store output in cache
CaptureOutputControlWrapper wrapper = new CaptureOutputControlWrapper();
wrapper.ControlToWrap = CreateControl();
wrapper.OutputGenerated += new EventHandler(WrapperOutputGenerated);
this.Controls.Add(wrapper);
}
}
protected void WrapperOutputGenerated(object sender, EventArgs e)
{
CaptureOutputControlWrapper wrapper = (CaptureOutputControlWrapper)sender;
HttpRuntime.Cache.Insert(CachingKey, wrapper.CapturedOutput);
}
}
在我的 aspx 页面中我替换了
// This controls needs to be cached
form1.Controls.Add(new ReportControl() { ReportViewModel = reportContent });
与
CachingControlWrapper cachingControlWrapper = new CachingControlWrapper();
// CachingKey - Each Report must be cached independently
cachingControlWrapper.CachingKey = "ReportControl_" + reportID;
// Create Control Delegate - Control to cache, generated only if control does not exist in cache
cachingControlWrapper.CreateControl = () => { return new ReportControl() { ReportViewModel = reportContent }; };
form1.Controls.Add(cachingControlWrapper);
【问题讨论】:
-
设置页面指令。
<%@ OutputCache Duration="30000" VaryByParam="ReportID" %>根据reportID 或<%@ OutputCache Duration="30000" VaryByParam="*" %>用于所有查询字符串参数 -
我无法缓存整个页面,如上所述。
标签: asp.net caching web-controls