在ASP.net中如何使用WebUserControl使输出缓存生效

4

我有一个带有公共int属性SelectedCatIDwebusercontrol。 我在其他页面和其他控件中使用此控件,如下所示:

<NewStore:LeftMenuLinks runat="server" SelectedCatID="<%#CatIDToSelect%>" />

我该如何基于SelectedCatID输出缓存此控件? 我尝试过的所有方法都失败了。 我最接近的一次是成功进行了缓存,但它不会根据SelectedCatID进行变化,导致菜单项保持相同,直到缓存过期。如果没有缓存,则该控件按预期工作。

VaryByControl 属性应该匹配要缓存的用户控件的 ID,而不仅仅是控件的任意属性。如果您在代码后台检查了控件是否存在,并设置了变量供 NewStore:LeftMenuLinks 用户控件使用,那么它能正常工作吗? - Justin
你是否在数据绑定控件中使用了SelectedCatID="<%#SelectedCatID %>"版本,还是只将其绑定到页面类的属性? SelectedCatID和SelectedMenu之间有什么关联?还是这只是打字错误? - Menno van den Heuvel
@menno 打错了,立即修复! - Tom Gullen
我想做的就是根据控件的公共属性输出缓存,但我就是想不出该怎么做。问题没得到太多关注,所以我会编辑一下以使它更清晰明了。 - Tom Gullen
3个回答

