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); } } }