Думаю все, кто использует Asp.net для разработки web-сайтов, прекрасно знают, что в Asp.net имеет встроенное кеширование UserControl`ов.
Любой пользовательский элемент может быть закеширован на определённое время в зависимости от различных условий. такой кэш работает крайне быстро и в большинстве случаев этого вполне достаточно, однако в проектах, в которых я принимаю участие, этого оказалось мало.
Основные недостатки заключались в следующем:
Изначально мы периодически генерировали кэш для всего сайта, но со временем это стало очень большой проблемой из-за усложнения контролов, поэтому я решил поискать другое решение.
Суть его заключается в том, чтобы создать контрол, который бы сам знал, как себя кэшировать.
Для этого берем класс UserControl и создаем производный от него класс CacheUserControl:
Как видно, тут имеются два абстрактных метода.
Метод GetCachePath возвращает путь к файлу с кешем. Этот путь должен отражать все зависимости контрола, чтобы тот смог выбрать правильный файл.
Метод GetCacheTime возвращает интервал времени, через который кэш будет считаться устаревшим и будет обновлён при необходимости.
Далее необходимо переопределить два метода класса UserControl:
Метод OnInit должен проверять существует ли файл с кэшем, и если это так, то элемент управления должен удалять все свои дочерние контролы, чтобы в них не вызывались никакие их события и загружать кэш из файла:
Метод RenderControl должен проверять, загружен ли кэш, и если загружен, то рендерить именно его, а если нет, то рендерить контрол в обычном режиме и дополнительно создавать файл с кэшем:
В итоге, для использования самокэширующегося контрола необходимо изменить базовый класс с UserControl на CacheUserConrtol и дополнительно определить два метода, например так:
Теперь контрол сам будет создавать отдельные файлы с кэшем для различных категорий (определяется по id) для каждой валюты каталога (определяется из cookie)
Единственный минус данного подхода в том, что со временем в кэше будет накапливаться мусор, поэтому его периодически придётся полностью очищать. Также следует отметить, метод Page_Load такого контрола вызывается в любом случае, а вот события дочерних контролов только в случае, если содержимое берётся не из файла.
Ну вот собственно и всё.
Любой пользовательский элемент может быть закеширован на определённое время в зависимости от различных условий. такой кэш работает крайне быстро и в большинстве случаев этого вполне достаточно, однако в проектах, в которых я принимаю участие, этого оказалось мало.
Основные недостатки заключались в следующем:
- Кэш может зависеть не только от параметров строки запроса, но и от каких-то других параметров, например от файлов-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 такого контрола вызывается в любом случае, а вот события дочерних контролов только в случае, если содержимое берётся не из файла.
Ну вот собственно и всё.