diff --git a/.editorconfig b/.editorconfig
index c63ef39430..5a35b71ce6 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -34,4 +34,7 @@ dotnet_naming_style.prefix_underscore.required_prefix = _
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
-csharp_prefer_braces = false : none
+csharp_prefer_braces = false : none
+
+[*.{js,less}]
+trim_trailing_whitespace = false
diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md
index 8c2bfffd87..422dd1f664 100644
--- a/.github/CONTRIBUTING_DETAILED.md
+++ b/.github/CONTRIBUTING_DETAILED.md
@@ -104,7 +104,6 @@ There's two big areas that you should know about:
You may need to run the following commands to set up gulp properly:
```
npm cache clean
- npm install -g bower
npm install -g gulp
npm install -g gulp-cli
npm install
diff --git a/.github/README.md b/.github/README.md
index 5a1340006e..a60eed0a97 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -1,4 +1,5 @@
_You are browsing the Umbraco v8 branch. Umbraco 8 is currently under development._
+[](https://pullreminders.com?ref=badge)
_Looking for Umbraco version 7? [Click here](https://github.com/umbraco/Umbraco-CMS) to go to the v7 branch._
@@ -6,7 +7,7 @@ _Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md).
When is Umbraco 8 coming?
=========================
-When it's ready. We're done with the major parts of the architecture work and are focusing on three seperate tracks to prepare Umbraco 8 for release:
+When it's ready. We're done with the major parts of the architecture work and are focusing on three separate tracks to prepare Umbraco 8 for release:
1) Editor Track (_currently in progress_). Without editors, there's no market for Umbraco. So we want to make sure that Umbraco 8 is full of love for editors.
2) Partner Track. Without anyone implementing Umbraco, there's nothing for editors to update. So we want to make sure that Umbraco 8 is a joy to implement
3) Contributor Track. Without our fabulous ecosystem of both individual Umbracians and 3rd party ISVs, Umbraco wouldn't be as rich a platform as it is today. We want to make sure that it's easy, straight forward and as backwards-compatible as possible to create packages for Umbraco
diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md
index 1cc33bb126..eaf4aa334e 100644
--- a/.github/V8_GETTING_STARTED.md
+++ b/.github/V8_GETTING_STARTED.md
@@ -11,7 +11,7 @@
* Open the `/src/umbraco.sln` Visual Studio solution
* Start the solution (easiest way is to use `ctrl + F5`)
-* When the solution is first built this may take some time since it will restore all nuget, npm and bower packages, build the .net solution and also build the angular solution
+* When the solution is first built this may take some time since it will restore all nuget and npm packages, build the .net solution and also build the angular solution
* When the website starts you'll see the Umbraco installer and just follow the prompts
* You're all set!
diff --git a/.gitignore b/.gitignore
index 279bdb39dd..57b4e18aaa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
*.suo
*.vs10x
*.ndproj
+*.ignorer.*
# common directories
.DS_Store
@@ -156,4 +157,4 @@ build/temp/
-# eof
\ No newline at end of file
+# eof
diff --git a/build/Azure/azuregalleryrelease.ps1 b/build/Azure/azuregalleryrelease.ps1
index 502ca3010e..61505171a2 100644
--- a/build/Azure/azuregalleryrelease.ps1
+++ b/build/Azure/azuregalleryrelease.ps1
@@ -3,57 +3,57 @@
[string]$Directory
)
$workingDirectory = $Directory
-CD $workingDirectory
+CD "$($workingDirectory)"
# Clone repo
-$fullGitUrl = "https://$env:GIT_URL/$env:GIT_REPOSITORYNAME.git"
-git clone $fullGitUrl 2>&1 | % { $_.ToString() }
+$fullGitUrl = "https://$($env:GIT_URL)/$($env:GIT_REPOSITORYNAME).git"
+git clone $($fullGitUrl) $($env:GIT_REPOSITORYNAME) 2>&1 | % { $_.ToString() }
# Remove everything so that unzipping the release later will update everything
# Don't remove the readme file nor the git directory
Write-Host "Cleaning up git directory before adding new version"
-Remove-Item -Recurse $workingDirectory\$env:GIT_REPOSITORYNAME\* -Exclude README.md,.git
+Remove-Item -Recurse "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)\*" -Exclude README.md,.git
# Find release zip
-$zipsDir = "$workingDirectory\$env:BUILD_DEFINITIONNAME\zips"
+$zipsDir = "$($workingDirectory)\$($env:BUILD_DEFINITIONNAME)\zips"
$pattern = "UmbracoCms.([0-9]{1,2}.[0-9]{1,3}.[0-9]{1,3}).zip"
-Write-Host "Searching for Umbraco release files in $workingDirectory\$zipsDir for a file with pattern $pattern"
-$file = (Get-ChildItem $zipsDir | Where-Object { $_.Name -match "$pattern" })
+Write-Host "Searching for Umbraco release files in $($zipsDir) for a file with pattern $($pattern)"
+$file = (Get-ChildItem "$($zipsDir)" | Where-Object { $_.Name -match "$($pattern)" })
if($file)
{
# Get release name
- $version = [regex]::Match($file.Name, $pattern).captures.groups[1].value
- $releaseName = "Umbraco $version"
- Write-Host "Found $releaseName"
+ $version = [regex]::Match($($file.Name), $($pattern)).captures.groups[1].value
+ $releaseName = "Umbraco $($version)"
+ Write-Host "Found $($releaseName)"
# Unzip into repository to update release
Add-Type -AssemblyName System.IO.Compression.FileSystem
- Write-Host "Unzipping $($file.FullName) to $workingDirectory\$env:GIT_REPOSITORYNAME"
- [System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$workingDirectory\$env:GIT_REPOSITORYNAME")
+ Write-Host "Unzipping $($file.FullName) to $($workingDirectory)\$($env:GIT_REPOSITORYNAME)"
+ [System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)")
# Telling git who we are
git config --global user.email "coffee@umbraco.com" 2>&1 | % { $_.ToString() }
git config --global user.name "Umbraco HQ" 2>&1 | % { $_.ToString() }
# Commit
- CD $env:GIT_REPOSITORYNAME
- Write-Host "Committing Umbraco $version Release from Build Output"
+ CD "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)"
+ Write-Host "Committing Umbraco $($version) Release from Build Output"
git add . 2>&1 | % { $_.ToString() }
- git commit -m " Release $releaseName from Build Output" 2>&1 | % { $_.ToString() }
+ git commit -m " Release $($releaseName) from Build Output" 2>&1 | % { $_.ToString() }
# Tag the release
- git tag -a "v$version" -m "v$version"
+ git tag -a "v$($version)" -m "v$($version)"
# Push release to master
- $fullGitAuthUrl = "https://$($env:GIT_USERNAME):$GitHubPersonalAccessToken@$env:GIT_URL/$env:GIT_REPOSITORYNAME.git"
- git push $fullGitAuthUrl 2>&1 | % { $_.ToString() }
+ $fullGitAuthUrl = "https://$($env:GIT_USERNAME):$($GitHubPersonalAccessToken)@$($env:GIT_URL)/$($env:GIT_REPOSITORYNAME).git"
+ git push $($fullGitAuthUrl) 2>&1 | % { $_.ToString() }
#Push tag to master
- git push $fullGitAuthUrl --tags 2>&1 | % { $_.ToString() }
+ git push $($fullGitAuthUrl) --tags 2>&1 | % { $_.ToString() }
}
else
{
- Write-Error "Umbraco release file not found, searched in $workingDirectory\$zipsDir for a file with pattern $pattern - cancelling"
+ Write-Error "Umbraco release file not found, searched in $($workingDirectory)\$($zipsDir) for a file with pattern $($pattern) - canceling"
}
diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
index 51d7e3b8d0..30fa303b30 100644
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -25,7 +25,7 @@
-
+
diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
index 434fe812ef..a188377c19 100644
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ b/build/NuSpecs/UmbracoCms.nuspec
@@ -22,11 +22,11 @@
not want this to happen as the alpha of the next major is, really, the next major already.
-->
-
+
-
-
+
+
@@ -52,10 +52,8 @@
-
-
diff --git a/build/build.ps1 b/build/build.ps1
index ec53199251..65b4041e30 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -330,9 +330,6 @@
$ubuild.DefineMethod("PrepareBuild",
{
- Write-Host "Clear folders and files"
- $this.RemoveDirectory("$($this.SolutionRoot)\src\Umbraco.Web.UI.Client\bower_components")
-
$this.TempStoreFile("$($this.SolutionRoot)\src\Umbraco.Web.UI\web.config")
Write-Host "Create clean web.config"
$this.CopyFile("$($this.SolutionRoot)\src\Umbraco.Web.UI\web.Template.config", "$($this.SolutionRoot)\src\Umbraco.Web.UI\web.config")
@@ -452,9 +449,22 @@
if ($this.OnError()) { return }
$this.PrepareAzureGallery()
if ($this.OnError()) { return }
+ $this.PostPackageHook()
+ if ($this.OnError()) { return }
Write-Host "Done"
})
+ $ubuild.DefineMethod("PostPackageHook",
+ {
+ # run hook
+ if ($this.HasMethod("PostPackage"))
+ {
+ Write-Host "Run PostPackage hook"
+ $this.PostPackage();
+ if (-not $?) { throw "Failed to run hook." }
+ }
+ })
+
# ################################################################
# RUN
# ################################################################
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index b5af335791..ce40bd9baa 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -19,4 +19,4 @@ using System.Resources;
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.0.0")]
-[assembly: AssemblyInformationalVersion("8.0.0-alpha.52")]
+[assembly: AssemblyInformationalVersion("8.0.0-alpha.58")]
diff --git a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs b/src/Umbraco.Core/Cache/AppCacheExtensions.cs
similarity index 59%
rename from src/Umbraco.Core/Cache/CacheProviderExtensions.cs
rename to src/Umbraco.Core/Cache/AppCacheExtensions.cs
index e42cb4d9ad..ddba8be1b2 100644
--- a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs
+++ b/src/Umbraco.Core/Cache/AppCacheExtensions.cs
@@ -8,9 +8,9 @@ namespace Umbraco.Core.Cache
///
/// Extensions for strongly typed access
///
- public static class CacheProviderExtensions
+ public static class AppCacheExtensions
{
- public static T GetCacheItem(this IRuntimeCacheProvider provider,
+ public static T GetCacheItem(this IAppPolicyCache 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 IAppPolicyCache 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/AppCaches.cs b/src/Umbraco.Core/Cache/AppCaches.cs
new file mode 100644
index 0000000000..e8bc49605a
--- /dev/null
+++ b/src/Umbraco.Core/Cache/AppCaches.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Web;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Represents the application caches.
+ ///
+ public class AppCaches
+ {
+ ///
+ /// Initializes a new instance of the for use in a web application.
+ ///
+ public AppCaches()
+ : this(HttpRuntime.Cache)
+ { }
+
+ ///
+ /// Initializes a new instance of the for use in a web application.
+ ///
+ public AppCaches(System.Web.Caching.Cache cache)
+ : this(
+ new WebCachingAppCache(cache),
+ new HttpRequestAppCache(),
+ new IsolatedCaches(t => new ObjectCacheAppCache()))
+ { }
+
+ ///
+ /// Initializes a new instance of the with cache providers.
+ ///
+ public AppCaches(
+ IAppPolicyCache runtimeCache,
+ IAppCache requestCache,
+ IsolatedCaches isolatedCaches)
+ {
+ RuntimeCache = runtimeCache ?? throw new ArgumentNullException(nameof(runtimeCache));
+ RequestCache = requestCache ?? throw new ArgumentNullException(nameof(requestCache));
+ IsolatedCaches = isolatedCaches ?? throw new ArgumentNullException(nameof(isolatedCaches));
+ }
+
+ ///
+ /// Gets the special disabled instance.
+ ///
+ ///
+ /// 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(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance));
+
+ ///
+ /// Gets the special no-cache instance.
+ ///
+ ///
+ /// When used by repositories, all cache policies are bypassed.
+ /// Used by repositories that do no cache.
+ ///
+ public static AppCaches NoCache { get; } = new AppCaches(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(_ => NoAppCache.Instance));
+
+ ///
+ /// Gets the per-request cache.
+ ///
+ ///
+ /// The per-request caches works on top of the current HttpContext items.
+ /// fixme - what about non-web applications?
+ ///
+ public IAppCache RequestCache { get; }
+
+ ///
+ /// Gets the runtime cache.
+ ///
+ ///
+ /// The runtime cache is the main application cache.
+ ///
+ public IAppPolicyCache RuntimeCache { get; }
+
+ ///
+ /// Gets the isolated caches.
+ ///
+ ///
+ /// 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..5c60dededa
--- /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 IAppPolicyCache 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/CacheHelper.cs b/src/Umbraco.Core/Cache/CacheHelper.cs
deleted file mode 100644
index f99b1e847b..0000000000
--- a/src/Umbraco.Core/Cache/CacheHelper.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-using System;
-using System.Web;
-
-namespace Umbraco.Core.Cache
-{
- ///
- /// Class that is exposed by the ApplicationContext for application wide caching purposes
- ///
- public class CacheHelper
- {
- public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
-
- ///
- /// Creates a cache helper with disabled caches
- ///
- ///
- ///
- /// Good for unit testing
- ///
- public static CacheHelper CreateDisabledCacheHelper()
- {
- // do *not* return NoCache
- // NoCache is a special instance that is detected by RepositoryBase and disables all cache policies
- // CreateDisabledCacheHelper is used in tests to use no cache, *but* keep all cache policies
- return new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
- }
-
- ///
- /// Initializes a new instance for use in the web
- ///
- public CacheHelper()
- : this(
- new HttpRuntimeCacheProvider(HttpRuntime.Cache),
- new StaticCacheProvider(),
- new HttpRequestCacheProvider(),
- new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
- {
- }
-
- ///
- /// Initializes a new instance for use in the web
- ///
- ///
- public CacheHelper(System.Web.Caching.Cache cache)
- : this(
- new HttpRuntimeCacheProvider(cache),
- new StaticCacheProvider(),
- new HttpRequestCacheProvider(),
- new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
- {
- }
-
-
- ///
- /// Initializes a new instance based on the provided providers
- ///
- ///
- ///
- ///
- ///
- public CacheHelper(
- IRuntimeCacheProvider httpCacheProvider,
- ICacheProvider staticCacheProvider,
- ICacheProvider requestCacheProvider,
- IsolatedRuntimeCache isolatedCacheManager)
- {
- if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider");
- if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider");
- if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider");
- if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager");
- RuntimeCache = httpCacheProvider;
- StaticCache = staticCacheProvider;
- RequestCache = requestCacheProvider;
- IsolatedRuntimeCache = isolatedCacheManager;
- }
-
- ///
- /// Returns the current Request cache
- ///
- public ICacheProvider RequestCache { get; internal set; }
-
- ///
- /// Returns the current Runtime cache
- ///
- public ICacheProvider StaticCache { get; internal set; }
-
- ///
- /// Returns the current Runtime cache
- ///
- public IRuntimeCacheProvider RuntimeCache { get; internal set; }
-
- ///
- /// Returns the current Isolated Runtime cache manager
- ///
- public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; }
-
- }
-
-}
diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
index 7242ab225e..bfa16ff3fa 100644
--- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
@@ -16,10 +16,10 @@ namespace Umbraco.Core.Cache
///
/// Initializes a new instance of the .
///
- /// A cache helper.
- protected CacheRefresherBase(CacheHelper cacheHelper)
+ /// A cache helper.
+ protected CacheRefresherBase(AppCaches appCaches)
{
- CacheHelper = cacheHelper;
+ AppCaches = appCaches;
}
///
@@ -93,7 +93,7 @@ namespace Umbraco.Core.Cache
///
/// Gets the cache helper.
///
- protected CacheHelper CacheHelper { get; }
+ protected AppCaches AppCaches { get; }
///
/// Clears the cache for all repository entities of a specified type.
@@ -102,7 +102,7 @@ namespace Umbraco.Core.Cache
protected void ClearAllIsolatedCacheByEntityType()
where TEntity : class, IEntity
{
- CacheHelper.IsolatedRuntimeCache.ClearCache();
+ AppCaches.IsolatedCaches.ClearCache();
}
///
diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs
index 11ac05844b..8bae755149 100644
--- a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs
+++ b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs
@@ -1,15 +1,9 @@
-using System.Collections.Generic;
-using LightInject;
-using Umbraco.Core.Composing;
+using Umbraco.Core.Composing;
namespace Umbraco.Core.Cache
{
public class CacheRefresherCollectionBuilder : LazyCollectionBuilderBase
{
- public CacheRefresherCollectionBuilder(IServiceContainer container)
- : base(container)
- { }
-
protected override CacheRefresherCollectionBuilder This => this;
}
}
diff --git a/src/Umbraco.Core/Cache/DeepCloneAppCache.cs b/src/Umbraco.Core/Cache/DeepCloneAppCache.cs
new file mode 100644
index 0000000000..eff06e2aad
--- /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 : IAppPolicyCache
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DeepCloneAppCache(IAppPolicyCache 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 IAppPolicyCache 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..c11309c827 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(IAppPolicyCache 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/DictionaryAppCache.cs b/src/Umbraco.Core/Cache/DictionaryAppCache.cs
new file mode 100644
index 0000000000..4c08bd0524
--- /dev/null
+++ b/src/Umbraco.Core/Cache/DictionaryAppCache.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Implements on top of a concurrent dictionary.
+ ///
+ public class DictionaryAppCache : IAppCache
+ {
+ ///
+ /// Gets the internal items dictionary, for tests only!
+ ///
+ internal readonly ConcurrentDictionary Items = new ConcurrentDictionary();
+
+ ///
+ public virtual object Get(string key)
+ {
+ // fixme throws if non-existing, shouldn't it return null?
+ return Items[key];
+ }
+
+ ///
+ public virtual object Get(string key, Func factory)
+ {
+ return Items.GetOrAdd(key, _ => factory());
+ }
+
+ ///
+ public virtual IEnumerable SearchByKey(string keyStartsWith)
+ {
+ var items = new List();
+ foreach (var (key, value) in Items)
+ if (key.InvariantStartsWith(keyStartsWith))
+ items.Add(value);
+ return items;
+ }
+
+ ///
+ 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);
+ Items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT);
+ }
+
+ ///
+ public virtual void ClearOfType(Func predicate)
+ {
+ var typeOfT = typeof(T);
+ Items.RemoveAll(kvp => kvp.Value != null && kvp.Value.GetType() == typeOfT && predicate(kvp.Key, (T)kvp.Value));
+ }
+
+ ///
+ public virtual void ClearByKey(string keyStartsWith)
+ {
+ Items.RemoveAll(kvp => kvp.Key.InvariantStartsWith(keyStartsWith));
+ }
+
+ ///
+ public virtual void ClearByRegex(string regex)
+ {
+ var compiled = new Regex(regex, RegexOptions.Compiled);
+ Items.RemoveAll(kvp => compiled.IsMatch(kvp.Key));
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs
similarity index 53%
rename from src/Umbraco.Core/Cache/DictionaryCacheProvider.cs
rename to src/Umbraco.Core/Cache/FastDictionaryAppCache.cs
index 98dceb80b0..bd545694f7 100644
--- a/src/Umbraco.Core/Cache/DictionaryCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/FastDictionaryAppCache.cs
@@ -7,79 +7,130 @@ using Umbraco.Core.Composing;
namespace Umbraco.Core.Cache
{
- internal class DictionaryCacheProvider : ICacheProvider
+ ///
+ /// Implements a fast on top of a concurrent dictionary.
+ ///
+ internal class FastDictionaryAppCache : 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 object Get(string cacheKey)
{
- _items.Clear();
+ Items.TryGetValue(cacheKey, out var result); // else null
+ return result == null ? null : FastDictionaryAppCacheBase.GetSafeLazyValue(result); // return exceptions as null
}
- public void ClearCacheItem(string key)
+ ///
+ public object Get(string cacheKey, Func getCacheItem)
{
- _items.TryRemove(key, out _);
+ 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 void ClearCacheObjectTypes(string typeName)
+ ///
+ 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
+ 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);
+ 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 _);
+ Items.TryRemove(kvp.Key, out _);
}
- public void ClearCacheObjectTypes()
+ ///
+ public void ClearOfType()
{
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
// 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);
+ 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 _);
+ Items.TryRemove(kvp.Key, out _);
}
- public void ClearCacheObjectTypes(Func predicate)
+ ///
+ public void ClearOfType(Func predicate)
{
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
// 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);
+ var value = FastDictionaryAppCacheBase.GetSafeLazyValue(x.Value, true);
if (value == null) return true;
// if T is an interface remove anything that implements that interface
@@ -88,60 +139,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 ClearCacheByKeySearch(string keyStartsWith)
+ ///
+ 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 ClearCacheByKeyExpression(string regexString)
+ ///
+ public 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);
+ foreach (var ikvp in Items
+ .Where(kvp => compiled.IsMatch(kvp.Key)))
+ Items.TryRemove(ikvp.Key, out _);
}
}
}
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/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs
index 319d84d41f..c3b69d9a6d 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(IAppPolicyCache 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/IAppPolicyCache.cs b/src/Umbraco.Core/Cache/IAppPolicyCache.cs
new file mode 100644
index 0000000000..90b0ccb9fd
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IAppPolicyCache.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 IAppPolicyCache : 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..f070fe8b55
--- /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 IAppPolicyCache 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/JsonCacheRefresherBase.cs b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs
index 27302f0786..d9d9644a36 100644
--- a/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/JsonCacheRefresherBase.cs
@@ -13,8 +13,8 @@ namespace Umbraco.Core.Cache
///
/// Initializes a new instance of the .
///
- /// A cache helper.
- protected JsonCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper)
+ /// A cache helper.
+ protected JsonCacheRefresherBase(AppCaches appCaches) : base(appCaches)
{ }
///
diff --git a/src/Umbraco.Core/Cache/NoAppCache.cs b/src/Umbraco.Core/Cache/NoAppCache.cs
new file mode 100644
index 0000000000..d3359a30ba
--- /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 : IAppPolicyCache
+ {
+ 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..b1a12ec411 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(IAppPolicyCache 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..5c4f76f51d 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 : IAppPolicyCache
{
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 ObjectCache MemoryCache { get; private set; }
- 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 HttpRuntimeAppCache
+
+ 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/PayloadCacheRefresherBase.cs b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs
index 22bc46e020..4a3a03a0d6 100644
--- a/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/PayloadCacheRefresherBase.cs
@@ -15,8 +15,8 @@ namespace Umbraco.Core.Cache
///
/// Initializes a new instance of the .
///
- /// A cache helper.
- protected PayloadCacheRefresherBase(CacheHelper cacheHelper) : base(cacheHelper)
+ /// A cache helper.
+ protected PayloadCacheRefresherBase(AppCaches appCaches) : base(appCaches)
{ }
#region Json
diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs
index 2d21d410a7..27fe4e3035 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 IAppPolicyCache _globalCache;
private readonly IScopeAccessor _scopeAccessor;
- protected RepositoryCachePolicyBase(IRuntimeCacheProvider globalCache, IScopeAccessor scopeAccessor)
+ protected RepositoryCachePolicyBase(IAppPolicyCache globalCache, IScopeAccessor scopeAccessor)
{
_globalCache = globalCache ?? throw new ArgumentNullException(nameof(globalCache));
_scopeAccessor = scopeAccessor ?? throw new ArgumentNullException(nameof(scopeAccessor));
}
- protected IRuntimeCacheProvider Cache
+ protected IAppPolicyCache 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..9de7519edb 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(IAppPolicyCache 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