diff --git a/README.md b/README.md
index 5988cc2a19..5fb4cb868b 100644
--- a/README.md
+++ b/README.md
@@ -15,11 +15,11 @@ If you're interested in making changes to Belle make sure to read the [Belle Rea
**More than 177,000 sites trust Umbraco**
-For the first time on the Microsoft platform a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. It's a developers dream and your users will love it too.
+For the first time on the Microsoft platform, there is a free user and developer friendly CMS that makes it quick and easy to create websites - or a breeze to build complex web applications. Umbraco has award-winning integration capabilities and supports ASP.NET MVC or Web Forms, including User and Custom Controls, out of the box. It's a developer's dream and your users will love it too.
-Used by more than 177,000 active websites including [http://daviscup.com](http://daviscup.com), [http://heinz.com](http://heinz.com), [http://peugeot.com](http://peugeot.com), [http://www.hersheys.com/](http://www.hersheys.com/) and **The Official ASP.NET and IIS.NET website from Microsoft** ([http://asp.net](http://asp.net) / [http://iis.net](http://iis.net)) you can be sure that the technology is proven, stable and scales.
+Used by more than 177,000 active websites including [http://daviscup.com](http://daviscup.com), [http://heinz.com](http://heinz.com), [http://peugeot.com](http://peugeot.com), [http://www.hersheys.com/](http://www.hersheys.com/) and **The Official ASP.NET and IIS.NET website from Microsoft** ([http://asp.net](http://asp.net) / [http://iis.net](http://iis.net)), you can be sure that the technology is proven, stable and scales.
-To view more examples please visit [http://umbraco.com/why-umbraco/#caseStudies](http://umbraco.com/why-umbraco/#caseStudies)
+To view more examples, please visit [http://umbraco.com/why-umbraco/#caseStudies](http://umbraco.com/why-umbraco/#caseStudies)
## Downloading ##
@@ -35,6 +35,6 @@ If you want to contribute back to Umbraco you should check out our [guide to con
## Found a bug? ##
-Another way you can contribute to Umbraco is by providing issue reports, for information on how to submit an issue report refer to our [online guide for reporting issues](http://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
+Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](http://our.umbraco.org/contribute/report-an-issue-or-request-a-feature).
-To view existing issues please visit [http://issues.umbraco.org](http://issues.umbraco.org)
+To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org).
diff --git a/build/Build.bat b/build/Build.bat
index bd18f719ad..1167d9fcb6 100644
--- a/build/Build.bat
+++ b/build/Build.bat
@@ -21,14 +21,6 @@ ECHO Building Umbraco %version%
ReplaceIISExpressPortNumber.exe ..\src\Umbraco.Web.UI\Umbraco.Web.UI.csproj %release%
-ECHO Installing the Microsoft.Bcl.Build package before anything else, otherwise you'd have to run build.cmd twice
-SET nuGetFolder=%CD%\..\src\packages\
-..\src\.nuget\NuGet.exe sources Remove -Name MyGetUmbracoCore >NUL
-..\src\.nuget\NuGet.exe sources Add -Name MyGetUmbracoCore -Source https://www.myget.org/F/umbracocore/api/v2/ >NUL
-..\src\.nuget\NuGet.exe install ..\src\Umbraco.Web.UI\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
-..\src\.nuget\NuGet.exe install ..\src\umbraco.businesslogic\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
-..\src\.nuget\NuGet.exe install ..\src\Umbraco.Core\packages.config -OutputDirectory %nuGetFolder% -Verbosity quiet
-
ECHO Removing the belle build folder and bower_components folder to make sure everything is clean as a whistle
RD ..\src\Umbraco.Web.UI.Client\build /Q /S
RD ..\src\Umbraco.Web.UI.Client\bower_components /Q /S
diff --git a/build/Build.proj b/build/Build.proj
index e937df15bd..9413b49d15 100644
--- a/build/Build.proj
+++ b/build/Build.proj
@@ -175,8 +175,8 @@
diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat
index 0d58204f65..6c11cc9fc5 100644
--- a/build/BuildBelle.bat
+++ b/build/BuildBelle.bat
@@ -1,4 +1,6 @@
@ECHO OFF
+SETLOCAL
+
SET release=%1
ECHO Installing Npm NuGet Package
@@ -11,12 +13,9 @@ ECHO Current folder: %CD%
for /f "delims=" %%A in ('dir %nuGetFolder%node.js.* /b') do set "nodePath=%nuGetFolder%%%A\"
for /f "delims=" %%A in ('dir %nuGetFolder%npm.js.* /b') do set "npmPath=%nuGetFolder%%%A\tools\"
-ECHO Temporarily adding Npm and Node to path
-SET oldPath=%PATH%
-
-path=%npmPath%;%nodePath%;%PATH%
-
-ECHO %path%
+ECHO Adding Npm and Node to path
+REM SETLOCAL is on, so changes to the path not persist to the actual user's path
+PATH=%npmPath%;%nodePath%;%PATH%
SET buildFolder=%CD%
@@ -29,8 +28,5 @@ call npm install -g grunt-cli --quiet
call npm install -g bower --quiet
call grunt build --buildversion=%release%
-ECHO Reset path to what it was before
-path=%oldPath%
-
ECHO Move back to the build folder
CD %buildFolder%
\ No newline at end of file
diff --git a/build/InstallGit.cmd b/build/InstallGit.cmd
index 2bd6d7cc35..b6ba71df9b 100644
--- a/build/InstallGit.cmd
+++ b/build/InstallGit.cmd
@@ -1,5 +1,6 @@
@ECHO OFF
-SET oldPath=%PATH%
+SETLOCAL
+REM SETLOCAL is on, so changes to the path not persist to the actual user's path
git.exe 2> NUL
if %ERRORLEVEL%==9009 GOTO :trydefaultpath
@@ -12,8 +13,7 @@ if %ERRORLEVEL%==9009 GOTO :showerror
GOTO :EOF
:showerror
-path=%oldPath%
-ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd
+ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd
set /p install=" Do you want to install Git through Chocolatey [y/n]? " %=%
if %install%==y (
GOTO :installgit
@@ -29,5 +29,4 @@ GOTO :EOF
ECHO Installing Chocolatey first
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
ECHO Installing Git through Chocolatey
-choco install git
-path=C:\Program Files (x86)\Git\cmd;%path%
+choco install git
\ No newline at end of file
diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index 6852b04510..ae2c45e329 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -17,11 +17,10 @@
-
-
+
@@ -31,13 +30,13 @@
-
+
-
-
+
+
-
-
+
+
@@ -72,35 +71,35 @@
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec
index 61a8188804..46703a0c39 100644
--- a/build/NuSpecs/UmbracoCms.nuspec
+++ b/build/NuSpecs/UmbracoCms.nuspec
@@ -16,7 +16,8 @@
umbraco
-
+
+
@@ -32,6 +33,7 @@
+
diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt
index 197f9c1b6f..a77632926c 100644
--- a/build/NuSpecs/tools/Dashboard.config.install.xdt
+++ b/build/NuSpecs/tools/Dashboard.config.install.xdt
@@ -77,4 +77,8 @@
+
+
\ No newline at end of file
diff --git a/build/NuSpecs/tools/Readme.txt b/build/NuSpecs/tools/Readme.txt
index 53d9c3c7da..b6b55c1c4f 100644
--- a/build/NuSpecs/tools/Readme.txt
+++ b/build/NuSpecs/tools/Readme.txt
@@ -10,6 +10,7 @@
Don't forget to build!
+
When upgrading your website using NuGet you should answer "No" to the questions to overwrite the Web.config
file (and config files in the config folder).
diff --git a/build/NuSpecs/tools/ReadmeUpgrade.txt b/build/NuSpecs/tools/ReadmeUpgrade.txt
index 894f5e5f93..e0d660a795 100644
--- a/build/NuSpecs/tools/ReadmeUpgrade.txt
+++ b/build/NuSpecs/tools/ReadmeUpgrade.txt
@@ -10,6 +10,7 @@
Don't forget to build!
+
We've done our best to transform your configuration files but in case something is not quite right: remember we
backed up your files in App_Data\NuGetBackup so you can find the original files before they were transformed.
diff --git a/build/NuSpecs/tools/Views.Web.config.install.xdt b/build/NuSpecs/tools/Views.Web.config.install.xdt
index c34963f2b0..a5797232b8 100644
--- a/build/NuSpecs/tools/Views.Web.config.install.xdt
+++ b/build/NuSpecs/tools/Views.Web.config.install.xdt
@@ -17,6 +17,11 @@
+
+
+
+
+
diff --git a/build/NuSpecs/tools/Web.config.install.xdt b/build/NuSpecs/tools/Web.config.install.xdt
index cff81ba2d8..f8ec871700 100644
--- a/build/NuSpecs/tools/Web.config.install.xdt
+++ b/build/NuSpecs/tools/Web.config.install.xdt
@@ -7,8 +7,8 @@
-
-
+
+
@@ -22,7 +22,7 @@
-
+
@@ -37,7 +37,7 @@
-
+
@@ -48,13 +48,15 @@
-
+
-
+
-
+
+
+
>
@@ -63,9 +65,9 @@
-
+
+
@@ -77,6 +79,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -84,10 +136,114 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -122,6 +278,8 @@
+
+
@@ -140,20 +298,27 @@
-
-
+
+
+
+
+
+
+
-
+
+
+
@@ -165,6 +330,10 @@
+
+
+
+
@@ -175,7 +344,11 @@
-
+
+
+
+
+
@@ -188,16 +361,16 @@
-
+
-
+
-
-
+
+
@@ -205,19 +378,19 @@
-
+
-
+
-
+
-
+
-
+
\ No newline at end of file
diff --git a/build/NuSpecs/tools/install.core.ps1 b/build/NuSpecs/tools/install.core.ps1
index c81fa419c9..32dfecbb00 100644
--- a/build/NuSpecs/tools/install.core.ps1
+++ b/build/NuSpecs/tools/install.core.ps1
@@ -1,18 +1,30 @@
-param($rootPath, $toolsPath, $package, $project)
+param($installPath, $toolsPath, $package, $project)
+
+Write-Host "installPath:" "${installPath}"
+Write-Host "toolsPath:" "${toolsPath}"
+
+Write-Host " "
if ($project) {
$dateTime = Get-Date -Format yyyyMMdd-HHmmss
- $backupPath = Join-Path (Split-Path $project.FullName -Parent) "\App_Data\NuGetBackup\$dateTime"
+
+ # Create paths and list them
+ $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
+ Write-Host "projectPath:" "${projectPath}"
+ $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime"
+ Write-Host "backupPath:" "${backupPath}"
$copyLogsPath = Join-Path $backupPath "CopyLogs"
- $projectDestinationPath = Split-Path $project.FullName -Parent
-
+ Write-Host "copyLogsPath:" "${copyLogsPath}"
+ $umbracoBinFolder = Join-Path $projectPath "bin"
+ Write-Host "umbracoBinFolder:" "${umbracoBinFolder}"
+
# Create backup folder and logs folder if it doesn't exist yet
New-Item -ItemType Directory -Force -Path $backupPath
New-Item -ItemType Directory -Force -Path $copyLogsPath
# After backing up, remove all umbraco dlls from bin folder in case dll files are included in the VS project
# See: http://issues.umbraco.org/issue/U4-4930
- $umbracoBinFolder = Join-Path $projectDestinationPath "bin"
+
if(Test-Path $umbracoBinFolder) {
$umbracoBinBackupPath = Join-Path $backupPath "bin"
@@ -20,7 +32,7 @@ if ($project) {
robocopy $umbracoBinFolder $umbracoBinBackupPath /e /LOG:$copyLogsPath\UmbracoBinBackup.log
- # Delete files Umbraco brings in
+ # Delete files Umbraco ships with
if(Test-Path $umbracoBinFolder\businesslogic.dll) { Remove-Item $umbracoBinFolder\businesslogic.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\cms.dll) { Remove-Item $umbracoBinFolder\cms.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\controls.dll) { Remove-Item $umbracoBinFolder\controls.dll -Force -Confirm:$false }
@@ -35,16 +47,18 @@ if ($project) {
if(Test-Path $umbracoBinFolder\umbraco.DataLayer.dll) { Remove-Item $umbracoBinFolder\umbraco.DataLayer.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.editorControls.dll) { Remove-Item $umbracoBinFolder\umbraco.editorControls.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.MacroEngines.dll) { Remove-Item $umbracoBinFolder\umbraco.MacroEngines.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll) { Remove-Item $umbracoBinFolder\Umbraco.ModelsBuilder.AspNet.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\umbraco.providers.dll) { Remove-Item $umbracoBinFolder\umbraco.providers.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\UmbracoExamine.dll) { Remove-Item $umbracoBinFolder\UmbracoExamine.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\UrlRewritingNet.UrlRewriter.dll) { Remove-Item $umbracoBinFolder\UrlRewritingNet.UrlRewriter.dll -Force -Confirm:$false }
# Delete files Umbraco depends upon
- $amd64Folder = Join-Path $projectDestinationPath "bin\amd64"
+ $amd64Folder = Join-Path $umbracoBinFolder "amd64"
if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false }
- $x86Folder = Join-Path $projectDestinationPath "bin\x86"
- if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false }
+ $x86Folder = Join-Path $umbracoBinFolder "x86"
+ if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false }
if(Test-Path $umbracoBinFolder\AutoMapper.dll) { Remove-Item $umbracoBinFolder\AutoMapper.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\AutoMapper.Net4.dll) { Remove-Item $umbracoBinFolder\AutoMapper.Net4.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\ClientDependency.Core.dll) { Remove-Item $umbracoBinFolder\ClientDependency.Core.dll -Force -Confirm:$false }
@@ -58,6 +72,8 @@ if ($project) {
if(Test-Path $umbracoBinFolder\Lucene.Net.dll) { Remove-Item $umbracoBinFolder\Lucene.Net.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Core.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.AspNet.Identity.Owin.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.CSharp.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Microsoft.CodeAnalysis.dll) { Remove-Item $umbracoBinFolder\Microsoft.CodeAnalysis.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Host.SystemWeb.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Microsoft.Owin.Security.dll) { Remove-Item $umbracoBinFolder\Microsoft.Owin.Security.dll -Force -Confirm:$false }
@@ -71,6 +87,8 @@ if ($project) {
if(Test-Path $umbracoBinFolder\Newtonsoft.Json.dll) { Remove-Item $umbracoBinFolder\Newtonsoft.Json.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Owin.dll) { Remove-Item $umbracoBinFolder\Owin.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\Semver.dll) { Remove-Item $umbracoBinFolder\Semver.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\System.Collections.Immutable.dll) { Remove-Item $umbracoBinFolder\System.Collections.Immutable.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\System.Reflection.Metadata.dll) { Remove-Item $umbracoBinFolder\System.Reflection.Metadata.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Extensions.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Extensions.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Formatting.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Formatting.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Net.Http.Primitives.dll) { Remove-Item $umbracoBinFolder\System.Net.Http.Primitives.dll -Force -Confirm:$false }
@@ -81,6 +99,6 @@ if ($project) {
if(Test-Path $umbracoBinFolder\System.Web.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.Razor.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.WebPages.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.dll -Force -Confirm:$false }
if(Test-Path $umbracoBinFolder\System.Web.WebPages.Deployment.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Deployment.dll -Force -Confirm:$false }
- if(Test-Path $umbracoBinFolder\System.Web.WebPages.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Razor.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\System.Web.WebPages.Razor.dll) { Remove-Item $umbracoBinFolder\System.Web.WebPages.Razor.dll -Force -Confirm:$false }
}
}
\ No newline at end of file
diff --git a/build/NuSpecs/tools/install.ps1 b/build/NuSpecs/tools/install.ps1
index 49b49f6cfe..de7a6cc16e 100644
--- a/build/NuSpecs/tools/install.ps1
+++ b/build/NuSpecs/tools/install.ps1
@@ -1,21 +1,33 @@
-param($rootPath, $toolsPath, $package, $project)
+param($installPath, $toolsPath, $package, $project)
+
+Write-Host "installPath:" "${installPath}"
+Write-Host "toolsPath:" "${toolsPath}"
+
+Write-Host " "
if ($project) {
$dateTime = Get-Date -Format yyyyMMdd-HHmmss
- $backupPath = Join-Path (Split-Path $project.FullName -Parent) "\App_Data\NuGetBackup\$dateTime"
+
+ # Create paths and list them
+ $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
+ Write-Host "projectPath:" "${projectPath}"
+ $backupPath = Join-Path $projectPath "App_Data\NuGetBackup\$dateTime"
+ Write-Host "backupPath:" "${backupPath}"
$copyLogsPath = Join-Path $backupPath "CopyLogs"
- $projectDestinationPath = Split-Path $project.FullName -Parent
+ Write-Host "copyLogsPath:" "${copyLogsPath}"
+ $webConfigSource = Join-Path $projectPath "Web.config"
+ Write-Host "webConfigSource:" "${webConfigSource}"
+ $configFolder = Join-Path $projectPath "Config"
+ Write-Host "configFolder:" "${configFolder}"
# Create backup folder and logs folder if it doesn't exist yet
New-Item -ItemType Directory -Force -Path $backupPath
New-Item -ItemType Directory -Force -Path $copyLogsPath
# Create a backup of original web.config
- $webConfigSource = Join-Path $projectDestinationPath "Web.config"
Copy-Item $webConfigSource $backupPath -Force
- # Backup config files folder
- $configFolder = Join-Path $projectDestinationPath "Config"
+ # Backup config files folder
if(Test-Path $configFolder) {
$umbracoBackupPath = Join-Path $backupPath "Config"
New-Item -ItemType Directory -Force -Path $umbracoBackupPath
@@ -24,32 +36,24 @@ if ($project) {
}
# Copy umbraco and umbraco_files from package to project folder
- # This is only done when these folders already exist because we
- # only want to do this for upgrades
- $umbracoFolder = Join-Path $projectDestinationPath "Umbraco"
- if(Test-Path $umbracoFolder) {
- $umbracoFolderSource = Join-Path $rootPath "UmbracoFiles\Umbraco"
-
- $umbracoBackupPath = Join-Path $backupPath "Umbraco"
- New-Item -ItemType Directory -Force -Path $umbracoBackupPath
-
- robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log
- robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log
- }
+ $umbracoFolder = Join-Path $projectPath "Umbraco"
+ New-Item -ItemType Directory -Force -Path $umbracoFolder
+ $umbracoFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco"
+ $umbracoBackupPath = Join-Path $backupPath "Umbraco"
+ New-Item -ItemType Directory -Force -Path $umbracoBackupPath
+ robocopy $umbracoFolder $umbracoBackupPath /e /LOG:$copyLogsPath\UmbracoBackup.log
+ robocopy $umbracoFolderSource $umbracoFolder /is /it /e /xf UI.xml /LOG:$copyLogsPath\UmbracoCopy.log
- $umbracoClientFolder = Join-Path $projectDestinationPath "Umbraco_Client"
- if(Test-Path $umbracoClientFolder) {
- $umbracoClientFolderSource = Join-Path $rootPath "UmbracoFiles\Umbraco_Client"
-
- $umbracoClientBackupPath = Join-Path $backupPath "Umbraco_Client"
- New-Item -ItemType Directory -Force -Path $umbracoClientBackupPath
-
- robocopy $umbracoClientFolder $umbracoClientBackupPath /e /LOG:$copyLogsPath\UmbracoClientBackup.log
- robocopy $umbracoClientFolderSource $umbracoClientFolder /is /it /e /LOG:$copyLogsPath\UmbracoClientCopy.log
- }
+ $umbracoClientFolder = Join-Path $projectPath "Umbraco_Client"
+ New-Item -ItemType Directory -Force -Path $umbracoClientFolder
+ $umbracoClientFolderSource = Join-Path $installPath "UmbracoFiles\Umbraco_Client"
+ $umbracoClientBackupPath = Join-Path $backupPath "Umbraco_Client"
+ New-Item -ItemType Directory -Force -Path $umbracoClientBackupPath
+ robocopy $umbracoClientFolder $umbracoClientBackupPath /e /LOG:$copyLogsPath\UmbracoClientBackup.log
+ robocopy $umbracoClientFolderSource $umbracoClientFolder /is /it /e /LOG:$copyLogsPath\UmbracoClientCopy.log
$copyWebconfig = $true
- $destinationWebConfig = Join-Path $projectDestinationPath "Web.config"
+ $destinationWebConfig = Join-Path $projectPath "Web.config"
if(Test-Path $destinationWebConfig)
{
@@ -71,11 +75,11 @@ if ($project) {
if($copyWebconfig -eq $true)
{
- $packageWebConfigSource = Join-Path $rootPath "UmbracoFiles\Web.config"
+ $packageWebConfigSource = Join-Path $installPath "UmbracoFiles\Web.config"
Copy-Item $packageWebConfigSource $destinationWebConfig -Force
}
- $installFolder = Join-Path $projectDestinationPath "Install"
+ $installFolder = Join-Path $projectPath "Install"
if(Test-Path $installFolder) {
Remove-Item $installFolder -Force -Recurse -Confirm:$false
}
diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt
index f5a807b4bf..580c619547 100644
--- a/build/NuSpecs/tools/trees.config.install.xdt
+++ b/build/NuSpecs/tools/trees.config.install.xdt
@@ -95,19 +95,19 @@
xdt:Transform="SetAttributes()" />
-
-
-
diff --git a/build/NuSpecs/tools/uninstall.core.ps1 b/build/NuSpecs/tools/uninstall.core.ps1
new file mode 100644
index 0000000000..3daa6a1ba7
--- /dev/null
+++ b/build/NuSpecs/tools/uninstall.core.ps1
@@ -0,0 +1,48 @@
+param($installPath, $toolsPath, $package, $project)
+
+Write-Host "installPath:" "${installPath}"
+Write-Host "toolsPath:" "${toolsPath}"
+
+Write-Host " "
+
+if ($project) {
+
+ # Create paths and list them
+ $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
+ Write-Host "projectPath:" "${projectPath}"
+ $backupPath = Join-Path $projectPath "App_Data\NuGetBackup"
+ Write-Host "backupPath:" "${backupPath}"
+ $umbracoBinFolder = Join-Path $projectPath "bin"
+ Write-Host "umbracoBinFolder:" "${umbracoBinFolder}"
+
+ # Remove backups
+ Write-Host "removing backups:" "${backupPath}"
+ if(Test-Path $backupPath) { Remove-Item -Recurse -Force $backupPath -Confirm:$false }
+
+ # Delete files Umbraco ships with
+
+ Write-Host "removing dlls:" "${umbracoBinFolder}"
+ if(Test-Path $umbracoBinFolder\businesslogic.dll) { Remove-Item $umbracoBinFolder\businesslogic.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\cms.dll) { Remove-Item $umbracoBinFolder\cms.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\controls.dll) { Remove-Item $umbracoBinFolder\controls.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\interfaces.dll) { Remove-Item $umbracoBinFolder\interfaces.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\log4net.dll) { Remove-Item $umbracoBinFolder\log4net.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll) { Remove-Item $umbracoBinFolder\Microsoft.ApplicationBlocks.Data.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\SQLCE4Umbraco.dll) { Remove-Item $umbracoBinFolder\SQLCE4Umbraco.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll) { Remove-Item $umbracoBinFolder\System.Data.SqlServerCe.Entity.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\TidyNet.dll) { Remove-Item $umbracoBinFolder\TidyNet.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\umbraco.dll) { Remove-Item $umbracoBinFolder\umbraco.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Umbraco.Core.dll) { Remove-Item $umbracoBinFolder\Umbraco.Core.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\umbraco.DataLayer.dll) { Remove-Item $umbracoBinFolder\umbraco.DataLayer.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\umbraco.editorControls.dll) { Remove-Item $umbracoBinFolder\umbraco.editorControls.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\umbraco.MacroEngines.dll) { Remove-Item $umbracoBinFolder\umbraco.MacroEngines.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\umbraco.providers.dll) { Remove-Item $umbracoBinFolder\umbraco.providers.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\Umbraco.Web.UI.dll) { Remove-Item $umbracoBinFolder\Umbraco.Web.UI.dll -Force -Confirm:$false }
+ if(Test-Path $umbracoBinFolder\UmbracoExamine.dll) { Remove-Item $umbracoBinFolder\UmbracoExamine.dll -Force -Confirm:$false }
+
+ $amd64Folder = Join-Path $umbracoBinFolder "amd64"
+ if(Test-Path $amd64Folder) { Remove-Item $amd64Folder -Force -Recurse -Confirm:$false }
+ $x86Folder = Join-Path $umbracoBinFolder "x86"
+ if(Test-Path $x86Folder) { Remove-Item $x86Folder -Force -Recurse -Confirm:$false }
+}
\ No newline at end of file
diff --git a/build/NuSpecs/tools/uninstall.ps1 b/build/NuSpecs/tools/uninstall.ps1
new file mode 100644
index 0000000000..4f7dd35384
--- /dev/null
+++ b/build/NuSpecs/tools/uninstall.ps1
@@ -0,0 +1,39 @@
+param($installPath, $toolsPath, $package, $project)
+
+Write-Host "installPath:" "${installPath}"
+Write-Host "toolsPath:" "${toolsPath}"
+
+Write-Host " "
+
+if ($project) {
+
+ # Create paths and list them
+ $projectPath = (Get-Item $project.Properties.Item("FullPath").Value).FullName
+ Write-Host "projectPath:" "${projectPath}"
+ $backupPath = Join-Path $projectPath "App_Data\NuGetBackup"
+ Write-Host "backupPath:" "${backupPath}"
+ $appBrowsers = Join-Path $projectPath "App_Browsers"
+ Write-Host "appBrowsers:" "${appBrowsers}"
+ $appData = Join-Path $projectPath "App_Data"
+ Write-Host "appData:" "${appData}"
+
+ # Remove backups
+ Write-Host "removing backups:" "${backupPath}"
+ if(Test-Path $backupPath) { Remove-Item -Recurse -Force $backupPath -Confirm:$false }
+
+ # Remove app_data files
+ Write-Host "removing app_data files:" "${appData}"
+ if(Test-Path $appData\packages) { Remove-Item $appData\packages -Recurse -Force -Confirm:$false }
+
+ Write-Host "removing app_browsers:" "${appBrowsers}"
+ if(Test-Path $appBrowsers\Form.browser) { Remove-Item $appBrowsers\Form.browser -Force -Confirm:$false }
+ if(Test-Path $appBrowsers\w3cvalidator.browser) { Remove-Item $appBrowsers\w3cvalidator.browser -Force -Confirm:$false }
+
+ # Remove umbraco and umbraco_files
+ $umbracoFolder = Join-Path $projectPath "Umbraco"
+ Write-Host "removing umbraco folder:" "${umbracoFolder}"
+ if(Test-Path $umbracoFolder) { Remove-Item $umbracoFolder -Recurse -Force -Confirm:$false }
+ $umbracoClientFolder = Join-Path $projectPath "Umbraco_Client"
+ Write-Host "removing umbraco client folder:" "${umbracoClientFolder}"
+ if(Test-Path $umbracoClientFolder) { Remove-Item $umbracoClientFolder -Recurse -Force -Confirm:$false }
+}
diff --git a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj
index ceb8381b48..216fa74a67 100644
--- a/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj
+++ b/src/SQLCE4Umbraco/SqlCE4Umbraco.csproj
@@ -47,13 +47,15 @@
-
- True
- ..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.dll
+
+ ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.dll
+ False
+ False
-
- True
- ..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.Entity.dll
+
+ ..\packages\SqlServerCE.4.0.0.1\lib\System.Data.SqlServerCe.Entity.dll
+ False
+ False
diff --git a/src/SQLCE4Umbraco/packages.config b/src/SQLCE4Umbraco/packages.config
index 5d2d5789e7..e2435f3e8b 100644
--- a/src/SQLCE4Umbraco/packages.config
+++ b/src/SQLCE4Umbraco/packages.config
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 28b314750a..2e29693e9a 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -2,7 +2,7 @@
using System.Resources;
[assembly: AssemblyCompany("Umbraco")]
-[assembly: AssemblyCopyright("Copyright © Umbraco 2015")]
+[assembly: AssemblyCopyright("Copyright © Umbraco 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyFileVersion("7.4.0")]
-[assembly: AssemblyInformationalVersion("7.4.0-beta")]
\ No newline at end of file
+[assembly: AssemblyFileVersion("7.4.1")]
+[assembly: AssemblyInformationalVersion("7.4.1")]
\ No newline at end of file
diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs
index 0e497531f1..db905be43f 100644
--- a/src/Umbraco.Core/ApplicationContext.cs
+++ b/src/Umbraco.Core/ApplicationContext.cs
@@ -356,7 +356,8 @@ namespace Umbraco.Core
//clear the cache
if (ApplicationCache != null)
{
- ApplicationCache.ClearAllCache();
+ ApplicationCache.RuntimeCache.ClearAllCache();
+ ApplicationCache.IsolatedRuntimeCache.ClearAllCaches();
}
//reset all resolvers
ResolverCollection.ResetAll();
diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs
index 88d570beff..0c1a202b66 100644
--- a/src/Umbraco.Core/Cache/CacheKeys.cs
+++ b/src/Umbraco.Core/Cache/CacheKeys.cs
@@ -1,8 +1,9 @@
using System;
+using System.ComponentModel;
+using Umbraco.Core.CodeAnnotations;
namespace Umbraco.Core.Cache
{
-
///
/// Constants storing cache keys used in caching
///
@@ -12,52 +13,78 @@ namespace Umbraco.Core.Cache
public const string ApplicationsCacheKey = "ApplicationCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string UserTypeCacheKey = "UserTypeCache";
+ [Obsolete("This is no longer used and will be removed from the codebase in the future - it is referenced but no cache is stored against this key")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string ContentItemCacheKey = "contentItem";
+ [UmbracoWillObsolete("This cache key is only used for the legacy 'library' caching, remove in v8")]
public const string MediaCacheKey = "UL_GetMedia";
public const string MacroXsltCacheKey = "macroXslt_";
+
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string MacroCacheKey = "UmbracoMacroCache";
+
public const string MacroHtmlCacheKey = "macroHtml_";
public const string MacroControlCacheKey = "macroControl_";
public const string MacroHtmlDateAddedCacheKey = "macroHtml_DateAdded_";
public const string MacroControlDateAddedCacheKey = "macroControl_DateAdded_";
+ [UmbracoWillObsolete("This cache key is only used for legacy 'library' member caching, remove in v8")]
public const string MemberLibraryCacheKey = "UL_GetMember";
+
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string MemberBusinessLogicCacheKey = "MemberCacheItem_";
-
+
+ [UmbracoWillObsolete("This cache key is only used for legacy template business logic caching, remove in v8")]
public const string TemplateFrontEndCacheKey = "template";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string TemplateBusinessLogicCacheKey = "UmbracoTemplateCache";
+ [Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string UserContextCacheKey = "UmbracoUserContext";
+
public const string UserContextTimeoutCacheKey = "UmbracoUserContextTimeout";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string UserCacheKey = "UmbracoUser";
public const string UserPermissionsCacheKey = "UmbracoUserPermissions";
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string ContentTypeCacheKey = "UmbracoContentType";
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:";
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string LanguageCacheKey = "UmbracoLanguageCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string DomainCacheKey = "UmbracoDomainList";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string StylesheetCacheKey = "UmbracoStylesheet";
+
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string StylesheetPropertyCacheKey = "UmbracoStylesheetProperty";
+ [Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string DataTypeCacheKey = "UmbracoDataTypeDefinition";
public const string DataTypePreValuesCacheKey = "UmbracoPreVal";
diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
index 2931805b08..60ad69b6fc 100644
--- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
@@ -2,6 +2,7 @@
using Umbraco.Core.Events;
using Umbraco.Core.Sync;
using umbraco.interfaces;
+using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
@@ -63,5 +64,15 @@ namespace Umbraco.Core.Cache
{
OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RefreshById));
}
+
+ ///
+ /// Clears the cache for all repository entities of this type
+ ///
+ ///
+ internal void ClearAllIsolatedCacheByEntityType()
+ where TEntity : class, IAggregateRoot
+ {
+ ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.ClearCache();
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs
similarity index 93%
rename from src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs
rename to src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs
index 0b5b42660d..0ae721943d 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs
@@ -2,20 +2,27 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Caching;
-using Umbraco.Core.Cache;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
-namespace Umbraco.Core.Persistence.Repositories
+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
+ internal class DeepCloneRuntimeCacheProvider : IRuntimeCacheProvider, IRuntimeCacheProviderWrapper
{
- internal IRuntimeCacheProvider InnerProvider { get; private set; }
+ public IRuntimeCacheProvider InnerProvider { get; private set; }
public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider)
{
diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..1f51fc3ccc
--- /dev/null
+++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
@@ -0,0 +1,268 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// The default cache policy for retrieving a single entity
+ ///
+ ///
+ ///
+ ///
+ /// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the
+ /// default policy with no expiry.
+ ///
+ internal class DefaultRepositoryCachePolicy : RepositoryCachePolicyBase
+ where TEntity : class, IAggregateRoot
+ {
+ private readonly RepositoryCachePolicyOptions _options;
+
+ public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options)
+ : base(cache)
+ {
+ if (options == null) throw new ArgumentNullException("options");
+ _options = options;
+ }
+
+ protected string GetCacheIdKey(object id)
+ {
+ if (id == null) throw new ArgumentNullException("id");
+
+ return string.Format("{0}{1}", GetCacheTypeKey(), id);
+ }
+
+ protected string GetCacheTypeKey()
+ {
+ return string.Format("uRepo_{0}_", typeof(TEntity).Name);
+ }
+
+ public override void CreateOrUpdate(TEntity entity, Action persistMethod)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ if (persistMethod == null) throw new ArgumentNullException("persistMethod");
+
+ try
+ {
+ persistMethod(entity);
+
+ //set the disposal action
+ SetCacheAction(() =>
+ {
+ //just to be safe, we cannot cache an item without an identity
+ if (entity.HasIdentity)
+ {
+ Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity,
+ timeout: TimeSpan.FromMinutes(5),
+ isSliding: true);
+ }
+
+ //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
+ Cache.ClearCacheItem(GetCacheTypeKey());
+ });
+
+ }
+ catch
+ {
+ //set the disposal action
+ SetCacheAction(() =>
+ {
+ //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(GetCacheIdKey(entity.Id));
+
+ //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
+ Cache.ClearCacheItem(GetCacheTypeKey());
+ });
+
+ throw;
+ }
+ }
+
+ public override void Remove(TEntity entity, Action persistMethod)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ if (persistMethod == null) throw new ArgumentNullException("persistMethod");
+
+ try
+ {
+ persistMethod(entity);
+ }
+ finally
+ {
+ //set the disposal action
+ var cacheKey = GetCacheIdKey(entity.Id);
+ SetCacheAction(() =>
+ {
+ Cache.ClearCacheItem(cacheKey);
+ //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
+ Cache.ClearCacheItem(GetCacheTypeKey());
+ });
+ }
+ }
+
+ public override TEntity Get(TId id, Func getFromRepo)
+ {
+ if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
+
+ var cacheKey = GetCacheIdKey(id);
+ var fromCache = Cache.GetCacheItem(cacheKey);
+ if (fromCache != null)
+ return fromCache;
+
+ var entity = getFromRepo(id);
+
+ //set the disposal action
+ SetCacheAction(cacheKey, entity);
+
+ return entity;
+ }
+
+ public override TEntity Get(TId id)
+ {
+ var cacheKey = GetCacheIdKey(id);
+ return Cache.GetCacheItem(cacheKey);
+ }
+
+ public override bool Exists(TId id, Func getFromRepo)
+ {
+ if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
+
+ var cacheKey = GetCacheIdKey(id);
+ var fromCache = Cache.GetCacheItem(cacheKey);
+ return fromCache != null || getFromRepo(id);
+ }
+
+ public override TEntity[] GetAll(TId[] ids, Func> getFromRepo)
+ {
+ if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
+
+ if (ids.Any())
+ {
+ var entities = ids.Select(Get).ToArray();
+ if (ids.Length.Equals(entities.Length) && entities.Any(x => x == null) == false)
+ return entities;
+ }
+ else
+ {
+ var allEntities = GetAllFromCache();
+ if (allEntities.Any())
+ {
+ if (_options.GetAllCacheValidateCount)
+ {
+ //Get count of all entities of current type (TEntity) to ensure cached result is correct
+ var totalCount = _options.PerformCount();
+ if (allEntities.Length == totalCount)
+ return allEntities;
+ }
+ else
+ {
+ return allEntities;
+ }
+ }
+ else if (_options.GetAllCacheAllowZeroCount)
+ {
+ //if the repository allows caching a zero count, then check the zero count cache
+ if (HasZeroCountCache())
+ {
+ //there is a zero count cache so return an empty list
+ return new TEntity[] {};
+ }
+ }
+ }
+
+ //we need to do the lookup from the repo
+ var entityCollection = getFromRepo(ids)
+ //ensure we don't include any null refs in the returned collection!
+ .WhereNotNull()
+ .ToArray();
+
+ //set the disposal action
+ SetCacheAction(ids, entityCollection);
+
+ return entityCollection;
+ }
+
+ ///
+ /// Looks up the zero count cache, must return null if it doesn't exist
+ ///
+ ///
+ protected bool HasZeroCountCache()
+ {
+ var zeroCount = Cache.GetCacheItem(GetCacheTypeKey());
+ return (zeroCount != null && zeroCount.Any() == false);
+ }
+
+ ///
+ /// Performs the lookup for all entities of this type from the cache
+ ///
+ ///
+ protected TEntity[] GetAllFromCache()
+ {
+ var allEntities = Cache.GetCacheItemsByKeySearch(GetCacheTypeKey())
+ .WhereNotNull()
+ .ToArray();
+ return allEntities.Any() ? allEntities : new TEntity[] {};
+ }
+
+ ///
+ /// Sets the action to execute on disposal for a single entity
+ ///
+ ///
+ ///
+ protected virtual void SetCacheAction(string cacheKey, TEntity entity)
+ {
+ if (entity == null) return;
+
+ SetCacheAction(() =>
+ {
+ //just to be safe, we cannot cache an item without an identity
+ if (entity.HasIdentity)
+ {
+ Cache.InsertCacheItem(cacheKey, () => entity,
+ timeout: TimeSpan.FromMinutes(5),
+ isSliding: true);
+ }
+ });
+ }
+
+ ///
+ /// Sets the action to execute on disposal for an entity collection
+ ///
+ ///
+ ///
+ protected virtual void SetCacheAction(TId[] ids, TEntity[] entityCollection)
+ {
+ SetCacheAction(() =>
+ {
+ //This option cannot execute if we are looking up specific Ids
+ if (ids.Any() == false && entityCollection.Length == 0 && _options.GetAllCacheAllowZeroCount)
+ {
+ //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache
+ // to signify that there is a zero count cache
+ //NOTE: Don't set expiry/sliding for a zero count
+ Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {});
+ }
+ else
+ {
+ //This is the default behavior, we'll individually cache each item so that if/when these items are resolved
+ // by id, they are returned from the already existing cache.
+ foreach (var entity in entityCollection.WhereNotNull())
+ {
+ var localCopy = entity;
+ //just to be safe, we cannot cache an item without an identity
+ if (localCopy.HasIdentity)
+ {
+ Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy,
+ timeout: TimeSpan.FromMinutes(5),
+ isSliding: true);
+ }
+ }
+ }
+ });
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs
new file mode 100644
index 0000000000..5c02e41a48
--- /dev/null
+++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs
@@ -0,0 +1,27 @@
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Creates cache policies
+ ///
+ ///
+ ///
+ internal class DefaultRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory
+ where TEntity : class, IAggregateRoot
+ {
+ private readonly IRuntimeCacheProvider _runtimeCache;
+ private readonly RepositoryCachePolicyOptions _options;
+
+ public DefaultRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options)
+ {
+ _runtimeCache = runtimeCache;
+ _options = options;
+ }
+
+ public virtual IRepositoryCachePolicy CreatePolicy()
+ {
+ return new DefaultRepositoryCachePolicy(_runtimeCache, _options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..9b37d1861f
--- /dev/null
+++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.Collections;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// A caching policy that caches an entire dataset as a single collection
+ ///
+ ///
+ ///
+ internal class FullDataSetRepositoryCachePolicy : RepositoryCachePolicyBase
+ where TEntity : class, IAggregateRoot
+ {
+ private readonly Func _getEntityId;
+ private readonly Func> _getAllFromRepo;
+ private readonly bool _expires;
+
+ public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func getEntityId, Func> getAllFromRepo, bool expires)
+ : base(cache)
+ {
+ _getEntityId = getEntityId;
+ _getAllFromRepo = getAllFromRepo;
+ _expires = expires;
+ }
+
+ private bool? _hasZeroCountCache;
+
+
+ protected string GetCacheTypeKey()
+ {
+ return string.Format("uRepo_{0}_", typeof(TEntity).Name);
+ }
+
+ public override void CreateOrUpdate(TEntity entity, Action persistMethod)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ if (persistMethod == null) throw new ArgumentNullException("persistMethod");
+
+ try
+ {
+ persistMethod(entity);
+
+ //set the disposal action
+ SetCacheAction(() =>
+ {
+ //Clear all
+ Cache.ClearCacheItem(GetCacheTypeKey());
+ });
+ }
+ catch
+ {
+ //set the disposal action
+ SetCacheAction(() =>
+ {
+ //Clear all
+ Cache.ClearCacheItem(GetCacheTypeKey());
+ });
+ throw;
+ }
+ }
+
+ public override void Remove(TEntity entity, Action persistMethod)
+ {
+ if (entity == null) throw new ArgumentNullException("entity");
+ if (persistMethod == null) throw new ArgumentNullException("persistMethod");
+
+ try
+ {
+ persistMethod(entity);
+ }
+ finally
+ {
+ //set the disposal action
+ SetCacheAction(() =>
+ {
+ //Clear all
+ Cache.ClearCacheItem(GetCacheTypeKey());
+ });
+ }
+ }
+
+ public override TEntity Get(TId id, Func getFromRepo)
+ {
+ //Force get all with cache
+ var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
+
+ //we don't have anything in cache (this should never happen), just return from the repo
+ if (found == null) return getFromRepo(id);
+ var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id));
+ if (entity == null) return null;
+
+ //We must ensure to deep clone each one out manually since the deep clone list only clones one way
+ return (TEntity)entity.DeepClone();
+ }
+
+ public override TEntity Get(TId id)
+ {
+ //Force get all with cache
+ var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
+
+ //we don't have anything in cache (this should never happen), just return null
+ if (found == null) return null;
+ var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id));
+ if (entity == null) return null;
+
+ //We must ensure to deep clone each one out manually since the deep clone list only clones one way
+ return (TEntity)entity.DeepClone();
+ }
+
+ public override bool Exists(TId id, Func getFromRepo)
+ {
+ //Force get all with cache
+ var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
+
+ //we don't have anything in cache (this should never happen), just return from the repo
+ return found == null
+ ? getFromRepo(id)
+ : found.Any(x => _getEntityId(x).Equals(id));
+ }
+
+ public override TEntity[] GetAll(TId[] ids, Func> getFromRepo)
+ {
+ //process getting all including setting the cache callback
+ var result = PerformGetAll(getFromRepo);
+
+ //now that the base result has been calculated, they will all be cached.
+ // Now we can just filter by ids if they have been supplied
+
+ return (ids.Any()
+ ? result.Where(x => ids.Contains(_getEntityId(x))).ToArray()
+ : result)
+ //We must ensure to deep clone each one out manually since the deep clone list only clones one way
+ .Select(x => (TEntity)x.DeepClone())
+ .ToArray();
+ }
+
+ private TEntity[] PerformGetAll(Func> getFromRepo)
+ {
+ var allEntities = GetAllFromCache();
+ if (allEntities.Any())
+ {
+ return allEntities;
+ }
+
+ //check the zero count cache
+ if (HasZeroCountCache())
+ {
+ //there is a zero count cache so return an empty list
+ return new TEntity[] { };
+ }
+
+ //we need to do the lookup from the repo
+ var entityCollection = getFromRepo(new TId[] { })
+ //ensure we don't include any null refs in the returned collection!
+ .WhereNotNull()
+ .ToArray();
+
+ //set the disposal action
+ SetCacheAction(entityCollection);
+
+ return entityCollection;
+ }
+
+ ///
+ /// For this type of caching policy, we don't cache individual items
+ ///
+ ///
+ ///
+ protected void SetCacheAction(string cacheKey, TEntity entity)
+ {
+ //No-op
+ }
+
+ ///
+ /// Sets the action to execute on disposal for an entity collection
+ ///
+ ///
+ protected void SetCacheAction(TEntity[] entityCollection)
+ {
+ //set the disposal action
+ SetCacheAction(() =>
+ {
+ //We want to cache the result as a single collection
+
+ if (_expires)
+ {
+ Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection),
+ timeout: TimeSpan.FromMinutes(5),
+ isSliding: true);
+ }
+ else
+ {
+ Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection));
+ }
+ });
+ }
+
+ ///
+ /// Looks up the zero count cache, must return null if it doesn't exist
+ ///
+ ///
+ protected bool HasZeroCountCache()
+ {
+ if (_hasZeroCountCache.HasValue)
+ return _hasZeroCountCache.Value;
+
+ _hasZeroCountCache = Cache.GetCacheItem>(GetCacheTypeKey()) != null;
+ return _hasZeroCountCache.Value;
+ }
+
+ ///
+ /// This policy will cache the full data set as a single collection
+ ///
+ ///
+ protected TEntity[] GetAllFromCache()
+ {
+ var found = Cache.GetCacheItem>(GetCacheTypeKey());
+
+ //This method will get called before checking for zero count cache, so we'll just set the flag here
+ _hasZeroCountCache = found != null;
+
+ return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs
new file mode 100644
index 0000000000..e4addcf355
--- /dev/null
+++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Creates cache policies
+ ///
+ ///
+ ///
+ internal class FullDataSetRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory
+ where TEntity : class, IAggregateRoot
+ {
+ private readonly IRuntimeCacheProvider _runtimeCache;
+ private readonly Func _getEntityId;
+ private readonly Func> _getAllFromRepo;
+ private readonly bool _expires;
+
+ public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func getEntityId, Func> getAllFromRepo, bool expires)
+ {
+ _runtimeCache = runtimeCache;
+ _getEntityId = getEntityId;
+ _getAllFromRepo = getAllFromRepo;
+ _expires = expires;
+ }
+
+ public virtual IRepositoryCachePolicy CreatePolicy()
+ {
+ return new FullDataSetRepositoryCachePolicy(_runtimeCache, _getEntityId, _getAllFromRepo, _expires);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..215487c3be
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ internal interface IRepositoryCachePolicy : IDisposable
+ where TEntity : class, IAggregateRoot
+ {
+ TEntity Get(TId id, Func getFromRepo);
+ TEntity Get(TId id);
+ bool Exists(TId id, Func getFromRepo);
+
+ void CreateOrUpdate(TEntity entity, Action persistMethod);
+ void Remove(TEntity entity, Action persistMethod);
+ TEntity[] GetAll(TId[] ids, Func> getFromRepo);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs
new file mode 100644
index 0000000000..2d69704b63
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs
@@ -0,0 +1,9 @@
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ internal interface IRepositoryCachePolicyFactory where TEntity : class, IAggregateRoot
+ {
+ IRepositoryCachePolicy CreatePolicy();
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs
new file mode 100644
index 0000000000..da20f7eb73
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs
@@ -0,0 +1,91 @@
+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();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs
new file mode 100644
index 0000000000..b24838bc3b
--- /dev/null
+++ b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs
@@ -0,0 +1,27 @@
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Creates cache policies
+ ///
+ ///
+ ///
+ internal class OnlySingleItemsRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory
+ where TEntity : class, IAggregateRoot
+ {
+ private readonly IRuntimeCacheProvider _runtimeCache;
+ private readonly RepositoryCachePolicyOptions _options;
+
+ public OnlySingleItemsRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options)
+ {
+ _runtimeCache = runtimeCache;
+ _options = options;
+ }
+
+ public virtual IRepositoryCachePolicy CreatePolicy()
+ {
+ return new SingleItemsOnlyRepositoryCachePolicy(_runtimeCache, _options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs
new file mode 100644
index 0000000000..b939cd14e6
--- /dev/null
+++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ internal abstract class RepositoryCachePolicyBase : DisposableObject, IRepositoryCachePolicy
+ where TEntity : class, IAggregateRoot
+ {
+ private Action _action;
+
+ protected RepositoryCachePolicyBase(IRuntimeCacheProvider cache)
+ {
+ if (cache == null) throw new ArgumentNullException("cache");
+
+ Cache = cache;
+ }
+
+ protected IRuntimeCacheProvider Cache { get; private set; }
+
+ ///
+ /// The disposal performs the caching
+ ///
+ protected override void DisposeResources()
+ {
+ if (_action != null)
+ {
+ _action();
+ }
+ }
+
+ ///
+ /// Sets the action to execute on disposal
+ ///
+ ///
+ protected void SetCacheAction(Action action)
+ {
+ _action = action;
+ }
+
+ public abstract TEntity Get(TId id, Func getFromRepo);
+ public abstract TEntity Get(TId id);
+ public abstract bool Exists(TId id, Func getFromRepo);
+ public abstract void CreateOrUpdate(TEntity entity, Action persistMethod);
+ public abstract void Remove(TEntity entity, Action persistMethod);
+ public abstract TEntity[] GetAll(TId[] ids, Func> getFromRepo);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs
similarity index 57%
rename from src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs
rename to src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs
index 9ac8aa6abd..e8c6ac02b0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs
+++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs
@@ -1,17 +1,34 @@
-namespace Umbraco.Core.Persistence.Repositories
+using System;
+
+namespace Umbraco.Core.Cache
{
- internal class RepositoryCacheOptions
+ internal class RepositoryCachePolicyOptions
{
///
- /// Constructor sets defaults
+ /// Ctor - sets GetAllCacheValidateCount = true
///
- public RepositoryCacheOptions()
+ public RepositoryCachePolicyOptions(Func performCount)
{
+ PerformCount = performCount;
GetAllCacheValidateCount = true;
GetAllCacheAllowZeroCount = false;
- GetAllCacheThresholdLimit = 100;
}
+ ///
+ /// Ctor - sets GetAllCacheValidateCount = false
+ ///
+ public RepositoryCachePolicyOptions()
+ {
+ PerformCount = null;
+ GetAllCacheValidateCount = false;
+ GetAllCacheAllowZeroCount = false;
+ }
+
+ ///
+ /// Callback required to get count for GetAllCacheValidateCount
+ ///
+ public Func PerformCount { get; private set; }
+
///
/// True/false as to validate the total item count when all items are returned from cache, the default is true but this
/// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the normal
@@ -21,16 +38,11 @@ namespace Umbraco.Core.Persistence.Repositories
/// setting this to return false will improve performance of GetAll cache with no params but should only be used
/// for specific circumstances
///
- public bool GetAllCacheValidateCount { get; set; }
+ public bool GetAllCacheValidateCount { get; private set; }
///
/// True if the GetAll method will cache that there are zero results so that the db is not hit when there are no results found
///
public bool GetAllCacheAllowZeroCount { get; set; }
-
- ///
- /// The threshold entity count for which the GetAll method will cache entities
- ///
- public int GetAllCacheThresholdLimit { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..28ac4ee2d1
--- /dev/null
+++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs
@@ -0,0 +1,24 @@
+using System.Linq;
+using Umbraco.Core.Collections;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// A caching policy that ignores all caches for GetAll - it will only cache calls for individual items
+ ///
+ ///
+ ///
+ internal class SingleItemsOnlyRepositoryCachePolicy : DefaultRepositoryCachePolicy
+ where TEntity : class, IAggregateRoot
+ {
+ public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) : base(cache, options)
+ {
+ }
+
+ protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection)
+ {
+ //no-op
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs
index 51cf37aa23..0dc5f5b00f 100644
--- a/src/Umbraco.Core/CacheHelper.cs
+++ b/src/Umbraco.Core/CacheHelper.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections;
+using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -11,20 +13,15 @@ using Umbraco.Core.Logging;
namespace Umbraco.Core
{
-
///
/// Class that is exposed by the ApplicationContext for application wide caching purposes
///
public class CacheHelper
{
- private readonly bool _enableCache;
- private readonly ICacheProvider _requestCache;
- private readonly ICacheProvider _nullRequestCache = new NullCacheProvider();
- private readonly ICacheProvider _staticCache;
- private readonly ICacheProvider _nullStaticCache = new NullCacheProvider();
- private readonly IRuntimeCacheProvider _httpCache;
- private readonly IRuntimeCacheProvider _nullHttpCache = new NullCacheProvider();
-
+ private static readonly ICacheProvider NullRequestCache = new NullCacheProvider();
+ private static readonly ICacheProvider NullStaticCache = new NullCacheProvider();
+ private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider();
+
///
/// Creates a cache helper with disabled caches
///
@@ -34,7 +31,7 @@ namespace Umbraco.Core
///
public static CacheHelper CreateDisabledCacheHelper()
{
- return new CacheHelper(null, null, null, false);
+ return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, new IsolatedRuntimeCache(t => NullRuntimeCache));
}
///
@@ -44,7 +41,8 @@ namespace Umbraco.Core
: this(
new HttpRuntimeCacheProvider(HttpRuntime.Cache),
new StaticCacheProvider(),
- new HttpRequestCacheProvider())
+ new HttpRequestCacheProvider(),
+ new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
{
}
@@ -56,93 +54,75 @@ namespace Umbraco.Core
: this(
new HttpRuntimeCacheProvider(cache),
new StaticCacheProvider(),
- new HttpRequestCacheProvider())
+ new HttpRequestCacheProvider(),
+ new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
{
}
- ///
- /// Initializes a new instance based on the provided providers
- ///
- ///
- ///
- ///
+ [Obsolete("Use the constructor the specifies all dependencies")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public CacheHelper(
IRuntimeCacheProvider httpCacheProvider,
ICacheProvider staticCacheProvider,
ICacheProvider requestCacheProvider)
- : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, true)
+ : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
{
}
- ///
- /// Private ctor used for creating a disabled cache helper
- ///
- ///
- ///
- ///
- ///
- private CacheHelper(
+ ///
+ /// Initializes a new instance based on the provided providers
+ ///
+ ///
+ ///
+ ///
+ ///
+ public CacheHelper(
IRuntimeCacheProvider httpCacheProvider,
ICacheProvider staticCacheProvider,
- ICacheProvider requestCacheProvider,
- bool enableCache)
+ ICacheProvider requestCacheProvider,
+ IsolatedRuntimeCache isolatedCacheManager)
{
- if (enableCache)
- {
- _httpCache = httpCacheProvider;
- _staticCache = staticCacheProvider;
- _requestCache = requestCacheProvider;
- }
- else
- {
- _httpCache = null;
- _staticCache = null;
- _requestCache = null;
- }
-
- _enableCache = enableCache;
+ 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 { return _enableCache ? _requestCache : _nullRequestCache; }
- }
-
- ///
- /// Returns the current Runtime cache
- ///
- public ICacheProvider StaticCache
- {
- get { return _enableCache ? _staticCache : _nullStaticCache; }
- }
-
- ///
- /// Returns the current Runtime cache
- ///
- public IRuntimeCacheProvider RuntimeCache
- {
- get { return _enableCache ? _httpCache : _nullHttpCache; }
- }
+ public ICacheProvider RequestCache { get; internal set; }
- #region Legacy Runtime/Http Cache accessors
+ ///
+ /// 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; }
+
+ #region Legacy Runtime/Http Cache accessors
///
/// Clears the item in umbraco's runtime cache
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearAllCache()
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearAllCache();
- }
- else
- {
- _httpCache.ClearAllCache();
- }
+ RuntimeCache.ClearAllCache();
+ IsolatedRuntimeCache.ClearAllCaches();
}
///
@@ -150,16 +130,10 @@ namespace Umbraco.Core
///
/// Key
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearCacheItem(string key)
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheItem(key);
- }
- else
- {
- _httpCache.ClearCacheItem(key);
- }
+ RuntimeCache.ClearCacheItem(key);
}
@@ -171,30 +145,17 @@ namespace Umbraco.Core
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
public void ClearCacheObjectTypes(string typeName)
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheObjectTypes(typeName);
- }
- else
- {
- _httpCache.ClearCacheObjectTypes(typeName);
- }
+ RuntimeCache.ClearCacheObjectTypes(typeName);
}
///
/// Clears all objects in the System.Web.Cache with the System.Type specified
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearCacheObjectTypes()
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheObjectTypes();
- }
- else
- {
- _httpCache.ClearCacheObjectTypes();
- }
+ RuntimeCache.ClearCacheObjectTypes();
}
///
@@ -202,16 +163,10 @@ namespace Umbraco.Core
///
/// The start of the key
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearCacheByKeySearch(string keyStartsWith)
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheByKeySearch(keyStartsWith);
- }
- else
- {
- _httpCache.ClearCacheByKeySearch(keyStartsWith);
- }
+ RuntimeCache.ClearCacheByKeySearch(keyStartsWith);
}
///
@@ -219,29 +174,17 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearCacheByKeyExpression(string regexString)
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheByKeyExpression(regexString);
- }
- else
- {
- _httpCache.ClearCacheByKeyExpression(regexString);
- }
+ RuntimeCache.ClearCacheByKeyExpression(regexString);
}
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItemsByKeySearch(keyStartsWith);
- }
- else
- {
- return _httpCache.GetCacheItemsByKeySearch(keyStartsWith);
- }
+ return RuntimeCache.GetCacheItemsByKeySearch(keyStartsWith);
}
///
@@ -251,16 +194,10 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItem(cacheKey);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey);
- }
+ return RuntimeCache.GetCacheItem(cacheKey);
}
///
@@ -271,16 +208,11 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey, Func getCacheItem)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey, getCacheItem);
- }
+ return RuntimeCache.GetCacheItem(cacheKey, getCacheItem);
+
}
///
@@ -292,17 +224,12 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey,
TimeSpan timeout, Func getCacheItem)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout);
- }
+ return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout);
+
}
///
@@ -315,18 +242,13 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey,
CacheItemRemovedCallback refreshAction, TimeSpan timeout,
Func getCacheItem)
{
- if (!_enableCache)
- {
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction);
- }
+ return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction);
+
}
///
@@ -340,18 +262,13 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey,
CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout,
Func getCacheItem)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction);
- }
+ return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction);
+
}
///
@@ -373,20 +290,13 @@ namespace Umbraco.Core
TimeSpan timeout,
Func getCacheItem)
{
- if (_enableCache == false)
+ var cache = GetHttpRuntimeCacheProvider(RuntimeCache);
+ if (cache != null)
{
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction, null);
- }
- else
- {
- var cache = _httpCache as HttpRuntimeCacheProvider;
- if (cache != null)
- {
- var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency);
- return result == null ? default(TT) : result.TryConvertTo().Result;
- }
- throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
+ var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency);
+ return result == null ? default(TT) : result.TryConvertTo().Result;
}
+ throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
}
///
@@ -404,20 +314,13 @@ namespace Umbraco.Core
CacheDependency cacheDependency,
Func getCacheItem)
{
- if (!_enableCache)
+ var cache = GetHttpRuntimeCacheProvider(RuntimeCache);
+ if (cache != null)
{
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, null, false, priority, null, null);
- }
- else
- {
- var cache = _httpCache as HttpRuntimeCacheProvider;
- if (cache != null)
- {
- var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency);
- return result == null ? default(TT) : result.TryConvertTo().Result;
- }
- throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
+ var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency);
+ return result == null ? default(TT) : result.TryConvertTo().Result;
}
+ throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
}
///
@@ -427,18 +330,14 @@ namespace Umbraco.Core
///
///
///
+ [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void InsertCacheItem(string cacheKey,
CacheItemPriority priority,
Func getCacheItem)
{
- if (_enableCache == false)
- {
- _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority);
- }
- else
- {
- _httpCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority);
- }
+ RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority);
+
}
///
@@ -449,19 +348,14 @@ namespace Umbraco.Core
///
/// This will set an absolute expiration from now until the timeout
///
+ [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void InsertCacheItem(string cacheKey,
CacheItemPriority priority,
TimeSpan timeout,
Func getCacheItem)
{
- if (_enableCache == false)
- {
- _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority);
- }
- else
- {
- _httpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority);
- }
+ RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority);
}
///
@@ -480,19 +374,12 @@ namespace Umbraco.Core
TimeSpan timeout,
Func getCacheItem)
{
- if (_enableCache == false)
+ var cache = GetHttpRuntimeCacheProvider(RuntimeCache);
+ if (cache != null)
{
- _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority, dependentFiles:null);
- }
- else
- {
- var cache = _httpCache as HttpRuntimeCacheProvider;
- if (cache != null)
- {
- cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency);
- }
- throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
+ cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency);
}
+ throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
}
///
@@ -513,22 +400,29 @@ namespace Umbraco.Core
TimeSpan? timeout,
Func getCacheItem)
{
- if (_enableCache == false)
+ var cache = GetHttpRuntimeCacheProvider(RuntimeCache);
+ if (cache != null)
{
- _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction, null);
- }
- else
- {
- var cache = _httpCache as HttpRuntimeCacheProvider;
- if (cache != null)
- {
- cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency);
- }
- throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
+ cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency);
}
+ throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
}
#endregion
- }
+ private HttpRuntimeCacheProvider GetHttpRuntimeCacheProvider(IRuntimeCacheProvider runtimeCache)
+ {
+ HttpRuntimeCacheProvider cache;
+ var wrapper = RuntimeCache as IRuntimeCacheProviderWrapper;
+ if (wrapper != null)
+ {
+ cache = wrapper.InnerProvider as HttpRuntimeCacheProvider;
+ }
+ else
+ {
+ cache = RuntimeCache as HttpRuntimeCacheProvider;
+ }
+ return cache;
+ }
+ }
}
diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs
new file mode 100644
index 0000000000..5067562aa7
--- /dev/null
+++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Collections
+{
+ ///
+ /// A List that can be deep cloned with deep cloned elements and can reset the collection's items dirty flags
+ ///
+ ///
+ internal class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty
+ {
+ private readonly ListCloneBehavior _listCloneBehavior;
+
+ public DeepCloneableList(ListCloneBehavior listCloneBehavior)
+ {
+ _listCloneBehavior = listCloneBehavior;
+ }
+
+ public DeepCloneableList(IEnumerable collection, ListCloneBehavior listCloneBehavior) : base(collection)
+ {
+ _listCloneBehavior = listCloneBehavior;
+ }
+
+ ///
+ /// Default behavior is CloneOnce
+ ///
+ ///
+ public DeepCloneableList(IEnumerable collection)
+ : this(collection, ListCloneBehavior.CloneOnce)
+ {
+ }
+
+ ///
+ /// Creates a new list and adds each element as a deep cloned element if it is of type IDeepCloneable
+ ///
+ ///
+ public object DeepClone()
+ {
+ switch (_listCloneBehavior)
+ {
+ case ListCloneBehavior.CloneOnce:
+ //we are cloning once, so create a new list in none mode
+ // and deep clone all items into it
+ var newList = new DeepCloneableList(ListCloneBehavior.None);
+ foreach (var item in this)
+ {
+ var dc = item as IDeepCloneable;
+ if (dc != null)
+ {
+ newList.Add((T)dc.DeepClone());
+ }
+ else
+ {
+ newList.Add(item);
+ }
+ }
+ return newList;
+ case ListCloneBehavior.None:
+ //we are in none mode, so just return a new list with the same items
+ return new DeepCloneableList(this, ListCloneBehavior.None);
+ case ListCloneBehavior.Always:
+ //always clone to new list
+ var newList2 = new DeepCloneableList(ListCloneBehavior.Always);
+ foreach (var item in this)
+ {
+ var dc = item as IDeepCloneable;
+ if (dc != null)
+ {
+ newList2.Add((T)dc.DeepClone());
+ }
+ else
+ {
+ newList2.Add(item);
+ }
+ }
+ return newList2;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ public bool IsDirty()
+ {
+ return this.OfType().Any(x => x.IsDirty());
+ }
+
+ public bool WasDirty()
+ {
+ return this.OfType().Any(x => x.WasDirty());
+ }
+
+ ///
+ /// Always returns false, the list has no properties we need to report
+ ///
+ ///
+ ///
+ public bool IsPropertyDirty(string propName)
+ {
+ return false;
+ }
+
+ ///
+ /// Always returns false, the list has no properties we need to report
+ ///
+ ///
+ ///
+ public bool WasPropertyDirty(string propertyName)
+ {
+ return false;
+ }
+
+ public void ResetDirtyProperties()
+ {
+ foreach (var dc in this.OfType())
+ {
+ dc.ResetDirtyProperties();
+ }
+ }
+
+ public void ForgetPreviouslyDirtyProperties()
+ {
+ foreach (var dc in this.OfType())
+ {
+ dc.ForgetPreviouslyDirtyProperties();
+ }
+ }
+
+ public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties)
+ {
+ foreach (var dc in this.OfType())
+ {
+ dc.ResetDirtyProperties(rememberPreviouslyChangedProperties);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Collections/ListCloneBehavior.cs b/src/Umbraco.Core/Collections/ListCloneBehavior.cs
new file mode 100644
index 0000000000..4fe935f7ff
--- /dev/null
+++ b/src/Umbraco.Core/Collections/ListCloneBehavior.cs
@@ -0,0 +1,20 @@
+namespace Umbraco.Core.Collections
+{
+ internal enum ListCloneBehavior
+ {
+ ///
+ /// When set, DeepClone will clone the items one time and the result list behavior will be None
+ ///
+ CloneOnce,
+
+ ///
+ /// When set, DeepClone will not clone any items
+ ///
+ None,
+
+ ///
+ /// When set, DeepClone will always clone all items
+ ///
+ Always
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
index 083bda6982..5d4acdc0f3 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ContentElement.cs
@@ -245,6 +245,18 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
}
}
+ [ConfigurationProperty("EnableInheritedDocumentTypes")]
+ internal InnerTextConfigurationElement EnableInheritedDocumentTypes
+ {
+ get
+ {
+ return new OptionalInnerTextConfigurationElement(
+ (InnerTextConfigurationElement) this["EnableInheritedDocumentTypes"],
+ //set the default
+ true);
+ }
+ }
+
string IContentSection.NotificationEmailAddress
{
get { return Notifications.NotificationEmailAddress; }
@@ -364,5 +376,10 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
{
get { return DefaultDocumentTypeProperty; }
}
+
+ bool IContentSection.EnableInheritedDocumentTypes
+ {
+ get { return EnableInheritedDocumentTypes; }
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
index d28dede2e2..c7e3d6b836 100644
--- a/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoSettings/IContentSection.cs
@@ -53,5 +53,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
bool GlobalPreviewStorageEnabled { get; }
string DefaultDocumentTypeProperty { get; }
+
+ bool EnableInheritedDocumentTypes { get; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
index ef33e7a966..4f6833e000 100644
--- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs
+++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
///
/// The version comment.
- public static string CurrentComment { get { return "beta"; } }
+ public static string CurrentComment { get { return ""; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs
index 60fba0ae40..f596820506 100644
--- a/src/Umbraco.Core/Constants-Web.cs
+++ b/src/Umbraco.Core/Constants-Web.cs
@@ -10,6 +10,12 @@ namespace Umbraco.Core
///
public static class Web
{
+ public const string UmbracoContextDataToken = "umbraco-context";
+ public const string UmbracoDataToken = "umbraco";
+ public const string PublishedDocumentRequestDataToken = "umbraco-doc-request";
+ public const string CustomRouteDataToken = "umbraco-custom-route";
+ public const string UmbracoRouteDefinitionDataToken = "umbraco-route-def";
+
///
/// The preview cookie name
///
diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs
index 50a9fbf847..b4395e53eb 100644
--- a/src/Umbraco.Core/CoreBootManager.cs
+++ b/src/Umbraco.Core/CoreBootManager.cs
@@ -140,8 +140,11 @@ namespace Umbraco.Core
{
try
{
+ using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationInitialized", x.GetType())))
+ {
x.OnApplicationInitialized(UmbracoApplication, ApplicationContext);
}
+ }
catch (Exception ex)
{
ProfilingLogger.Logger.Error("An error occurred running OnApplicationInitialized for handler " + x.GetType(), ex);
@@ -202,10 +205,16 @@ namespace Umbraco.Core
protected virtual CacheHelper CreateApplicationCache()
{
var cacheHelper = new CacheHelper(
- new ObjectCacheRuntimeCacheProvider(),
+ //we need to have the dep clone runtime cache provider to ensure
+ //all entities are cached properly (cloned in and cloned out)
+ new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()),
new StaticCacheProvider(),
//we have no request based cache when not running in web-based context
- new NullCacheProvider());
+ new NullCacheProvider(),
+ new IsolatedRuntimeCache(type =>
+ //we need to have the dep clone runtime cache provider to ensure
+ //all entities are cached properly (cloned in and cloned out)
+ new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
return cacheHelper;
}
@@ -264,8 +273,11 @@ namespace Umbraco.Core
{
try
{
+ using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarting", x.GetType())))
+ {
x.OnApplicationStarting(UmbracoApplication, ApplicationContext);
}
+ }
catch (Exception ex)
{
ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarting for handler " + x.GetType(), ex);
@@ -310,7 +322,10 @@ namespace Umbraco.Core
{
try
{
+ using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarted", x.GetType())))
+ {
x.OnApplicationStarted(UmbracoApplication, ApplicationContext);
+ }
}
catch (Exception ex)
{
diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs
index 816256360a..c7e8874449 100644
--- a/src/Umbraco.Core/DisposableTimer.cs
+++ b/src/Umbraco.Core/DisposableTimer.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Web;
@@ -12,6 +13,12 @@ namespace Umbraco.Core
///
public class DisposableTimer : DisposableObject
{
+ private readonly ILogger _logger;
+ private readonly LogType? _logType;
+ private readonly IProfiler _profiler;
+ private readonly Type _loggerType;
+ private readonly string _endMessage;
+ private readonly IDisposable _profilerStep;
private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
private readonly Action _callback;
@@ -25,25 +32,12 @@ namespace Umbraco.Core
if (logger == null) throw new ArgumentNullException("logger");
if (loggerType == null) throw new ArgumentNullException("loggerType");
- _callback = x =>
- {
- if (profiler != null)
- {
- profiler.DisposeIfDisposable();
- }
- switch (logType)
- {
- case LogType.Debug:
- logger.Debug(loggerType, () => endMessage + " (took " + x + "ms)");
- break;
- case LogType.Info:
- logger.Info(loggerType, () => endMessage + " (took " + x + "ms)");
- break;
- default:
- throw new ArgumentOutOfRangeException("logType");
- }
-
- };
+ _logger = logger;
+ _logType = logType;
+ _profiler = profiler;
+ _loggerType = loggerType;
+ _endMessage = endMessage;
+
switch (logType)
{
case LogType.Debug:
@@ -58,7 +52,7 @@ namespace Umbraco.Core
if (profiler != null)
{
- profiler.Step(loggerType, startMessage);
+ _profilerStep = profiler.Step(loggerType, startMessage);
}
}
@@ -223,7 +217,36 @@ namespace Umbraco.Core
///
protected override void DisposeResources()
{
- _callback.Invoke(Stopwatch.ElapsedMilliseconds);
+ if (_profiler != null)
+ {
+ _profiler.DisposeIfDisposable();
+ }
+
+ if (_profilerStep != null)
+ {
+ _profilerStep.Dispose();
+ }
+
+ if (_logType.HasValue && _endMessage.IsNullOrWhiteSpace() == false && _loggerType != null && _logger != null)
+ {
+ switch (_logType)
+ {
+ case LogType.Debug:
+ _logger.Debug(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)");
+ break;
+ case LogType.Info:
+ _logger.Info(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)");
+ break;
+ default:
+ throw new ArgumentOutOfRangeException("logType");
+ }
+ }
+
+ if (_callback != null)
+ {
+ _callback.Invoke(Stopwatch.ElapsedMilliseconds);
+ }
+
}
}
diff --git a/src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs b/src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs
new file mode 100644
index 0000000000..43d7adc647
--- /dev/null
+++ b/src/Umbraco.Core/Dynamics/CaseInsensitiveDynamicObject.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Dynamic;
+using System.Linq;
+using System.Reflection;
+
+namespace Umbraco.Core.Dynamics
+{
+ ///
+ /// This will check enable dynamic access to properties and methods in a case insensitive manner
+ ///
+ ///
+ ///
+ /// This works by using reflection on the type - the reflection lookup is lazy so it will not execute unless a dynamic method needs to be accessed
+ ///
+ public abstract class CaseInsensitiveDynamicObject : DynamicObject
+ where T: class
+ {
+ ///
+ /// Used for dynamic access for case insensitive property access
+ /// `
+ private static readonly Lazy>> CaseInsensitivePropertyAccess = new Lazy>>(() =>
+ {
+ var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public)
+ .DistinctBy(x => x.Name);
+ return props.Select(propInfo =>
+ {
+ var name = propInfo.Name.ToLowerInvariant();
+ Func getVal = propInfo.GetValue;
+ return new KeyValuePair>(name, getVal);
+
+ }).ToDictionary(x => x.Key, x => x.Value);
+ });
+
+ ///
+ /// Used for dynamic access for case insensitive property access
+ ///
+ private static readonly Lazy>>> CaseInsensitiveMethodAccess
+ = new Lazy>>>(() =>
+ {
+ var props = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public)
+ .Where(x => x.IsSpecialName == false && x.IsVirtual == false)
+ .DistinctBy(x => x.Name);
+ return props.Select(methodInfo =>
+ {
+ var name = methodInfo.Name.ToLowerInvariant();
+ Func getVal = methodInfo.Invoke;
+ var val = new Tuple>(methodInfo.GetParameters(), getVal);
+ return new KeyValuePair>>(name, val);
+
+ }).ToDictionary(x => x.Key, x => x.Value);
+ });
+
+ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
+ {
+ var name = binder.Name.ToLowerInvariant();
+ if (CaseInsensitiveMethodAccess.Value.ContainsKey(name) == false)
+ return base.TryInvokeMember(binder, args, out result);
+
+ var val = CaseInsensitiveMethodAccess.Value[name];
+ var parameters = val.Item1;
+ var callback = val.Item2;
+ var fullArgs = new List
public class DynamicXmlConverter : TypeConverter
{
- public override bool CanConvertTo(ITypeDescriptorContext context, Type sourceType)
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
var convertableTypes = new[]
{
@@ -78,8 +78,8 @@ namespace Umbraco.Core.Dynamics
typeof(RawXmlDocument)
};
- return convertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, sourceType))
- || base.CanConvertFrom(context, sourceType);
+ return convertableTypes.Any(x => TypeHelper.IsTypeAssignableFrom(x, destinationType))
+ || base.CanConvertFrom(context, destinationType);
}
public override object ConvertTo(
diff --git a/src/Umbraco.Core/Events/CancellableEventArgs.cs b/src/Umbraco.Core/Events/CancellableEventArgs.cs
index 506ba1c22e..72cef19f7a 100644
--- a/src/Umbraco.Core/Events/CancellableEventArgs.cs
+++ b/src/Umbraco.Core/Events/CancellableEventArgs.cs
@@ -1,5 +1,8 @@
using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Security.Permissions;
+using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Core.Events
{
@@ -11,11 +14,19 @@ namespace Umbraco.Core.Events
{
private bool _cancel;
+ public CancellableEventArgs(bool canCancel, EventMessages messages, IDictionary additionalData)
+ {
+ CanCancel = canCancel;
+ Messages = messages;
+ AdditionalData = new ReadOnlyDictionary(additionalData);
+ }
+
public CancellableEventArgs(bool canCancel, EventMessages eventMessages)
{
if (eventMessages == null) throw new ArgumentNullException("eventMessages");
CanCancel = canCancel;
Messages = eventMessages;
+ AdditionalData = new ReadOnlyDictionary(new Dictionary());
}
public CancellableEventArgs(bool canCancel)
@@ -23,6 +34,7 @@ namespace Umbraco.Core.Events
CanCancel = canCancel;
//create a standalone messages
Messages = new EventMessages();
+ AdditionalData = new ReadOnlyDictionary(new Dictionary());
}
public CancellableEventArgs(EventMessages eventMessages)
@@ -78,5 +90,14 @@ namespace Umbraco.Core.Events
/// Returns the EventMessages object which is used to add messages to the message collection for this event
///
public EventMessages Messages { get; private set; }
+
+ ///
+ /// In some cases raised evens might need to contain additional arbitrary readonly data which can be read by event subscribers
+ ///
+ ///
+ /// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility
+ /// so we cannot change the strongly typed nature for some events.
+ ///
+ public ReadOnlyDictionary AdditionalData { get; private set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
index 726d32d7b0..a05f09ece5 100644
--- a/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
+++ b/src/Umbraco.Core/Events/CancellableObjectEventArgs.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using System.Security.Permissions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
@@ -11,8 +12,13 @@ namespace Umbraco.Core.Events
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
public class CancellableObjectEventArgs : CancellableEventArgs
{
+ public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary additionalData)
+ : base(canCancel, messages, additionalData)
+ {
+ EventObject = eventObject;
+ }
- public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages)
+ public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages eventMessages)
: base(canCancel, eventMessages)
{
EventObject = eventObject;
diff --git a/src/Umbraco.Core/Events/EventExtensions.cs b/src/Umbraco.Core/Events/EventExtensions.cs
index 8c645aead6..700a02457a 100644
--- a/src/Umbraco.Core/Events/EventExtensions.cs
+++ b/src/Umbraco.Core/Events/EventExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
namespace Umbraco.Core.Events
diff --git a/src/Umbraco.Core/Events/SaveEventArgs.cs b/src/Umbraco.Core/Events/SaveEventArgs.cs
index b84e28285e..dafd326e1c 100644
--- a/src/Umbraco.Core/Events/SaveEventArgs.cs
+++ b/src/Umbraco.Core/Events/SaveEventArgs.cs
@@ -4,6 +4,18 @@ namespace Umbraco.Core.Events
{
public class SaveEventArgs : CancellableObjectEventArgs>
{
+ ///
+ /// Constructor accepting multiple entities that are used in the saving operation
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SaveEventArgs(IEnumerable eventObject, bool canCancel, EventMessages messages, IDictionary additionalData)
+ : base(eventObject, canCancel, messages, additionalData)
+ {
+ }
+
///
/// Constructor accepting multiple entities that are used in the saving operation
///
@@ -25,12 +37,24 @@ namespace Umbraco.Core.Events
{
}
- ///
- /// Constructor accepting a single entity instance
- ///
- ///
- ///
- public SaveEventArgs(TEntity eventObject, EventMessages eventMessages)
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ ///
+ ///
+ public SaveEventArgs(TEntity eventObject, bool canCancel, EventMessages messages, IDictionary additionalData)
+ : base(new List { eventObject }, canCancel, messages, additionalData)
+ {
+ }
+
+ ///
+ /// Constructor accepting a single entity instance
+ ///
+ ///
+ ///
+ public SaveEventArgs(TEntity eventObject, EventMessages eventMessages)
: base(new List { eventObject }, eventMessages)
{
}
diff --git a/src/Umbraco.Core/IO/ViewHelper.cs b/src/Umbraco.Core/IO/ViewHelper.cs
index 28dad49dc0..933a9d2956 100644
--- a/src/Umbraco.Core/IO/ViewHelper.cs
+++ b/src/Umbraco.Core/IO/ViewHelper.cs
@@ -7,7 +7,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Core.IO
{
- internal class ViewHelper
+ public class ViewHelper
{
private readonly IFileSystem _viewFileSystem;
@@ -66,17 +66,58 @@ namespace Umbraco.Core.IO
return viewContent;
}
- internal static string GetDefaultFileContent(string layoutPageAlias = null)
+ public static string GetDefaultFileContent(string layoutPageAlias = null, string modelClassName = null, string modelNamespace = null, string modelNamespaceAlias = null)
{
- var design = @"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
-@{
- Layout = null;
-}";
+ var content = new StringBuilder();
- if (layoutPageAlias.IsNullOrWhiteSpace() == false)
- design = design.Replace("null", string.Format("\"{0}.cshtml\"", layoutPageAlias));
+ if (string.IsNullOrWhiteSpace(modelNamespaceAlias))
+ modelNamespaceAlias = "ContentModels";
- return design;
+ // either
+ // @inherits Umbraco.Web.Mvc.UmbracoTemplatePage
+ // @inherits Umbraco.Web.Mvc.UmbracoTemplatePage
+ // @inherits Umbraco.Web.Mvc.UmbracoTemplatePage
+ content.Append("@inherits Umbraco.Web.Mvc.UmbracoTemplatePage");
+ if (modelClassName.IsNullOrWhiteSpace() == false)
+ {
+ content.Append("<");
+ if (modelNamespace.IsNullOrWhiteSpace() == false)
+ {
+ content.Append(modelNamespaceAlias);
+ content.Append(".");
+ }
+ content.Append(modelClassName);
+ content.Append(">");
+ }
+ content.Append("\r\n");
+
+ // if required, add
+ // @using ContentModels = ModelNamespace;
+ if (modelClassName.IsNullOrWhiteSpace() == false && modelNamespace.IsNullOrWhiteSpace() == false)
+ {
+ content.Append("@using ");
+ content.Append(modelNamespaceAlias);
+ content.Append(" = ");
+ content.Append(modelNamespace);
+ content.Append(";\r\n");
+ }
+
+ // either
+ // Layout = null;
+ // Layout = "layoutPage.cshtml";
+ content.Append("@{\r\n\tLayout = ");
+ if (layoutPageAlias.IsNullOrWhiteSpace())
+ {
+ content.Append("null");
+ }
+ else
+ {
+ content.Append("\"");
+ content.Append(layoutPageAlias);
+ content.Append(".cshtml\"");
+ }
+ content.Append(";\r\n}");
+ return content.ToString();
}
private string SaveTemplateToFile(ITemplate template)
diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs
index 452c05adb8..1be504ea45 100644
--- a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs
+++ b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Media.Exif
{
if (items.ContainsKey (key))
items.Remove (key);
- if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) {
+ if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) {
items.Add (key, new WindowsByteString (key, value));
} else {
items.Add (key, new ExifAscii (key, value, parent.Encoding));
diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs
index 2238260611..b8e96c2793 100644
--- a/src/Umbraco.Core/Models/Content.cs
+++ b/src/Umbraco.Core/Models/Content.cs
@@ -340,18 +340,6 @@ namespace Umbraco.Core.Models
}
}
- ///
- /// Method to call when Entity is being saved
- ///
- /// Created date is set and a Unique key is assigned
- internal override void AddingEntity()
- {
- base.AddingEntity();
-
- if(Key == Guid.Empty)
- Key = Guid.NewGuid();
- }
-
///
/// Method to call when Entity is being updated
///
diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs
index 6beead06e6..b3d0f693d9 100644
--- a/src/Umbraco.Core/Models/ContentBase.cs
+++ b/src/Umbraco.Core/Models/ContentBase.cs
@@ -367,8 +367,27 @@ namespace Umbraco.Core.Models
/// Value to set for the Property
public virtual void SetPropertyValue(string propertyTypeAlias, long value)
{
- string val = value.ToString();
- SetValueOnProperty(propertyTypeAlias, val);
+ SetValueOnProperty(propertyTypeAlias, value);
+ }
+
+ ///
+ /// Sets the value of a Property
+ ///
+ /// Alias of the PropertyType
+ /// Value to set for the Property
+ public virtual void SetPropertyValue(string propertyTypeAlias, decimal value)
+ {
+ SetValueOnProperty(propertyTypeAlias, value);
+ }
+
+ ///
+ /// Sets the value of a Property
+ ///
+ /// Alias of the PropertyType
+ /// Value to set for the Property
+ public virtual void SetPropertyValue(string propertyTypeAlias, double value)
+ {
+ SetValueOnProperty(propertyTypeAlias, value);
}
///
diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs
index 355d724fbe..0926e48e31 100644
--- a/src/Umbraco.Core/Models/ContentType.cs
+++ b/src/Umbraco.Core/Models/ContentType.cs
@@ -53,6 +53,9 @@ namespace Umbraco.Core.Models
///
/// Gets or sets the alias of the default Template.
+ /// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
+ /// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
+ /// we should not store direct entity
///
[IgnoreDataMember]
public ITemplate DefaultTemplate
@@ -79,6 +82,9 @@ namespace Umbraco.Core.Models
///
/// Gets or Sets a list of Templates which are allowed for the ContentType
+ /// TODO: This should be ignored from cloning!!!!!!!!!!!!!!
+ /// - but to do that we have to implement callback hacks, this needs to be fixed in v8,
+ /// we should not store direct entity
///
[DataMember]
public IEnumerable AllowedTemplates
@@ -137,19 +143,6 @@ namespace Umbraco.Core.Models
return result;
}
- ///
- /// Method to call when Entity is being saved
- ///
- /// Created date is set and a Unique key is assigned
- internal override void AddingEntity()
- {
- base.AddingEntity();
-
- if (Key == Guid.Empty)
- Key = Guid.NewGuid();
- }
-
-
///
/// Creates a deep clone of the current entity with its identity/alias and it's property identities reset
///
diff --git a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs
new file mode 100644
index 0000000000..b1d2b45dc5
--- /dev/null
+++ b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResult.cs
@@ -0,0 +1,17 @@
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Used when determining available compositions for a given content type
+ ///
+ internal class ContentTypeAvailableCompositionsResult
+ {
+ public ContentTypeAvailableCompositionsResult(IContentTypeComposition composition, bool allowed)
+ {
+ Composition = composition;
+ Allowed = allowed;
+ }
+
+ public IContentTypeComposition Composition { get; private set; }
+ public bool Allowed { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs
new file mode 100644
index 0000000000..653d7a10a9
--- /dev/null
+++ b/src/Umbraco.Core/Models/ContentTypeAvailableCompositionsResults.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Used when determining available compositions for a given content type
+ ///
+ internal class ContentTypeAvailableCompositionsResults
+ {
+ public ContentTypeAvailableCompositionsResults()
+ {
+ Ancestors = Enumerable.Empty();
+ Results = Enumerable.Empty();
+ }
+
+ public ContentTypeAvailableCompositionsResults(IEnumerable ancestors, IEnumerable results)
+ {
+ Ancestors = ancestors;
+ Results = results;
+ }
+
+ public IEnumerable Ancestors { get; private set; }
+ public IEnumerable Results { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs
index a83defb7b5..7218a2421d 100644
--- a/src/Umbraco.Core/Models/ContentTypeBase.cs
+++ b/src/Umbraco.Core/Models/ContentTypeBase.cs
@@ -452,11 +452,6 @@ namespace Umbraco.Core.Models
/// Returns True if PropertyType was added, otherwise False
public bool AddPropertyType(PropertyType propertyType)
{
- if (propertyType.HasIdentity == false)
- {
- propertyType.Key = Guid.NewGuid();
- }
-
if (PropertyTypeExists(propertyType.Alias) == false)
{
_propertyTypes.Add(propertyType);
diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs
index 5ac21885d7..4cf4a08bf1 100644
--- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs
+++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs
@@ -23,8 +23,8 @@ namespace Umbraco.Core.Models
protected ContentTypeCompositionBase(IContentTypeComposition parent)
: this(parent, null)
- {
- }
+ {
+ }
protected ContentTypeCompositionBase(IContentTypeComposition parent, string alias)
: base(parent, alias)
@@ -122,10 +122,10 @@ namespace Umbraco.Core.Models
return false;
RemovedContentTypeKeyTracker.Add(contentTypeComposition.Id);
-
+
//If the ContentType we are removing has Compositions of its own these needs to be removed as well
var compositionIdsToRemove = contentTypeComposition.CompositionIds().ToList();
- if(compositionIdsToRemove.Any())
+ if (compositionIdsToRemove.Any())
RemovedContentTypeKeyTracker.AddRange(compositionIdsToRemove);
OnPropertyChanged(ContentTypeCompositionSelector);
@@ -210,16 +210,13 @@ namespace Umbraco.Core.Models
/// Returns True if PropertyType was added, otherwise False
public override bool AddPropertyType(PropertyType propertyType, string propertyGroupName)
{
- if (propertyType.HasIdentity == false)
- propertyType.Key = Guid.NewGuid();
-
// ensure no duplicate alias - over all composition properties
if (PropertyTypeExists(propertyType.Alias))
return false;
// get and ensure a group local to this content type
- var group = PropertyGroups.Contains(propertyGroupName)
- ? PropertyGroups[propertyGroupName]
+ var group = PropertyGroups.Contains(propertyGroupName)
+ ? PropertyGroups[propertyGroupName]
: AddAndReturnPropertyGroup(propertyGroupName);
if (group == null)
return false;
diff --git a/src/Umbraco.Core/Models/DataTypeDefinition.cs b/src/Umbraco.Core/Models/DataTypeDefinition.cs
index 5cf3976613..a52d305278 100644
--- a/src/Umbraco.Core/Models/DataTypeDefinition.cs
+++ b/src/Umbraco.Core/Models/DataTypeDefinition.cs
@@ -228,13 +228,5 @@ namespace Umbraco.Core.Models
{
get { return _additionalData; }
}
-
- internal override void AddingEntity()
- {
- base.AddingEntity();
-
- if(Key == default(Guid))
- Key = Guid.NewGuid();
- }
}
}
diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs
index 7523555c24..c1b45f63ce 100644
--- a/src/Umbraco.Core/Models/DeepCloneHelper.cs
+++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs
@@ -9,10 +9,30 @@ namespace Umbraco.Core.Models
{
public static class DeepCloneHelper
{
+ ///
+ /// Stores the metadata for the properties for a given type so we know how to create them
+ ///
+ private struct ClonePropertyInfo
+ {
+ public ClonePropertyInfo(PropertyInfo propertyInfo) : this()
+ {
+ if (propertyInfo == null) throw new ArgumentNullException("propertyInfo");
+ PropertyInfo = propertyInfo;
+ }
+
+ public PropertyInfo PropertyInfo { get; private set; }
+ public bool IsDeepCloneable { get; set; }
+ public Type GenericListType { get; set; }
+ public bool IsList
+ {
+ get { return GenericListType != null; }
+ }
+ }
+
///
/// Used to avoid constant reflection (perf)
///
- private static readonly ConcurrentDictionary PropCache = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary PropCache = new ConcurrentDictionary();
///
/// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output')
@@ -30,81 +50,99 @@ namespace Umbraco.Core.Models
throw new InvalidOperationException("Both the input and output types must be the same");
}
+ //get the property metadata from cache so we only have to figure this out once per type
var refProperties = PropCache.GetOrAdd(inputType, type =>
inputType.GetProperties()
- .Where(x =>
- //is not attributed with the ignore clone attribute
- x.GetCustomAttribute() == null
+ .Select(propertyInfo =>
+ {
+ if (
+ //is not attributed with the ignore clone attribute
+ propertyInfo.GetCustomAttribute() != null
//reference type but not string
- && x.PropertyType.IsValueType == false && x.PropertyType != typeof (string)
+ || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof (string)
//settable
- && x.CanWrite
+ || propertyInfo.CanWrite == false
//non-indexed
- && x.GetIndexParameters().Any() == false)
+ || propertyInfo.GetIndexParameters().Any())
+ {
+ return null;
+ }
+
+
+ if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType))
+ {
+ return new ClonePropertyInfo(propertyInfo) { IsDeepCloneable = true };
+ }
+
+ if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)
+ && TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false)
+ {
+ if (propertyInfo.PropertyType.IsGenericType
+ && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
+ || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
+ || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
+ {
+ //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<>
+ var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments());
+ return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType };
+ }
+ if (propertyInfo.PropertyType.IsArray
+ || (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false))
+ {
+ //if its an array, we'll create a list to work with first and then convert to array later
+ //otherwise if its just a regular derivitave of IEnumerable, we can use a list too
+ return new ClonePropertyInfo(propertyInfo) { GenericListType = typeof(List