5

I've got a user control that should use caching, with VaryByControl. The .ascx file looks like this :

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="TestControl.ascx.cs" Inherits="mynamespace.TestControl" %>
<%@ OutputCache Duration="10" Shared="true" VaryByControl="Test" %>
<p id="SomeText" runat="server">Nothing</p>

The TestControl class in the code-behind file has a int Test {...} property and an Page_Load() event handler that fills the SomeText paragraph with:

SomeText.InnerText = string.Format(@"Test={0} at {1}", Test, DateTime.Now)

I've got a .aspx file that looks like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestPage.aspx.cs" Inherits="mynamespace.TestPage" %>
<%@ Register TagPrefix="xxx" TagName="TestControl" Src="Controls\TestControl.ascx" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <xxx:TestControl Test="6" runat="server" />
    <xxx:TestControl Test="7" runat="server" />
    <hr />
    <asp:PlaceHolder ID="Suport" runat="server" />
</body>
</html>

The two <xxx:TestControl> tags properly load instances of TestControl with Test set to the expected value, I can refresh the browser a few times and I can see the cache properly doing it's job.

Now I'd like to fill the <asp:PlaceHolder ID="Suport" /> with some instances of TestControl, using varying Test values, that should all benefit from proper caching. I'm trying to use the LoadControl method, but I can't find a way to specify a value for the Test property. I expect such a method to exist, after all asp.net code loading the .aspx page manages to find the proper cached control. All I get is an instance of PartialCachingControl without CachedControl initialized and at runtime the rendered TestControl shows Test has the default value of 0.

This is how my .aspx Page_Load() event handler looks like:

protected void Page_Load(object sender, EventArgs e)
{
    PartialCachingControl tc = (PartialCachingControl) LoadControl(@"Controls\TestControl.ascx");
    if (tc.CachedControl != null)
        ((TestControl)tc.CachedControl).Test = 67;            
    Suport.Controls.Add(tc);
}

Edit

I could work around the problem by caching the whole page, but it just seems odd that I can't find a way to do it this way. Especially since invoking the control through the ASPX file works as expected (proving there's a way).

Edit 2

Hmm, no answers so far. I started a bounty, hopefully it gets a bit more attention.

Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104

3 Answers3

3

You have to swap 2 lines in order to make your code work :

PartialCachingControl tc = (PartialCachingControl) LoadControl(@"Controls\TestControl.ascx");
Suport.Controls.Add(tc);
if (tc.CachedControl != null)
    ((TestControl)tc.CachedControl).Test = 67;            

As soon as you add the control, the cached control is initialized.

E.G.

emmguyot
  • 51
  • 3
  • This was the piece of information I was missing, controls must be added before the cachedcontrol object is available. – Tod Mar 16 '15 at 14:25
3

To get a control participate in the full page life cycle it shall be added in the Init event or the CreateChildControls method rather than to add it on Load. Since VaryByControl needs fully qualified control identifiers to work it must be initialized before the page cycle begins.

Something similar to this:

protected override void OnInit(EventArgs e) {
    var testControl = LoadControl(@"TestControl.ascx");
    testControl.ID =  "TestControl";
    Suport.Controls.Add(testControl);
    base.OnInit(e);
}

protected override void OnLoad(EventArgs e) {
    TestControl testControl = GetTestControl("TestControl");
    if(testControl != null){ //If it is null it is cached and can not be changed
        testControl.Test = 242;
    }
    base.OnLoad(e);
}

private TestControl GetTestControl(string name) {
    var control = this.Suport.FindControl(name);
    var partialCachedControl = control as PartialCachingControl;
    if(partialCachedControl != null) {
        control = partialCachedControl.CachedControl;
    }
    return control as TestControl;
}

Since the output is cached per control you can not change the control until the cache is cleared. If you want to change the value and regenerate the content you either have to get the cache cleared or create a new control (with a new ID). One way to clear the cache is to use VaryByCustom instead and generates a cache key that changes if your Test-value is changing.

Also remember to implement INamingContainer interface on your test-control to avoid naming conflicts between the different objects. To do this, just add the interface to the control, like this:

