Частичное кеширование пользовательских WebControls

Мне нужно кэшировать сгенерированный контент пользовательских WebControls. Поскольку построение иерархии коллекции элементов управления очень дорого, простого кэширования результатов базы данных недостаточно. Кэширование всей страницы невозможно, потому что внутри страницы есть другие динамические части.

Мой вопрос: есть ли лучший практический подход к этой проблеме? Я нашел много решений, кэширующих целые страницы или статические UserControls, но ничего подходящего для меня. Я пришел к своему собственному решению, но я весьма сомневаюсь, что это осуществимый подход.

Пользовательский 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);

person Dresel    schedule 27.03.2012    source источник
comment
Установить директиву страницы. <%@ OutputCache Duration="30000" VaryByParam="ReportID" %> согласно reportID или <%@ OutputCache Duration="30000" VaryByParam="*" %> для всех параметров строки запроса   -  person Pankaj    schedule 27.03.2012
comment
Я не могу кэшировать всю страницу, как писали выше.   -  person Dresel    schedule 28.03.2012


Ответы (1)


Вроде хорошая идея, может, стоит обратить внимание на:

  • ClientIdMode дочерних элементов управления вашего настраиваемого элемента управления для предотвращения конфликтов, если эти элементы управления должны отображаться в другом контексте.
  • LiteralMode вашего Literal: он должен быть PassThrough
  • режим истечения срока действия вашего кэшированного элемента (absoluteExpiration / movingExpiration)
  • отключить ViewState вашего CustomControl

В последнее время я склоняюсь к другому подходу: мои элементы управления оболочкой содержат только некоторый javascript, который выполняет запрос AJAX GET на странице, содержащей только мой настраиваемый элемент управления. Кэширование выполняется на стороне клиента с помощью заголовков http и на стороне сервера с помощью директивы OutputCache (если только HTTPS не использует протокол HTTPS, содержимое должно быть общедоступным)

person jbl    schedule 27.03.2012
comment
Хорошие замечания - учту. Итак, вы делаете одну страницу для каждого элемента управления для кеширования - и содержимое страницы вызывается клиентом через JS? - person Dresel; 28.03.2012
comment
Да, при использовании в правильном контексте, он помогает совместно использовать элементы управления (например, элементы навигации) между приложениями, серверами и сторонними сайтами. Это снижает нагрузку на полосу пропускания и накладные расходы на сервер. - person jbl; 28.03.2012
comment
Звучит разумно для ваших требований. Кажется немного случайным, чтобы приспособиться к моей конкретной ситуации. Мне пришлось бы сделать вызовы на стороне сервера и страницу с элементом управления для кеширования, недоступными напрямую от клиента. Если мой UserCanAccessReport истинен, я бы позвонил и мог бы сделать LiteralControl с возвращенным содержимым. Тем не менее, спасибо за ваш вклад. - person Dresel; 30.03.2012