diff --git a/src/Umbraco.Core/Cache/AppCaches.cs b/src/Umbraco.Core/Cache/AppCaches.cs
index 7dd95624ef..6372bccbab 100644
--- a/src/Umbraco.Core/Cache/AppCaches.cs
+++ b/src/Umbraco.Core/Cache/AppCaches.cs
@@ -4,47 +4,41 @@ using System.Web;
namespace Umbraco.Core.Cache
{
///
- /// Represents the application-wide caches.
+ /// Represents the application caches.
///
public class AppCaches
{
///
- /// Initializes a new instance for use in the web
+ /// Initializes a new instance of the for use in a web application.
///
public AppCaches()
- : this(
- new HttpRuntimeCacheProvider(HttpRuntime.Cache),
- new StaticCacheProvider(),
- new HttpRequestCacheProvider(),
- new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
- {
- }
+ : this(HttpRuntime.Cache)
+ { }
///
- /// Initializes a new instance for use in the web
+ /// Initializes a new instance of the for use in a web application.
///
public AppCaches(System.Web.Caching.Cache cache)
: this(
- new HttpRuntimeCacheProvider(cache),
- new StaticCacheProvider(),
- new HttpRequestCacheProvider(),
- new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
- {
- }
+ new WebCachingAppCache(cache),
+ new DictionaryCacheProvider(),
+ new HttpRequestAppCache(),
+ new IsolatedCaches(t => new ObjectCacheAppCache()))
+ { }
///
- /// Initializes a new instance based on the provided providers
+ /// Initializes a new instance of the with cache providers.
///
public AppCaches(
- IRuntimeCacheProvider httpCacheProvider,
- ICacheProvider staticCacheProvider,
- ICacheProvider requestCacheProvider,
- IsolatedRuntimeCache isolatedCacheManager)
+ IAppPolicedCache runtimeCache,
+ IAppCache staticCacheProvider,
+ IAppCache requestCache,
+ IsolatedCaches isolatedCaches)
{
- RuntimeCache = httpCacheProvider ?? throw new ArgumentNullException(nameof(httpCacheProvider));
+ RuntimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache));
StaticCache = staticCacheProvider ?? throw new ArgumentNullException(nameof(staticCacheProvider));
- RequestCache = requestCacheProvider ?? throw new ArgumentNullException(nameof(requestCacheProvider));
- IsolatedRuntimeCache = isolatedCacheManager ?? throw new ArgumentNullException(nameof(isolatedCacheManager));
+ RequestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
+ IsolatedCaches = isolatedCaches ?? throw new ArgumentNullException(nameof(isolatedCaches));
}
///
@@ -54,7 +48,7 @@ namespace Umbraco.Core.Cache
/// When used by repositories, all cache policies apply, but the underlying caches do not cache anything.
/// Used by tests.
///
- public static AppCaches Disabled { get; } = new AppCaches(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
+ public static AppCaches Disabled { get; } = new AppCaches(NoAppCache.Instance, NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance));
///
/// Gets the special no-cache instance.
@@ -63,27 +57,42 @@ namespace Umbraco.Core.Cache
/// When used by repositories, all cache policies are bypassed.
/// Used by repositories that do no cache.
///
- public static AppCaches NoCache { get; } = new AppCaches(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
+ public static AppCaches NoCache { get; } = new AppCaches(NoAppCache.Instance, NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance));
///
- /// Returns the current Request cache
+ /// Gets the per-request cache.
///
- public ICacheProvider RequestCache { get; internal set; }
+ ///
+ /// The per-request caches works on top of the current HttpContext items.
+ /// fixme - what about non-web applications?
+ ///
+ public IAppCache RequestCache { get; }
///
/// Returns the current Runtime cache
///
- public ICacheProvider StaticCache { get; internal set; }
+ ///
+ /// fixme - what is this? why not use RuntimeCache?
+ ///
+ public IAppCache StaticCache { get; }
///
- /// Returns the current Runtime cache
+ /// Gets the runtime cache.
///
- public IRuntimeCacheProvider RuntimeCache { get; internal set; }
+ ///
+ /// The runtime cache is the main application cache.
+ ///
+ public IAppPolicedCache RuntimeCache { get; }
///
- /// Returns the current Isolated Runtime cache manager
+ /// Gets the isolated caches.
///
- public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; }
+ ///
+ /// Isolated caches are used by e.g. repositories, to ensure that each cached entity
+ /// type has its own cache, so that lookups are fast and the repository does not need to
+ /// search through all keys on a global scale.
+ ///
+ public IsolatedCaches IsolatedCaches { get; }
}
diff --git a/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs
new file mode 100644
index 0000000000..51cc3c4c53
--- /dev/null
+++ b/src/Umbraco.Core/Cache/AppPolicedCacheDictionary.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Concurrent;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Provides a base class for implementing a dictionary of .
+ ///
+ /// The type of the dictionary key.
+ public abstract class AppPolicedCacheDictionary
+ {
+ private readonly ConcurrentDictionary _caches = new ConcurrentDictionary();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ protected AppPolicedCacheDictionary(Func cacheFactory)
+ {
+ CacheFactory = cacheFactory;
+ }
+
+ ///
+ /// Gets the internal cache factory, for tests only!
+ ///
+ internal readonly Func CacheFactory;
+
+ ///
+ /// Gets or creates a cache.
+ ///
+ public IAppPolicedCache GetOrCreate(TKey key)
+ => _caches.GetOrAdd(key, k => CacheFactory(k));
+
+ ///
+ /// Tries to get a cache.
+ ///
+ public Attempt Get(TKey key)
+ => _caches.TryGetValue(key, out var cache) ? Attempt.Succeed(cache) : Attempt.Fail();
+
+ ///
+ /// Removes a cache.
+ ///
+ public void Remove(TKey key)
+ {
+ _caches.TryRemove(key, out _);
+ }
+
+ ///
+ /// Removes all caches.
+ ///
+ public void RemoveAll()
+ {
+ _caches.Clear();
+ }
+
+ ///
+ /// Clears a cache.
+ ///
+ public void ClearCache(TKey key)
+ {
+ if (_caches.TryGetValue(key, out var cache))
+ cache.Clear();
+ }
+
+ ///
+ /// Clears all caches.
+ ///
+ public void ClearAllCaches()
+ {
+ foreach (var cache in _caches.Values)
+ cache.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs
index e42cb4d9ad..0e41b981fb 100644
--- a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs
+++ b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Cache
///
public static class CacheProviderExtensions
{
- public static T GetCacheItem(this IRuntimeCacheProvider provider,
+ public static T GetCacheItem(this IAppPolicedCache provider,
string cacheKey,
Func getCacheItem,
TimeSpan? timeout,
@@ -19,11 +19,11 @@ namespace Umbraco.Core.Cache
CacheItemRemovedCallback removedCallback = null,
string[] dependentFiles = null)
{
- var result = provider.GetCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles);
+ var result = provider.Get(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles);
return result == null ? default(T) : result.TryConvertTo().Result;
}
- public static void InsertCacheItem(this IRuntimeCacheProvider provider,
+ public static void InsertCacheItem(this IAppPolicedCache provider,
string cacheKey,
Func getCacheItem,
TimeSpan? timeout = null,
@@ -32,24 +32,24 @@ namespace Umbraco.Core.Cache
CacheItemRemovedCallback removedCallback = null,
string[] dependentFiles = null)
{
- provider.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles);
+ provider.Insert(cacheKey, () => getCacheItem(), timeout, isSliding, priority, removedCallback, dependentFiles);
}
- public static IEnumerable GetCacheItemsByKeySearch(this ICacheProvider provider, string keyStartsWith)
+ public static IEnumerable GetCacheItemsByKeySearch(this IAppCache provider, string keyStartsWith)
{
- var result = provider.GetCacheItemsByKeySearch(keyStartsWith);
+ var result = provider.SearchByKey(keyStartsWith);
return result.Select(x => x.TryConvertTo().Result);
}
- public static IEnumerable GetCacheItemsByKeyExpression(this ICacheProvider provider, string regexString)
+ public static IEnumerable GetCacheItemsByKeyExpression(this IAppCache provider, string regexString)
{
- var result = provider.GetCacheItemsByKeyExpression(regexString);
+ var result = provider.SearchByRegex(regexString);
return result.Select(x => x.TryConvertTo().Result);
}
- public static T GetCacheItem(this ICacheProvider provider, string cacheKey)
+ public static T GetCacheItem(this IAppCache provider, string cacheKey)
{
- var result = provider.GetCacheItem(cacheKey);
+ var result = provider.Get(cacheKey);
if (result == null)
{
return default(T);
@@ -57,9 +57,9 @@ namespace Umbraco.Core.Cache
return result.TryConvertTo().Result;
}
- public static T GetCacheItem(this ICacheProvider provider, string cacheKey, Func getCacheItem)
+ public static T GetCacheItem(this IAppCache provider, string cacheKey, Func getCacheItem)
{
- var result = provider.GetCacheItem(cacheKey, () => getCacheItem());
+ var result = provider.Get(cacheKey, () => getCacheItem());
if (result == null)
{
return default(T);
diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
index 5e6ec593af..bfa16ff3fa 100644
--- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
@@ -102,7 +102,7 @@ namespace Umbraco.Core.Cache
protected void ClearAllIsolatedCacheByEntityType()
where TEntity : class, IEntity
{
- AppCaches.IsolatedRuntimeCache.ClearCache();
+ AppCaches.IsolatedCaches.ClearCache();
}
///
diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs
new file mode 100644
index 0000000000..cdc66f1db7
--- /dev/null
+++ b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Caching;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.Entities;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Implements by wrapping an inner other
+ /// instance, and ensuring that all inserts and returns are deep cloned copies of the cache item,
+ /// when the item is deep-cloneable.
+ ///
+ internal class DeepCloneAppCache : IAppPolicedCache
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DeepCloneAppCache(IAppPolicedCache innerCache)
+ {
+ var type = typeof (DeepCloneAppCache);
+
+ if (innerCache.GetType() == type)
+ throw new InvalidOperationException($"A {type} cannot wrap another instance of itself.");
+
+ InnerCache = innerCache;
+ }
+
+ ///
+ /// Gets the inner cache.
+ ///
+ public IAppPolicedCache InnerCache { get; }
+
+ ///
+ public object Get(string key)
+ {
+ var item = InnerCache.Get(key);
+ return CheckCloneableAndTracksChanges(item);
+ }
+
+ ///
+ public object Get(string key, Func factory)
+ {
+ var cached = InnerCache.Get(key, () =>
+ {
+ var result = FastDictionaryAppCacheBase.GetSafeLazy(factory);
+ var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
+ // do not store null values (backward compat), clone / reset to go into the cache
+ return value == null ? null : CheckCloneableAndTracksChanges(value);
+ });
+ return CheckCloneableAndTracksChanges(cached);
+ }
+
+ ///
+ public IEnumerable SearchByKey(string keyStartsWith)
+ {
+ return InnerCache.SearchByKey(keyStartsWith)
+ .Select(CheckCloneableAndTracksChanges);
+ }
+
+ ///
+ public IEnumerable SearchByRegex(string regex)
+ {
+ return InnerCache.SearchByRegex(regex)
+ .Select(CheckCloneableAndTracksChanges);
+ }
+
+ ///
+ public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ {
+ var cached = InnerCache.Get(key, () =>
+ {
+ var result = FastDictionaryAppCacheBase.GetSafeLazy(factory);
+ var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
+ // do not store null values (backward compat), clone / reset to go into the cache
+ return value == null ? null : CheckCloneableAndTracksChanges(value);
+
+ // clone / reset to go into the cache
+ }, timeout, isSliding, priority, removedCallback, dependentFiles);
+
+ // clone / reset to go into the cache
+ return CheckCloneableAndTracksChanges(cached);
+ }
+
+ ///
+ public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ {
+ InnerCache.Insert(key, () =>
+ {
+ var result = FastDictionaryAppCacheBase.GetSafeLazy(factory);
+ var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
+ // do not store null values (backward compat), clone / reset to go into the cache
+ return value == null ? null : CheckCloneableAndTracksChanges(value);
+ }, timeout, isSliding, priority, removedCallback, dependentFiles);
+ }
+
+ ///
+ public void Clear()
+ {
+ InnerCache.Clear();
+ }
+
+ ///
+ public void Clear(string key)
+ {
+ InnerCache.Clear(key);
+ }
+
+ ///
+ public void ClearOfType(string typeName)
+ {
+ InnerCache.ClearOfType(typeName);
+ }
+
+ ///
+ public void ClearOfType()
+ {
+ InnerCache.ClearOfType();
+ }
+
+ ///
+ public void ClearOfType(Func predicate)
+ {
+ InnerCache.ClearOfType(predicate);
+ }
+
+ ///
+ public void ClearByKey(string keyStartsWith)
+ {
+ InnerCache.ClearByKey(keyStartsWith);
+ }
+
+ ///
+ public void ClearByRegex(string regex)
+ {
+ InnerCache.ClearByRegex(regex);
+ }
+
+ private static object CheckCloneableAndTracksChanges(object input)
+ {
+ if (input is IDeepCloneable cloneable)
+ {
+ input = cloneable.DeepClone();
+ }
+
+ // reset dirty initial properties
+ if (input is IRememberBeingDirty tracksChanges)
+ {
+ tracksChanges.ResetDirtyProperties(false);
+ input = tracksChanges;
+ }
+
+ return input;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs
deleted file mode 100644
index 255d7b526d..0000000000
--- a/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Web.Caching;
-using Umbraco.Core.Models;
-using Umbraco.Core.Models.Entities;
-
-namespace Umbraco.Core.Cache
-{
- ///
- /// Interface describing this cache provider as a wrapper for another
- ///
- internal interface IRuntimeCacheProviderWrapper
- {
- IRuntimeCacheProvider InnerProvider { get; }
- }
-
- ///
- /// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns
- /// are a deep cloned copy of the item when the item is IDeepCloneable and that tracks changes are
- /// reset if the object is TracksChangesEntityBase
- ///
- internal class DeepCloneRuntimeCacheProvider : IRuntimeCacheProvider, IRuntimeCacheProviderWrapper
- {
- public IRuntimeCacheProvider InnerProvider { get; }
-
- public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider)
- {
- var type = typeof (DeepCloneRuntimeCacheProvider);
-
- if (innerProvider.GetType() == type)
- throw new InvalidOperationException($"A {type} cannot wrap another instance of {type}.");
-
- InnerProvider = innerProvider;
- }
-
- #region Clear - doesn't require any changes
-
- public void ClearAllCache()
- {
- InnerProvider.ClearAllCache();
- }
-
- public void ClearCacheItem(string key)
- {
- InnerProvider.ClearCacheItem(key);
- }
-
- public void ClearCacheObjectTypes(string typeName)
- {
- InnerProvider.ClearCacheObjectTypes(typeName);
- }
-
- public void ClearCacheObjectTypes()
- {
- InnerProvider.ClearCacheObjectTypes();
- }
-
- public void ClearCacheObjectTypes(Func predicate)
- {
- InnerProvider.ClearCacheObjectTypes(predicate);
- }
-
- public void ClearCacheByKeySearch(string keyStartsWith)
- {
- InnerProvider.ClearCacheByKeySearch(keyStartsWith);
- }
-
- public void ClearCacheByKeyExpression(string regexString)
- {
- InnerProvider.ClearCacheByKeyExpression(regexString);
- }
-
- #endregion
-
- public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
- {
- return InnerProvider.GetCacheItemsByKeySearch(keyStartsWith)
- .Select(CheckCloneableAndTracksChanges);
- }
-
- public IEnumerable GetCacheItemsByKeyExpression(string regexString)
- {
- return InnerProvider.GetCacheItemsByKeyExpression(regexString)
- .Select(CheckCloneableAndTracksChanges);
- }
-
- public object GetCacheItem(string cacheKey)
- {
- var item = InnerProvider.GetCacheItem(cacheKey);
- return CheckCloneableAndTracksChanges(item);
- }
-
- public object GetCacheItem(string cacheKey, Func getCacheItem)
- {
- var cached = InnerProvider.GetCacheItem(cacheKey, () =>
- {
- var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
- var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
- if (value == null) return null; // do not store null values (backward compat)
-
- return CheckCloneableAndTracksChanges(value);
- });
- return CheckCloneableAndTracksChanges(cached);
- }
-
- public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
- {
- var cached = InnerProvider.GetCacheItem(cacheKey, () =>
- {
- var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
- var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
- if (value == null) return null; // do not store null values (backward compat)
-
- // clone / reset to go into the cache
- return CheckCloneableAndTracksChanges(value);
- }, timeout, isSliding, priority, removedCallback, dependentFiles);
-
- // clone / reset to go into the cache
- return CheckCloneableAndTracksChanges(cached);
- }
-
- public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
- {
- InnerProvider.InsertCacheItem(cacheKey, () =>
- {
- var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
- var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
- if (value == null) return null; // do not store null values (backward compat)
-
- // clone / reset to go into the cache
- return CheckCloneableAndTracksChanges(value);
- }, timeout, isSliding, priority, removedCallback, dependentFiles);
- }
-
- private static object CheckCloneableAndTracksChanges(object input)
- {
- var cloneable = input as IDeepCloneable;
- if (cloneable != null)
- {
- input = cloneable.DeepClone();
- }
-
- // reset dirty initial properties (U4-1946)
- var tracksChanges = input as IRememberBeingDirty;
- if (tracksChanges != null)
- {
- tracksChanges.ResetDirtyProperties(false);
- input = tracksChanges;
- }
-
- return input;
- }
- }
-}
diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
index 8387f547d3..6f160cd552 100644
--- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
+++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Cache
private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const
private readonly RepositoryCachePolicyOptions _options;
- public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
+ public DefaultRepositoryCachePolicy(IAppPolicedCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
: base(cache, scopeAccessor)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
@@ -42,7 +42,7 @@ namespace Umbraco.Core.Cache
protected virtual void InsertEntity(string cacheKey, TEntity entity)
{
- Cache.InsertCacheItem(cacheKey, () => entity, TimeSpan.FromMinutes(5), true);
+ Cache.Insert(cacheKey, () => entity, TimeSpan.FromMinutes(5), true);
}
protected virtual void InsertEntities(TId[] ids, TEntity[] entities)
@@ -52,7 +52,7 @@ namespace Umbraco.Core.Cache
// getting all of them, and finding nothing.
// if we can cache a zero count, cache an empty array,
// for as long as the cache is not cleared (no expiration)
- Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => EmptyEntities);
+ Cache.Insert(GetEntityTypeCacheKey(), () => EmptyEntities);
}
else
{
@@ -60,7 +60,7 @@ namespace Umbraco.Core.Cache
foreach (var entity in entities)
{
var capture = entity;
- Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => capture, TimeSpan.FromMinutes(5), true);
+ Cache.Insert(GetEntityCacheKey(entity.Id), () => capture, TimeSpan.FromMinutes(5), true);
}
}
}
@@ -77,21 +77,21 @@ namespace Umbraco.Core.Cache
// just to be safe, we cannot cache an item without an identity
if (entity.HasIdentity)
{
- Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true);
+ Cache.Insert(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true);
}
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
- Cache.ClearCacheItem(GetEntityTypeCacheKey());
+ Cache.Clear(GetEntityTypeCacheKey());
}
catch
{
// if an exception is thrown we need to remove the entry from cache,
// this is ONLY a work around because of the way
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
- Cache.ClearCacheItem(GetEntityCacheKey(entity.Id));
+ Cache.Clear(GetEntityCacheKey(entity.Id));
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
- Cache.ClearCacheItem(GetEntityTypeCacheKey());
+ Cache.Clear(GetEntityTypeCacheKey());
throw;
}
@@ -109,21 +109,21 @@ namespace Umbraco.Core.Cache
// just to be safe, we cannot cache an item without an identity
if (entity.HasIdentity)
{
- Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true);
+ Cache.Insert(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true);
}
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
- Cache.ClearCacheItem(GetEntityTypeCacheKey());
+ Cache.Clear(GetEntityTypeCacheKey());
}
catch
{
// if an exception is thrown we need to remove the entry from cache,
// this is ONLY a work around because of the way
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
- Cache.ClearCacheItem(GetEntityCacheKey(entity.Id));
+ Cache.Clear(GetEntityCacheKey(entity.Id));
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
- Cache.ClearCacheItem(GetEntityTypeCacheKey());
+ Cache.Clear(GetEntityTypeCacheKey());
throw;
}
@@ -142,9 +142,9 @@ namespace Umbraco.Core.Cache
{
// whatever happens, clear the cache
var cacheKey = GetEntityCacheKey(entity.Id);
- Cache.ClearCacheItem(cacheKey);
+ Cache.Clear(cacheKey);
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
- Cache.ClearCacheItem(GetEntityTypeCacheKey());
+ Cache.Clear(GetEntityTypeCacheKey());
}
}
@@ -238,7 +238,7 @@ namespace Umbraco.Core.Cache
///
public override void ClearAll()
{
- Cache.ClearCacheByKeySearch(GetEntityTypeCacheKey());
+ Cache.ClearByKey(GetEntityTypeCacheKey());
}
}
}
diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs b/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs
index 98dceb80b0..2ff5f6ea83 100644
--- a/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs
@@ -1,147 +1,97 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Linq;
using System.Text.RegularExpressions;
-using Umbraco.Core.Composing;
namespace Umbraco.Core.Cache
{
- internal class DictionaryCacheProvider : ICacheProvider
+ ///
+ /// Implements on top of a concurrent dictionary.
+ ///
+ public class DictionaryCacheProvider : IAppCache
{
- private readonly ConcurrentDictionary> _items
- = new ConcurrentDictionary>();
+ ///
+ /// Gets the internal items dictionary, for tests only!
+ ///
+ internal readonly ConcurrentDictionary Items = new ConcurrentDictionary();
- // for tests
- internal ConcurrentDictionary> Items => _items;
-
- public void ClearAllCache()
+ ///
+ public virtual object Get(string key)
{
- _items.Clear();
+ // fixme throws if non-existing, shouldn't it return null?
+ return Items[key];
}
- public void ClearCacheItem(string key)
+ ///
+ public virtual object Get(string key, Func factory)
{
- _items.TryRemove(key, out _);
+ return Items.GetOrAdd(key, _ => factory());
}
- public void ClearCacheObjectTypes(string typeName)
+ ///
+ public virtual IEnumerable SearchByKey(string keyStartsWith)
{
- var type = TypeFinder.GetTypeByName(typeName);
- if (type == null) return;
- var isInterface = type.IsInterface;
-
- foreach (var kvp in _items
- .Where(x =>
- {
- // entry.Value is Lazy and not null, its value may be null
- // remove null values as well, does not hurt
- // get non-created as NonCreatedValue & exceptions as null
- var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true);
-
- // if T is an interface remove anything that implements that interface
- // otherwise remove exact types (not inherited types)
- return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type));
- }))
- _items.TryRemove(kvp.Key, out _);
+ var items = new List();
+ foreach (var (key, value) in Items)
+ if (key.InvariantStartsWith(keyStartsWith))
+ items.Add(value);
+ return items;
}
- public void ClearCacheObjectTypes()
+ ///
+ public IEnumerable SearchByRegex(string regex)
+ {
+ var compiled = new Regex(regex, RegexOptions.Compiled);
+ var items = new List();
+ foreach (var (key, value) in Items)
+ if (compiled.IsMatch(key))
+ items.Add(value);
+ return items;
+ }
+
+ ///
+ public virtual void Clear()
+ {
+ Items.Clear();
+ }
+
+ ///
+ public virtual void Clear(string key)
+ {
+ Items.TryRemove(key, out _);
+ }
+
+ ///
+ public virtual void ClearOfType(string typeName)
+ {
+ Items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName));
+ }
+
+ ///
+ public virtual void ClearOfType()
{
var typeOfT = typeof(T);
- var isInterface = typeOfT.IsInterface;
-
- foreach (var kvp in _items
- .Where(x =>
- {
- // entry.Value is Lazy and not null, its value may be null
- // remove null values as well, does not hurt
- // compare on exact type, don't use "is"
- // get non-created as NonCreatedValue & exceptions as null
- var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true);
-
- // if T is an interface remove anything that implements that interface
- // otherwise remove exact types (not inherited types)
- return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT));
- }))
- _items.TryRemove(kvp.Key, out _);
+ Items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT);
}
- public void ClearCacheObjectTypes(Func predicate)
+ ///
+ public virtual void ClearOfType(Func predicate)
{
var typeOfT = typeof(T);
- var isInterface = typeOfT.IsInterface;
-
- foreach (var kvp in _items
- .Where(x =>
- {
- // entry.Value is Lazy and not null, its value may be null
- // remove null values as well, does not hurt
- // compare on exact type, don't use "is"
- // get non-created as NonCreatedValue & exceptions as null
- var value = DictionaryCacheProviderBase.GetSafeLazyValue(x.Value, true);
- if (value == null) return true;
-
- // if T is an interface remove anything that implements that interface
- // otherwise remove exact types (not inherited types)
- return (isInterface ? (value is T) : (value.GetType() == typeOfT))
- // run predicate on the 'public key' part only, ie without prefix
- && predicate(x.Key, (T)value);
- }))
- _items.TryRemove(kvp.Key, out _);
+ Items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value));
}
- public void ClearCacheByKeySearch(string keyStartsWith)
+ ///
+ public virtual void ClearByKey(string keyStartsWith)
{
- foreach (var ikvp in _items
- .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)))
- _items.TryRemove(ikvp.Key, out _);
+ Items.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith));
}
- public void ClearCacheByKeyExpression(string regexString)
+ ///
+ public virtual void ClearByRegex(string regex)
{
- foreach (var ikvp in _items
- .Where(kvp => Regex.IsMatch(kvp.Key, regexString)))
- _items.TryRemove(ikvp.Key, out _);
- }
-
- public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
- {
- return _items
- .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))
- .Select(kvp => DictionaryCacheProviderBase.GetSafeLazyValue(kvp.Value))
- .Where(x => x != null);
- }
-
- public IEnumerable GetCacheItemsByKeyExpression(string regexString)
- {
- return _items
- .Where(kvp => Regex.IsMatch(kvp.Key, regexString))
- .Select(kvp => DictionaryCacheProviderBase.GetSafeLazyValue(kvp.Value))
- .Where(x => x != null);
- }
-
- public object GetCacheItem(string cacheKey)
- {
- _items.TryGetValue(cacheKey, out var result); // else null
- return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null
- }
-
- public object GetCacheItem(string cacheKey, Func getCacheItem)
- {
- var result = _items.GetOrAdd(cacheKey, k => DictionaryCacheProviderBase.GetSafeLazy(getCacheItem));
-
- var value = result.Value; // will not throw (safe lazy)
- if (!(value is DictionaryCacheProviderBase.ExceptionHolder eh))
- return value;
-
- // and... it's in the cache anyway - so contrary to other cache providers,
- // which would trick with GetSafeLazyValue, we need to remove by ourselves,
- // in order NOT to cache exceptions
-
- _items.TryRemove(cacheKey, out result);
- eh.Exception.Throw(); // throw once!
- return null; // never reached
+ var compiled = new Regex(regex, RegexOptions.Compiled);
+ Items.RemoveAll(kvp => compiled.IsMatch(kvp.Key));
}
}
}
diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs
similarity index 81%
rename from src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs
rename to src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs
index f556d47a8e..371ab90a57 100644
--- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs
+++ b/src/Umbraco.Core/Cache/FastDictionaryAppCacheBase.cs
@@ -8,7 +8,10 @@ using Umbraco.Core.Composing;
namespace Umbraco.Core.Cache
{
- internal abstract class DictionaryCacheProviderBase : ICacheProvider
+ ///
+ /// Provides a base class to fast, dictionary-based implementations.
+ ///
+ internal abstract class FastDictionaryAppCacheBase : IAppCache
{
// prefix cache keys so we know which one are ours
protected const string CacheItemPrefix = "umbrtmche";
@@ -16,82 +19,75 @@ namespace Umbraco.Core.Cache
// an object that represent a value that has not been created yet
protected internal static readonly object ValueNotCreated = new object();
- // manupulate the underlying cache entries
- // these *must* be called from within the appropriate locks
- // and use the full prefixed cache keys
- protected abstract IEnumerable GetDictionaryEntries();
- protected abstract void RemoveEntry(string key);
- protected abstract object GetEntry(string key);
+ #region IAppCache
- // read-write lock the underlying cache
- //protected abstract IDisposable ReadLock { get; }
- //protected abstract IDisposable WriteLock { get; }
-
- protected abstract void EnterReadLock();
- protected abstract void ExitReadLock();
- protected abstract void EnterWriteLock();
- protected abstract void ExitWriteLock();
-
- protected string GetCacheKey(string key)
+ ///
+ public virtual object Get(string key)
{
- return string.Format("{0}-{1}", CacheItemPrefix, key);
- }
-
- protected internal static Lazy GetSafeLazy(Func getCacheItem)
- {
- // try to generate the value and if it fails,
- // wrap in an ExceptionHolder - would be much simpler
- // to just use lazy.IsValueFaulted alas that field is
- // internal
- return new Lazy(() =>
- {
- try
- {
- return getCacheItem();
- }
- catch (Exception e)
- {
- return new ExceptionHolder(ExceptionDispatchInfo.Capture(e));
- }
- });
- }
-
- protected internal static object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false)
- {
- // if onlyIfValueIsCreated, do not trigger value creation
- // must return something, though, to differenciate from null values
- if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
-
- // if execution has thrown then lazy.IsValueCreated is false
- // and lazy.IsValueFaulted is true (but internal) so we use our
- // own exception holder (see Lazy source code) to return null
- if (lazy.Value is ExceptionHolder) return null;
-
- // we have a value and execution has not thrown so returning
- // here does not throw - unless we're re-entering, take care of it
+ key = GetCacheKey(key);
+ Lazy result;
try
{
- return lazy.Value;
+ EnterReadLock();
+ result = GetEntry(key) as Lazy; // null if key not found
}
- catch (InvalidOperationException e)
+ finally
{
- throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e);
+ ExitReadLock();
}
+ return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
}
- internal class ExceptionHolder
+ ///
+ public abstract object Get(string key, Func factory);
+
+ ///
+ public virtual IEnumerable SearchByKey(string keyStartsWith)
{
- public ExceptionHolder(ExceptionDispatchInfo e)
+ var plen = CacheItemPrefix.Length + 1;
+ IEnumerable entries;
+ try
{
- Exception = e;
+ EnterReadLock();
+ entries = GetDictionaryEntries()
+ .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
+ .ToArray(); // evaluate while locked
+ }
+ finally
+ {
+ ExitReadLock();
}
- public ExceptionDispatchInfo Exception { get; }
+ return entries
+ .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
+ .Where(x => x != null); // backward compat, don't store null values in the cache
}
- #region Clear
+ ///
+ public virtual IEnumerable SearchByRegex(string regex)
+ {
+ const string prefix = CacheItemPrefix + "-";
+ var compiled = new Regex(regex, RegexOptions.Compiled);
+ var plen = prefix.Length;
+ IEnumerable entries;
+ try
+ {
+ EnterReadLock();
+ entries = GetDictionaryEntries()
+ .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen)))
+ .ToArray(); // evaluate while locked
+ }
+ finally
+ {
+ ExitReadLock();
+ }
+ return entries
+ .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
+ .Where(x => x != null); // backward compat, don't store null values in the cache
+ }
- public virtual void ClearAllCache()
+ ///
+ public virtual void Clear()
{
try
{
@@ -106,7 +102,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheItem(string key)
+ ///
+ public virtual void Clear(string key)
{
var cacheKey = GetCacheKey(key);
try
@@ -120,7 +117,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheObjectTypes(string typeName)
+ ///
+ public virtual void ClearOfType(string typeName)
{
var type = TypeFinder.GetTypeByName(typeName);
if (type == null) return;
@@ -149,7 +147,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheObjectTypes()
+ ///
+ public virtual void ClearOfType()
{
var typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
@@ -178,7 +177,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheObjectTypes(Func predicate)
+ ///
+ public virtual void ClearOfType(Func predicate)
{
var typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
@@ -210,7 +210,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheByKeySearch(string keyStartsWith)
+ ///
+ public virtual void ClearByKey(string keyStartsWith)
{
var plen = CacheItemPrefix.Length + 1;
try
@@ -227,14 +228,16 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheByKeyExpression(string regexString)
+ ///
+ public virtual void ClearByRegex(string regex)
{
+ var compiled = new Regex(regex, RegexOptions.Compiled);
var plen = CacheItemPrefix.Length + 1;
try
{
EnterWriteLock();
foreach (var entry in GetDictionaryEntries()
- .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
+ .Where(x => compiled.IsMatch(((string)x.Key).Substring(plen)))
.ToArray())
RemoveEntry((string) entry.Key);
}
@@ -246,67 +249,80 @@ namespace Umbraco.Core.Cache
#endregion
- #region Get
+ #region Dictionary
- public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
+ // manipulate the underlying cache entries
+ // these *must* be called from within the appropriate locks
+ // and use the full prefixed cache keys
+ protected abstract IEnumerable GetDictionaryEntries();
+ protected abstract void RemoveEntry(string key);
+ protected abstract object GetEntry(string key);
+
+ // read-write lock the underlying cache
+ //protected abstract IDisposable ReadLock { get; }
+ //protected abstract IDisposable WriteLock { get; }
+
+ protected abstract void EnterReadLock();
+ protected abstract void ExitReadLock();
+ protected abstract void EnterWriteLock();
+ protected abstract void ExitWriteLock();
+
+ protected string GetCacheKey(string key)
{
- var plen = CacheItemPrefix.Length + 1;
- IEnumerable entries;
- try
- {
- EnterReadLock();
- entries = GetDictionaryEntries()
- .Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
- .ToArray(); // evaluate while locked
- }
- finally
- {
- ExitReadLock();
- }
-
- return entries
- .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
- .Where(x => x != null); // backward compat, don't store null values in the cache
+ return $"{CacheItemPrefix}-{key}";
}
- public virtual IEnumerable GetCacheItemsByKeyExpression(string regexString)
+ protected internal static Lazy GetSafeLazy(Func getCacheItem)
{
- const string prefix = CacheItemPrefix + "-";
- var plen = prefix.Length;
- IEnumerable entries;
- try
+ // try to generate the value and if it fails,
+ // wrap in an ExceptionHolder - would be much simpler
+ // to just use lazy.IsValueFaulted alas that field is
+ // internal
+ return new Lazy(() =>
{
- EnterReadLock();
- entries = GetDictionaryEntries()
- .Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
- .ToArray(); // evaluate while locked
- }
- finally
- {
- ExitReadLock();
- }
- return entries
- .Select(x => GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
- .Where(x => x != null); // backward compat, don't store null values in the cache
+ try
+ {
+ return getCacheItem();
+ }
+ catch (Exception e)
+ {
+ return new ExceptionHolder(ExceptionDispatchInfo.Capture(e));
+ }
+ });
}
- public virtual object GetCacheItem(string cacheKey)
+ protected internal static object GetSafeLazyValue(Lazy lazy, bool onlyIfValueIsCreated = false)
{
- cacheKey = GetCacheKey(cacheKey);
- Lazy result;
+ // if onlyIfValueIsCreated, do not trigger value creation
+ // must return something, though, to differentiate from null values
+ if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
+
+ // if execution has thrown then lazy.IsValueCreated is false
+ // and lazy.IsValueFaulted is true (but internal) so we use our
+ // own exception holder (see Lazy source code) to return null
+ if (lazy.Value is ExceptionHolder) return null;
+
+ // we have a value and execution has not thrown so returning
+ // here does not throw - unless we're re-entering, take care of it
try
{
- EnterReadLock();
- result = GetEntry(cacheKey) as Lazy; // null if key not found
+ return lazy.Value;
}
- finally
+ catch (InvalidOperationException e)
{
- ExitReadLock();
+ throw new InvalidOperationException("The method that computes a value for the cache has tried to read that value from the cache.", e);
}
- return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
}
- public abstract object GetCacheItem(string cacheKey, Func getCacheItem);
+ internal class ExceptionHolder
+ {
+ public ExceptionHolder(ExceptionDispatchInfo e)
+ {
+ Exception = e;
+ }
+
+ public ExceptionDispatchInfo Exception { get; }
+ }
#endregion
}
diff --git a/src/Umbraco.Core/Cache/FastDictionaryCacheProvider.cs b/src/Umbraco.Core/Cache/FastDictionaryCacheProvider.cs
new file mode 100644
index 0000000000..a3863dac52
--- /dev/null
+++ b/src/Umbraco.Core/Cache/FastDictionaryCacheProvider.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Implements a fast on top of a concurrent dictionary.
+ ///
+ internal class FastDictionaryCacheProvider : IAppCache
+ {
+ ///
+ /// Gets the internal items dictionary, for tests only!
+ ///
+ internal readonly ConcurrentDictionary> Items = new ConcurrentDictionary>();
+
+ ///
+ public object Get(string cacheKey)
+ {
+ Items.TryGetValue(cacheKey, out var result); // else null
+ return result == null ? null : FastDictionaryAppCacheBase.GetSafeLazyValue(result); // return exceptions as null
+ }
+
+ ///
+ public object Get(string cacheKey, Func getCacheItem)
+ {
+ var result = Items.GetOrAdd(cacheKey, k => FastDictionaryAppCacheBase.GetSafeLazy(getCacheItem));
+
+ var value = result.Value; // will not throw (safe lazy)
+ if (!(value is FastDictionaryAppCacheBase.ExceptionHolder eh))
+ return value;
+
+ // and... it's in the cache anyway - so contrary to other cache providers,
+ // which would trick with GetSafeLazyValue, we need to remove by ourselves,
+ // in order NOT to cache exceptions
+
+ Items.TryRemove(cacheKey, out result);
+ eh.Exception.Throw(); // throw once!
+ return null; // never reached
+ }
+
+ ///
+ public IEnumerable SearchByKey(string keyStartsWith)
+ {
+ return Items
+ .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))
+ .Select(kvp => FastDictionaryAppCacheBase.GetSafeLazyValue(kvp.Value))
+ .Where(x => x != null);
+ }
+
+ ///
+ public IEnumerable SearchByRegex(string regex)
+ {
+ var compiled = new Regex(regex, RegexOptions.Compiled);
+ return Items
+ .Where(kvp => compiled.IsMatch(kvp.Key))
+ .Select(kvp => FastDictionaryAppCacheBase.GetSafeLazyValue(kvp.Value))
+ .Where(x => x != null);
+ }
+
+ ///
+ public void Clear()
+ {
+ Items.Clear();
+ }
+
+ ///
+ public void Clear(string key)
+ {
+ Items.TryRemove(key, out _);
+ }
+
+ ///
+ public void ClearOfType(string typeName)
+ {
+ var type = TypeFinder.GetTypeByName(typeName);
+ if (type == null) return;
+ var isInterface = type.IsInterface;
+
+ foreach (var kvp in Items
+ .Where(x =>
+ {
+ // entry.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = FastDictionaryAppCacheBase.GetSafeLazyValue(x.Value, true);
+
+ // if T is an interface remove anything that implements that interface
+ // otherwise remove exact types (not inherited types)
+ return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type));
+ }))
+ Items.TryRemove(kvp.Key, out _);
+ }
+
+ ///
+ public void ClearOfType()
+ {
+ var typeOfT = typeof(T);
+ var isInterface = typeOfT.IsInterface;
+
+ foreach (var kvp in Items
+ .Where(x =>
+ {
+ // entry.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // compare on exact type, don't use "is"
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = FastDictionaryAppCacheBase.GetSafeLazyValue(x.Value, true);
+
+ // if T is an interface remove anything that implements that interface
+ // otherwise remove exact types (not inherited types)
+ return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT));
+ }))
+ Items.TryRemove(kvp.Key, out _);
+ }
+
+ ///
+ public void ClearOfType(Func predicate)
+ {
+ var typeOfT = typeof(T);
+ var isInterface = typeOfT.IsInterface;
+
+ foreach (var kvp in Items
+ .Where(x =>
+ {
+ // entry.Value is Lazy and not null, its value may be null
+ // remove null values as well, does not hurt
+ // compare on exact type, don't use "is"
+ // get non-created as NonCreatedValue & exceptions as null
+ var value = FastDictionaryAppCacheBase.GetSafeLazyValue(x.Value, true);
+ if (value == null) return true;
+
+ // if T is an interface remove anything that implements that interface
+ // otherwise remove exact types (not inherited types)
+ return (isInterface ? (value is T) : (value.GetType() == typeOfT))
+ // run predicate on the 'public key' part only, ie without prefix
+ && predicate(x.Key, (T)value);
+ }))
+ Items.TryRemove(kvp.Key, out _);
+ }
+
+ ///
+ public void ClearByKey(string keyStartsWith)
+ {
+ foreach (var ikvp in Items
+ .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)))
+ Items.TryRemove(ikvp.Key, out _);
+ }
+
+ ///
+ public void ClearByRegex(string regex)
+ {
+ var compiled = new Regex(regex, RegexOptions.Compiled);
+ foreach (var ikvp in Items
+ .Where(kvp => compiled.IsMatch(kvp.Key)))
+ Items.TryRemove(ikvp.Key, out _);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs
index 319d84d41f..3bc4c9d059 100644
--- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs
+++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Cache
private readonly Func _entityGetId;
private readonly bool _expires;
- public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, Func entityGetId, bool expires)
+ public FullDataSetRepositoryCachePolicy(IAppPolicedCache cache, IScopeAccessor scopeAccessor, Func entityGetId, bool expires)
: base(cache, scopeAccessor)
{
_entityGetId = entityGetId;
@@ -55,11 +55,11 @@ namespace Umbraco.Core.Cache
if (_expires)
{
- Cache.InsertCacheItem(key, () => new DeepCloneableList(entities), TimeSpan.FromMinutes(5), true);
+ Cache.Insert(key, () => new DeepCloneableList(entities), TimeSpan.FromMinutes(5), true);
}
else
{
- Cache.InsertCacheItem(key, () => new DeepCloneableList(entities));
+ Cache.Insert(key, () => new DeepCloneableList(entities));
}
}
@@ -171,7 +171,7 @@ namespace Umbraco.Core.Cache
///
public override void ClearAll()
{
- Cache.ClearCacheItem(GetEntityTypeCacheKey());
+ Cache.Clear(GetEntityTypeCacheKey());
}
}
}
diff --git a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs
similarity index 53%
rename from src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs
rename to src/Umbraco.Core/Cache/HttpRequestAppCache.cs
index 52c230ff71..dcb2621d75 100644
--- a/src/Umbraco.Core/Cache/HttpRequestCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/HttpRequestAppCache.cs
@@ -7,54 +7,83 @@ using System.Web;
namespace Umbraco.Core.Cache
{
///
- /// A cache provider that caches items in the HttpContext.Items
+ /// Implements a fast on top of HttpContext.Items.
///
///
- /// If the Items collection is null, then this provider has no effect
+ /// If no current HttpContext items can be found (no current HttpContext,
+ /// or no Items...) then this cache acts as a pass-through and does not cache
+ /// anything.
///
- internal class HttpRequestCacheProvider : DictionaryCacheProviderBase
+ internal class HttpRequestAppCache : FastDictionaryAppCacheBase
{
- // context provider
- // the idea is that there is only one, application-wide HttpRequestCacheProvider instance,
- // that is initialized with a method that returns the "current" context.
- // NOTE
- // but then it is initialized with () => new HttpContextWrapper(HttpContent.Current)
- // which is higly inefficient because it creates a new wrapper each time we refer to _context()
- // so replace it with _context1 and _context2 below + a way to get context.Items.
- //private readonly Func _context;
-
- // NOTE
- // and then in almost 100% cases _context2 will be () => HttpContext.Current
- // so why not bring that logic in here and fallback on to HttpContext.Current when
- // _context1 is null?
- //private readonly HttpContextBase _context1;
- //private readonly Func _context2;
private readonly HttpContextBase _context;
- private IDictionary ContextItems
- {
- //get { return _context1 != null ? _context1.Items : _context2().Items; }
- get { return _context != null ? _context.Items : HttpContext.Current.Items; }
- }
-
- private bool HasContextItems
- {
- get { return (_context != null && _context.Items != null) || HttpContext.Current != null; }
- }
-
- // for unit tests
- public HttpRequestCacheProvider(HttpContextBase context)
+ ///
+ /// Initializes a new instance of the class with a context, for unit tests!
+ ///
+ public HttpRequestAppCache(HttpContextBase context)
{
_context = context;
}
- // main constructor
- // will use HttpContext.Current
- public HttpRequestCacheProvider(/*Func context*/)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// Will use HttpContext.Current.
+ /// fixme - should use IHttpContextAccessor
+ ///
+ public HttpRequestAppCache()
+ { }
+
+ private IDictionary ContextItems => _context?.Items ?? HttpContext.Current?.Items;
+
+ private bool HasContextItems => _context?.Items != null || HttpContext.Current != null;
+
+ ///
+ public override object Get(string key, Func factory)
{
- //_context2 = context;
+ //no place to cache so just return the callback result
+ if (HasContextItems == false) return factory();
+
+ key = GetCacheKey(key);
+
+ Lazy result;
+
+ try
+ {
+ EnterWriteLock();
+ result = ContextItems[key] as Lazy; // null if key not found
+
+ // cannot create value within the lock, so if result.IsValueCreated is false, just
+ // do nothing here - means that if creation throws, a race condition could cause
+ // more than one thread to reach the return statement below and throw - accepted.
+
+ if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
+ {
+ result = GetSafeLazy(factory);
+ ContextItems[key] = result;
+ }
+ }
+ finally
+ {
+ ExitWriteLock();
+ }
+
+ // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
+ // exceptions (but try again and again) and silently eat them - however at
+ // some point we have to report them - so need to re-throw here
+
+ // this does not throw anymore
+ //return result.Value;
+
+ var value = result.Value; // will not throw (safe lazy)
+ if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once!
+ return value;
}
+ #region Entries
+
protected override IEnumerable GetDictionaryEntries()
{
const string prefix = CacheItemPrefix + "-";
@@ -62,7 +91,7 @@ namespace Umbraco.Core.Cache
if (HasContextItems == false) return Enumerable.Empty();
return ContextItems.Cast()
- .Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix));
+ .Where(x => x.Key is string s && s.StartsWith(prefix));
}
protected override void RemoveEntry(string key)
@@ -77,6 +106,8 @@ namespace Umbraco.Core.Cache
return HasContextItems ? ContextItems[key] : null;
}
+ #endregion
+
#region Lock
private bool _entered;
@@ -103,59 +134,5 @@ namespace Umbraco.Core.Cache
}
#endregion
-
- #region Get
-
- public override object GetCacheItem(string cacheKey, Func getCacheItem)
- {
- //no place to cache so just return the callback result
- if (HasContextItems == false) return getCacheItem();
-
- cacheKey = GetCacheKey(cacheKey);
-
- Lazy result;
-
- try
- {
- EnterWriteLock();
- result = ContextItems[cacheKey] as Lazy; // null if key not found
-
- // cannot create value within the lock, so if result.IsValueCreated is false, just
- // do nothing here - means that if creation throws, a race condition could cause
- // more than one thread to reach the return statement below and throw - accepted.
-
- if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
- {
- result = GetSafeLazy(getCacheItem);
- ContextItems[cacheKey] = result;
- }
- }
- finally
- {
- ExitWriteLock();
- }
-
- // using GetSafeLazy and GetSafeLazyValue ensures that we don't cache
- // exceptions (but try again and again) and silently eat them - however at
- // some point we have to report them - so need to re-throw here
-
- // this does not throw anymore
- //return result.Value;
-
- var value = result.Value; // will not throw (safe lazy)
- if (value is ExceptionHolder eh) eh.Exception.Throw(); // throw once!
- return value;
- }
-
- #endregion
-
- #region Insert
- #endregion
-
- private class NoopLocker : DisposableObjectSlim
- {
- protected override void DisposeResources()
- { }
- }
}
}
diff --git a/src/Umbraco.Core/Cache/IAppCache.cs b/src/Umbraco.Core/Cache/IAppCache.cs
new file mode 100644
index 0000000000..674781f6d6
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IAppCache.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Defines an application cache.
+ ///
+ public interface IAppCache
+ {
+ ///
+ /// Gets an item identified by its key.
+ ///
+ /// The key of the item.
+ /// The item, or null if the item was not found.
+ object Get(string key);
+
+ ///
+ /// Gets or creates an item identified by its key.
+ ///
+ /// The key of the item.
+ /// A factory function that can create the item.
+ /// The item.
+ object Get(string key, Func factory);
+
+ ///
+ /// Gets items with a key starting with the specified value.
+ ///
+ /// The StartsWith value to use in the search.
+ /// Items matching the search.
+ IEnumerable SearchByKey(string keyStartsWith);
+
+ ///
+ /// Gets items with a key matching a regular expression.
+ ///
+ /// The regular expression.
+ /// Items matching the search.
+ IEnumerable SearchByRegex(string regex);
+
+ ///
+ /// Removes all items from the cache.
+ ///
+ void Clear();
+
+ ///
+ /// Removes an item identified by its key from the cache.
+ ///
+ /// The key of the item.
+ void Clear(string key);
+
+ ///
+ /// Removes items of a specified type from the cache.
+ ///
+ /// The name of the type to remove.
+ ///
+ /// If the type is an interface, then all items of a type implementing that interface are
+ /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
+ /// the specified type are not removed).
+ /// Performs a case-sensitive search.
+ ///
+ void ClearOfType(string typeName);
+
+ ///
+ /// Removes items of a specified type from the cache.
+ ///
+ /// The type of the items to remove.
+ /// If the type is an interface, then all items of a type implementing that interface are
+ /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
+ /// the specified type are not removed).
+ void ClearOfType();
+
+ ///
+ /// Removes items of a specified type from the cache.
+ ///
+ /// The type of the items to remove.
+ /// The predicate to satisfy.
+ /// If the type is an interface, then all items of a type implementing that interface are
+ /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
+ /// the specified type are not removed).
+ void ClearOfType(Func predicate);
+
+ ///
+ /// Clears items with a key starting with the specified value.
+ ///
+ /// The StartsWith value to use in the search.
+ void ClearByKey(string keyStartsWith);
+
+ ///
+ /// Clears items with a key matching a regular expression.
+ ///
+ /// The regular expression.
+ void ClearByRegex(string regex);
+ }
+}
diff --git a/src/Umbraco.Core/Cache/IAppPolicedCache.cs b/src/Umbraco.Core/Cache/IAppPolicedCache.cs
new file mode 100644
index 0000000000..0aee7584df
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IAppPolicedCache.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Web.Caching;
+
+// fixme should this be/support non-web?
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Defines an application cache that support cache policies.
+ ///
+ /// A cache policy can be used to cache with timeouts,
+ /// or depending on files, and with a remove callback, etc.
+ public interface IAppPolicedCache : IAppCache
+ {
+ ///
+ /// Gets an item identified by its key.
+ ///
+ /// The key of the item.
+ /// A factory function that can create the item.
+ /// An optional cache timeout.
+ /// An optional value indicating whether the cache timeout is sliding (default is false).
+ /// An optional cache priority (default is Normal).
+ /// An optional callback to handle removals.
+ /// Files the cache entry depends on.
+ /// The item.
+ object Get(
+ string key,
+ Func factory,
+ TimeSpan? timeout,
+ bool isSliding = false,
+ CacheItemPriority priority = CacheItemPriority.Normal,
+ CacheItemRemovedCallback removedCallback = null,
+ string[] dependentFiles = null);
+
+ ///
+ /// Inserts an item.
+ ///
+ /// The key of the item.
+ /// A factory function that can create the item.
+ /// An optional cache timeout.
+ /// An optional value indicating whether the cache timeout is sliding (default is false).
+ /// An optional cache priority (default is Normal).
+ /// An optional callback to handle removals.
+ /// Files the cache entry depends on.
+ void Insert(
+ string key,
+ Func factory,
+ TimeSpan? timeout = null,
+ bool isSliding = false,
+ CacheItemPriority priority = CacheItemPriority.Normal,
+ CacheItemRemovedCallback removedCallback = null,
+ string[] dependentFiles = null);
+ }
+}
diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs
deleted file mode 100644
index 177f7570c2..0000000000
--- a/src/Umbraco.Core/Cache/ICacheProvider.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Umbraco.Core.Cache
-{
- ///
- /// An interface for implementing a basic cache provider
- ///
- public interface ICacheProvider
- {
- ///
- /// Removes all items from the cache.
- ///
- void ClearAllCache();
-
- ///
- /// Removes an item from the cache, identified by its key.
- ///
- /// The key of the item.
- void ClearCacheItem(string key);
-
- ///
- /// Removes items from the cache, of a specified type.
- ///
- /// The name of the type to remove.
- ///
- /// If the type is an interface, then all items of a type implementing that interface are
- /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
- /// the specified type are not removed).
- /// Performs a case-sensitive search.
- ///
- void ClearCacheObjectTypes(string typeName);
-
- ///
- /// Removes items from the cache, of a specified type.
- ///
- /// The type of the items to remove.
- /// If the type is an interface, then all items of a type implementing that interface are
- /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
- /// the specified type are not removed).
- void ClearCacheObjectTypes();
-
- ///
- /// Removes items from the cache, of a specified type, satisfying a predicate.
- ///
- /// The type of the items to remove.
- /// The predicate to satisfy.
- /// If the type is an interface, then all items of a type implementing that interface are
- /// removed. Otherwise, only items of that exact type are removed (items of type inheriting from
- /// the specified type are not removed).
- void ClearCacheObjectTypes(Func predicate);
-
- void ClearCacheByKeySearch(string keyStartsWith);
- void ClearCacheByKeyExpression(string regexString);
-
- IEnumerable GetCacheItemsByKeySearch(string keyStartsWith);
- IEnumerable GetCacheItemsByKeyExpression(string regexString);
-
- ///
- /// Returns an item with a given key
- ///
- ///
- ///
- object GetCacheItem(string cacheKey);
-
- object GetCacheItem(string cacheKey, Func getCacheItem);
- }
-}
diff --git a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs
deleted file mode 100644
index 9f3d687e1d..0000000000
--- a/src/Umbraco.Core/Cache/IRuntimeCacheProvider.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Runtime.Caching;
-using System.Text;
-using System.Web.Caching;
-using CacheItemPriority = System.Web.Caching.CacheItemPriority;
-
-namespace Umbraco.Core.Cache
-{
- ///
- /// An abstract class for implementing a runtime cache provider
- ///
- ///
- ///
- public interface IRuntimeCacheProvider : ICacheProvider
- {
- object GetCacheItem(
- string cacheKey,
- Func getCacheItem,
- TimeSpan? timeout,
- bool isSliding = false,
- CacheItemPriority priority = CacheItemPriority.Normal,
- CacheItemRemovedCallback removedCallback = null,
- string[] dependentFiles = null);
-
- void InsertCacheItem(
- string cacheKey,
- Func getCacheItem,
- TimeSpan? timeout = null,
- bool isSliding = false,
- CacheItemPriority priority = CacheItemPriority.Normal,
- CacheItemRemovedCallback removedCallback = null,
- string[] dependentFiles = null);
-
- }
-}
diff --git a/src/Umbraco.Core/Cache/IsolatedCaches.cs b/src/Umbraco.Core/Cache/IsolatedCaches.cs
new file mode 100644
index 0000000000..bc624be20d
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IsolatedCaches.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Represents a dictionary of for types.
+ ///
+ ///
+ /// Isolated caches are used by e.g. repositories, to ensure that each cached entity
+ /// type has its own cache, so that lookups are fast and the repository does not need to
+ /// search through all keys on a global scale.
+ ///
+ public class IsolatedCaches : AppPolicedCacheDictionary
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ public IsolatedCaches(Func cacheFactory)
+ : base(cacheFactory)
+ { }
+
+ ///
+ /// Gets a cache.
+ ///
+ public IAppPolicedCache GetOrCreate()
+ => GetOrCreate(typeof(T));
+
+ ///
+ /// Tries to get a cache.
+ ///
+ public Attempt Get()
+ => Get(typeof(T));
+
+ ///
+ /// Clears a cache.
+ ///
+ public void ClearCache()
+ => ClearCache(typeof(T));
+ }
+}
diff --git a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs
deleted file mode 100644
index ee73a17532..0000000000
--- a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-
-namespace Umbraco.Core.Cache
-{
- ///
- /// Used to get/create/manipulate isolated runtime cache
- ///
- ///
- /// This is useful for repository level caches to ensure that cache lookups by key are fast so
- /// that the repository doesn't need to search through all keys on a global scale.
- ///
- public class IsolatedRuntimeCache
- {
- internal Func CacheFactory { get; set; }
-
- ///
- /// Constructor that allows specifying a factory for the type of runtime isolated cache to create
- ///
- ///
- public IsolatedRuntimeCache(Func cacheFactory)
- {
- CacheFactory = cacheFactory;
- }
-
- private readonly ConcurrentDictionary _isolatedCache = new ConcurrentDictionary();
-
- ///
- /// Returns an isolated runtime cache for a given type
- ///
- ///
- ///
- public IRuntimeCacheProvider GetOrCreateCache()
- {
- return _isolatedCache.GetOrAdd(typeof(T), type => CacheFactory(type));
- }
-
- ///
- /// Returns an isolated runtime cache for a given type
- ///
- ///
- public IRuntimeCacheProvider GetOrCreateCache(Type type)
- {
- return _isolatedCache.GetOrAdd(type, t => CacheFactory(t));
- }
-
- ///
- /// Tries to get a cache by the type specified
- ///
- ///
- ///
- public Attempt GetCache()
- {
- IRuntimeCacheProvider cache;
- if (_isolatedCache.TryGetValue(typeof(T), out cache))
- {
- return Attempt.Succeed(cache);
- }
- return Attempt.Fail();
- }
-
- ///
- /// Clears all values inside this isolated runtime cache
- ///
- ///
- ///
- public void ClearCache()
- {
- IRuntimeCacheProvider cache;
- if (_isolatedCache.TryGetValue(typeof(T), out cache))
- {
- cache.ClearAllCache();
- }
- }
-
- ///
- /// Clears all of the isolated caches
- ///
- public void ClearAllCaches()
- {
- foreach (var key in _isolatedCache.Keys)
- {
- IRuntimeCacheProvider cache;
- if (_isolatedCache.TryRemove(key, out cache))
- {
- cache.ClearAllCache();
- }
- }
- }
- }
-}
diff --git a/src/Umbraco.Core/Cache/NoAppCache.cs b/src/Umbraco.Core/Cache/NoAppCache.cs
new file mode 100644
index 0000000000..8a7e15cb29
--- /dev/null
+++ b/src/Umbraco.Core/Cache/NoAppCache.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web.Caching;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Implements and do not cache.
+ ///
+ public class NoAppCache : IAppPolicedCache
+ {
+ private NoAppCache() { }
+
+ ///
+ /// Gets the singleton instance.
+ ///
+ public static NoAppCache Instance { get; } = new NoAppCache();
+
+ ///
+ public virtual object Get(string cacheKey)
+ {
+ return null;
+ }
+
+ ///
+ public virtual object Get(string cacheKey, Func factory)
+ {
+ return factory();
+ }
+
+ ///
+ public virtual IEnumerable SearchByKey(string keyStartsWith)
+ {
+ return Enumerable.Empty();
+ }
+
+ ///
+ public IEnumerable SearchByRegex(string regex)
+ {
+ return Enumerable.Empty();
+ }
+
+ ///
+ public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ {
+ return factory();
+ }
+
+ ///
+ public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ { }
+
+ ///
+ public virtual void Clear()
+ { }
+
+ ///
+ public virtual void Clear(string key)
+ { }
+
+ ///
+ public virtual void ClearOfType(string typeName)
+ { }
+
+ ///
+ public virtual void ClearOfType()
+ { }
+
+ ///
+ public virtual void ClearOfType(Func predicate)
+ { }
+
+ ///
+ public virtual void ClearByKey(string keyStartsWith)
+ { }
+
+ ///
+ public virtual void ClearByRegex(string regex)
+ { }
+ }
+}
diff --git a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs
index a3e7335d7f..acc67be679 100644
--- a/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs
+++ b/src/Umbraco.Core/Cache/NoCacheRepositoryCachePolicy.cs
@@ -13,7 +13,7 @@ namespace Umbraco.Core.Cache
public static NoCacheRepositoryCachePolicy Instance { get; } = new NoCacheRepositoryCachePolicy();
- public IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope)
+ public IRepositoryCachePolicy Scoped(IAppPolicedCache runtimeCache, IScope scope)
{
throw new NotImplementedException();
}
diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs
deleted file mode 100644
index 78286f75e2..0000000000
--- a/src/Umbraco.Core/Cache/NullCacheProvider.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Web.Caching;
-
-namespace Umbraco.Core.Cache
-{
- ///
- /// Represents a cache provider that does not cache anything.
- ///
- public class NullCacheProvider : IRuntimeCacheProvider
- {
- private NullCacheProvider() { }
-
- public static NullCacheProvider Instance { get; } = new NullCacheProvider();
-
- public virtual void ClearAllCache()
- { }
-
- public virtual void ClearCacheItem(string key)
- { }
-
- public virtual void ClearCacheObjectTypes(string typeName)
- { }
-
- public virtual void ClearCacheObjectTypes()
- { }
-
- public virtual void ClearCacheObjectTypes(Func predicate)
- { }
-
- public virtual void ClearCacheByKeySearch(string keyStartsWith)
- { }
-
- public virtual void ClearCacheByKeyExpression(string regexString)
- { }
-
- public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
- {
- return Enumerable.Empty();
- }
-
- public IEnumerable GetCacheItemsByKeyExpression(string regexString)
- {
- return Enumerable.Empty();
- }
-
- public virtual object GetCacheItem(string cacheKey)
- {
- return default(object);
- }
-
- public virtual object GetCacheItem(string cacheKey, Func getCacheItem)
- {
- return getCacheItem();
- }
-
- public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
- {
- return getCacheItem();
- }
-
- public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
- { }
- }
-}
diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs
similarity index 73%
rename from src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
rename to src/Umbraco.Core/Cache/ObjectCacheAppCache.cs
index 8a844bbc9b..9c5917a53c 100644
--- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/ObjectCacheAppCache.cs
@@ -11,31 +11,142 @@ using CacheItemPriority = System.Web.Caching.CacheItemPriority;
namespace Umbraco.Core.Cache
{
///
- /// Represents a cache provider that caches item in a .
- /// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache
+ /// Implements on top of a .
///
- /// The is created with name "in-memory". That name is
- /// used to retrieve configuration options. It does not identify the memory cache, i.e.
- /// each instance of this class has its own, independent, memory cache.
- public class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
+ public class ObjectCacheAppCache : IAppPolicedCache
{
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
- internal ObjectCache MemoryCache;
///
- /// Used for debugging
+ /// Initializes a new instance of the .
///
- internal Guid InstanceId { get; private set; }
-
- public ObjectCacheRuntimeCacheProvider()
+ public ObjectCacheAppCache()
{
+ // the MemoryCache is created with name "in-memory". That name is
+ // used to retrieve configuration options. It does not identify the memory cache, i.e.
+ // each instance of this class has its own, independent, memory cache.
MemoryCache = new MemoryCache("in-memory");
- InstanceId = Guid.NewGuid();
}
- #region Clear
+ ///
+ /// Gets the internal memory cache, for tests only!
+ ///
+ internal readonly ObjectCache MemoryCache;
- public virtual void ClearAllCache()
+ ///
+ public object Get(string key)
+ {
+ Lazy result;
+ try
+ {
+ _locker.EnterReadLock();
+ result = MemoryCache.Get(key) as Lazy; // null if key not found
+ }
+ finally
+ {
+ if (_locker.IsReadLockHeld)
+ _locker.ExitReadLock();
+ }
+ return result == null ? null : FastDictionaryAppCacheBase.GetSafeLazyValue(result); // return exceptions as null
+ }
+
+ ///
+ public object Get(string key, Func factory)
+ {
+ return Get(key, factory, null);
+ }
+
+ ///
+ public IEnumerable SearchByKey(string keyStartsWith)
+ {
+ KeyValuePair[] entries;
+ try
+ {
+ _locker.EnterReadLock();
+ entries = MemoryCache
+ .Where(x => x.Key.InvariantStartsWith(keyStartsWith))
+ .ToArray(); // evaluate while locked
+ }
+ finally
+ {
+ if (_locker.IsReadLockHeld)
+ _locker.ExitReadLock();
+ }
+ return entries
+ .Select(x => FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
+ .Where(x => x != null) // backward compat, don't store null values in the cache
+ .ToList();
+ }
+
+ ///
+ public IEnumerable SearchByRegex(string regex)
+ {
+ var compiled = new Regex(regex, RegexOptions.Compiled);
+
+ KeyValuePair[] entries;
+ try
+ {
+ _locker.EnterReadLock();
+ entries = MemoryCache
+ .Where(x => compiled.IsMatch(x.Key))
+ .ToArray(); // evaluate while locked
+ }
+ finally
+ {
+ if (_locker.IsReadLockHeld)
+ _locker.ExitReadLock();
+ }
+ return entries
+ .Select(x => FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
+ .Where(x => x != null) // backward compat, don't store null values in the cache
+ .ToList();
+ }
+
+ ///
+ public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ {
+ // see notes in HttpRuntimeCacheProvider
+
+ Lazy result;
+
+ using (var lck = new UpgradeableReadLock(_locker))
+ {
+ result = MemoryCache.Get(key) as Lazy;
+ if (result == null || FastDictionaryAppCacheBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
+ {
+ result = FastDictionaryAppCacheBase.GetSafeLazy(factory);
+ var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
+
+ lck.UpgradeToWriteLock();
+ //NOTE: This does an add or update
+ MemoryCache.Set(key, result, policy);
+ }
+ }
+
+ //return result.Value;
+
+ var value = result.Value; // will not throw (safe lazy)
+ if (value is FastDictionaryAppCacheBase.ExceptionHolder eh) eh.Exception.Throw(); // throw once!
+ return value;
+ }
+
+ ///
+ public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ {
+ // NOTE - here also we must insert a Lazy but we can evaluate it right now
+ // and make sure we don't store a null value.
+
+ var result = FastDictionaryAppCacheBase.GetSafeLazy(factory);
+ var value = result.Value; // force evaluation now
+ if (value == null) return; // do not store null values (backward compat)
+
+ var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
+ //NOTE: This does an add or update
+ MemoryCache.Set(key, result, policy);
+ }
+
+ ///
+ public virtual void Clear()
{
try
{
@@ -50,7 +161,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheItem(string key)
+ ///
+ public virtual void Clear(string key)
{
try
{
@@ -65,7 +177,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheObjectTypes(string typeName)
+ ///
+ public virtual void ClearOfType(string typeName)
{
var type = TypeFinder.GetTypeByName(typeName);
if (type == null) return;
@@ -79,7 +192,7 @@ namespace Umbraco.Core.Cache
// x.Value is Lazy and not null, its value may be null
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
- var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true);
+ var value = FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value, true);
// if T is an interface remove anything that implements that interface
// otherwise remove exact types (not inherited types)
@@ -96,12 +209,13 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheObjectTypes()
+ ///
+ public virtual void ClearOfType()
{
try
{
_locker.EnterWriteLock();
- var typeOfT = typeof (T);
+ var typeOfT = typeof(T);
var isInterface = typeOfT.IsInterface;
foreach (var key in MemoryCache
.Where(x =>
@@ -109,7 +223,7 @@ namespace Umbraco.Core.Cache
// x.Value is Lazy and not null, its value may be null
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
- var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true);
+ var value = FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value, true);
// if T is an interface remove anything that implements that interface
// otherwise remove exact types (not inherited types)
@@ -127,7 +241,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheObjectTypes(Func predicate)
+ ///
+ public virtual void ClearOfType(Func predicate)
{
try
{
@@ -140,7 +255,7 @@ namespace Umbraco.Core.Cache
// x.Value is Lazy and not null, its value may be null
// remove null values as well, does not hurt
// get non-created as NonCreatedValue & exceptions as null
- var value = DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value, true);
+ var value = FastDictionaryAppCacheBase.GetSafeLazyValue((Lazy)x.Value, true);
if (value == null) return true;
// if T is an interface remove anything that implements that interface
@@ -159,7 +274,8 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheByKeySearch(string keyStartsWith)
+ ///
+ public virtual void ClearByKey(string keyStartsWith)
{
try
{
@@ -177,13 +293,16 @@ namespace Umbraco.Core.Cache
}
}
- public virtual void ClearCacheByKeyExpression(string regexString)
+ ///
+ public virtual void ClearByRegex(string regex)
{
+ var compiled = new Regex(regex, RegexOptions.Compiled);
+
try
{
_locker.EnterWriteLock();
foreach (var key in MemoryCache
- .Where(x => Regex.IsMatch(x.Key, regexString))
+ .Where(x => compiled.IsMatch(x.Key))
.Select(x => x.Key)
.ToArray()) // ToArray required to remove
MemoryCache.Remove(key);
@@ -195,120 +314,6 @@ namespace Umbraco.Core.Cache
}
}
- #endregion
-
- #region Get
-
- public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
- {
- KeyValuePair[] entries;
- try
- {
- _locker.EnterReadLock();
- entries = MemoryCache
- .Where(x => x.Key.InvariantStartsWith(keyStartsWith))
- .ToArray(); // evaluate while locked
- }
- finally
- {
- if (_locker.IsReadLockHeld)
- _locker.ExitReadLock();
- }
- return entries
- .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
- .Where(x => x != null) // backward compat, don't store null values in the cache
- .ToList();
- }
-
- public IEnumerable GetCacheItemsByKeyExpression(string regexString)
- {
- KeyValuePair[] entries;
- try
- {
- _locker.EnterReadLock();
- entries = MemoryCache
- .Where(x => Regex.IsMatch(x.Key, regexString))
- .ToArray(); // evaluate while locked
- }
- finally
- {
- if (_locker.IsReadLockHeld)
- _locker.ExitReadLock();
- }
- return entries
- .Select(x => DictionaryCacheProviderBase.GetSafeLazyValue((Lazy)x.Value)) // return exceptions as null
- .Where(x => x != null) // backward compat, don't store null values in the cache
- .ToList();
- }
-
- public object GetCacheItem(string cacheKey)
- {
- Lazy result;
- try
- {
- _locker.EnterReadLock();
- result = MemoryCache.Get(cacheKey) as Lazy; // null if key not found
- }
- finally
- {
- if (_locker.IsReadLockHeld)
- _locker.ExitReadLock();
- }
- return result == null ? null : DictionaryCacheProviderBase.GetSafeLazyValue(result); // return exceptions as null
- }
-
- public object GetCacheItem(string cacheKey, Func getCacheItem)
- {
- return GetCacheItem(cacheKey, getCacheItem, null);
- }
-
- public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
- {
- // see notes in HttpRuntimeCacheProvider
-
- Lazy result;
-
- using (var lck = new UpgradeableReadLock(_locker))
- {
- result = MemoryCache.Get(cacheKey) as Lazy;
- if (result == null || DictionaryCacheProviderBase.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
- {
- result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
- var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
-
- lck.UpgradeToWriteLock();
- //NOTE: This does an add or update
- MemoryCache.Set(cacheKey, result, policy);
- }
- }
-
- //return result.Value;
-
- var value = result.Value; // will not throw (safe lazy)
- if (value is DictionaryCacheProviderBase.ExceptionHolder eh) eh.Exception.Throw(); // throw once!
- return value;
- }
-
- #endregion
-
- #region Insert
-
- public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
- {
- // NOTE - here also we must insert a Lazy but we can evaluate it right now
- // and make sure we don't store a null value.
-
- var result = DictionaryCacheProviderBase.GetSafeLazy(getCacheItem);
- var value = result.Value; // force evaluation now
- if (value == null) return; // do not store null values (backward compat)
-
- var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
- //NOTE: This does an add or update
- MemoryCache.Set(cacheKey, result, policy);
- }
-
- #endregion
-
private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
{
var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs
index 2d21d410a7..f8bba4b033 100644
--- a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs
+++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs
@@ -13,16 +13,16 @@ namespace Umbraco.Core.Cache
internal abstract class RepositoryCachePolicyBase : IRepositoryCachePolicy
where TEntity : class, IEntity
{
- private readonly IRuntimeCacheProvider _globalCache;
+ private readonly IAppPolicedCache _globalCache;
private readonly IScopeAccessor _scopeAccessor;
- protected RepositoryCachePolicyBase(IRuntimeCacheProvider globalCache, IScopeAccessor scopeAccessor)
+ protected RepositoryCachePolicyBase(IAppPolicedCache globalCache, IScopeAccessor scopeAccessor)
{
_globalCache = globalCache ?? throw new ArgumentNullException(nameof(globalCache));
_scopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
}
- protected IRuntimeCacheProvider Cache
+ protected IAppPolicedCache Cache
{
get
{
@@ -32,9 +32,9 @@ namespace Umbraco.Core.Cache
case RepositoryCacheMode.Default:
return _globalCache;
case RepositoryCacheMode.Scoped:
- return ambientScope.IsolatedRuntimeCache.GetOrCreateCache();
+ return ambientScope.IsolatedCaches.GetOrCreate();
case RepositoryCacheMode.None:
- return NullCacheProvider.Instance;
+ return NoAppCache.Instance;
default:
throw new NotSupportedException($"Repository cache mode {ambientScope.RepositoryCacheMode} is not supported.");
}
diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs
index d89524d4f9..714798a47c 100644
--- a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs
+++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Core.Cache
internal class SingleItemsOnlyRepositoryCachePolicy : DefaultRepositoryCachePolicy
where TEntity : class, IEntity
{
- public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
+ public SingleItemsOnlyRepositoryCachePolicy(IAppPolicedCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
: base(cache, scopeAccessor, options)
{ }
diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs
deleted file mode 100644
index 1604add4d7..0000000000
--- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-
-namespace Umbraco.Core.Cache
-{
- ///
- /// Represents a cache provider that statically caches item in a concurrent dictionary.
- ///
- public class StaticCacheProvider : ICacheProvider
- {
- internal readonly ConcurrentDictionary StaticCache = new ConcurrentDictionary();
-
- public virtual void ClearAllCache()
- {
- StaticCache.Clear();
- }
-
- public virtual void ClearCacheItem(string key)
- {
- object val;
- StaticCache.TryRemove(key, out val);
- }
-
- public virtual void ClearCacheObjectTypes(string typeName)
- {
- StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType().ToString().InvariantEquals(typeName));
- }
-
- public virtual void ClearCacheObjectTypes()
- {
- var typeOfT = typeof(T);
- StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT);
- }
-
- public virtual void ClearCacheObjectTypes(Func predicate)
- {
- var typeOfT = typeof(T);
- StaticCache.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value));
- }
-
- public virtual void ClearCacheByKeySearch(string keyStartsWith)
- {
- StaticCache.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith));
- }
-
- public virtual void ClearCacheByKeyExpression(string regexString)
- {
- StaticCache.RemoveAll(kvp => Regex.IsMatch(kvp.Key, regexString));
- }
-
- public virtual IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
- {
- return (from KeyValuePair c in StaticCache
- where c.Key.InvariantStartsWith(keyStartsWith)
- select c.Value).ToList();
- }
-
- public IEnumerable GetCacheItemsByKeyExpression(string regexString)
- {
- return (from KeyValuePair c in StaticCache
- where Regex.IsMatch(c.Key, regexString)
- select c.Value).ToList();
- }
-
- public virtual object GetCacheItem(string cacheKey)
- {
- var result = StaticCache[cacheKey];
- return result;
- }
-
- public virtual object GetCacheItem(string cacheKey, Func getCacheItem)
- {
- return StaticCache.GetOrAdd(cacheKey, key => getCacheItem());
- }
- }
-}
diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs
similarity index 68%
rename from src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs
rename to src/Umbraco.Core/Cache/WebCachingAppCache.cs
index 835c5d1ee6..b762fcda07 100644
--- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs
@@ -4,14 +4,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Web.Caching;
-using CacheItemPriority = System.Web.Caching.CacheItemPriority;
namespace Umbraco.Core.Cache
{
///
+ /// Implements on top of a .
/// A CacheProvider that wraps the logic of the HttpRuntime.Cache
///
- internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider
+ /// The underlying cache is expected to be HttpRuntime.Cache.
+ internal class WebCachingAppCache : FastDictionaryAppCacheBase, IAppPolicedCache
{
// locker object that supports upgradeable read locking
// does not need to support recursion if we implement the cache correctly and ensure
@@ -21,16 +22,43 @@ namespace Umbraco.Core.Cache
private readonly System.Web.Caching.Cache _cache;
///
- /// Used for debugging
+ /// Initializes a new instance of the class.
///
- internal Guid InstanceId { get; private set; }
-
- public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache)
+ public WebCachingAppCache(System.Web.Caching.Cache cache)
{
_cache = cache;
- InstanceId = Guid.NewGuid();
}
+ ///
+ public override object Get(string key, Func factory)
+ {
+ return Get(key, factory, null, dependentFiles: null);
+ }
+
+ ///
+ public object Get(string key, Func factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ {
+ CacheDependency dependency = null;
+ if (dependentFiles != null && dependentFiles.Any())
+ {
+ dependency = new CacheDependency(dependentFiles);
+ }
+ return Get(key, factory, timeout, isSliding, priority, removedCallback, dependency);
+ }
+
+ ///
+ public void Insert(string key, Func factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
+ {
+ CacheDependency dependency = null;
+ if (dependentFiles != null && dependentFiles.Any())
+ {
+ dependency = new CacheDependency(dependentFiles);
+ }
+ Insert(key, factory, timeout, isSliding, priority, removedCallback, dependency);
+ }
+
+ #region Dictionary
+
protected override IEnumerable