Pull to refresh

Альтернативный способ кэширования UserControl`ов в Asp.net

Reading time4 min
Views1.1K
Думаю все, кто использует Asp.net для разработки web-сайтов, прекрасно знают, что в Asp.net имеет встроенное кеширование UserControl`ов.
Любой пользовательский элемент может быть закеширован на определённое время в зависимости от различных условий. такой кэш работает крайне быстро и в большинстве случаев этого вполне достаточно, однако в проектах, в которых я принимаю участие, этого оказалось мало.
Основные недостатки заключались в следующем:
  • Кэш может зависеть не только от параметров строки запроса, но и от каких-то других параметров, например от файлов-cookie или типа пользователя (например, в интернет-магазине это может быть физическое или юридическое лицо)
  • Весь кэш не должен пропадать при перекомпиляции проекта (это обычно происходит при изменении файла конфигурации web.config)
  • При необходимости нужно иметь возможность очистить весь кэш

Изначально мы периодически генерировали кэш для всего сайта, но со временем это стало очень большой проблемой из-за усложнения контролов, поэтому я решил поискать другое решение.

Суть его заключается в том, чтобы создать контрол, который бы сам знал, как себя кэшировать.
Для этого берем класс UserControl и создаем производный от него класс CacheUserControl:
public abstract class CacheUserControl : UserControl
{
  public abstract string GetCachePath();
  public abstract TimeSpan GetCacheTime();

  public bool IsCache { get; protected set; }

  protected string cacheText;
  protected string cachePath;
  protected sealed override void OnInit(EventArgs e)
  {
    ...
  }
  public sealed override void RenderControl(HtmlTextWriter htmlTextWriter)
  {
    ...
  }
}



Как видно, тут имеются два абстрактных метода.
Метод GetCachePath возвращает путь к файлу с кешем. Этот путь должен отражать все зависимости контрола, чтобы тот смог выбрать правильный файл.
Метод GetCacheTime возвращает интервал времени, через который кэш будет считаться устаревшим и будет обновлён при необходимости.

Далее необходимо переопределить два метода класса UserControl:

Метод OnInit должен проверять существует ли файл с кэшем, и если это так, то элемент управления должен удалять все свои дочерние контролы, чтобы в них не вызывались никакие их события и загружать кэш из файла:

protected sealed override void OnInit(EventArgs e)
{
  try
  {
    cachePath = GetCachePath();
    if (cachePath != null)
    {
      var cacheFile = new FileInfo(cachePath);
      if (cacheFile.Exists)
      {
        var cacheTime = GetCacheTime();

        var cacheTimeExpired = cacheFile.CreationTime + cacheTime < DateTime.Now;
        if (!cacheTimeExpired)
        {
          using (var reader = cacheFile.OpenText())
          {
            cacheText = reader.ReadToEnd();
            IsCache = true;
            Controls.Clear();
          }
        }
      }
    }
  }
  catch (IOException)
  {
  }

  base.OnInit(e);
}


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

public sealed override void RenderControl(HtmlTextWriter htmlTextWriter)
{
  if(IsCache)
  {
    htmlTextWriter.Write(cacheText);
  }
  else
  {
    base.RenderControl(htmlTextWriter);

    try
    {
      if (cachePath != null)
      {
        using (var streamWriter = new StreamWriter(cachePath, false, Encoding.UTF8))
        {
          using (htmlTextWriter = new HtmlTextWriter(streamWriter))
          {
            base.RenderControl(htmlTextWriter);
          }
        }
      }
    }
    catch (IOException)
    {
    }
  }
}


В итоге, для использования самокэширующегося контрола необходимо изменить базовый класс с UserControl на CacheUserConrtol и дополнительно определить два метода, например так:

public override string GetCachePath()
{
  var id = Request.QueryString["id"];
  if(string.IsNullOrEmpty(id)) return null;

  var currency = "rur";
  var cookie = Request.Cookies["currency"];
  if(cookie != null)
  {
    var value = cookie.Value;
    if (value == "usd") currency = "usd";
    else if (value == "euro") currency = "euro";
  }

  var path = string.Format("~/cache/cat_{0}_cur_{1}.html", id, currency);
  return Server.MapPath(path);
}
public override TimeSpan GetCacheTime()
{
  return TimeSpan.FromDays(7);
}


Теперь контрол сам будет создавать отдельные файлы с кэшем для различных категорий (определяется по id) для каждой валюты каталога (определяется из cookie)

Единственный минус данного подхода в том, что со временем в кэше будет накапливаться мусор, поэтому его периодически придётся полностью очищать. Также следует отметить, метод Page_Load такого контрола вызывается в любом случае, а вот события дочерних контролов только в случае, если содержимое берётся не из файла.

Ну вот собственно и всё.

Tags:
Hubs:
Total votes 29: ↑20 and ↓9+11
Comments19

Articles