From 0db6c2cc11638fc619a4c7da428d7565dbb34d85 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 31 Oct 2012 11:36:22 +0600 Subject: [PATCH] Fixed issue that locally declared SurfaceControllers are routed through 'umbraco' area, this should not have been the case. Created new CacheHelper which replaces the old Cache class and is accessible via the UmbracoContext. Added new CachedPartial extension method to be able to cache the output of your partials (also by page and by member) Added mew CacheHelperApplicationEventListener in order to automatically clear the partials cache when content is published, media or members is saved. --- src/Umbraco.Core/ApplicationContext.cs | 25 ++- src/Umbraco.Core/CacheHelper.cs | 164 ++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../config/umbracoSettings.config | 6 +- src/Umbraco.Web/ApplicationEventsResolver.cs | 7 +- src/Umbraco.Web/CacheHelperExtensions.cs | 95 ++++++++++ src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 27 +++ src/Umbraco.Web/LegacyScheduledTasks.cs | 11 +- src/Umbraco.Web/Mvc/SurfaceController.cs | 12 ++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/WebBootManager.cs | 13 +- src/umbraco.cms/businesslogic/cache/Cache.cs | 100 +++-------- 12 files changed, 362 insertions(+), 100 deletions(-) create mode 100644 src/Umbraco.Core/CacheHelper.cs create mode 100644 src/Umbraco.Web/CacheHelperExtensions.cs diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index f67587d8fa..4ffcd9f2b7 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -4,6 +4,8 @@ using System.Configuration; using System.Data; using System.Diagnostics; using System.Linq; +using System.Web; +using System.Web.Caching; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -22,21 +24,32 @@ namespace Umbraco.Core /// /// Constructor /// - public ApplicationContext() - { - - } + internal ApplicationContext() + { + //create a new application cache from the HttpRuntime.Cache + ApplicationCache = HttpRuntime.Cache == null + ? new CacheHelper(new Cache()) + : new CacheHelper(HttpRuntime.Cache); + } - /// + /// /// Singleton accessor /// public static ApplicationContext Current { get; internal set; } + /// + /// Returns the application wide cache accessor + /// + /// + /// Any caching that is done in the application (app wide) should be done through this property + /// + internal CacheHelper ApplicationCache { get; private set; } + // IsReady is set to true by the boot manager once it has successfully booted // note - the original umbraco module checks on content.Instance in umbraco.dll // now, the boot task that setup the content store ensures that it is ready bool _isReady = false; - System.Threading.ManualResetEventSlim _isReadyEvent = new System.Threading.ManualResetEventSlim(false); + readonly System.Threading.ManualResetEventSlim _isReadyEvent = new System.Threading.ManualResetEventSlim(false); public bool IsReady { get diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs new file mode 100644 index 0000000000..a9b36498dd --- /dev/null +++ b/src/Umbraco.Core/CacheHelper.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; +using System.Web.Caching; +using Umbraco.Core.Logging; + +namespace Umbraco.Core +{ + + /// + /// Class that is exposed by the ApplicationContext for application wide caching purposes + /// + /// + /// This class may be opened publicly at some point but needs a review of what is absoletely necessary. + /// + internal class CacheHelper + { + private readonly Cache _cache; + + public CacheHelper(System.Web.Caching.Cache cache) + { + _cache = cache; + } + + private static readonly object Locker = new object(); + + /// + /// Clears everything in umbraco's runtime cache, which means that not only + /// umbraco content is removed, but also other cache items from pages running in + /// the same application / website. Use with care :-) + /// + public void ClearAllCache() + { + var cacheEnumerator = _cache.GetEnumerator(); + while (cacheEnumerator.MoveNext()) + { + _cache.Remove(cacheEnumerator.Key.ToString()); + } + } + + /// + /// Clears the item in umbraco's runtime cache with the given key + /// + /// Key + public void ClearCacheItem(string key) + { + // NH 10 jan 2012 + // Patch by the always wonderful Stéphane Gay to avoid cache null refs + lock (Locker) + { + if (_cache[key] == null) return; + _cache.Remove(key);; + } + } + + + /// + /// Clears all objects in the System.Web.Cache with the System.Type name as the + /// input parameter. (using [object].GetType()) + /// + /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" + public void ClearCacheObjectTypes(string typeName) + { + try + { + lock (Locker) + { + foreach (var c in from DictionaryEntry c in _cache where _cache[c.Key.ToString()] != null && _cache[c.Key.ToString()].GetType().ToString() == typeName select c) + { + _cache.Remove(c.Key.ToString()); + } + } + } + catch (Exception e) + { + LogHelper.Error("Cache clearing error", e); + } + } + + /// + /// Clears all cache items that starts with the key passed. + /// + /// The start of the key + public void ClearCacheByKeySearch(string keyStartsWith) + { + foreach (var c in from DictionaryEntry c in _cache where c.Key is string && ((string)c.Key).StartsWith(keyStartsWith) select c) + { + ClearCacheItem((string)c.Key); + } + } + + public TT GetCacheItem(string cacheKey, + TimeSpan timeout, Func getCacheItem) + { + return GetCacheItem(cacheKey, null, timeout, getCacheItem); + } + + public TT GetCacheItem(string cacheKey, + CacheItemRemovedCallback refreshAction, TimeSpan timeout, + Func getCacheItem) + { + return GetCacheItem(cacheKey, CacheItemPriority.Normal, refreshAction, timeout, getCacheItem); + } + + public TT GetCacheItem(string cacheKey, + CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, + Func getCacheItem) + { + return GetCacheItem(cacheKey, priority, refreshAction, null, timeout, getCacheItem); + } + + public TT GetCacheItem(string cacheKey, + CacheItemPriority priority, + CacheItemRemovedCallback refreshAction, + CacheDependency cacheDependency, + TimeSpan timeout, + Func getCacheItem) + { + return GetCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem, Locker); + } + + /// + /// This is used only for legacy purposes as I did not want to change all of the locking to one lock found on this object, + /// however, the reason this is used for legacy purposes is because I see zero reason to use different sync locks, just the one + /// lock (Locker) on this class should be sufficient. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + internal TT GetCacheItem(string cacheKey, + CacheItemPriority priority, CacheItemRemovedCallback refreshAction, + CacheDependency cacheDependency, TimeSpan timeout, Func getCacheItem, object syncLock) + { + var result = _cache.Get(cacheKey); + if (result == null) + { + lock (syncLock) + { + result = _cache.Get(cacheKey); + if (result == null) + { + result = getCacheItem(); + if (result != null) + { + //we use Insert instead of add if for some crazy reason there is now a cache with the cache key in there, it will just overwrite it. + _cache.Insert(cacheKey, result, cacheDependency, DateTime.Now.Add(timeout), TimeSpan.Zero, priority, refreshAction); + } + } + } + } + return (TT)result; + } + } + +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 02ac725951..14e8553e6c 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -53,6 +53,7 @@ + diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index fe496e18df..79d8dd0ea9 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -35,9 +35,9 @@ 200 --> - 1134 - 1135 - 1135 + 1047 + 1047 + 1047 diff --git a/src/Umbraco.Web/ApplicationEventsResolver.cs b/src/Umbraco.Web/ApplicationEventsResolver.cs index b215485fb4..93edaae441 100644 --- a/src/Umbraco.Web/ApplicationEventsResolver.cs +++ b/src/Umbraco.Web/ApplicationEventsResolver.cs @@ -33,12 +33,7 @@ namespace Umbraco.Web protected override bool SupportsClear { get { return false; } - } - - protected override bool SupportsAdd - { - get { return false; } - } + } protected override bool SupportsInsert { diff --git a/src/Umbraco.Web/CacheHelperExtensions.cs b/src/Umbraco.Web/CacheHelperExtensions.cs new file mode 100644 index 0000000000..cb8de5a1b6 --- /dev/null +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -0,0 +1,95 @@ +using System; +using System.Web; +using System.Web.Caching; +using System.Web.Mvc; +using System.Web.Mvc.Html; +using Umbraco.Core; +using umbraco.cms.businesslogic; +using umbraco.cms.businesslogic.web; + +namespace Umbraco.Web +{ + + /// + /// Extension methods for the cache helper + /// + internal static class CacheHelperExtensions + { + + /// + /// Application event handler to bind to events to clear the cache for the cache helper extensions + /// + /// + /// This would be better left internal, however + /// + public sealed class CacheHelperApplicationEventListener : IApplicationEventHandler + { + public void OnApplicationInitialized(UmbracoApplication httpApplication, ApplicationContext applicationContext) + { + if (ApplicationContext.Current != null) + { + //bind to events to clear the cache, after publish, after media save and after member save + + Document.AfterPublish + += (sender, args) => + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + + global::umbraco.cms.businesslogic.media.Media.AfterSave + += (sender, args) => + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + + global::umbraco.cms.businesslogic.member.Member.AfterSave + += (sender, args) => + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + } + } + + public void OnApplicationStarting(UmbracoApplication httpApplication, ApplicationContext applicationContext) + { + } + + public void OnApplicationStarted(UmbracoApplication httpApplication, ApplicationContext applicationContext) + { + } + } + + public const string PartialViewCacheKey = "Umbraco.Web.PartialViewCacheKey"; + + /// + /// Outputs and caches a partial view in MVC + /// + /// + /// + /// + /// + /// + /// used to cache the partial view, this key could change if it is cached by page or by member + /// + /// + public static IHtmlString CachedPartialView( + this CacheHelper cacheHelper, + HtmlHelper htmlHelper, + string partialViewName, + object model, + int cacheMilliseconds, + string cacheKey, + ViewDataDictionary viewData = null) + { + return cacheHelper.GetCacheItem( + PartialViewCacheKey + cacheKey, + CacheItemPriority.NotRemovable, //not removable, the same as macros (apparently issue #27610) + null, + new TimeSpan(0, 0, 0, 0, cacheMilliseconds), + () => htmlHelper.Partial(partialViewName, model, viewData)); + } + + /// + /// Clears the cache for partial views + /// + /// + public static void ClearPartialViewCache(this CacheHelper cacheHelper) + { + cacheHelper.ClearCacheByKeySearch(PartialViewCacheKey); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 0303609a5b..7aa702d7b0 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Web; using System.Web.Mvc; using System.Web.Mvc.Html; using Umbraco.Core; using Umbraco.Core.Dynamics; using Umbraco.Web.Mvc; using umbraco; +using umbraco.cms.businesslogic.member; namespace Umbraco.Web { @@ -17,6 +19,31 @@ namespace Umbraco.Web /// public static class HtmlHelperRenderExtensions { + public static IHtmlString CachedPartial( + this HtmlHelper htmlHelper, + string partialViewName, + object model, + int cacheMilliseconds, + bool cacheByPage = false, + bool cacheByMember = false, + ViewDataDictionary viewData = null) + { + var cacheKey = new StringBuilder(partialViewName); + if (cacheByPage) + { + if (UmbracoContext.Current == null) + { + throw new InvalidOperationException("Cannot cache by page if the UmbracoContext has not been initialized, this parameter can only be used in the context of an Umbraco request"); + } + cacheKey.AppendFormat("{0}-", UmbracoContext.Current.PageId); + } + if (cacheByMember) + { + var currentMember = Member.GetCurrentMember(); + cacheKey.AppendFormat("m{0}-", currentMember == null ? 0 : currentMember.Id); + } + return ApplicationContext.Current.ApplicationCache.CachedPartialView(htmlHelper, partialViewName, model, cacheMilliseconds, cacheKey.ToString(), viewData); + } public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) where T : new() diff --git a/src/Umbraco.Web/LegacyScheduledTasks.cs b/src/Umbraco.Web/LegacyScheduledTasks.cs index 3b8e586c11..1ebf9d7903 100644 --- a/src/Umbraco.Web/LegacyScheduledTasks.cs +++ b/src/Umbraco.Web/LegacyScheduledTasks.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Web; using System.Web.Caching; +using Umbraco.Core.Logging; using global::umbraco.BusinessLogic; namespace Umbraco.Web @@ -14,7 +15,7 @@ namespace Umbraco.Web // and it needs to be manually registered - which we want to avoid, in order // to be as unobtrusive as possible - public sealed class LegacyScheduledTasks : IApplicationEventHandler + internal sealed class LegacyScheduledTasks : IApplicationEventHandler { Timer pingTimer; Timer publishingTimer; @@ -62,9 +63,9 @@ namespace Umbraco.Web if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1) interval = global::umbraco.UmbracoSettings.CleaningMiliseconds; } - catch (Exception) + catch (Exception e) { - Log.Add(LogTypes.System, -1, "Unable to locate a log scrubbing interval. Defaulting to 24 horus"); + LogHelper.Error("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e); } return interval; } @@ -77,9 +78,9 @@ namespace Umbraco.Web if (global::umbraco.UmbracoSettings.MaxLogAge > -1) maximumAge = global::umbraco.UmbracoSettings.MaxLogAge; } - catch (Exception) + catch (Exception e) { - Log.Add(LogTypes.System, -1, "Unable to locate a log scrubbing maximum age. Defaulting to 24 horus"); + LogHelper.Error("Unable to locate a log scrubbing maximum age. Defaulting to 24 horus", e); } return maximumAge; diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index baa4bfd920..ee4cc7c3ec 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -20,6 +20,12 @@ namespace Umbraco.Web.Mvc // ModelState.AddModelError("name", "bad name!"); // return CurrentUmbracoPage(); // } + + // [ChildActionOnly] + // public ActionResult DoThis(string asdf) + // { + // return PartialView("DoThis", asdf + " DONE!"); + // } //} //public class LocalSurfaceController : SurfaceController @@ -34,6 +40,12 @@ namespace Umbraco.Web.Mvc // ModelState.AddModelError("name", "you suck!"); // return this.RedirectToCurrentUmbracoPage(); // } + + // [ChildActionOnly] + // public ActionResult DoThis(string asdf) + // { + // return PartialView("DoThis", asdf + " DONE Again!"); + // } //} /// diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2fd2210313..18ab74ccdb 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -241,6 +241,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 0a49d7673d..788330bc19 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -78,11 +78,15 @@ namespace Umbraco.Web //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to //events and to call their events during bootup. //ApplicationStartupHandler.RegisterHandlers(); + //... and set the special flag to let us resolve before frozen resolution ApplicationEventsResolver.Current = new ApplicationEventsResolver( - PluginManager.Current.ResolveApplicationStartupHandlers()); - - //set the special flag to let us resolve before frozen resolution - ApplicationEventsResolver.Current.CanResolveBeforeFrozen = true; + PluginManager.Current.ResolveApplicationStartupHandlers()) + { + CanResolveBeforeFrozen = true + }; + //add the internal types since we don't want to mark these public + ApplicationEventsResolver.Current.AddType(); + ApplicationEventsResolver.Current.AddType(); //now we need to call the initialize methods ApplicationEventsResolver.Current.ApplicationEventHandlers @@ -165,7 +169,6 @@ namespace Umbraco.Web umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, new[] { meta.ControllerNamespace }); //only match this namespace - route.DataTokens.Add("area", umbracoPath); //only match this area route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set } diff --git a/src/umbraco.cms/businesslogic/cache/Cache.cs b/src/umbraco.cms/businesslogic/cache/Cache.cs index 30d4ff160a..5cbbdf0db8 100644 --- a/src/umbraco.cms/businesslogic/cache/Cache.cs +++ b/src/umbraco.cms/businesslogic/cache/Cache.cs @@ -1,12 +1,18 @@ using System; using System.Web.Caching; using System.Web; +using Umbraco.Core; namespace umbraco.cms.businesslogic.cache { /// /// Used to easily store and retreive items from the cache. /// + /// + /// This whole class will become obsolete, however one of the methods is still used that is not ported over to the new CacheHelper + /// class so that is why the class declaration is not marked obsolete. + /// We haven't migrated it because I don't know why it is needed. + /// public class Cache { private static readonly object m_Locker = new object(); @@ -16,40 +22,22 @@ namespace umbraco.cms.businesslogic.cache /// umbraco content is removed, but also other cache items from pages running in /// the same application / website. Use with care :-) /// + [Obsolete("Use the ApplicationContext.Cache.ClearAllCache instead")] public static void ClearAllCache() { - System.Web.Caching.Cache c = System.Web.HttpRuntime.Cache; - if (c != null) - { - System.Collections.IDictionaryEnumerator cacheEnumerator = c.GetEnumerator(); - while (cacheEnumerator.MoveNext()) - { - c.Remove(cacheEnumerator.Key.ToString()); - } - } + var helper = new CacheHelper(System.Web.HttpRuntime.Cache); + helper.ClearAllCache(); } /// /// Clears the item in umbraco's runtime cache with the given key /// /// Key + [Obsolete("Use the ApplicationContext.Cache.ClearCacheItem instead")] public static void ClearCacheItem(string key) { - // NH 10 jan 2012 - // Patch by the always wonderful Stéphane Gay to avoid cache null refs - lock (m_Locker) - { - var cache = HttpRuntime.Cache; - if (cache[key] != null) - { - cache.Remove(key); - var context = HttpContext.Current; - if (context != null) - { - context.Trace.Warn("Cache", "Item " + key + " removed from cache"); - } - } - } + var helper = new CacheHelper(System.Web.HttpRuntime.Cache); + helper.ClearCacheItem(key); } @@ -58,48 +46,22 @@ namespace umbraco.cms.businesslogic.cache /// input parameter. (using [object].GetType()) /// /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" + [Obsolete("Use the ApplicationContext.Cache.ClearCacheObjectTypes instead")] public static void ClearCacheObjectTypes(string TypeName) { - System.Web.Caching.Cache c = System.Web.HttpRuntime.Cache; - try - { - if (c != null) - { - System.Collections.IDictionaryEnumerator cacheEnumerator = c.GetEnumerator(); - while (cacheEnumerator.MoveNext()) - { - if (cacheEnumerator.Key != null && c[cacheEnumerator.Key.ToString()] != null && c[cacheEnumerator.Key.ToString()].GetType() != null && c[cacheEnumerator.Key.ToString()].GetType().ToString() == TypeName) - { - c.Remove(cacheEnumerator.Key.ToString()); - } - } - } - } - catch (Exception CacheE) - { - BusinessLogic.Log.Add(BusinessLogic.LogTypes.Error, BusinessLogic.User.GetUser(0), -1, "CacheClearing : " + CacheE.ToString()); - } + var helper = new CacheHelper(System.Web.HttpRuntime.Cache); + helper.ClearCacheObjectTypes(TypeName); } /// /// Clears all cache items that starts with the key passed. /// /// The start of the key + [Obsolete("Use the ApplicationContext.Cache.ClearCacheByKeySearch instead")] public static void ClearCacheByKeySearch(string KeyStartsWith) { - System.Web.Caching.Cache c = System.Web.HttpRuntime.Cache; - if (c != null) - { - System.Collections.IDictionaryEnumerator cacheEnumerator = c.GetEnumerator(); - while (cacheEnumerator.MoveNext()) - { - if (cacheEnumerator.Key is string && ((string)cacheEnumerator.Key).StartsWith(KeyStartsWith)) - { - Cache.ClearCacheItem((string)cacheEnumerator.Key); - } - } - } - + var helper = new CacheHelper(System.Web.HttpRuntime.Cache); + helper.ClearCacheByKeySearch(KeyStartsWith); } /// @@ -124,15 +86,16 @@ namespace umbraco.cms.businesslogic.cache return ht; } - public delegate TT GetCacheItemDelegate(); + [Obsolete("Use the ApplicationContext.Cache.GetCacheItem instead")] public static TT GetCacheItem(string cacheKey, object syncLock, TimeSpan timeout, GetCacheItemDelegate getCacheItem) { return GetCacheItem(cacheKey, syncLock, null, timeout, getCacheItem); } + [Obsolete("Use the ApplicationContext.Cache.GetCacheItem instead")] public static TT GetCacheItem(string cacheKey, object syncLock, CacheItemRemovedCallback refreshAction, TimeSpan timeout, GetCacheItemDelegate getCacheItem) @@ -140,6 +103,7 @@ namespace umbraco.cms.businesslogic.cache return GetCacheItem(cacheKey, syncLock, CacheItemPriority.Normal, refreshAction, timeout, getCacheItem); } + [Obsolete("Use the ApplicationContext.Cache.GetCacheItem instead")] public static TT GetCacheItem(string cacheKey, object syncLock, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, GetCacheItemDelegate getCacheItem) @@ -147,28 +111,14 @@ namespace umbraco.cms.businesslogic.cache return GetCacheItem(cacheKey, syncLock, priority, refreshAction, null, timeout, getCacheItem); } + [Obsolete("Use the ApplicationContext.Cache.GetCacheItem instead")] public static TT GetCacheItem(string cacheKey, object syncLock, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan timeout, GetCacheItemDelegate getCacheItem) { - object result = System.Web.HttpRuntime.Cache.Get(cacheKey); - if (result == null) - { - lock (syncLock) - { - result = System.Web.HttpRuntime.Cache.Get(cacheKey); - if (result == null) - { - result = getCacheItem(); - if (result != null) - { - System.Web.HttpRuntime.Cache.Add(cacheKey, result, cacheDependency, - DateTime.Now.Add(timeout), TimeSpan.Zero, priority, refreshAction); - } - } - } - } - return (TT)result; + var helper = new CacheHelper(System.Web.HttpRuntime.Cache); + Func f = () => getCacheItem(); + return helper.GetCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, f, syncLock); } } }