Как загрузить элемент управления, который использует VaryByControl OutputCache, указав значения свойств

У меня есть пользовательский элемент управления, который должен использовать кэширование с VaryByControl. Файл .ascx выглядит так:

<%@ 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>

Класс TestControl в файле кода программной части имеет свойство int Test {...} и обработчик событий Page_Load(), который заполняет абзац SomeText:

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

У меня есть файл .aspx, который выглядит так:

<%@ 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>

Два тега <xxx:TestControl> правильно загружают экземпляры TestControl с Test, установленным на ожидаемое значение, я могу обновить браузер несколько раз и вижу, что кеш правильно выполняет свою работу.

Теперь я хотел бы заполнить <asp:PlaceHolder ID="Suport" /> некоторыми экземплярами TestControl, используя различные значения Test, которые должны выиграть от правильного кэширования. Я пытаюсь использовать метод LoadControl, но не могу найти способ указать значение свойства Test. Я ожидаю, что такой метод существует, ведь asp.net коду, загружающему .aspx страницу, удается найти правильный кэшированный элемент управления. Все, что я получаю, это экземпляр PartialCachingControl без инициализированного CachedControl, и во время выполнения визуализированный TestControl показывает, что Test имеет значение по умолчанию 0.

Вот как выглядит мой обработчик событий .aspx Page_Load():

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);
}

Редактировать

Я мог бы обойти эту проблему, закэшировав всю страницу, но мне кажется странным, что я не могу найти способ сделать это таким образом. Тем более, что вызов элемента управления через файл ASPX работает, как и ожидалось (доказывая, что есть способ).

Редактировать 2

Хм, ответов пока нет. Я начал щедрость, надеюсь, она получит немного больше внимания.


person Cosmin Prund    schedule 04.07.2011    source источник


Ответы (3)


Я думаю, вы неправильно поняли свойство VarByControl, оно сообщает кешу об изменении не свойства элемента управления, а идентификатора элементов управления на странице. Вот текст из MSDN:

Для свойства VaryByControl заданы полные идентификаторы элементов управления, где идентификатор представляет собой объединение идентификаторов элементов управления, начиная с родительского элемента управления верхнего уровня и разделенных символом доллара ($).

В вашем случае вы можете установить VaryByCustom вместо VaryByControl и сгенерировать ключ кэша из значения свойства Test и изменить его, если оно изменится.

person Martin Odhelius    schedule 14.07.2011

Чтобы элемент управления участвовал в полном жизненном цикле страницы, его следует добавить в событие Init или метод CreateChildControls, а не добавлять его при загрузке. Поскольку для работы VaryByControl требуются полные идентификаторы элементов управления, он должен быть инициализирован до начала страничного цикла.

Что-то похожее на это:

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;
}

Поскольку вывод кэшируется для каждого элемента управления, вы не можете изменить элемент управления, пока кеш не будет очищен. Если вы хотите изменить значение и восстановить содержимое, вам нужно либо очистить кеш, либо создать новый элемент управления (с новым идентификатором). Один из способов очистить кеш — использовать вместо этого VaryByCustom и сгенерировать ключ кеша, который изменяется, если меняется ваше тестовое значение.

Также не забудьте реализовать интерфейс INamingContainer в вашем тестовом элементе управления, чтобы избежать конфликтов имен между различными объектами. Для этого просто добавьте интерфейс к элементу управления, например:

