diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Abstractions/Cache/CacheRefresherCollectionBuilder.cs similarity index 100% rename from src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs rename to src/Umbraco.Abstractions/Cache/CacheRefresherCollectionBuilder.cs diff --git a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs index 3bd29c3f5f..35ba4d08ca 100644 --- a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCache.cs @@ -15,21 +15,21 @@ namespace Umbraco.Core.Cache /// /// Gets the internal items dictionary, for tests only! /// - internal readonly ConcurrentDictionary> Items = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _items = new ConcurrentDictionary>(); - public int Count => Items.Count; + public int Count => _items.Count; /// public object Get(string cacheKey) { - Items.TryGetValue(cacheKey, out var result); // else null + _items.TryGetValue(cacheKey, out var result); // else null return result == null ? null : SafeLazy.GetSafeLazyValue(result); // return exceptions as null } /// public object Get(string cacheKey, Func getCacheItem) { - var result = Items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); + var result = _items.GetOrAdd(cacheKey, k => SafeLazy.GetSafeLazy(getCacheItem)); var value = result.Value; // will not throw (safe lazy) if (!(value is SafeLazy.ExceptionHolder eh)) @@ -39,7 +39,7 @@ namespace Umbraco.Core.Cache // which would trick with GetSafeLazyValue, we need to remove by ourselves, // in order NOT to cache exceptions - Items.TryRemove(cacheKey, out result); + _items.TryRemove(cacheKey, out result); eh.Exception.Throw(); // throw once! return null; // never reached } @@ -47,7 +47,7 @@ namespace Umbraco.Core.Cache /// public IEnumerable SearchByKey(string keyStartsWith) { - return Items + return _items .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith)) .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) .Where(x => x != null); @@ -57,7 +57,7 @@ namespace Umbraco.Core.Cache public IEnumerable SearchByRegex(string regex) { var compiled = new Regex(regex, RegexOptions.Compiled); - return Items + return _items .Where(kvp => compiled.IsMatch(kvp.Key)) .Select(kvp => SafeLazy.GetSafeLazyValue(kvp.Value)) .Where(x => x != null); @@ -66,13 +66,13 @@ namespace Umbraco.Core.Cache /// public void Clear() { - Items.Clear(); + _items.Clear(); } /// public void Clear(string key) { - Items.TryRemove(key, out _); + _items.TryRemove(key, out _); } /// @@ -82,7 +82,7 @@ namespace Umbraco.Core.Cache if (type == null) return; var isInterface = type.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -94,7 +94,7 @@ namespace Umbraco.Core.Cache // otherwise remove exact types (not inherited types) return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type)); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// @@ -103,7 +103,7 @@ namespace Umbraco.Core.Cache var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -116,7 +116,7 @@ namespace Umbraco.Core.Cache // otherwise remove exact types (not inherited types) return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT)); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// @@ -125,7 +125,7 @@ namespace Umbraco.Core.Cache var typeOfT = typeof(T); var isInterface = typeOfT.IsInterface; - foreach (var kvp in Items + foreach (var kvp in _items .Where(x => { // entry.Value is Lazy and not null, its value may be null @@ -141,24 +141,24 @@ namespace Umbraco.Core.Cache // run predicate on the 'public key' part only, ie without prefix && predicate(x.Key, (T)value); })) - Items.TryRemove(kvp.Key, out _); + _items.TryRemove(kvp.Key, out _); } /// public void ClearByKey(string keyStartsWith) { - foreach (var ikvp in Items + foreach (var ikvp in _items .Where(kvp => kvp.Key.InvariantStartsWith(keyStartsWith))) - Items.TryRemove(ikvp.Key, out _); + _items.TryRemove(ikvp.Key, out _); } /// public void ClearByRegex(string regex) { var compiled = new Regex(regex, RegexOptions.Compiled); - foreach (var ikvp in Items + foreach (var ikvp in _items .Where(kvp => compiled.IsMatch(kvp.Key))) - Items.TryRemove(ikvp.Key, out _); + _items.TryRemove(ikvp.Key, out _); } } } diff --git a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs similarity index 61% rename from src/Umbraco.Core/Cache/HttpRequestAppCache.cs rename to src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs index 018726538b..4fad382cc8 100644 --- a/src/Umbraco.Core/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs @@ -15,37 +15,29 @@ namespace Umbraco.Core.Cache /// or no Items...) then this cache acts as a pass-through and does not cache /// anything. /// - internal class HttpRequestAppCache : FastDictionaryAppCacheBase + public class HttpRequestAppCache : FastDictionaryAppCacheBase { - private readonly HttpContextBase _context; - /// /// Initializes a new instance of the class with a context, for unit tests! /// - public HttpRequestAppCache(HttpContextBase context) + public HttpRequestAppCache(Func requestItems) { - _context = context; + ContextItems = requestItems; } - /// - /// Initializes a new instance of the class. - /// - /// - /// Will use HttpContext.Current. - /// TODO: https://github.com/umbraco/Umbraco-CMS/issues/4239 - use IHttpContextAccessor NOT HttpContext.Current - /// - public HttpRequestAppCache() - { } + private Func ContextItems { get; } - private IDictionary ContextItems => _context?.Items ?? HttpContext.Current?.Items; - - private bool HasContextItems => _context?.Items != null || HttpContext.Current != null; + private bool TryGetContextItems(out IDictionary items) + { + items = ContextItems?.Invoke(); + return items != null; + } /// public override object Get(string key, Func factory) { //no place to cache so just return the callback result - if (HasContextItems == false) return factory(); + if (!TryGetContextItems(out var items)) return factory(); key = GetCacheKey(key); @@ -54,7 +46,7 @@ namespace Umbraco.Core.Cache try { EnterWriteLock(); - result = ContextItems[key] as Lazy; // null if key not found + result = items[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 @@ -63,7 +55,7 @@ namespace Umbraco.Core.Cache if (result == null || SafeLazy.GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null { result = SafeLazy.GetSafeLazy(factory); - ContextItems[key] = result; + items[key] = result; } } finally @@ -89,22 +81,22 @@ namespace Umbraco.Core.Cache { const string prefix = CacheItemPrefix + "-"; - if (HasContextItems == false) return Enumerable.Empty(); + if (!TryGetContextItems(out var items)) return Enumerable.Empty(); - return ContextItems.Cast() + return items.Cast() .Where(x => x.Key is string s && s.StartsWith(prefix)); } protected override void RemoveEntry(string key) { - if (HasContextItems == false) return; + if (!TryGetContextItems(out var items)) return; - ContextItems.Remove(key); + items.Remove(key); } protected override object GetEntry(string key) { - return HasContextItems ? ContextItems[key] : null; + return !TryGetContextItems(out var items) ? null : items[key]; } #endregion @@ -117,26 +109,27 @@ namespace Umbraco.Core.Cache protected override void EnterWriteLock() { - if (HasContextItems) - { - // note: cannot keep 'entered' as a class variable here, - // since there is one per request - so storing it within - // ContextItems - which is locked, so this should be safe + if (!TryGetContextItems(out var items)) return; - var entered = false; - Monitor.Enter(ContextItems.SyncRoot, ref entered); - ContextItems[ContextItemsLockKey] = entered; - } + // note: cannot keep 'entered' as a class variable here, + // since there is one per request - so storing it within + // ContextItems - which is locked, so this should be safe + + var entered = false; + Monitor.Enter(items.SyncRoot, ref entered); + items[ContextItemsLockKey] = entered; } protected override void ExitReadLock() => ExitWriteLock(); protected override void ExitWriteLock() { - var entered = (bool?) ContextItems[ContextItemsLockKey] ?? false; + if (!TryGetContextItems(out var items)) return; + + var entered = (bool?)items[ContextItemsLockKey] ?? false; if (entered) - Monitor.Exit(ContextItems.SyncRoot); - ContextItems.Remove(ContextItemsLockKey); + Monitor.Exit(items.SyncRoot); + items.Remove(ContextItemsLockKey); } #endregion diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 87f20eb070..76460493c8 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -126,9 +126,7 @@ --> - - diff --git a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs index 042830e059..81a784fc01 100644 --- a/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs +++ b/src/Umbraco.Tests/Cache/HttpRequestAppCacheTests.cs @@ -14,7 +14,7 @@ namespace Umbraco.Tests.Cache { base.Setup(); _ctx = new FakeHttpContextFactory("http://localhost/test"); - _appCache = new HttpRequestAppCache(_ctx.HttpContext); + _appCache = new HttpRequestAppCache(() => _ctx.HttpContext.Items); } internal override IAppCache AppCache diff --git a/src/Umbraco.Web/Runtime/WebRuntime.cs b/src/Umbraco.Web/Runtime/WebRuntime.cs index 13cd717fd1..afbaa36cb4 100644 --- a/src/Umbraco.Web/Runtime/WebRuntime.cs +++ b/src/Umbraco.Web/Runtime/WebRuntime.cs @@ -63,7 +63,7 @@ namespace Umbraco.Web.Runtime // all entities are cached properly (cloned in and cloned out) new DeepCloneAppCache(new WebCachingAppCache(HttpRuntime.Cache)), // we need request based cache when running in web-based context - new HttpRequestAppCache(), + new HttpRequestAppCache(() => HttpContext.Current?.Items), new IsolatedCaches(type => // we need to have the dep clone runtime cache provider to ensure // all entities are cached properly (cloned in and cloned out)