none
Странное поведение при работе с OutputCacheProvider RRS feed

  • Вопрос

  • Использую кастомный OutputCacheProvider

    public class FileCacheProvider : OutputCacheProvider
        {
            
            private const string rootCacheLocation = @"~/CacheView/";
     
            [Serializable]
            public class CacheItem
            {
                public object Item { get; set; }
                public DateTime Expiry { get; set; }
            }
     
            private string CacheLocation
            {
                get
                {                
                    return HttpContext.Current.Server.MapPath(rootCacheLocation) + @"\";
                }
            }
            private string GetFullPathForKey(string key, bool createFolder = false)
            {
                string temp = key.Replace('/', '$');
                var route = System.Web.Routing.RouteTable.Routes.GetRouteData(new HttpContextWrapper(HttpContext.Current));
                System.Web.Routing.RouteValueDictionary routeData = null;
                if (route != null)
                {
                    routeData = route.Values;
                    if (routeData["controller"] == null)
                    {
                        return CacheLocation + temp;
                    }
                }
                else
                {
                    return CacheLocation + temp;
                }
                var _controllerFolder = CacheLocation + routeData["controller"].ToString().ToLower() + @"\";
               
                return _controllerFolder + temp;
            }
            private void CreateCacheFolders(string filePath)
            {
                FileInfo info = new FileInfo(filePath);
                if (!Directory.Exists(info.Directory.FullName))
                {
                    Directory.CreateDirectory(info.Directory.FullName);
                }
           
            }
            public override void Set(string key, object entry, DateTime utcExpiry)
            {
                if (System.Web.HttpContext.Current.Request.HttpMethod == System.Net.Http.HttpMethod.Get.Method)
                {
                    var routeData = ((MvcHandler)System.Web.HttpContext.Current.Handler).RequestContext.RouteData;
                    string filePath = GetFullPathForKey(key);
                    CreateCacheFolders(filePath);
                    CacheItem item = new CacheItem { Expiry = utcExpiry, Item = entry };
                    lock (filePath)
                    {
                        FileStream fileStream = File.OpenWrite(filePath);
                        BinaryFormatter formatter = new BinaryFormatter();
                        formatter.Serialize(fileStream, item);
                        fileStream.Close();
     
                    }
     
                }
            }
     
            public override object Get(string key)
            {
               
                string filePath = GetFullPathForKey(key);
                if (!File.Exists(filePath))
                {
                    return null;
                }
                CacheItem item = null;
                lock (filePath)
                {
                    FileStream fileStream = File.OpenRead(filePath);
                    BinaryFormatter formatter = new BinaryFormatter();
                    item = (CacheItem)formatter.Deserialize(fileStream);
                    fileStream.Close();
                }
                if (item == null || item.Expiry <= DateTime.UtcNow)
                {
                    Remove(key);
                    return null;
                }
                return item.Item;
            }
            public override object Add(string key, object entry, DateTime utcExpiry)
            {
                object obj = this.Get(key);
                if (obj != null)
                {
                    return obj;
                }
                else
                {
                    this.Set(key, entry, utcExpiry);
                    return entry;
                }
            }
            public override void Remove(string key)
            {
                string filePath = GetFullPathForKey(key);
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                }
            }

    Хочу сделать так, что бы кэш сохранялся в файлы.

    Например, я первый раз захожу по URL: http://localhost/MySite/en/News/Details?idn=78
    Вызывается функция Add с key = a2/mysite/en/news/details" и entry пренадлежит System.Web.Caching.CachedVary
    Потом вызывается метод Set, но уже с ключом "a2/mysite/en/news/detailsHQNidnV78FCDE" и entry класса System.Web.Caching.OutputCacheEntry

    После всех махинаций у меня в папке сохраняются два файла. 

    После открытия ссылок 
    http://localhost/MySite/en/News/Details?idn=77

    http://localhost/MySite/en/News/Details?idn=75

    в папке уже сохранилось:
    • a2$mysite$en$news$details
    • a2$mysite$en$news$detailsHQNidnV78FCDE
    • a2$mysite$en$news$detailsHQNidnV77FCDE
    • a2$mysite$en$news$detailsHQNidnV75FCDE

    Я хочу удалить кэш только для url http://localhost/MySite/en/ News/Details?idn=75

    Пытался использовать HttpResponse.RemoveOutputCacheItem

    Вызывал ее из другого контроллера HttpResponse.RemoveOutputCacheItem("/MySite/en/News/Details?idn=75 "); 

    После этого вызывалась функция Remove с ключом "a2/mysite/en/news/details?idn=78"

    Почему ключи разные? И как удалить именно кэш с idn=78?




    • Изменено Niksan_la2 24 марта 2014 г. 12:13
    24 марта 2014 г. 12:11