public class TestControl: WebControl, INamingContainer {}
Martin Odhelius
  • 990
  • 6
  • 15
  • **Doesn't work:** if `TestControl.ascx` enables caching (using `@OutputCache`) then `LoadControl` returns a `System.Web.UI.PartialCachingControl` object, so the cast to `TestControl` fails, raising **InvalidCastException**. As mentioned in my #1 edit, two days ago, I do *not* want to cache the whole page, if that's what you mean by "let the framework take care of the caching". – Cosmin Prund Jul 14 '11 at 09:38
  • I actually created my own test page here now and is doesn't work. I was obviously remembered things wrong. Sorry. I have edited my post now, that code works for me. – Martin Odhelius Jul 14 '11 at 10:07
  • This still can't possibly work because `OnLoad` is called *before* `CreateChildControls`. Revoked the downvote because I think you're on to something. – Cosmin Prund Jul 14 '11 at 10:11
  • Hmm, are you sure? For me CreateChildControl is called before OnLoad. But if I move the code to OnInit it also works. – Martin Odhelius Jul 14 '11 at 10:24
  • Changed the code now to use oninit instead. Try that one out. If I understand the question right that shall work because it does so for me ;) – Martin Odhelius Jul 14 '11 at 10:25
  • By the way, in your example you have a very low Duration, try to set that to a higher value while testing to avoid that the object is dropped from the cache while in debug mode. If the testControl is null in OnLoad it is cahced, but still get the correct value (242 in my example ) – Martin Odhelius Jul 14 '11 at 10:33
  • This is still flowed. The use cached / create new control decision is made *before* the `testControl.Test` property is set, so it can't possibly take it's value into account. Try it: Send the `Test` value as a query parameter, and try varying the `Test` value within the caching timeout period. It'll take the *first* value you send, then ignore all future values until the timeout expires. – Cosmin Prund Jul 14 '11 at 10:52
  • The duration is *not* small, it's 10 seconds: I can refresh the page a few times to see the caching works, yet it's short enough to also see what happens when it expires. – Cosmin Prund Jul 14 '11 at 10:54
  • "It'll take the first value you send, then ignore all future values until the timeout expires." Yes, wasn't that the thing you wanted to achieve, to cache the control? I maybe misunderstood something here, but as long as the controled is output cached you can not change anything in it because it is only the generated output that is cached. If you want to change the values when a querystring is set you shall use VaryByParam instead. Even a cached control must be added to the control tree though so you still have to load it and add it if the output cache shall be loded into it – Martin Odhelius Jul 14 '11 at 11:29
  • If you want to cache an object rather than the output you shall not use output caching but object caching. Because if you want to change any values you still have to regenerate the controls content and then you can not use output caching. – Martin Odhelius Jul 14 '11 at 11:34
  • As mentioned in the question title, the question body and the sample `.ASCX` file, my control uses `VarByControl="Test"`; That is, it's caching should be controlled by the value of the `Test` property. `VarByParam` is for forms, not for controls. If you think I didn't express the question clear enough, please point me to whatever confused you so I can edit and clarify. – Cosmin Prund Jul 14 '11 at 11:47
  • The idea is, I should have *cached* copies of the control for varying `Test` values. The sample ASPX file I provided shows this in action: The two `TestControl` instances created with `Test=6` and `Test=7` are properly cached, I can see the numbers in the page and I can see the time is not changing when I refresh the page (proving the controls are loaded from cache). I try to do the same thing for dynamically created controls, for varying values of `Test`. – Cosmin Prund Jul 14 '11 at 11:49
  • 1
    Ah, I think you have misunderstood the VarByControl-property, it does not tell the cache to change upon a property on the control, but on the ID of controls on the page. Here is the text from MSDN: "The VaryByControl property is set to fully qualified control identifiers, where the identifier is a concatenation of control IDs starting from the top-level parent control and delimited with a dollar sign ($) character.". – Martin Odhelius Jul 14 '11 at 11:55
  • The way I understood the question was how to set properties on a cachable control when adding controls dynamically. But now I see that you probably mean how you want different cached controls for different values of a property, am I right now? ;) The only way to achieve that what I can think of is maybe to have different ID:s for different values. – Martin Odhelius Jul 14 '11 at 12:01
  • In your case you can maybe set VaryByCustom instead of VaryByControl and generate a cache key from the Test-property-value and vary it if it changes. – Martin Odhelius Jul 14 '11 at 12:14
  • Unfortunately this is it: `"I think you have misunderstood the VarByControl-property"`. Oh well, did learn quite a few things about asp.net caching those days; Thanks for the help! – Cosmin Prund Jul 14 '11 at 12:23
  • Unfortunately I can only award the bounty in 18 hours: you'll have it tomorrow. – Cosmin Prund Jul 14 '11 at 12:25
  • That explains my initial confusing answers as well :) Anyway, I hope you can solve your problem with VaryByCustom instead. Best regards! – Martin Odhelius Jul 14 '11 at 12:25
  • Oh boy, your answer turned "community wiki" because of the many edits. Please add an other answer simply stating "you misunderstood what VarByControl does, maybe you can use VarByCustom". I'll accept that so you get your rep! – Cosmin Prund Jul 14 '11 at 12:28
  • Oki, I'm pretty new to stack overflow so I do not know what the differences is, but I have done that now :) – Martin Odhelius Jul 14 '11 at 12:38
2

I think you have misunderstood the VarByControl-property, it does not tell the cache to change upon a property on the control, but on the ID of controls on the page. Here is the text from MSDN:

The VaryByControl property is set to fully qualified control identifiers, where the identifier is a concatenation of control IDs starting from the top-level parent control and delimited with a dollar sign ($) character.

In your case you can maybe set VaryByCustom instead of VaryByControl and generate a cache key from the Test-property-value and vary it if it changes.

Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
Martin Odhelius
  • 990
  • 6
  • 15