3
我找出了你最初使用的VaryByControls方法不起作用的原因。遗憾的是,你在问题中编辑掉了它,所以我的研究只能写入博客文章中。更新:相关博客文章:http://tabeokatech.blogspot.be/2014/09/outputcache-on-user-controls.html
长话短说,VaryByControls有点像VaryByParams的速记方式,对属性没有任何作用:它只查看POST值。它曾经对具有静态值的属性产生作用的事实似乎是一个bug——VaryByControls中的任何字符串都会使该部分起作用。这个问题的被接受答案是错误的:Vary by control properties using PartialCaching in ASP.NET
没有内置的方法可以根据控件属性值进行变化。
无论如何,这是没有意义的,因为用户控件需要被创建才能拥有属性值,并且你希望避免创建它们,而是缓存它们呈现的标记——如果为它们提供了缓存标记,则缓存的用户控件字段在代码后台中为空。 这通过将PartialCachingControl注入页面而不是实际的用户控件来实现。这个PartialCachingControl检查缓存,只有在没有缓存版本的情况下才创建控件。
至于让它起作用,我看到两个选择:
  1. If you only have 1 usercontrol per page, you could use the VaryByCustom approach. To make things easy you could write an interface that returns your property value for that page, and implement it on every page that hosts the user control, e.g.:

    interface INumberProvider
    {
        int GetNumber();
    }
    
    // and the page:
    public partial class _Default : Page, INumberProvider
    {
        public int GetNumber()
        {
            return this.SomeNumberPropertyOrWhatever;
        }
    ...
    

    In your Global.asax you cast the current handler to INumberProvider and get the number:

        public override string GetVaryByCustomString(HttpContext context, string custom)
        {
            if (custom == "INumberProvider")
            {
                var page = context.CurrentHandler as INumberProvider;
    
                if (page != null)
                {
                    return page.GetNumber().ToString();
                }
            }
            return base.GetVaryByCustomString(context, custom);
        }
    

    And in your control you obviously add:

    OutputCache Duration="180" VaryByCustom="INumberProvider" VaryByParam="None" Shared="true"

    That's if you only have one user control per page, and should be pretty straightforward. If you need more than one user control per page you're out of luck:

  2. Build your own wrapper around your user control by writing a custom WebControl. Add the properties you need, capture the output of the rendered user control, and insert it into HttpContext.Current.Cache with a key that includes the SelectedCatID. Basically write your own custom PartialCachingControl. There's also option 3:
  3. Decide caching is not that important after all

如果您每个页面只有一个用户控件,那么这应该是相当简单的。如果您需要每个页面多个用户控件,那么您就不幸了:这是错误的。您只需将指令添加到各个用户控件而不是页面本身即可。缓存单个用户控件是可能的。 - Hillboy
以上内容确实与用户控件的缓存有关。我的意思是:根据Global.asax重写中可用的信息,除非页面上只有一个用户控件,否则无法推断缓存应该为哪个用户控件创建变体的属性值。 - Menno van den Heuvel
非常好的答案,谢谢你,这正是我想要的信息。完成后,请在此处链接到您的博客文章! - Tom Gullen

0
<%@ OutputCache Duration="60" VaryByParam="SelectedCatID" %>

现在将您的<%#CatIDToSelect%>存储为参数,例如?SelectedCatID=12 现在,根据Request.Param["SelectedCatID"]等于什么,您的页面或用户控件(取决于您想要缓存什么)将输出缓存。

您还可以像这样做(虽然不是最简单的方法)

这适用于您想要缓存的页面/用户控件:

<%@ OutputCache duration="120" varybyparam="None" varybycustom="SelectedCatID" %>

这个内容需要放到 Global.asax 文件中:

public override string GetVaryByCustomString(HttpContext context, string custom)
{
    if(custom == "SelectedCatID")
    {
        return CatIDToSelect;
    }
    return String.Empty;
}

看一下这个 pastebin:http://pastebin.com/NLmZkfP9它抛出错误 CS0115: 'Controls_Blog_LatestEntries.GetVaryByCustomString(System.Web.HttpContext, string)': 未找到合适的方法来覆盖。缓存定义为 <%@ OutputCache duration="120" varybyparam="None" varybycustom="SelectedBlogID" %>我从使用谷歌了解到这应该放在 global.asax 中,但这对我没有帮助。我需要一种基于传递给它的属性缓存控件的方法。 - Tom Gullen
好的,我测试了一下,似乎不需要使用([OutputCache(CacheProfile = "CacheProfile")])。并且在刷新页面后可以正常工作。我用一个显示精确时间(包括秒)的页面进行了测试,在第二次刷新后,秒数不再改变,时间将保持不变直到过期时间到达。只需按照我对帖子所做的更改即可。 - Hillboy
以下是我的测试代码,共4个文件,供您查看:http://pastebin.com/6v10bSgg http://pastebin.com/QFNXE7Z1 http://pastebin.com/Jbfuwywf http://pastebin.com/AXC2vT95 - Hillboy

0

我来晚了,因为已经有一个被接受的答案和500点赏金奖励。但我仍想提供我的意见,关于如何实现这个。

它可以在控件本身中工作。您可以让控件将自己的输出存储在缓存中,并在Render方法中使用缓存版本(如果找到)。我制作了一个非常简单的UserControl进行测试。标记看起来像这样:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TestUC.ascx.cs" 
    Inherits="Webforms_Test.UserControls.TestUC" %>
<div>
    <asp:Label ID="curTime" runat="server"></asp:Label>
</div>

它只包含一个标签,当初始化时设置为DateTime.Now。其后的代码如下:

public partial class TestUC : System.Web.UI.UserControl
{
    private string cachedOutput = null;
    public bool RenderFromCache = true; // set to false in containing page if this control needs to be re-rendered

    protected void Page_Load(object sender, EventArgs e)
    {
        cachedOutput = HttpContext.Current.Cache["key"] as string;
        if (cachedOutput == null)
        {
            // not found in cache, do the heavy lifting here to setup the control
            curTime.Text = "UC:" + DateTime.Now.ToString("yy-MM-dd hh:mm:ss");
        }
    }
    protected void Page_PreRender(object sender, EventArgs e)
    {
        if (cachedOutput == null || !RenderFromCache)
        {
            RenderFromCache = false;
            StringBuilder b = new StringBuilder();
            HtmlTextWriter h = new HtmlTextWriter(new StringWriter(b));
            this.RenderControl(h);
            cachedOutput = b.ToString();
            HttpContext.Current.Cache.Insert("key", cachedOutput, null, DateTime.UtcNow.AddSeconds(10), TimeSpan.Zero);
            RenderFromCache = true;
        }
    }
    protected override void Render(HtmlTextWriter writer)
    {
        if (!RenderFromCache)
            base.Render(writer);
        else
            writer.Write(cachedOutput);
    }
}

在这个示例中,控件本身会检查其输出是否在缓存中找到,如果是,则Render方法将只写入缓存的输出。如果在缓存中未找到,则PreRender方法将正常运行Render方法并捕获输出并将其存储在缓存中。
在您的情况下,您当然需要更多的逻辑来检查控件上的相关属性,并使用该属性来检查是否存在缓存版本。
免责声明:这是一个非常简单的测试控件。我没有尝试弄清楚如何使所有这些与包含事件处理程序等控件一起工作。所以请根据实际情况进行理解...

我明白你的意思,但我并不认为它会像包装类一样有效。您的控件仍将构建整个控件树,然后将其全部丢弃并提供缓存的HTML。PartialCachingControl覆盖了InitRecursive方法,但它被标记为内部方法,因此我们无法使用它。也许OnInit方法可以起作用。 - Menno van den Heuvel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接