From 41639740d2a5d48d036c180939d5bfe4a0d3fc11 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 27 Nov 2019 14:10:48 +0100 Subject: [PATCH 1/4] AB3980 - Replaced HttpContext.Current.Items with IRequestCache in ScopeProvider --- src/Umbraco.Abstractions/Cache/AppCaches.cs | 4 +- .../Cache/DictionaryAppCache.cs | 6 +- .../Cache/FastDictionaryAppCacheBase.cs | 2 +- .../Cache/HttpRequestAppCache.cs | 38 +++++++++++- .../Cache/IRequestCache.cs | 8 +++ src/Umbraco.Abstractions/Cache/NoAppCache.cs | 8 ++- .../CompositionExtensions_Essentials.cs | 1 + src/Umbraco.Core/Scoping/ScopeProvider.cs | 59 ++++++------------ .../Components/ComponentTests.cs | 2 +- src/Umbraco.Tests/Scoping/ScopeTests.cs | 61 ++++++++----------- src/Umbraco.Tests/TestHelpers/TestHelper.cs | 5 ++ src/Umbraco.Tests/TestHelpers/TestObjects.cs | 4 +- 12 files changed, 112 insertions(+), 86 deletions(-) create mode 100644 src/Umbraco.Abstractions/Cache/IRequestCache.cs diff --git a/src/Umbraco.Abstractions/Cache/AppCaches.cs b/src/Umbraco.Abstractions/Cache/AppCaches.cs index 5e8c460ae4..8930320447 100644 --- a/src/Umbraco.Abstractions/Cache/AppCaches.cs +++ b/src/Umbraco.Abstractions/Cache/AppCaches.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Cache /// public AppCaches( IAppPolicyCache runtimeCache, - IAppCache requestCache, + IRequestCache requestCache, IsolatedCaches isolatedCaches) { RuntimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache)); @@ -45,7 +45,7 @@ namespace Umbraco.Core.Cache /// The per-request caches works on top of the current HttpContext items. /// Outside a web environment, the behavior of that cache is unspecified. /// - public IAppCache RequestCache { get; } + public IRequestCache RequestCache { get; } /// /// Gets the runtime cache. diff --git a/src/Umbraco.Abstractions/Cache/DictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/DictionaryAppCache.cs index 6e528165a0..8b7c72a6ba 100644 --- a/src/Umbraco.Abstractions/Cache/DictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/DictionaryAppCache.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Cache /// /// Implements on top of a concurrent dictionary. /// - public class DictionaryAppCache : IAppCache + public class DictionaryAppCache : IRequestCache { /// /// Gets the internal items dictionary, for tests only! @@ -29,6 +29,10 @@ namespace Umbraco.Core.Cache return _items.GetOrAdd(key, _ => factory()); } + public bool Set(string key, object value) => _items.TryAdd(key, value); + + public bool Remove(string key) => _items.TryRemove(key, out _); + /// public virtual IEnumerable SearchByKey(string keyStartsWith) { diff --git a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs index 57f4e7b9a2..f417c5ffd0 100644 --- a/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs +++ b/src/Umbraco.Abstractions/Cache/FastDictionaryAppCacheBase.cs @@ -275,7 +275,7 @@ namespace Umbraco.Core.Cache return $"{CacheItemPrefix}-{key}"; } - + #endregion } diff --git a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs index bdb087f0d2..6a785f7c16 100644 --- a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Cache /// or no Items...) then this cache acts as a pass-through and does not cache /// anything. /// - public class HttpRequestAppCache : FastDictionaryAppCacheBase + public class HttpRequestAppCache : FastDictionaryAppCacheBase, IRequestCache { /// /// Initializes a new instance of the class with a context, for unit tests! @@ -75,6 +75,42 @@ namespace Umbraco.Core.Cache return value; } + public bool Set(string key, object value) + { + //no place to cache so just return the callback result + if (!TryGetContextItems(out var items)) return false; + key = GetCacheKey(key); + try + { + + EnterWriteLock(); + items[key] = SafeLazy.GetSafeLazy(() => value); + } + finally + { + ExitWriteLock(); + } + return true; + } + + public bool Remove(string key) + { + //no place to cache so just return the callback result + if (!TryGetContextItems(out var items)) return false; + key = GetCacheKey(key); + try + { + + EnterWriteLock(); + items.Remove(key); + } + finally + { + ExitWriteLock(); + } + return true; + } + #region Entries protected override IEnumerable GetDictionaryEntries() diff --git a/src/Umbraco.Abstractions/Cache/IRequestCache.cs b/src/Umbraco.Abstractions/Cache/IRequestCache.cs new file mode 100644 index 0000000000..c4e25763c4 --- /dev/null +++ b/src/Umbraco.Abstractions/Cache/IRequestCache.cs @@ -0,0 +1,8 @@ +namespace Umbraco.Core.Cache +{ + public interface IRequestCache : IAppCache + { + bool Set(string key, object value); + bool Remove(string key); + } +} diff --git a/src/Umbraco.Abstractions/Cache/NoAppCache.cs b/src/Umbraco.Abstractions/Cache/NoAppCache.cs index 24c51c935a..c14698f380 100644 --- a/src/Umbraco.Abstractions/Cache/NoAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/NoAppCache.cs @@ -7,9 +7,9 @@ namespace Umbraco.Core.Cache /// /// Implements and do not cache. /// - public class NoAppCache : IAppPolicyCache + public class NoAppCache : IAppPolicyCache, IRequestCache { - private NoAppCache() { } + protected NoAppCache() { } /// /// Gets the singleton instance. @@ -28,6 +28,10 @@ namespace Umbraco.Core.Cache return factory(); } + public bool Set(string key, object value) => false; + + public bool Remove(string key) => false; + /// public virtual IEnumerable SearchByKey(string keyStartsWith) { diff --git a/src/Umbraco.Core/CompositionExtensions_Essentials.cs b/src/Umbraco.Core/CompositionExtensions_Essentials.cs index 3174f07468..dcea789616 100644 --- a/src/Umbraco.Core/CompositionExtensions_Essentials.cs +++ b/src/Umbraco.Core/CompositionExtensions_Essentials.cs @@ -31,6 +31,7 @@ namespace Umbraco.Core composition.RegisterUnique(profilingLogger); composition.RegisterUnique(mainDom); composition.RegisterUnique(appCaches); + composition.RegisterUnique(appCaches.RequestCache); composition.RegisterUnique(databaseFactory); composition.RegisterUnique(factory => factory.GetInstance().SqlContext); composition.RegisterUnique(typeLoader); diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 60cb83fcd5..4de2b64c51 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -1,9 +1,8 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Data; using System.Runtime.Remoting.Messaging; -using System.Web; +using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Events; using Umbraco.Core.IO; @@ -23,14 +22,16 @@ namespace Umbraco.Core.Scoping { private readonly ILogger _logger; private readonly ITypeFinder _typeFinder; + private readonly IRequestCache _requestCache; private readonly FileSystems _fileSystems; - public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ILogger logger, ITypeFinder typeFinder) + public ScopeProvider(IUmbracoDatabaseFactory databaseFactory, FileSystems fileSystems, ILogger logger, ITypeFinder typeFinder, IRequestCache requestCache) { DatabaseFactory = databaseFactory; _fileSystems = fileSystems; _logger = logger; _typeFinder = typeFinder; + _requestCache = requestCache; // take control of the FileSystems _fileSystems.IsScoped = () => AmbientScope != null && AmbientScope.ScopedFileSystems; @@ -181,51 +182,31 @@ namespace Umbraco.Core.Scoping } } - // this is for tests exclusively until we have a proper accessor in v8 - internal static Func HttpContextItemsGetter { get; set; } - private static IDictionary HttpContextItems => HttpContextItemsGetter == null - ? HttpContext.Current?.Items - : HttpContextItemsGetter(); - - public static T GetHttpContextObject(string key, bool required = true) + private T GetHttpContextObject(string key, bool required = true) where T : class { - var httpContextItems = HttpContextItems; - if (httpContextItems != null) - return (T)httpContextItems[key]; - if (required) + + var result = (T) _requestCache.Get(key); + + if (result is null && required) + { throw new Exception("HttpContext.Current is null."); - return null; + } + + return result; + } - private static bool SetHttpContextObject(string key, object value, bool required = true) + private bool SetHttpContextObject(string key, object value, bool required = true) { - var httpContextItems = HttpContextItems; - if (httpContextItems == null) + var done = value is null ? _requestCache.Remove(key) : _requestCache.Set(key, value); + + if (!done && required) { - if (required) - throw new Exception("HttpContext.Current is null."); - return false; + throw new Exception("HttpContext.Current is null."); } -#if DEBUG_SCOPES - // manage the 'context' that contains the scope (null, "http" or "call") - // only for scopes of course! - if (key == ScopeItemKey) - { - // first, null-register the existing value - var ambientScope = (IScope)httpContextItems[ScopeItemKey]; - if (ambientScope != null) RegisterContext(ambientScope, null); - // then register the new value - var scope = value as IScope; - if (scope != null) RegisterContext(scope, "http"); - } -#endif - if (value == null) - httpContextItems.Remove(key); - else - httpContextItems[key] = value; - return true; + return done; } #endregion diff --git a/src/Umbraco.Tests/Components/ComponentTests.cs b/src/Umbraco.Tests/Components/ComponentTests.cs index 89bcd48d05..8211c2fa39 100644 --- a/src/Umbraco.Tests/Components/ComponentTests.cs +++ b/src/Umbraco.Tests/Components/ComponentTests.cs @@ -38,7 +38,7 @@ namespace Umbraco.Tests.Components var typeFinder = new TypeFinder(logger); var f = new UmbracoDatabaseFactory(logger, new Lazy(() => new MapperCollection(Enumerable.Empty())), TestHelper.GetConfigs()); var fs = new FileSystems(mock.Object, logger, IOHelper.Default, SettingsForTests.GenerateMockGlobalSettings()); - var p = new ScopeProvider(f, fs, logger, typeFinder); + var p = new ScopeProvider(f, fs, logger, typeFinder, TestHelper.GetRequestCache()); mock.Setup(x => x.GetInstance(typeof (ILogger))).Returns(logger); mock.Setup(x => x.GetInstance(typeof (IProfilingLogger))).Returns(new ProfilingLogger(Mock.Of(), Mock.Of())); diff --git a/src/Umbraco.Tests/Scoping/ScopeTests.cs b/src/Umbraco.Tests/Scoping/ScopeTests.cs index 6c5e9a74b5..a4a9d32084 100644 --- a/src/Umbraco.Tests/Scoping/ScopeTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeTests.cs @@ -107,46 +107,33 @@ namespace Umbraco.Tests.Scoping var scopeProvider = ScopeProvider; Assert.IsNull(scopeProvider.AmbientScope); - var httpContextItems = new Hashtable(); - ScopeProviderStatic.HttpContextItemsGetter = () => httpContextItems; - try + using (var scope = scopeProvider.CreateScope()) { - using (var scope = scopeProvider.CreateScope()) + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + // only if Core.DEBUG_SCOPES are defined + //Assert.IsEmpty(scopeProvider.CallContextObjects); + + using (var nested = scopeProvider.CreateScope(callContext: true)) { - Assert.IsInstanceOf(scope); + Assert.IsInstanceOf(nested); Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.AreSame(scope, scopeProvider.AmbientScope); - Assert.AreSame(scope, httpContextItems[ScopeProviderStatic.ScopeItemKey]); + Assert.AreSame(nested, scopeProvider.AmbientScope); + Assert.AreSame(scope, ((Scope) nested).ParentScope); + + // it's moved over to call context + var callContextKey = CallContext.LogicalGetData(ScopeProviderStatic.ScopeItemKey).AsGuid(); + Assert.AreNotEqual(Guid.Empty, callContextKey); // only if Core.DEBUG_SCOPES are defined - //Assert.IsEmpty(scopeProvider.CallContextObjects); - - using (var nested = scopeProvider.CreateScope(callContext: true)) - { - Assert.IsInstanceOf(nested); - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.AreSame(nested, scopeProvider.AmbientScope); - Assert.AreSame(scope, ((Scope) nested).ParentScope); - - // it's moved over to call context - Assert.IsNull(httpContextItems[ScopeProviderStatic.ScopeItemKey]); - var callContextKey = CallContext.LogicalGetData(ScopeProviderStatic.ScopeItemKey).AsGuid(); - Assert.AreNotEqual(Guid.Empty, callContextKey); - - // only if Core.DEBUG_SCOPES are defined - //var ccnested = scopeProvider.CallContextObjects[callContextKey]; - //Assert.AreSame(nested, ccnested); - } - - // it's naturally back in http context - Assert.AreSame(scope, httpContextItems[ScopeProviderStatic.ScopeItemKey]); + //var ccnested = scopeProvider.CallContextObjects[callContextKey]; + //Assert.AreSame(nested, ccnested); } - Assert.IsNull(scopeProvider.AmbientScope); - } - finally - { - ScopeProviderStatic.HttpContextItemsGetter = null; + + // it's naturally back in http context } + Assert.IsNull(scopeProvider.AmbientScope); } [Test] @@ -442,7 +429,7 @@ namespace Umbraco.Tests.Scoping Assert.IsNull(scopeProvider.AmbientScope); var httpContextItems = new Hashtable(); - ScopeProviderStatic.HttpContextItemsGetter = () => httpContextItems; + // ScopeProviderStatic.HttpContextItemsGetter = () => httpContextItems; try { using (var scope = scopeProvider.CreateScope()) @@ -452,7 +439,7 @@ namespace Umbraco.Tests.Scoping using (new SafeCallContext()) { // pretend it's another thread - ScopeProviderStatic.HttpContextItemsGetter = null; + // ScopeProviderStatic.HttpContextItemsGetter = null; Assert.IsNull(scopeProvider.AmbientScope); Assert.IsNull(scopeProvider.AmbientContext); @@ -468,7 +455,7 @@ namespace Umbraco.Tests.Scoping Assert.IsNull(scopeProvider.AmbientContext); // back to original thread - ScopeProviderStatic.HttpContextItemsGetter = () => httpContextItems; + // ScopeProviderStatic.HttpContextItemsGetter = () => httpContextItems; } Assert.IsNotNull(scopeProvider.AmbientScope); Assert.AreSame(scope, scopeProvider.AmbientScope); @@ -479,7 +466,7 @@ namespace Umbraco.Tests.Scoping } finally { - ScopeProviderStatic.HttpContextItemsGetter = null; + // ScopeProviderStatic.HttpContextItemsGetter = null; } } diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs index 64794fcea9..6fc2b08e2f 100644 --- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs +++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs @@ -296,5 +296,10 @@ namespace Umbraco.Tests.TestHelpers { return RegisterFactory.Create(GetConfigs().Global()); } + + public static IRequestCache GetRequestCache() + { + return new DictionaryAppCache(); + } } } diff --git a/src/Umbraco.Tests/TestHelpers/TestObjects.cs b/src/Umbraco.Tests/TestHelpers/TestObjects.cs index ac14ba3fbb..4c672549e8 100644 --- a/src/Umbraco.Tests/TestHelpers/TestObjects.cs +++ b/src/Umbraco.Tests/TestHelpers/TestObjects.cs @@ -173,7 +173,7 @@ namespace Umbraco.Tests.TestHelpers var macroService = GetLazyService(factory, c => new MacroService(scopeProvider, logger, eventMessagesFactory, GetRepo(c), GetRepo(c))); var packagingService = GetLazyService(factory, c => - { + { var compiledPackageXmlParser = new CompiledPackageXmlParser(new ConflictingPackageData(macroService.Value, fileService.Value), globalSettings); return new PackagingService( auditService.Value, @@ -247,7 +247,7 @@ namespace Umbraco.Tests.TestHelpers typeFinder = typeFinder ?? new TypeFinder(logger); fileSystems = fileSystems ?? new FileSystems(Current.Factory, logger, IOHelper.Default, SettingsForTests.GenerateMockGlobalSettings()); - var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger, typeFinder); + var scopeProvider = new ScopeProvider(databaseFactory, fileSystems, logger, typeFinder, NoAppCache.Instance); return scopeProvider; } From f7404064be9c53fb216d80156b41860f656f136c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 3 Dec 2019 08:43:09 +0100 Subject: [PATCH 2/4] AB3980 Cleanup tests --- src/Umbraco.Tests/Scoping/ScopeTests.cs | 55 ++++++++++--------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Tests/Scoping/ScopeTests.cs b/src/Umbraco.Tests/Scoping/ScopeTests.cs index a4a9d32084..b2a3b14800 100644 --- a/src/Umbraco.Tests/Scoping/ScopeTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopeTests.cs @@ -428,46 +428,33 @@ namespace Umbraco.Tests.Scoping var scopeProvider = ScopeProvider; Assert.IsNull(scopeProvider.AmbientScope); - var httpContextItems = new Hashtable(); - // ScopeProviderStatic.HttpContextItemsGetter = () => httpContextItems; - try + using (var scope = scopeProvider.CreateScope()) { - using (var scope = scopeProvider.CreateScope()) + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.IsNotNull(scopeProvider.AmbientContext); + using (new SafeCallContext()) { - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.IsNotNull(scopeProvider.AmbientContext); - using (new SafeCallContext()) + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + + using (var newScope = scopeProvider.CreateScope()) { - // pretend it's another thread - // ScopeProviderStatic.HttpContextItemsGetter = null; - - Assert.IsNull(scopeProvider.AmbientScope); - Assert.IsNull(scopeProvider.AmbientContext); - - using (var newScope = scopeProvider.CreateScope()) - { - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.IsNull(scopeProvider.AmbientScope.ParentScope); - Assert.IsNotNull(scopeProvider.AmbientContext); - } - - Assert.IsNull(scopeProvider.AmbientScope); - Assert.IsNull(scopeProvider.AmbientContext); - - // back to original thread - // ScopeProviderStatic.HttpContextItemsGetter = () => httpContextItems; + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientScope.ParentScope); + Assert.IsNotNull(scopeProvider.AmbientContext); } - Assert.IsNotNull(scopeProvider.AmbientScope); - Assert.AreSame(scope, scopeProvider.AmbientScope); - } - Assert.IsNull(scopeProvider.AmbientScope); - Assert.IsNull(scopeProvider.AmbientContext); - } - finally - { - // ScopeProviderStatic.HttpContextItemsGetter = null; + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + } + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); } + + Assert.IsNull(scopeProvider.AmbientScope); + Assert.IsNull(scopeProvider.AmbientContext); + + } [Test] From 898167f8c60f7ac093d4fe12205b5adf9506619f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 4 Dec 2019 13:30:46 +1100 Subject: [PATCH 3/4] Adds IsAvailable to IRequestCache and re-implements the DEBUG_SCOPES flag, fixes core proj build issue with DEBUG_SCOPES enabled --- .../Cache/DictionaryAppCache.cs | 3 ++ .../Cache/HttpRequestAppCache.cs | 2 + .../Cache/IRequestCache.cs | 5 +++ src/Umbraco.Abstractions/Cache/NoAppCache.cs | 3 ++ src/Umbraco.Core/Scoping/ScopeProvider.cs | 41 ++++++++++++------- 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Abstractions/Cache/DictionaryAppCache.cs b/src/Umbraco.Abstractions/Cache/DictionaryAppCache.cs index 8b7c72a6ba..fd360b303d 100644 --- a/src/Umbraco.Abstractions/Cache/DictionaryAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/DictionaryAppCache.cs @@ -17,6 +17,9 @@ namespace Umbraco.Core.Cache public int Count => _items.Count; + /// + public bool IsAvailable => true; + /// public virtual object Get(string key) { diff --git a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs index 6a785f7c16..87d87ad1c9 100644 --- a/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/HttpRequestAppCache.cs @@ -27,6 +27,8 @@ namespace Umbraco.Core.Cache private Func ContextItems { get; } + public bool IsAvailable => TryGetContextItems(out _); + private bool TryGetContextItems(out IDictionary items) { items = ContextItems?.Invoke(); diff --git a/src/Umbraco.Abstractions/Cache/IRequestCache.cs b/src/Umbraco.Abstractions/Cache/IRequestCache.cs index c4e25763c4..5ed32b5ba0 100644 --- a/src/Umbraco.Abstractions/Cache/IRequestCache.cs +++ b/src/Umbraco.Abstractions/Cache/IRequestCache.cs @@ -4,5 +4,10 @@ namespace Umbraco.Core.Cache { bool Set(string key, object value); bool Remove(string key); + + /// + /// Returns true if the request cache is available otherwise false + /// + bool IsAvailable { get; } } } diff --git a/src/Umbraco.Abstractions/Cache/NoAppCache.cs b/src/Umbraco.Abstractions/Cache/NoAppCache.cs index c14698f380..7174dd3233 100644 --- a/src/Umbraco.Abstractions/Cache/NoAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/NoAppCache.cs @@ -16,6 +16,9 @@ namespace Umbraco.Core.Cache /// public static NoAppCache Instance { get; } = new NoAppCache(); + /// + public bool IsAvailable => true; + /// public virtual object Get(string cacheKey) { diff --git a/src/Umbraco.Core/Scoping/ScopeProvider.cs b/src/Umbraco.Core/Scoping/ScopeProvider.cs index 4de2b64c51..c8b5b05f59 100644 --- a/src/Umbraco.Core/Scoping/ScopeProvider.cs +++ b/src/Umbraco.Core/Scoping/ScopeProvider.cs @@ -8,9 +8,9 @@ using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; - #if DEBUG_SCOPES using System.Linq; +using System.Text; #endif namespace Umbraco.Core.Scoping @@ -187,26 +187,39 @@ namespace Umbraco.Core.Scoping where T : class { - var result = (T) _requestCache.Get(key); - - if (result is null && required) - { - throw new Exception("HttpContext.Current is null."); - } - - return result; + if (!_requestCache.IsAvailable && required) + throw new Exception("Request cache is unavailable."); + return (T)_requestCache.Get(key); } private bool SetHttpContextObject(string key, object value, bool required = true) { - var done = value is null ? _requestCache.Remove(key) : _requestCache.Set(key, value); - - if (!done && required) + if (!_requestCache.IsAvailable) { - throw new Exception("HttpContext.Current is null."); + if (required) + throw new Exception("Request cache is unavailable."); + return false; } - return done; + +#if DEBUG_SCOPES + // manage the 'context' that contains the scope (null, "http" or "call") + // only for scopes of course! + if (key == ScopeItemKey) + { + // first, null-register the existing value + var ambientScope = (IScope)_requestCache.Get(ScopeItemKey); + if (ambientScope != null) RegisterContext(ambientScope, null); + // then register the new value + var scope = value as IScope; + if (scope != null) RegisterContext(scope, "http"); + } +#endif + if (value == null) + _requestCache.Remove(key); + else + _requestCache.Set(key, value); + return true; } #endregion From 5f5a6e460c473f22cdabb5b65038d70ffec4821c Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 4 Dec 2019 09:11:57 +0100 Subject: [PATCH 4/4] NoAppCache must return false in "IsAvailable" --- src/Umbraco.Abstractions/Cache/NoAppCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Abstractions/Cache/NoAppCache.cs b/src/Umbraco.Abstractions/Cache/NoAppCache.cs index 7174dd3233..5ca8f47059 100644 --- a/src/Umbraco.Abstractions/Cache/NoAppCache.cs +++ b/src/Umbraco.Abstractions/Cache/NoAppCache.cs @@ -17,7 +17,7 @@ namespace Umbraco.Core.Cache public static NoAppCache Instance { get; } = new NoAppCache(); /// - public bool IsAvailable => true; + public bool IsAvailable => false; /// public virtual object Get(string cacheKey)