Ответы

  • Наконец время появилось посмотреть. Всё дело в методе генерации ключей. Метод HttpResponse.RemoveOutputCacheItem попросту не умеет работать со строкой запроса и его параметрами. Если посмотрите, внутри него вызывается метод генерации ключа

    string key = OutputCacheModule.CreateOutputCachedItemKey(
                        path, HttpVerb.GET, null, null);

    с пустыми последними аргументами, именно в последнем должна была быть передана информация об CachedVary cachedVary (в нашем случае это id)

    y него её нет, поэтому ключ генерируется не тот и следовательно поставщик кеша не может удалить этот элемент. В случае же генерации ключа при создании файла, параметры другие:

    и ключ другой. Т.е. получается использовать HttpResponse.RemoveOutputCacheItem для удаления именно этих элементов невозможно. Так как других перегрузок у метода нет, классы генерации ключей закрытые. Можно достучаться до них рефлексией, но это очень трудно из-за параметра CachedVary. Нужно искать другие варианты. Есть пара идей. На досуге попробую реализовать.


    Сделаем содержимое сообщества лучше, вместе!

    28 марта 2014 г. 8:43
    Модератор

Все ответы

  • Сходу трудно вам ответить, особенно если исходный код у вас. Возможно автоматом удаляются неактуальные данные из кеша и в этом момет просто происходит останов отладчика не с теми данными которые вы ожидали.

    Сделаем содержимое сообщества лучше, вместе!

    24 марта 2014 г. 18:57
    Модератор
  • Автоматом ничего не удаляется. По сути нужно просто развернуть приложение, и написать этот код. Могу скинуть тестовое приложение.
    25 марта 2014 г. 8:41
  • Хорошо, скидывайте, вечером после работы посмотрю.

    Сделаем содержимое сообщества лучше, вместе!

    25 марта 2014 г. 9:20
    Модератор
  • http://rghost.ru/53358896 - тут проект

    Сначала заходи по ссылке localhost/Home/Details?id=2

    Потом по ссылке http://localhost/Home/DeleteDetails?id=2

    Бряки ставь не Remove в FileOutputCacheProvider и на экшене DeleteDetails

    И еще вопрос. Получается, что он кэширует когда мы заходим просто по  http://localhost,  http://localhost/Home и  http://localhost/Home/Index в  разные файлы (т.е. генерирует разные ключи, а по сути эта одна и та же страница)


    • Изменено Niksan_la2 26 марта 2014 г. 6:00
    26 марта 2014 г. 6:00
  • Наконец время появилось посмотреть. Всё дело в методе генерации ключей. Метод HttpResponse.RemoveOutputCacheItem попросту не умеет работать со строкой запроса и его параметрами. Если посмотрите, внутри него вызывается метод генерации ключа

    string key = OutputCacheModule.CreateOutputCachedItemKey(
                        path, HttpVerb.GET, null, null);

    с пустыми последними аргументами, именно в последнем должна была быть передана информация об CachedVary cachedVary (в нашем случае это id)

    y него её нет, поэтому ключ генерируется не тот и следовательно поставщик кеша не может удалить этот элемент. В случае же генерации ключа при создании файла, параметры другие:

    и ключ другой. Т.е. получается использовать HttpResponse.RemoveOutputCacheItem для удаления именно этих элементов невозможно. Так как других перегрузок у метода нет, классы генерации ключей закрытые. Можно достучаться до них рефлексией, но это очень трудно из-за параметра CachedVary. Нужно искать другие варианты. Есть пара идей. На досуге попробую реализовать.


    Сделаем содержимое сообщества лучше, вместе!

    28 марта 2014 г. 8:43
    Модератор
  • Спасибо, за проделанную работу!

    Я тоже смотрел, как внутри там все обстоит. 

    И меня удивило вот это:

    string key = OutputCacheModule.CreateOutputCachedItemKey(
                        path, HttpVerb.GET, null, null);

    Из этого я сделал вывод, что с помощью RemoveOutputCacheItem не получится удалить, ибо они null передают. 

    Есть вероятность, что это баг.

    По сути есть альтернативные пути, типа как http://mvcdonutcaching.codeplex.com/, но хочется все равно добить эту реализацию

    Было бы круто, как то самому ключи генерить, но там все internal



    31 марта 2014 г. 12:59
  • "Есть вероятность, что это баг." - нет, скорее всего это так и было задумано. Просто на практике, чаще всего кеш реализуется на уровне страниц.

    "Было бы круто, как то самому ключи генерить, но там все internal" - возможен такой вариант, но нужно будет проделать много работы.


    Сделаем содержимое сообщества лучше, вместе!

    31 марта 2014 г. 13:13
    Модератор
  • А какие пару идей у тебя есть?
    1 апреля 2014 г. 12:38
  • http://www.msdr.ru/55/ исходник .NET открыт, есть возможность "самому ключи генерить"
    1 апреля 2014 г. 14:02
  • Фишка в том, что там практически все internal. По сути придётся копипастить почти всю реализацию. Что не есть круто.


    • Изменено Niksan_la2 2 апреля 2014 г. 6:23
    2 апреля 2014 г. 6:22
  • Тред умер? (
    21 апреля 2014 г. 10:27
  • Нет почему, просто как обычно времени нет, и появляются более новые топики. Я уже порядком подзабыл тему. Но насколько помню, идея была такая: хранить полный URL и соответствующий к нему ключ в словаре. А потом уже при удалении (в этот момент у нас URL есть остаётся извлечь ключ) удалять данные по ключу, а не URL.

    Сделаем содержимое сообщества лучше, вместе!

    21 апреля 2014 г. 13:43
    Модератор