public class TestControl: WebControl, INamingContainer {}
person Community    schedule 14.07.2011
comment
Не работает: если TestControl.ascx включает кэширование (используя @OutputCache), то LoadControl возвращает объект System.Web.UI.PartialCachingControl, поэтому приведение к TestControl завершается ошибкой, вызывая InvalidCastException. Как упоминалось в моем редактировании № 1 два дня назад, я не хочу кэшировать всю страницу, если это то, что вы имеете в виду, говоря, что фреймворк позаботится о кэшировании. - person Cosmin Prund; 14.07.2011
comment
На самом деле я создал здесь свою собственную тестовую страницу, и она не работает. Я явно что-то неправильно запомнил. Прости. Я отредактировал свой пост сейчас, этот код работает для меня. - person Martin Odhelius; 14.07.2011
comment
Это по-прежнему не может работать, потому что OnLoad вызывается до CreateChildControls. Отменил отрицательный голос, потому что я думаю, что вы что-то делаете. - person Cosmin Prund; 14.07.2011
comment
Хм, ты уверен? Для меня CreateChildControl вызывается перед OnLoad. Но если я перенесу код в OnInit, он тоже сработает. - person Martin Odhelius; 14.07.2011
comment
Изменен код теперь, чтобы вместо этого использовать oninit. Попробуйте это. Если я правильно понимаю вопрос, это сработает, потому что это работает для меня;) - person Martin Odhelius; 14.07.2011
comment
Кстати, в вашем примере у вас очень низкая длительность, попробуйте установить для нее более высокое значение во время тестирования, чтобы избежать удаления объекта из кеша в режиме отладки. Если testControl имеет значение null в OnLoad, он кэшируется, но все равно получает правильное значение (242 в моем примере). - person Martin Odhelius; 14.07.2011
comment
Это до сих пор течет. Решение об использовании кэша/создании нового элемента управления принимается до установки свойства testControl.Test, поэтому оно не может учитывать его значение. Попробуйте: отправьте значение Test в качестве параметра запроса и попробуйте изменить значение Test в течение времени ожидания кэширования. Он примет первое значение, которое вы отправляете, а затем проигнорирует все будущие значения, пока не истечет время ожидания. - person Cosmin Prund; 14.07.2011
comment
Продолжительность не маленькая, она составляет 10 секунд: я могу обновить страницу несколько раз, чтобы увидеть, как работает кеширование, но этого достаточно, чтобы также увидеть, что произойдет, когда он истечет. - person Cosmin Prund; 14.07.2011
comment
Он примет первое отправленное вами значение, а затем проигнорирует все будущие значения, пока не истечет время ожидания. Да, разве это не то, чего вы хотели добиться, кэшировать элемент управления? Возможно, я что-то неправильно понял, но пока контролируемый вывод кэшируется, вы не можете ничего в нем изменить, потому что кешируется только сгенерированный вывод. Если вы хотите изменить значения при установке строки запроса, вы должны вместо этого использовать VaryByParam. Даже кешированный элемент управления должен быть добавлен в дерево элементов управления, поэтому вам все равно придется загрузить его и добавить, если в него должен быть загружен выходной кеш. - person Martin Odhelius; 14.07.2011
comment
Если вы хотите кэшировать объект, а не вывод, вы должны использовать не кэширование вывода, а кэширование объекта. Потому что, если вы хотите изменить какие-либо значения, вам все равно придется перегенерировать содержимое элементов управления, а затем вы не сможете использовать кэширование вывода. - person Martin Odhelius; 14.07.2011
comment
Как упоминалось в заголовке вопроса, тексте вопроса и образце файла .ASCX, мой элемент управления использует VarByControl="Test"; То есть его кеширование должно управляться значением свойства Test. VarByParam предназначен для форм, а не для элементов управления. Если вы считаете, что я недостаточно ясно изложил вопрос, пожалуйста, укажите мне на то, что вас смутило, чтобы я мог отредактировать и уточнить. - person Cosmin Prund; 14.07.2011
comment
Идея в том, что я должен иметь кэшированные копии элемента управления для различных значений Test. Предоставленный мной пример файла ASPX показывает это в действии: два экземпляра TestControl, созданные с помощью Test=6 и Test=7, правильно кэшированы, я вижу числа на странице и вижу, что время не меняется, когда я обновляю страницу (доказывая, что элементы управления загружаются из кеша). Я пытаюсь сделать то же самое для динамически создаваемых элементов управления для различных значений Test. - person Cosmin Prund; 14.07.2011
comment
Ах, я думаю, вы неправильно поняли свойство VarByControl, оно сообщает кешу изменить не свойство элемента управления, а идентификатор элемента управления на странице. Вот текст из MSDN: для свойства VaryByControl заданы полные идентификаторы элементов управления, где идентификатор представляет собой конкатенацию идентификаторов элементов управления, начиная с родительского элемента управления верхнего уровня и разделенных символом доллара ($). - person Martin Odhelius; 14.07.2011
comment
Как я понял, вопрос заключался в том, как установить свойства кэшируемого элемента управления при динамическом добавлении элементов управления. Но теперь я вижу, что вы, вероятно, имеете в виду, как вам нужны разные кэшированные элементы управления для разных значений свойства, не так ли? ;) Единственный способ добиться того, о чем я могу думать, - это, возможно, иметь разные идентификаторы для разных значений. - person Martin Odhelius; 14.07.2011
comment
В вашем случае вы можете установить VaryByCustom вместо VaryByControl и сгенерировать ключ кэша из значения свойства Test и изменить его, если оно изменится. - person Martin Odhelius; 14.07.2011
comment
К сожалению, вот оно: "I think you have misunderstood the VarByControl-property". Ну что ж, в те дни я узнал немало вещей о кэшировании asp.net; Спасибо за помощь! - person Cosmin Prund; 14.07.2011
comment
К сожалению, я могу присудить награду только через 18 часов: она будет у вас завтра. - person Cosmin Prund; 14.07.2011
comment
Это также объясняет мои первоначальные запутанные ответы :) В любом случае, я надеюсь, что вместо этого вы сможете решить свою проблему с помощью VaryByCustom. С наилучшими пожеланиями! - person Martin Odhelius; 14.07.2011
comment
О боже, ваш ответ превратился в вики сообщества из-за множества правок. Пожалуйста, добавьте другой ответ, просто заявив, что вы неправильно поняли, что делает VarByControl, возможно, вы можете использовать VarByCustom. Я соглашусь с этим, чтобы вы получили репутацию! - person Cosmin Prund; 14.07.2011
comment
Оки, я новичок в переполнении стека, поэтому не знаю, в чем разница, но я сделал это сейчас :) - person Martin Odhelius; 14.07.2011

Вам нужно поменять местами 2 строки, чтобы ваш код работал:

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

Как только вы добавляете элемент управления, кэшированный элемент управления инициализируется.

E.G.

person emmguyot    schedule 31.10.2012
comment
Это была часть информации, которую мне не хватало, элементы управления должны быть добавлены до того, как объект cachedcontrol станет доступен. - person Tod; 16.03.2015