diff --git a/.editorconfig b/.editorconfig
index 29e21d01ed..c63ef39430 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -33,4 +33,5 @@ dotnet_naming_style.prefix_underscore.required_prefix = _
[*.cs]
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
-csharp_style_var_elsewhere = true:suggestion
\ No newline at end of file
+csharp_style_var_elsewhere = true:suggestion
+csharp_prefer_braces = false : none
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 96014f65b7..9dc6f9457f 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -16,7 +16,7 @@ This document gives you a quick overview on how to get started, we will link to
## Guidelines for contributions we welcome
-Not all changes are wanted, so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valueable time.
+Not all changes are wanted, so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes.
@@ -82,7 +82,6 @@ You can get in touch with [the PR team](#the-pr-team) in multiple ways, we love
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely
-- We're also [active in the Gitter chatroom](https://gitter.im/umbraco/Umbraco-CMS)
## Code of Conduct
diff --git a/.github/CONTRIBUTING_DETAILED.md b/.github/CONTRIBUTING_DETAILED.md
index b3e34ef55d..8c2bfffd87 100644
--- a/.github/CONTRIBUTING_DETAILED.md
+++ b/.github/CONTRIBUTING_DETAILED.md
@@ -19,7 +19,7 @@ When contributing code to Umbraco there's plenty of things you'll want to know,
* [What branch should I target for my contributions?](#what-branch-should-i-target-for-my-contributions)
* [Building Umbraco from source code](#building-umbraco-from-source-code)
* [Keeping your Umbraco fork in sync with the main repository](#keeping-your-umbraco-fork-in-sync-with-the-main-repository)
-
+
## How Can I Contribute?
### Reporting Bugs
@@ -52,7 +52,7 @@ Provide more context by answering these questions:
Include details about your configuration and environment:
- * **Which version of Umbraco are you using?**
+ * **Which version of Umbraco are you using?**
* **What is the environment you're using Umbraco in?** Is this a problem on your local machine or on a server. Tell us about your configuration: Windows version, IIS/IISExpress, database type, etc.
* **Which packages do you have installed?**
@@ -80,7 +80,7 @@ The most successful pull requests usually look a like this:
* Unit tests, while optional are awesome, thank you!
* New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated
-Again, these are guidelines, not strict requirements.
+Again, these are guidelines, not strict requirements.
## Making changes after the PR was opened
@@ -90,7 +90,7 @@ If you make the corrections we ask for in the same branch and push them to your
To be honest, we don't like rules very much. We trust you have the best of intentions and we encourage you to create working code. If it doesn't look perfect then we'll happily help clean it up.
-That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
+That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
## What should I know before I get started?
@@ -125,6 +125,12 @@ We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-fl
### Building Umbraco from source code
+In order to build the Umbraco source code locally, first make sure you have the following installed.
+
+ * Visual Studio 2017 v15.3+
+ * Node v10+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v10)
+ * npm v6.4.1+ (Installed via `build.bat` script. If you already have it installed, make sure you're running at least v6.4.1)
+
The easiest way to get started is to run `build.bat` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
Alternatively, you can open `src\umbraco.sln` in Visual Studio 2017 (version 15.3 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
diff --git a/.github/CONTRIBUTION_GUIDELINES.md b/.github/CONTRIBUTION_GUIDELINES.md
index 2e4a9ed334..b94feb0b6b 100644
--- a/.github/CONTRIBUTION_GUIDELINES.md
+++ b/.github/CONTRIBUTION_GUIDELINES.md
@@ -13,7 +13,7 @@ We’re usually able to handle small PRs pretty quickly. A community volunteer w
Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. These issues are usually small enough to fit in the "Small PRs" category and we encourage anyone to pick them up and help out.
-If you do start working on something, make sure leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
+If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
## Large PRs
New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGet’s packages.config, NPM’s packages.json, etc.).
@@ -30,4 +30,4 @@ If a larger pull request is encouraged by Umbraco HQ, the process will be simila
If it doesn’t fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and to fix bugs.
-Eventually, a package could "graduate" to be included in the CMS.
\ No newline at end of file
+Eventually, a package could "graduate" to be included in the CMS.
diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md
index 62b376b0e7..1cc33bb126 100644
--- a/.github/V8_GETTING_STARTED.md
+++ b/.github/V8_GETTING_STARTED.md
@@ -33,5 +33,5 @@ We recommend running the site with the Visual Studio since you'll be able to rem
We are keeping track of [known issues and limitations here](http://issues.umbraco.org/issue/U4-11279). These line items will eventually be turned into actual tasks to be worked on. Feel free to help us keep this list updated if you find issues and even help fix some of these items. If there is a particular item you'd like to help fix please mention this on the task and we'll create a sub task for the item to continue discussion there.
-There's [a list of tasks for v8 that haven't been completed](https://issues.umbraco.org/issues?q=&project=U4&tagValue=&release=8.0.0&issueType=&resolvedState=open&search=search). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done.
+There's [a list of tasks for v8 that haven't been completed](https://github.com/umbraco/Umbraco-CMS/labels/release%2F8.0.0). If you are interested in helping out with any of these please mention this on the task. This list will be constantly updated as we begin to document and design some of the other tasks that still need to get done.
diff --git a/.gitignore b/.gitignore
index 529adef976..279bdb39dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -107,6 +107,7 @@ src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/
src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/
src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css
+src/Umbraco.Web.UI.Client/vwd.webinfo
src/Umbraco.Web.UI/App_Plugins/*
src/*.psess
diff --git a/build/Azure/azuregalleryrelease.ps1 b/build/Azure/azuregalleryrelease.ps1
index 502ca3010e..61505171a2 100644
--- a/build/Azure/azuregalleryrelease.ps1
+++ b/build/Azure/azuregalleryrelease.ps1
@@ -3,57 +3,57 @@
[string]$Directory
)
$workingDirectory = $Directory
-CD $workingDirectory
+CD "$($workingDirectory)"
# Clone repo
-$fullGitUrl = "https://$env:GIT_URL/$env:GIT_REPOSITORYNAME.git"
-git clone $fullGitUrl 2>&1 | % { $_.ToString() }
+$fullGitUrl = "https://$($env:GIT_URL)/$($env:GIT_REPOSITORYNAME).git"
+git clone $($fullGitUrl) $($env:GIT_REPOSITORYNAME) 2>&1 | % { $_.ToString() }
# Remove everything so that unzipping the release later will update everything
# Don't remove the readme file nor the git directory
Write-Host "Cleaning up git directory before adding new version"
-Remove-Item -Recurse $workingDirectory\$env:GIT_REPOSITORYNAME\* -Exclude README.md,.git
+Remove-Item -Recurse "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)\*" -Exclude README.md,.git
# Find release zip
-$zipsDir = "$workingDirectory\$env:BUILD_DEFINITIONNAME\zips"
+$zipsDir = "$($workingDirectory)\$($env:BUILD_DEFINITIONNAME)\zips"
$pattern = "UmbracoCms.([0-9]{1,2}.[0-9]{1,3}.[0-9]{1,3}).zip"
-Write-Host "Searching for Umbraco release files in $workingDirectory\$zipsDir for a file with pattern $pattern"
-$file = (Get-ChildItem $zipsDir | Where-Object { $_.Name -match "$pattern" })
+Write-Host "Searching for Umbraco release files in $($zipsDir) for a file with pattern $($pattern)"
+$file = (Get-ChildItem "$($zipsDir)" | Where-Object { $_.Name -match "$($pattern)" })
if($file)
{
# Get release name
- $version = [regex]::Match($file.Name, $pattern).captures.groups[1].value
- $releaseName = "Umbraco $version"
- Write-Host "Found $releaseName"
+ $version = [regex]::Match($($file.Name), $($pattern)).captures.groups[1].value
+ $releaseName = "Umbraco $($version)"
+ Write-Host "Found $($releaseName)"
# Unzip into repository to update release
Add-Type -AssemblyName System.IO.Compression.FileSystem
- Write-Host "Unzipping $($file.FullName) to $workingDirectory\$env:GIT_REPOSITORYNAME"
- [System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$workingDirectory\$env:GIT_REPOSITORYNAME")
+ Write-Host "Unzipping $($file.FullName) to $($workingDirectory)\$($env:GIT_REPOSITORYNAME)"
+ [System.IO.Compression.ZipFile]::ExtractToDirectory("$($file.FullName)", "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)")
# Telling git who we are
git config --global user.email "coffee@umbraco.com" 2>&1 | % { $_.ToString() }
git config --global user.name "Umbraco HQ" 2>&1 | % { $_.ToString() }
# Commit
- CD $env:GIT_REPOSITORYNAME
- Write-Host "Committing Umbraco $version Release from Build Output"
+ CD "$($workingDirectory)\$($env:GIT_REPOSITORYNAME)"
+ Write-Host "Committing Umbraco $($version) Release from Build Output"
git add . 2>&1 | % { $_.ToString() }
- git commit -m " Release $releaseName from Build Output" 2>&1 | % { $_.ToString() }
+ git commit -m " Release $($releaseName) from Build Output" 2>&1 | % { $_.ToString() }
# Tag the release
- git tag -a "v$version" -m "v$version"
+ git tag -a "v$($version)" -m "v$($version)"
# Push release to master
- $fullGitAuthUrl = "https://$($env:GIT_USERNAME):$GitHubPersonalAccessToken@$env:GIT_URL/$env:GIT_REPOSITORYNAME.git"
- git push $fullGitAuthUrl 2>&1 | % { $_.ToString() }
+ $fullGitAuthUrl = "https://$($env:GIT_USERNAME):$($GitHubPersonalAccessToken)@$($env:GIT_URL)/$($env:GIT_REPOSITORYNAME).git"
+ git push $($fullGitAuthUrl) 2>&1 | % { $_.ToString() }
#Push tag to master
- git push $fullGitAuthUrl --tags 2>&1 | % { $_.ToString() }
+ git push $($fullGitAuthUrl) --tags 2>&1 | % { $_.ToString() }
}
else
{
- Write-Error "Umbraco release file not found, searched in $workingDirectory\$zipsDir for a file with pattern $pattern - cancelling"
+ Write-Error "Umbraco release file not found, searched in $($workingDirectory)\$($zipsDir) for a file with pattern $($pattern) - canceling"
}
diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index daa0018668..dd565aa1d4 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -36,7 +36,7 @@
-
+
diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec
index e9bd8ca6ea..30fa303b30 100644
--- a/build/NuSpecs/UmbracoCms.Web.nuspec
+++ b/build/NuSpecs/UmbracoCms.Web.nuspec
@@ -14,7 +14,7 @@
Contains the core assemblies needed to run Umbraco Cmsen-USumbraco
-
+
-
+
-
-
+
+
diff --git a/build/NuSpecs/tools/Dashboard.config.install.xdt b/build/NuSpecs/tools/Dashboard.config.install.xdt
index 036beeba29..a81af8c365 100644
--- a/build/NuSpecs/tools/Dashboard.config.install.xdt
+++ b/build/NuSpecs/tools/Dashboard.config.install.xdt
@@ -3,7 +3,7 @@
-
+
views/dashboard/settings/settingsdashboardintro.html
@@ -14,7 +14,7 @@
forms
-
+
views/dashboard/forms/formsdashboardintro.html
@@ -28,7 +28,7 @@
-
+
views/dashboard/developer/developerdashboardvideos.html
@@ -47,7 +47,7 @@
-
+
views/dashboard/media/mediafolderbrowser.html
@@ -56,7 +56,7 @@
-
+
views/dashboard/members/membersdashboardvideos.html
@@ -92,4 +92,4 @@
-
\ No newline at end of file
+
diff --git a/build/build.ps1 b/build/build.ps1
index 1066c62876..dafae2665a 100644
--- a/build/build.ps1
+++ b/build/build.ps1
@@ -92,44 +92,40 @@
# so we have to take care of it else they'll bubble and kill the build
if ($error.Count -gt 0) { return }
- Push-Location "$($this.SolutionRoot)\src\Umbraco.Web.UI.Client"
- Write-Output "" > $log
+ try {
+ Push-Location "$($this.SolutionRoot)\src\Umbraco.Web.UI.Client"
+ Write-Output "" > $log
- Write-Output "### node version is:" > $log
- &node -v >> $log 2>&1
- if (-not $?) { throw "Failed to report node version." }
+ Write-Output "### node version is:" > $log
+ node -v >> $log 2>&1
+ if (-not $?) { throw "Failed to report node version." }
- Write-Output "### npm version is:" >> $log 2>&1
- &npm -v >> $log 2>&1
- if (-not $?) { throw "Failed to report npm version." }
+ Write-Output "### npm version is:" >> $log 2>&1
+ npm -v >> $log 2>&1
+ if (-not $?) { throw "Failed to report npm version." }
- Write-Output "### clean npm cache" >> $log 2>&1
- &npm cache clean --force >> $log 2>&1
- $error.Clear() # that one can fail 'cos security bug - ignore
+ Write-Output "### clean npm cache" >> $log 2>&1
+ npm cache clean --force >> $log 2>&1
+ $error.Clear() # that one can fail 'cos security bug - ignore
- Write-Output "### npm install" >> $log 2>&1
- &npm install >> $log 2>&1
- Write-Output ">> $? $($error.Count)" >> $log 2>&1
+ Write-Output "### npm install" >> $log 2>&1
+ npm install >> $log 2>&1
+ Write-Output ">> $? $($error.Count)" >> $log 2>&1
+ # Don't really care about the messages from npm install making us think there are errors
+ $error.Clear()
- Write-Output "### install gulp" >> $log 2>&1
- &npm install -g gulp >> $log 2>&1
- $error.Clear() # that one fails 'cos deprecated stuff - ignore
+ Write-Output "### gulp build for version $($this.Version.Release)" >> $log 2>&1
+ npx gulp build --buildversion=$this.Version.Release >> $log 2>&1
+ if (-not $?) { throw "Failed to build" } # that one is expected to work
+ } finally {
+ Pop-Location
- Write-Output "### install gulp-cli" >> $log 2>&1
- &npm install -g gulp-cli --quiet >> $log 2>&1
- $error.Clear() # that one fails 'cos some files not being removed - ignore
+ # fixme - should we filter the log to find errors?
+ #get-content .\build.tmp\belle.log | %{ if ($_ -match "build") { write $_}}
- Write-Output "### gulp build for version $($this.Version.Release)" >> $log 2>&1
- &gulp build --buildversion=$this.Version.Release >> $log 2>&1
- if (-not $?) { throw "Failed to build" } # that one is expected to work
-
- Pop-Location
-
- # fixme - should we filter the log to find errors?
- #get-content .\build.tmp\belle.log | %{ if ($_ -match "build") { write $_}}
-
- # restore
- $this.RestoreNode()
+ # restore
+ $this.RestoreNode()
+ }
# setting node_modules folder to hidden
# used to prevent VS13 from crashing on it while loading the websites project
@@ -456,9 +452,22 @@
if ($this.OnError()) { return }
$this.PrepareAzureGallery()
if ($this.OnError()) { return }
+ $this.PostPackageHook()
+ if ($this.OnError()) { return }
Write-Host "Done"
})
+ $ubuild.DefineMethod("PostPackageHook",
+ {
+ # run hook
+ if ($this.HasMethod("PostPackage"))
+ {
+ Write-Host "Run PostPackage hook"
+ $this.PostPackage();
+ if (-not $?) { throw "Failed to run hook." }
+ }
+ })
+
# ################################################################
# RUN
# ################################################################
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index b5af335791..ce40bd9baa 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -19,4 +19,4 @@ using System.Resources;
// these are FYI and changed automatically
[assembly: AssemblyFileVersion("8.0.0")]
-[assembly: AssemblyInformationalVersion("8.0.0-alpha.52")]
+[assembly: AssemblyInformationalVersion("8.0.0-alpha.58")]
diff --git a/src/Umbraco.Core/ByteArrayExtensions.cs b/src/Umbraco.Core/ByteArrayExtensions.cs
deleted file mode 100644
index dacdd509ca..0000000000
--- a/src/Umbraco.Core/ByteArrayExtensions.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-namespace Umbraco.Core
-{
- public static class ByteArrayExtensions
- {
- private static readonly char[] BytesToHexStringLookup = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
-
- public static string ToHexString(this byte[] bytes)
- {
- int i = 0, p = 0, bytesLength = bytes.Length;
- var chars = new char[bytesLength * 2];
- while (i < bytesLength)
- {
- var b = bytes[i++];
- chars[p++] = BytesToHexStringLookup[b / 0x10];
- chars[p++] = BytesToHexStringLookup[b % 0x10];
- }
- return new string(chars, 0, chars.Length);
- }
-
- public static string ToHexString(this byte[] bytes, char separator, int blockSize, int blockCount)
- {
- int p = 0, bytesLength = bytes.Length, count = 0, size = 0;
- var chars = new char[bytesLength * 2 + blockCount];
- for (var i = 0; i < bytesLength; i++)
- {
- var b = bytes[i++];
- chars[p++] = BytesToHexStringLookup[b / 0x10];
- chars[p++] = BytesToHexStringLookup[b % 0x10];
- if (count == blockCount) continue;
- if (++size < blockSize) continue;
-
- chars[p++] = '/';
- size = 0;
- count++;
- }
- return new string(chars, 0, chars.Length);
- }
- }
-}
diff --git a/src/Umbraco.Core/Cache/CacheHelper.cs b/src/Umbraco.Core/Cache/CacheHelper.cs
index f99b1e847b..cd2225ae9d 100644
--- a/src/Umbraco.Core/Cache/CacheHelper.cs
+++ b/src/Umbraco.Core/Cache/CacheHelper.cs
@@ -4,27 +4,10 @@ using System.Web;
namespace Umbraco.Core.Cache
{
///
- /// Class that is exposed by the ApplicationContext for application wide caching purposes
+ /// Represents the application-wide caches.
///
public class CacheHelper
{
- public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
-
- ///
- /// Creates a cache helper with disabled caches
- ///
- ///
- ///
- /// Good for unit testing
- ///
- public static CacheHelper CreateDisabledCacheHelper()
- {
- // do *not* return NoCache
- // NoCache is a special instance that is detected by RepositoryBase and disables all cache policies
- // CreateDisabledCacheHelper is used in tests to use no cache, *but* keep all cache policies
- return new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
- }
-
///
/// Initializes a new instance for use in the web
///
@@ -40,7 +23,6 @@ namespace Umbraco.Core.Cache
///
/// Initializes a new instance for use in the web
///
- ///
public CacheHelper(System.Web.Caching.Cache cache)
: this(
new HttpRuntimeCacheProvider(cache),
@@ -50,30 +32,39 @@ namespace Umbraco.Core.Cache
{
}
-
///
/// Initializes a new instance based on the provided providers
///
- ///
- ///
- ///
- ///
public CacheHelper(
IRuntimeCacheProvider httpCacheProvider,
ICacheProvider staticCacheProvider,
ICacheProvider requestCacheProvider,
IsolatedRuntimeCache isolatedCacheManager)
{
- if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider");
- if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider");
- if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider");
- if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager");
- RuntimeCache = httpCacheProvider;
- StaticCache = staticCacheProvider;
- RequestCache = requestCacheProvider;
- IsolatedRuntimeCache = isolatedCacheManager;
+ RuntimeCache = httpCacheProvider ?? throw new ArgumentNullException(nameof(httpCacheProvider));
+ StaticCache = staticCacheProvider ?? throw new ArgumentNullException(nameof(staticCacheProvider));
+ RequestCache = requestCacheProvider ?? throw new ArgumentNullException(nameof(requestCacheProvider));
+ IsolatedRuntimeCache = isolatedCacheManager ?? throw new ArgumentNullException(nameof(isolatedCacheManager));
}
+ ///
+ /// Gets the special disabled instance.
+ ///
+ ///
+ /// When used by repositories, all cache policies apply, but the underlying caches do not cache anything.
+ /// Used by tests.
+ ///
+ public static CacheHelper Disabled { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
+
+ ///
+ /// Gets the special no-cache instance.
+ ///
+ ///
+ /// When used by repositories, all cache policies are bypassed.
+ /// Used by repositories that do no cache.
+ ///
+ public static CacheHelper NoCache { get; } = new CacheHelper(NullCacheProvider.Instance, NullCacheProvider.Instance, NullCacheProvider.Instance, new IsolatedRuntimeCache(_ => NullCacheProvider.Instance));
+
///
/// Returns the current Request cache
///
diff --git a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs
index 11ac05844b..8bae755149 100644
--- a/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs
+++ b/src/Umbraco.Core/Cache/CacheRefresherCollectionBuilder.cs
@@ -1,15 +1,9 @@
-using System.Collections.Generic;
-using LightInject;
-using Umbraco.Core.Composing;
+using Umbraco.Core.Composing;
namespace Umbraco.Core.Cache
{
public class CacheRefresherCollectionBuilder : LazyCollectionBuilderBase
{
- public CacheRefresherCollectionBuilder(IServiceContainer container)
- : base(container)
- { }
-
protected override CacheRefresherCollectionBuilder This => this;
}
}
diff --git a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs
index 4cad6e9f15..54367ed588 100644
--- a/src/Umbraco.Core/Collections/ConcurrentHashSet.cs
+++ b/src/Umbraco.Core/Collections/ConcurrentHashSet.cs
@@ -70,10 +70,7 @@ namespace Umbraco.Core.Collections
/// The number of elements contained in the .
///
/// 2
- public int Count
- {
- get { return GetThreadSafeClone().Count; }
- }
+ public int Count => GetThreadSafeClone().Count;
///
/// Gets a value indicating whether the is read-only.
@@ -81,10 +78,7 @@ namespace Umbraco.Core.Collections
///
/// true if the is read-only; otherwise, false.
///
- public bool IsReadOnly
- {
- get { return false; }
- }
+ public bool IsReadOnly => false;
///
/// Adds an item to the .
diff --git a/src/Umbraco.Core/Components/AuditEventsComponent.cs b/src/Umbraco.Core/Components/AuditEventsComponent.cs
index 134aa18414..08d4702afa 100644
--- a/src/Umbraco.Core/Components/AuditEventsComponent.cs
+++ b/src/Umbraco.Core/Components/AuditEventsComponent.cs
@@ -12,11 +12,36 @@ using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Components
{
- public sealed class AuditEventsComponent : UmbracoComponentBase, IUmbracoCoreComponent
+ public sealed class AuditEventsComponent : IComponent
{
- private IAuditService _auditService;
- private IUserService _userService;
- private IEntityService _entityService;
+ private readonly IAuditService _auditService;
+ private readonly IUserService _userService;
+ private readonly IEntityService _entityService;
+
+ public AuditEventsComponent(IAuditService auditService, IUserService userService, IEntityService entityService)
+ {
+ _auditService = auditService;
+ _userService = userService;
+ _entityService = entityService;
+ }
+
+ public void Initialize()
+ {
+ UserService.SavedUserGroup += OnSavedUserGroupWithUsers;
+
+ UserService.SavedUser += OnSavedUser;
+ UserService.DeletedUser += OnDeletedUser;
+ UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned;
+
+ MemberService.Saved += OnSavedMember;
+ MemberService.Deleted += OnDeletedMember;
+ MemberService.AssignedRoles += OnAssignedRoles;
+ MemberService.RemovedRoles += OnRemovedRoles;
+ MemberService.Exported += OnMemberExported;
+ }
+
+ public void Terminate()
+ { }
private IUser CurrentPerformingUser
{
@@ -46,25 +71,6 @@ namespace Umbraco.Core.Components
}
}
- public void Initialize(IAuditService auditService, IUserService userService, IEntityService entityService)
- {
- _auditService = auditService;
- _userService = userService;
- _entityService = entityService;
-
- UserService.SavedUserGroup += OnSavedUserGroupWithUsers;
-
- UserService.SavedUser += OnSavedUser;
- UserService.DeletedUser += OnDeletedUser;
- UserService.UserGroupPermissionsAssigned += UserGroupPermissionAssigned;
-
- MemberService.Saved += OnSavedMember;
- MemberService.Deleted += OnDeletedMember;
- MemberService.AssignedRoles += OnAssignedRoles;
- MemberService.RemovedRoles += OnRemovedRoles;
- MemberService.Exported += OnMemberExported;
- }
-
private string FormatEmail(IMember member)
{
return member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? "" : $"<{member.Email}>";
diff --git a/src/Umbraco.Core/Components/AuditEventsComposer.cs b/src/Umbraco.Core/Components/AuditEventsComposer.cs
new file mode 100644
index 0000000000..692cb6c6dd
--- /dev/null
+++ b/src/Umbraco.Core/Components/AuditEventsComposer.cs
@@ -0,0 +1,5 @@
+namespace Umbraco.Core.Components
+{
+ public sealed class AuditEventsComposer : ComponentComposer, ICoreComposer
+ { }
+}
diff --git a/src/Umbraco.Core/Components/BootLoader.cs b/src/Umbraco.Core/Components/BootLoader.cs
deleted file mode 100644
index fd292990c8..0000000000
--- a/src/Umbraco.Core/Components/BootLoader.cs
+++ /dev/null
@@ -1,368 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using LightInject;
-using Umbraco.Core.Collections;
-using Umbraco.Core.Exceptions;
-using Umbraco.Core.Logging;
-using Umbraco.Core.Scoping;
-
-namespace Umbraco.Core.Components
-{
- // note: this class is NOT thread-safe in any ways
-
- internal class BootLoader
- {
- private readonly IServiceContainer _container;
- private readonly ProfilingLogger _proflog;
- private readonly ILogger _logger;
- private IUmbracoComponent[] _components;
- private bool _booted;
-
- private const int LogThresholdMilliseconds = 100;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The application container.
- public BootLoader(IServiceContainer container)
- {
- _container = container ?? throw new ArgumentNullException(nameof(container));
- _proflog = container.GetInstance();
- _logger = container.GetInstance();
- }
-
- private class EnableInfo
- {
- public bool Enabled;
- public int Weight = -1;
- }
-
- public void Boot(IEnumerable componentTypes, RuntimeLevel level)
- {
- if (_booted) throw new InvalidOperationException("Can not boot, has already booted.");
-
- var orderedComponentTypes = PrepareComponentTypes(componentTypes, level);
-
- InstanciateComponents(orderedComponentTypes);
- ComposeComponents(level);
-
- using (var scope = _container.GetInstance().CreateScope())
- {
- InitializeComponents();
- scope.Complete();
- }
-
- // rejoice!
- _booted = true;
- }
-
- private IEnumerable PrepareComponentTypes(IEnumerable componentTypes, RuntimeLevel level)
- {
- using (_proflog.DebugDuration("Preparing component types.", "Prepared component types."))
- {
- return PrepareComponentTypes2(componentTypes, level);
- }
- }
-
- private IEnumerable PrepareComponentTypes2(IEnumerable componentTypes, RuntimeLevel level)
- {
- // create a list, remove those that cannot be enabled due to runtime level
- var componentTypeList = componentTypes
- .Where(x =>
- {
- // use the min level specified by the attribute if any
- // otherwise, user components have Run min level, anything else is Unknown (always run)
- var attr = x.GetCustomAttribute();
- var minLevel = attr?.MinLevel ?? (x.Implements() ? RuntimeLevel.Run : RuntimeLevel.Unknown);
- return level >= minLevel;
- })
- .ToList();
-
- // cannot remove that one - ever
- if (componentTypeList.Contains(typeof(UmbracoCoreComponent)) == false)
- componentTypeList.Add(typeof(UmbracoCoreComponent));
-
- // enable or disable components
- EnableDisableComponents(componentTypeList);
-
- // sort the components according to their dependencies
- var requirements = new Dictionary>();
- foreach (var type in componentTypeList) requirements[type] = null;
- foreach (var type in componentTypeList)
- {
- GatherRequirementsFromRequireAttribute(type, componentTypeList, requirements);
- GatherRequirementsFromRequiredAttribute(type, componentTypeList, requirements);
- }
-
- // only for debugging, this is verbose
- //_logger.Debug(GetComponentsReport(requirements));
-
- // sort components
- var graph = new TopoGraph>>(kvp => kvp.Key, kvp => kvp.Value);
- graph.AddItems(requirements);
- List sortedComponentTypes;
- try
- {
- sortedComponentTypes = graph.GetSortedItems().Select(x => x.Key).ToList();
- }
- catch (Exception e)
- {
- // in case of an error, force-dump everything to log
- _logger.Info("Component Report:\r\n{ComponentReport}", GetComponentsReport(requirements));
- _logger.Error(e, "Failed to sort compontents.");
- throw;
- }
-
- // bit verbose but should help for troubleshooting
- var text = "Ordered Components: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComponentTypes) + Environment.NewLine;
- Console.WriteLine(text);
- _logger.Debug("Ordered Components: {SortedComponentTypes}", sortedComponentTypes);
-
- return sortedComponentTypes;
- }
-
- private static string GetComponentsReport(Dictionary> requirements)
- {
- var text = new StringBuilder();
- text.AppendLine("Components & Dependencies:");
- text.AppendLine();
-
- foreach (var kvp in requirements)
- {
- var type = kvp.Key;
-
- text.AppendLine(type.FullName);
- foreach (var attribute in type.GetCustomAttributes())
- text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
- ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
- : ""));
- foreach (var attribute in type.GetCustomAttributes())
- text.AppendLine(" -< " + attribute.RequiringType);
- foreach (var i in type.GetInterfaces())
- {
- text.AppendLine(" : " + i.FullName);
- foreach (var attribute in i.GetCustomAttributes())
- text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
- ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
- : ""));
- foreach (var attribute in i.GetCustomAttributes())
- text.AppendLine(" -< " + attribute.RequiringType);
- }
- if (kvp.Value != null)
- foreach (var t in kvp.Value)
- text.AppendLine(" = " + t);
- text.AppendLine();
- }
- text.AppendLine("/");
- text.AppendLine();
- return text.ToString();
- }
-
- private static void EnableDisableComponents(ICollection types)
- {
- var enabled = new Dictionary();
-
- // process the enable/disable attributes
- // these two attributes are *not* inherited and apply to *classes* only (not interfaces).
- // remote declarations (when a component enables/disables *another* component)
- // have priority over local declarations (when a component disables itself) so that
- // ppl can enable components that, by default, are disabled.
- // what happens in case of conflicting remote declarations is unspecified. more
- // precisely, the last declaration to be processed wins, but the order of the
- // declarations depends on the type finder and is unspecified.
- foreach (var componentType in types)
- {
- foreach (var attr in componentType.GetCustomAttributes())
- {
- var type = attr.EnabledType ?? componentType;
- if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo();
- var weight = type == componentType ? 1 : 2;
- if (enableInfo.Weight > weight) continue;
-
- enableInfo.Enabled = true;
- enableInfo.Weight = weight;
- }
- foreach (var attr in componentType.GetCustomAttributes())
- {
- var type = attr.DisabledType ?? componentType;
- if (type == typeof(UmbracoCoreComponent)) throw new InvalidOperationException("Cannot disable UmbracoCoreComponent.");
- if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo();
- var weight = type == componentType ? 1 : 2;
- if (enableInfo.Weight > weight) continue;
-
- enableInfo.Enabled = false;
- enableInfo.Weight = weight;
- }
- }
-
- // remove components that end up being disabled
- foreach (var kvp in enabled.Where(x => x.Value.Enabled == false))
- types.Remove(kvp.Key);
- }
-
- private static void GatherRequirementsFromRequireAttribute(Type type, ICollection types, IDictionary> requirements)
- {
- // get 'require' attributes
- // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
- var requireAttributes = type
- .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces
- .Concat(type.GetCustomAttributes()); // those marking the component
-
- // what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
- foreach (var attr in requireAttributes)
- {
- if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below)
-
- // requiring an interface = require any enabled component implementing that interface
- // unless strong, and then require at least one enabled component implementing that interface
- if (attr.RequiredType.IsInterface)
- {
- var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList();
- if (implems.Count > 0)
- {
- if (requirements[type] == null) requirements[type] = new List();
- requirements[type].AddRange(implems);
- }
- else if (attr.Weak == false) // if explicitely set to !weak, is strong, else is weak
- throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
- }
- // requiring a class = require that the component is enabled
- // unless weak, and then requires it if it is enabled
- else
- {
- if (types.Contains(attr.RequiredType))
- {
- if (requirements[type] == null) requirements[type] = new List();
- requirements[type].Add(attr.RequiredType);
- }
- else if (attr.Weak != true) // if not explicitely set to weak, is strong
- throw new Exception($"Broken component dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
- }
- }
- }
-
- private static void GatherRequirementsFromRequiredAttribute(Type type, ICollection types, IDictionary> requirements)
- {
- // get 'required' attributes
- // fixme explain
- var requiredAttributes = type
- .GetInterfaces().SelectMany(x => x.GetCustomAttributes())
- .Concat(type.GetCustomAttributes());
-
- foreach (var attr in requiredAttributes)
- {
- if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below)
-
- if (attr.RequiringType.IsInterface)
- {
- var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList();
- foreach (var implem in implems)
- {
- if (requirements[implem] == null) requirements[implem] = new List();
- requirements[implem].Add(type);
- }
- }
- else
- {
- if (types.Contains(attr.RequiringType))
- {
- if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List();
- requirements[attr.RequiringType].Add(type);
- }
- }
- }
- }
-
- private void InstanciateComponents(IEnumerable types)
- {
- using (_proflog.DebugDuration("Instanciating components.", "Instanciated components."))
- {
- _components = types.Select(x => (IUmbracoComponent) Activator.CreateInstance(x)).ToArray();
- }
- }
-
- private void ComposeComponents(RuntimeLevel level)
- {
- using (_proflog.DebugDuration($"Composing components. (log when >{LogThresholdMilliseconds}ms)", "Composed components."))
- {
- var composition = new Composition(_container, level);
- foreach (var component in _components)
- {
- var componentType = component.GetType();
- using (_proflog.DebugDuration($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
- {
- component.Compose(composition);
- }
- }
- }
- }
-
- private void InitializeComponents()
- {
- // use a container scope to ensure that PerScope instances are disposed
- // components that require instances that should not survive should register them with PerScope lifetime
- using (_proflog.DebugDuration($"Initializing components. (log when >{LogThresholdMilliseconds}ms)", "Initialized components."))
- using (_container.BeginScope())
- {
- foreach (var component in _components)
- {
- var componentType = component.GetType();
- var initializers = componentType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
- .Where(x => x.Name == "Initialize" && x.IsGenericMethod == false);
- using (_proflog.DebugDuration($"Initializing {componentType.FullName}.", $"Initialised {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
- {
- foreach (var initializer in initializers)
- {
- var parameters = initializer.GetParameters()
- .Select(x => GetParameter(componentType, x.ParameterType))
- .ToArray();
- initializer.Invoke(component, parameters);
- }
- }
- }
- }
- }
-
- private object GetParameter(Type componentType, Type parameterType)
- {
- object param;
-
- try
- {
- param = _container.TryGetInstance(parameterType);
- }
- catch (Exception e)
- {
- throw new BootFailedException($"Could not get parameter of type {parameterType.FullName} for component {componentType.FullName}.", e);
- }
-
- if (param == null) throw new BootFailedException($"Could not get parameter of type {parameterType.FullName} for component {componentType.FullName}.");
- return param;
- }
-
- public void Terminate()
- {
- if (_booted == false)
- {
- _proflog.Logger.Warn("Cannot terminate, has not booted.");
- return;
- }
-
- using (_proflog.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated."))
- {
- for (var i = _components.Length - 1; i >= 0; i--) // terminate components in reverse order
- {
- var component = _components[i];
- var componentType = component.GetType();
- using (_proflog.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
- {
- component.Terminate();
- }
- }
- }
- }
- }
-}
diff --git a/src/Umbraco.Core/Components/ComponentCollection.cs b/src/Umbraco.Core/Components/ComponentCollection.cs
new file mode 100644
index 0000000000..4fa81b9760
--- /dev/null
+++ b/src/Umbraco.Core/Components/ComponentCollection.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Logging;
+
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Represents the collection of implementations.
+ ///
+ public class ComponentCollection : BuilderCollectionBase
+ {
+ private const int LogThresholdMilliseconds = 100;
+
+ private readonly IProfilingLogger _logger;
+
+ public ComponentCollection(IEnumerable items, IProfilingLogger logger)
+ : base(items)
+ {
+ _logger = logger;
+ }
+
+ public void Initialize()
+ {
+ using (_logger.DebugDuration($"Initializing. (log components when >{LogThresholdMilliseconds}ms)", "Initialized."))
+ {
+ foreach (var component in this.Reverse()) // terminate components in reverse order
+ {
+ var componentType = component.GetType();
+ using (_logger.DebugDuration($"Initializing {componentType.FullName}.", $"Initialized {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
+ {
+ component.Initialize();
+ }
+ }
+ }
+ }
+
+ public void Terminate()
+ {
+ using (_logger.DebugDuration($"Terminating. (log components when >{LogThresholdMilliseconds}ms)", "Terminated."))
+ {
+ foreach (var component in this.Reverse()) // terminate components in reverse order
+ {
+ var componentType = component.GetType();
+ using (_logger.DebugDuration($"Terminating {componentType.FullName}.", $"Terminated {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
+ {
+ component.Terminate();
+ component.DisposeIfDisposable();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs b/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs
new file mode 100644
index 0000000000..584de7a8f2
--- /dev/null
+++ b/src/Umbraco.Core/Components/ComponentCollectionBuilder.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Logging;
+
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Builds a .
+ ///
+ public class ComponentCollectionBuilder : OrderedCollectionBuilderBase
+ {
+ private const int LogThresholdMilliseconds = 100;
+
+ private IProfilingLogger _logger;
+
+ public ComponentCollectionBuilder()
+ { }
+
+ protected override ComponentCollectionBuilder This => this;
+
+ protected override IEnumerable CreateItems(IFactory factory)
+ {
+ _logger = factory.GetInstance();
+
+ using (_logger.DebugDuration($"Creating components. (log when >{LogThresholdMilliseconds}ms)", "Created."))
+ {
+ return base.CreateItems(factory);
+ }
+ }
+
+ protected override IComponent CreateItem(IFactory factory, Type itemType)
+ {
+ using (_logger.DebugDuration($"Creating {itemType.FullName}.", $"Created {itemType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
+ {
+ return base.CreateItem(factory, itemType);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Components/ComponentComposer.cs b/src/Umbraco.Core/Components/ComponentComposer.cs
new file mode 100644
index 0000000000..792790c42f
--- /dev/null
+++ b/src/Umbraco.Core/Components/ComponentComposer.cs
@@ -0,0 +1,20 @@
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Provides a base class for composers which compose a component.
+ ///
+ /// The type of the component
+ public abstract class ComponentComposer : IComposer
+ where TComponent : IComponent
+ {
+ ///
+ public virtual void Compose(Composition composition)
+ {
+ composition.Components().Append();
+ }
+
+ // note: thanks to this class, a component that does not compose anything can be
+ // registered with one line:
+ // public class MyComponentComposer : ComponentComposer { }
+ }
+}
diff --git a/src/Umbraco.Core/Components/ComposeAfterAttribute.cs b/src/Umbraco.Core/Components/ComposeAfterAttribute.cs
new file mode 100644
index 0000000000..a8fdfaa92b
--- /dev/null
+++ b/src/Umbraco.Core/Components/ComposeAfterAttribute.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Indicates that a composer requires another composer.
+ ///
+ ///
+ /// This attribute is *not* inherited. This means that a composer class inheriting from
+ /// another composer class does *not* inherit its requirements. However, the runtime checks
+ /// the *interfaces* of every composer for their requirements, so requirements declared on
+ /// interfaces are inherited by every composer class implementing the interface.
+ /// When targeting a class, indicates a dependency on the composer which must be enabled,
+ /// unless the requirement has explicitly been declared as weak (and then, only if the composer
+ /// is enabled).
+ /// When targeting an interface, indicates a dependency on enabled composers implementing
+ /// the interface. It could be no composer at all, unless the requirement has explicitly been
+ /// declared as strong (and at least one composer must be enabled).
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
+ public sealed class ComposeAfterAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type of the required composer.
+ public ComposeAfterAttribute(Type requiredType)
+ {
+ if (typeof(IComposer).IsAssignableFrom(requiredType) == false)
+ throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}.");
+ RequiredType = requiredType;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type of the required composer.
+ /// A value indicating whether the requirement is weak.
+ public ComposeAfterAttribute(Type requiredType, bool weak)
+ : this(requiredType)
+ {
+ Weak = weak;
+ }
+
+ ///
+ /// Gets the required type.
+ ///
+ public Type RequiredType { get; }
+
+ ///
+ /// Gets a value indicating whether the requirement is weak.
+ ///
+ /// Returns true if the requirement is weak (requires the other composer if it
+ /// is enabled), false if the requirement is strong (requires the other composer to be
+ /// enabled), and null if unspecified, in which case it is strong for classes and weak for
+ /// interfaces.
+ public bool? Weak { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs b/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs
new file mode 100644
index 0000000000..17065d1676
--- /dev/null
+++ b/src/Umbraco.Core/Components/ComposeBeforeAttribute.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Indicates that a component is required by another composer.
+ ///
+ ///
+ /// This attribute is *not* inherited. This means that a composer class inheriting from
+ /// another composer class does *not* inherit its requirements. However, the runtime checks
+ /// the *interfaces* of every composer for their requirements, so requirements declared on
+ /// interfaces are inherited by every composer class implementing the interface.
+ /// When targeting a class, indicates a dependency on the composer which must be enabled,
+ /// unless the requirement has explicitly been declared as weak (and then, only if the composer
+ /// is enabled).
+ /// When targeting an interface, indicates a dependency on enabled composers implementing
+ /// the interface. It could be no composer at all, unless the requirement has explicitly been
+ /// declared as strong (and at least one composer must be enabled).
+ ///
+
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
+ public sealed class ComposeBeforeAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type of the required composer.
+ public ComposeBeforeAttribute(Type requiringType)
+ {
+ if (typeof(IComposer).IsAssignableFrom(requiringType) == false)
+ throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IComposer).FullName}.");
+ RequiringType = requiringType;
+ }
+
+ ///
+ /// Gets the required type.
+ ///
+ public Type RequiringType { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Components/Composers.cs b/src/Umbraco.Core/Components/Composers.cs
new file mode 100644
index 0000000000..1c836e9e5c
--- /dev/null
+++ b/src/Umbraco.Core/Components/Composers.cs
@@ -0,0 +1,294 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Umbraco.Core.Collections;
+using Umbraco.Core.Logging;
+
+namespace Umbraco.Core.Components
+{
+ // note: this class is NOT thread-safe in any ways
+
+ ///
+ /// Handles the composers.
+ ///
+ public class Composers
+ {
+ private readonly Composition _composition;
+ private readonly IProfilingLogger _logger;
+ private readonly IEnumerable _composerTypes;
+
+ private const int LogThresholdMilliseconds = 100;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The composition.
+ /// The composer types.
+ /// A profiling logger.
+ public Composers(Composition composition, IEnumerable composerTypes, IProfilingLogger logger)
+ {
+ _composition = composition ?? throw new ArgumentNullException(nameof(composition));
+ _composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes));
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ }
+
+ private class EnableInfo
+ {
+ public bool Enabled;
+ public int Weight = -1;
+ }
+
+ ///
+ /// Instantiates and composes the composers.
+ ///
+ public void Compose()
+ {
+ // make sure it is there
+ _composition.WithCollectionBuilder();
+
+ IEnumerable orderedComposerTypes;
+
+ using (_logger.DebugDuration("Preparing composer types.", "Prepared composer types."))
+ {
+ orderedComposerTypes = PrepareComposerTypes();
+ }
+
+ var composers = InstantiateComposers(orderedComposerTypes);
+
+ using (_logger.DebugDuration($"Composing composers. (log when >{LogThresholdMilliseconds}ms)", "Composed composers."))
+ {
+ foreach (var composer in composers)
+ {
+ var componentType = composer.GetType();
+ using (_logger.DebugDuration($"Composing {componentType.FullName}.", $"Composed {componentType.FullName}.", thresholdMilliseconds: LogThresholdMilliseconds))
+ {
+ composer.Compose(_composition);
+ }
+ }
+ }
+ }
+
+ private IEnumerable PrepareComposerTypes()
+ {
+ // create a list, remove those that cannot be enabled due to runtime level
+ var composerTypeList = _composerTypes
+ .Where(x =>
+ {
+ // use the min level specified by the attribute if any
+ // otherwise, user composers have Run min level, anything else is Unknown (always run)
+ var attr = x.GetCustomAttribute();
+ var minLevel = attr?.MinLevel ?? (x.Implements() ? RuntimeLevel.Run : RuntimeLevel.Unknown);
+ return _composition.RuntimeState.Level >= minLevel;
+ })
+ .ToList();
+
+ // enable or disable composers
+ EnableDisableComposers(composerTypeList);
+
+ // sort the composers according to their dependencies
+ var requirements = new Dictionary>();
+ foreach (var type in composerTypeList) requirements[type] = null;
+ foreach (var type in composerTypeList)
+ {
+ GatherRequirementsFromRequireAttribute(type, composerTypeList, requirements);
+ GatherRequirementsFromRequiredByAttribute(type, composerTypeList, requirements);
+ }
+
+ // only for debugging, this is verbose
+ //_logger.Debug(GetComposersReport(requirements));
+
+ // sort composers
+ var graph = new TopoGraph>>(kvp => kvp.Key, kvp => kvp.Value);
+ graph.AddItems(requirements);
+ List sortedComposerTypes;
+ try
+ {
+ sortedComposerTypes = graph.GetSortedItems().Select(x => x.Key).ToList();
+ }
+ catch (Exception e)
+ {
+ // in case of an error, force-dump everything to log
+ _logger.Info("Composer Report:\r\n{ComposerReport}", GetComposersReport(requirements));
+ _logger.Error(e, "Failed to sort composers.");
+ throw;
+ }
+
+ // bit verbose but should help for troubleshooting
+ //var text = "Ordered Composers: " + Environment.NewLine + string.Join(Environment.NewLine, sortedComposerTypes) + Environment.NewLine;
+ _logger.Debug("Ordered Composers: {SortedComposerTypes}", sortedComposerTypes);
+
+ return sortedComposerTypes;
+ }
+
+ private static string GetComposersReport(Dictionary> requirements)
+ {
+ var text = new StringBuilder();
+ text.AppendLine("Composers & Dependencies:");
+ text.AppendLine();
+
+ foreach (var kvp in requirements)
+ {
+ var type = kvp.Key;
+
+ text.AppendLine(type.FullName);
+ foreach (var attribute in type.GetCustomAttributes())
+ text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
+ ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
+ : ""));
+ foreach (var attribute in type.GetCustomAttributes())
+ text.AppendLine(" -< " + attribute.RequiringType);
+ foreach (var i in type.GetInterfaces())
+ {
+ text.AppendLine(" : " + i.FullName);
+ foreach (var attribute in i.GetCustomAttributes())
+ text.AppendLine(" -> " + attribute.RequiredType + (attribute.Weak.HasValue
+ ? (attribute.Weak.Value ? " (weak)" : (" (strong" + (requirements.ContainsKey(attribute.RequiredType) ? ", missing" : "") + ")"))
+ : ""));
+ foreach (var attribute in i.GetCustomAttributes())
+ text.AppendLine(" -< " + attribute.RequiringType);
+ }
+ if (kvp.Value != null)
+ foreach (var t in kvp.Value)
+ text.AppendLine(" = " + t);
+ text.AppendLine();
+ }
+ text.AppendLine("/");
+ text.AppendLine();
+ return text.ToString();
+ }
+
+ private static void EnableDisableComposers(ICollection types)
+ {
+ var enabled = new Dictionary();
+
+ // process the enable/disable attributes
+ // these two attributes are *not* inherited and apply to *classes* only (not interfaces).
+ // remote declarations (when a composer enables/disables *another* composer)
+ // have priority over local declarations (when a composer disables itself) so that
+ // ppl can enable composers that, by default, are disabled.
+ // what happens in case of conflicting remote declarations is unspecified. more
+ // precisely, the last declaration to be processed wins, but the order of the
+ // declarations depends on the type finder and is unspecified.
+ foreach (var composerType in types)
+ {
+ foreach (var attr in composerType.GetCustomAttributes())
+ {
+ var type = attr.EnabledType ?? composerType;
+ if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo();
+ var weight = type == composerType ? 1 : 2;
+ if (enableInfo.Weight > weight) continue;
+
+ enableInfo.Enabled = true;
+ enableInfo.Weight = weight;
+ }
+ foreach (var attr in composerType.GetCustomAttributes())
+ {
+ var type = attr.DisabledType ?? composerType;
+ if (enabled.TryGetValue(type, out var enableInfo) == false) enableInfo = enabled[type] = new EnableInfo();
+ var weight = type == composerType ? 1 : 2;
+ if (enableInfo.Weight > weight) continue;
+
+ enableInfo.Enabled = false;
+ enableInfo.Weight = weight;
+ }
+ }
+
+ // remove composers that end up being disabled
+ foreach (var kvp in enabled.Where(x => x.Value.Enabled == false))
+ types.Remove(kvp.Key);
+ }
+
+ private static void GatherRequirementsFromRequireAttribute(Type type, ICollection types, IDictionary> requirements)
+ {
+ // get 'require' attributes
+ // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
+ var requireAttributes = type
+ .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces
+ .Concat(type.GetCustomAttributes()); // those marking the composer
+
+ // what happens in case of conflicting attributes (different strong/weak for same type) is not specified.
+ foreach (var attr in requireAttributes)
+ {
+ if (attr.RequiredType == type) continue; // ignore self-requirements (+ exclude in implems, below)
+
+ // requiring an interface = require any enabled composer implementing that interface
+ // unless strong, and then require at least one enabled composer implementing that interface
+ if (attr.RequiredType.IsInterface)
+ {
+ var implems = types.Where(x => x != type && attr.RequiredType.IsAssignableFrom(x)).ToList();
+ if (implems.Count > 0)
+ {
+ if (requirements[type] == null) requirements[type] = new List();
+ requirements[type].AddRange(implems);
+ }
+ else if (attr.Weak == false) // if explicitly set to !weak, is strong, else is weak
+ throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
+ }
+ // requiring a class = require that the composer is enabled
+ // unless weak, and then requires it if it is enabled
+ else
+ {
+ if (types.Contains(attr.RequiredType))
+ {
+ if (requirements[type] == null) requirements[type] = new List();
+ requirements[type].Add(attr.RequiredType);
+ }
+ else if (attr.Weak != true) // if not explicitly set to weak, is strong
+ throw new Exception($"Broken composer dependency: {type.FullName} -> {attr.RequiredType.FullName}.");
+ }
+ }
+ }
+
+ private static void GatherRequirementsFromRequiredByAttribute(Type type, ICollection types, IDictionary> requirements)
+ {
+ // get 'required' attributes
+ // these attributes are *not* inherited because we want to "custom-inherit" for interfaces only
+ var requiredAttributes = type
+ .GetInterfaces().SelectMany(x => x.GetCustomAttributes()) // those marking interfaces
+ .Concat(type.GetCustomAttributes()); // those marking the composer
+
+ foreach (var attr in requiredAttributes)
+ {
+ if (attr.RequiringType == type) continue; // ignore self-requirements (+ exclude in implems, below)
+
+ // required by an interface = by any enabled composer implementing this that interface
+ if (attr.RequiringType.IsInterface)
+ {
+ var implems = types.Where(x => x != type && attr.RequiringType.IsAssignableFrom(x)).ToList();
+ foreach (var implem in implems)
+ {
+ if (requirements[implem] == null) requirements[implem] = new List();
+ requirements[implem].Add(type);
+ }
+ }
+ // required by a class
+ else
+ {
+ if (types.Contains(attr.RequiringType))
+ {
+ if (requirements[attr.RequiringType] == null) requirements[attr.RequiringType] = new List();
+ requirements[attr.RequiringType].Add(type);
+ }
+ }
+ }
+ }
+
+ private IEnumerable InstantiateComposers(IEnumerable types)
+ {
+ IComposer InstantiateComposer(Type type)
+ {
+ var ctor = type.GetConstructor(Array.Empty());
+ if (ctor == null)
+ throw new InvalidOperationException($"Composer {type.FullName} does not have a parameter-less constructor.");
+ return (IComposer) ctor.Invoke(Array.Empty
public static class CompositionExtensions
{
+ #region FileSystems
+
+ ///
+ /// Registers a filesystem.
+ ///
+ /// The type of the filesystem.
+ /// The implementing type.
+ /// The composition.
+ /// The register.
+ public static void RegisterFileSystem(this Composition composition)
+ where TImplementing : FileSystemWrapper, TFileSystem
+ where TFileSystem : class
+ {
+ composition.RegisterUnique(factory =>
+ {
+ var fileSystems = factory.GetInstance();
+ var supporting = factory.GetInstance();
+ return fileSystems.GetFileSystem(supporting.For());
+ });
+ }
+
+ ///
+ /// Registers a filesystem.
+ ///
+ /// The type of the filesystem.
+ /// The composition.
+ /// The register.
+ public static void RegisterFileSystem(this Composition composition)
+ where TFileSystem : FileSystemWrapper
+ {
+ composition.RegisterUnique(factory =>
+ {
+ var fileSystems = factory.GetInstance();
+ var supporting = factory.GetInstance();
+ return fileSystems.GetFileSystem(supporting.For());
+ });
+ }
+
+ #endregion
+
#region Collection Builders
///
@@ -26,60 +65,66 @@ namespace Umbraco.Core.Components
///
/// The composition.
public static CacheRefresherCollectionBuilder CacheRefreshers(this Composition composition)
- => composition.Container.GetInstance();
+ => composition.WithCollectionBuilder();
///
/// Gets the mappers collection builder.
///
/// The composition.
public static MapperCollectionBuilder Mappers(this Composition composition)
- => composition.Container.GetInstance();
+ => composition.WithCollectionBuilder();
///
/// Gets the package actions collection builder.
///
/// The composition.
internal static PackageActionCollectionBuilder PackageActions(this Composition composition)
- => composition.Container.GetInstance();
+ => composition.WithCollectionBuilder();
///
/// Gets the data editor collection builder.
///
/// The composition.
public static DataEditorCollectionBuilder DataEditors(this Composition composition)
- => composition.Container.GetInstance();
+ => composition.WithCollectionBuilder();
///
/// Gets the property value converters collection builder.
///
/// The composition.
public static PropertyValueConverterCollectionBuilder PropertyValueConverters(this Composition composition)
- => composition.Container.GetInstance();
+ => composition.WithCollectionBuilder();
///
/// Gets the url segment providers collection builder.
///
/// The composition.
public static UrlSegmentProviderCollectionBuilder UrlSegmentProviders(this Composition composition)
- => composition.Container.GetInstance();
+ => composition.WithCollectionBuilder();
///
/// Gets the validators collection builder.
///
/// The composition.
internal static ManifestValueValidatorCollectionBuilder Validators(this Composition composition)
- => composition.Container.GetInstance();
+ => composition.WithCollectionBuilder();
///
/// Gets the post-migrations collection builder.
///
/// The composition.
internal static PostMigrationCollectionBuilder PostMigrations(this Composition composition)
- => composition.Container.GetInstance();
+ => composition.WithCollectionBuilder();
+
+ ///
+ /// Gets the components collection builder.
+ ///
+ public static ComponentCollectionBuilder Components(this Composition composition)
+ => composition.WithCollectionBuilder();
#endregion
- #region Singleton
+ #region Uniques
///
/// Sets the culture dictionary factory.
@@ -89,7 +134,7 @@ namespace Umbraco.Core.Components
public static void SetCultureDictionaryFactory(this Composition composition)
where T : ICultureDictionaryFactory
{
- composition.Container.RegisterSingleton();
+ composition.RegisterUnique();
}
///
@@ -97,9 +142,9 @@ namespace Umbraco.Core.Components
///
/// The composition.
/// A function creating a culture dictionary factory.
- public static void SetCultureDictionaryFactory(this Composition composition, Func factory)
+ public static void SetCultureDictionaryFactory(this Composition composition, Func factory)
{
- composition.Container.RegisterSingleton(factory);
+ composition.RegisterUnique(factory);
}
///
@@ -109,7 +154,7 @@ namespace Umbraco.Core.Components
/// A factory.
public static void SetCultureDictionaryFactory(this Composition composition, ICultureDictionaryFactory factory)
{
- composition.Container.RegisterSingleton(_ => factory);
+ composition.RegisterUnique(_ => factory);
}
///
@@ -120,7 +165,7 @@ namespace Umbraco.Core.Components
public static void SetPublishedContentModelFactory(this Composition composition)
where T : IPublishedModelFactory
{
- composition.Container.RegisterSingleton();
+ composition.RegisterUnique();
}
///
@@ -128,9 +173,9 @@ namespace Umbraco.Core.Components
///
/// The composition.
/// A function creating a published content model factory.
- public static void SetPublishedContentModelFactory(this Composition composition, Func factory)
+ public static void SetPublishedContentModelFactory(this Composition composition, Func factory)
{
- composition.Container.RegisterSingleton(factory);
+ composition.RegisterUnique(factory);
}
///
@@ -140,7 +185,7 @@ namespace Umbraco.Core.Components
/// A published content model factory.
public static void SetPublishedContentModelFactory(this Composition composition, IPublishedModelFactory factory)
{
- composition.Container.RegisterSingleton(_ => factory);
+ composition.RegisterUnique(_ => factory);
}
///
@@ -151,7 +196,7 @@ namespace Umbraco.Core.Components
public static void SetServerRegistrar(this Composition composition)
where T : IServerRegistrar
{
- composition.Container.RegisterSingleton();
+ composition.RegisterUnique();
}
///
@@ -159,9 +204,9 @@ namespace Umbraco.Core.Components
///
/// The composition.
/// A function creating a server registar.
- public static void SetServerRegistrar(this Composition composition, Func factory)
+ public static void SetServerRegistrar(this Composition composition, Func factory)
{
- composition.Container.RegisterSingleton(factory);
+ composition.RegisterUnique(factory);
}
///
@@ -171,7 +216,7 @@ namespace Umbraco.Core.Components
/// A server registrar.
public static void SetServerRegistrar(this Composition composition, IServerRegistrar registrar)
{
- composition.Container.RegisterSingleton(_ => registrar);
+ composition.RegisterUnique(_ => registrar);
}
///
@@ -182,7 +227,7 @@ namespace Umbraco.Core.Components
public static void SetServerMessenger(this Composition composition)
where T : IServerMessenger
{
- composition.Container.RegisterSingleton();
+ composition.RegisterUnique();
}
///
@@ -190,9 +235,9 @@ namespace Umbraco.Core.Components
///
/// The composition.
/// A function creating a server messenger.
- public static void SetServerMessenger(this Composition composition, Func factory)
+ public static void SetServerMessenger(this Composition composition, Func factory)
{
- composition.Container.RegisterSingleton(factory);
+ composition.RegisterUnique(factory);
}
///
@@ -202,7 +247,7 @@ namespace Umbraco.Core.Components
/// A server messenger.
public static void SetServerMessenger(this Composition composition, IServerMessenger registrar)
{
- composition.Container.RegisterSingleton(_ => registrar);
+ composition.RegisterUnique(_ => registrar);
}
///
@@ -213,7 +258,7 @@ namespace Umbraco.Core.Components
public static void SetShortStringHelper(this Composition composition)
where T : IShortStringHelper
{
- composition.Container.RegisterSingleton();
+ composition.RegisterUnique();
}
///
@@ -221,9 +266,9 @@ namespace Umbraco.Core.Components
///
/// The composition.
/// A function creating a short string helper.
- public static void SetShortStringHelper(this Composition composition, Func factory)
+ public static void SetShortStringHelper(this Composition composition, Func factory)
{
- composition.Container.RegisterSingleton(factory);
+ composition.RegisterUnique(factory);
}
///
@@ -233,9 +278,25 @@ namespace Umbraco.Core.Components
/// A short string helper.
public static void SetShortStringHelper(this Composition composition, IShortStringHelper helper)
{
- composition.Container.RegisterSingleton(_ => helper);
+ composition.RegisterUnique(_ => helper);
}
+ ///
+ /// Sets the underlying media filesystem.
+ ///
+ /// A composition.
+ /// A filesystem factory.
+ public static void SetMediaFileSystem(this Composition composition, Func filesystemFactory)
+ => composition.RegisterUniqueFor(filesystemFactory);
+
+ ///
+ /// Sets the underlying media filesystem.
+ ///
+ /// A composition.
+ /// A filesystem factory.
+ public static void SetMediaFileSystem(this Composition composition, Func filesystemFactory)
+ => composition.RegisterUniqueFor(_ => filesystemFactory());
+
#endregion
}
}
diff --git a/src/Umbraco.Core/Components/DisableAttribute.cs b/src/Umbraco.Core/Components/DisableAttribute.cs
new file mode 100644
index 0000000000..f9a7249b89
--- /dev/null
+++ b/src/Umbraco.Core/Components/DisableAttribute.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Indicates that a composer should be disabled.
+ ///
+ ///
+ /// If a type is specified, disables the composer of that type, else disables the composer marked with the attribute.
+ /// This attribute is *not* inherited.
+ /// This attribute applies to classes only, it is not possible to enable/disable interfaces.
+ /// If a composer ends up being both enabled and disabled: attributes marking the composer itself have lower priority
+ /// than attributes on *other* composers, eg if a composer declares itself as disabled it is possible to enable it from
+ /// another composer. Anything else is unspecified.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+ public class DisableAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DisableAttribute()
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DisableAttribute(Type disabledType)
+ {
+ DisabledType = disabledType;
+ }
+
+ ///
+ /// Gets the disabled type, or null if it is the composer marked with the attribute.
+ ///
+ public Type DisabledType { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Components/DisableComponentAttribute.cs b/src/Umbraco.Core/Components/DisableComponentAttribute.cs
deleted file mode 100644
index f7ff71e119..0000000000
--- a/src/Umbraco.Core/Components/DisableComponentAttribute.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-
-namespace Umbraco.Core.Components
-{
- ///
- /// Indicates that a component should be disabled.
- ///
- ///
- /// If a type is specified, disables the component of that type, else disables the component marked with the attribute.
- /// This attribute is *not* inherited.
- /// This attribute applies to classes only, it is not possible to enable/disable interfaces.
- /// If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority
- /// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from
- /// another component. Anything else is unspecified.
- ///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
- public class DisableComponentAttribute : Attribute
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public DisableComponentAttribute()
- { }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public DisableComponentAttribute(Type disabledType)
- {
- DisabledType = disabledType;
- }
-
- ///
- /// Gets the disabled type, or null if it is the component marked with the attribute.
- ///
- public Type DisabledType { get; }
- }
-}
diff --git a/src/Umbraco.Core/Components/EnableAttribute.cs b/src/Umbraco.Core/Components/EnableAttribute.cs
new file mode 100644
index 0000000000..edf3cbdc2e
--- /dev/null
+++ b/src/Umbraco.Core/Components/EnableAttribute.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Indicates that a composer should be enabled.
+ ///
+ ///
+ /// If a type is specified, enables the composer of that type, else enables the composer marked with the attribute.
+ /// This attribute is *not* inherited.
+ /// This attribute applies to classes only, it is not possible to enable/disable interfaces.
+ /// If a composer ends up being both enabled and disabled: attributes marking the composer itself have lower priority
+ /// than attributes on *other* composers, eg if a composer declares itself as disabled it is possible to enable it from
+ /// another composer. Anything else is unspecified.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
+ public class EnableAttribute : Attribute
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EnableAttribute()
+ { }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EnableAttribute(Type enabledType)
+ {
+ EnabledType = enabledType;
+ }
+
+ ///
+ /// Gets the enabled type, or null if it is the composer marked with the attribute.
+ ///
+ public Type EnabledType { get; }
+ }
+}
diff --git a/src/Umbraco.Core/Components/EnableComponentAttribute.cs b/src/Umbraco.Core/Components/EnableComponentAttribute.cs
deleted file mode 100644
index fa76dc2404..0000000000
--- a/src/Umbraco.Core/Components/EnableComponentAttribute.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-
-namespace Umbraco.Core.Components
-{
- ///
- /// Indicates that a component should be enabled.
- ///
- ///
- /// If a type is specified, enables the component of that type, else enables the component marked with the attribute.
- /// This attribute is *not* inherited.
- /// This attribute applies to classes only, it is not possible to enable/disable interfaces.
- /// If a component ends up being both enabled and disabled: attributes marking the component itself have lower priority
- /// than attributes on *other* components, eg if a component declares itself as disabled it is possible to enable it from
- /// another component. Anything else is unspecified.
- ///
- [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
- public class EnableComponentAttribute : Attribute
- {
- ///
- /// Initializes a new instance of the class.
- ///
- public EnableComponentAttribute()
- { }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public EnableComponentAttribute(Type enabledType)
- {
- EnabledType = enabledType;
- }
-
- ///
- /// Gets the enabled type, or null if it is the component marked with the attribute.
- ///
- public Type EnabledType { get; }
- }
-}
diff --git a/src/Umbraco.Core/Components/IComponent.cs b/src/Umbraco.Core/Components/IComponent.cs
new file mode 100644
index 0000000000..b1954d821b
--- /dev/null
+++ b/src/Umbraco.Core/Components/IComponent.cs
@@ -0,0 +1,25 @@
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Represents a component.
+ ///
+ ///
+ /// Components are created by DI and therefore must have a public constructor.
+ /// All components are terminated in reverse order when Umbraco terminates, and
+ /// disposable components are disposed.
+ /// The Dispose method may be invoked more than once, and components
+ /// should ensure they support this.
+ ///
+ public interface IComponent
+ {
+ ///
+ /// Initializes the component.
+ ///
+ void Initialize();
+
+ ///
+ /// Terminates the component.
+ ///
+ void Terminate();
+ }
+}
diff --git a/src/Umbraco.Core/Components/IComposer.cs b/src/Umbraco.Core/Components/IComposer.cs
new file mode 100644
index 0000000000..ce02aa4f13
--- /dev/null
+++ b/src/Umbraco.Core/Components/IComposer.cs
@@ -0,0 +1,16 @@
+using Umbraco.Core.Composing;
+
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Represents a composer.
+ ///
+ public interface IComposer : IDiscoverable
+ {
+ ///
+ /// Compose.
+ ///
+ ///
+ void Compose(Composition composition);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Components/ICoreComposer.cs b/src/Umbraco.Core/Components/ICoreComposer.cs
new file mode 100644
index 0000000000..94aa9953a9
--- /dev/null
+++ b/src/Umbraco.Core/Components/ICoreComposer.cs
@@ -0,0 +1,13 @@
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Represents a core .
+ ///
+ ///
+ /// All core composers are required by (compose before) all user composers,
+ /// and require (compose after) all runtime composers.
+ ///
+ [ComposeAfter(typeof(IRuntimeComposer))]
+ public interface ICoreComposer : IComposer
+ { }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Components/IRuntimeComponent.cs b/src/Umbraco.Core/Components/IRuntimeComponent.cs
deleted file mode 100644
index 7f89d21e87..0000000000
--- a/src/Umbraco.Core/Components/IRuntimeComponent.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Umbraco.Core.Components
-{
- public interface IRuntimeComponent : IUmbracoComponent
- { }
-}
diff --git a/src/Umbraco.Core/Components/IRuntimeComposer.cs b/src/Umbraco.Core/Components/IRuntimeComposer.cs
new file mode 100644
index 0000000000..4b8253ee6c
--- /dev/null
+++ b/src/Umbraco.Core/Components/IRuntimeComposer.cs
@@ -0,0 +1,11 @@
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Represents a runtime .
+ ///
+ ///
+ /// All runtime composers are required by (compose before) all core composers
+ ///
+ public interface IRuntimeComposer : IComposer
+ { }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Components/IUmbracoComponent.cs b/src/Umbraco.Core/Components/IUmbracoComponent.cs
deleted file mode 100644
index d25b97c270..0000000000
--- a/src/Umbraco.Core/Components/IUmbracoComponent.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Umbraco.Core.Composing;
-
-namespace Umbraco.Core.Components
-{
- ///
- /// Represents an Umbraco component.
- ///
- public interface IUmbracoComponent : IDiscoverable
- {
- ///
- /// Composes the component.
- ///
- /// The composition.
- void Compose(Composition composition);
-
- ///
- /// Terminates the component.
- ///
- void Terminate();
- }
-}
diff --git a/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs b/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs
deleted file mode 100644
index 28ff286da3..0000000000
--- a/src/Umbraco.Core/Components/IUmbracoCoreComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Umbraco.Core.Components
-{
- [RequireComponent(typeof(IRuntimeComponent))]
- public interface IUmbracoCoreComponent : IUmbracoComponent
- { }
-}
diff --git a/src/Umbraco.Core/Components/IUmbracoUserComponent.cs b/src/Umbraco.Core/Components/IUmbracoUserComponent.cs
deleted file mode 100644
index 61a07cfd48..0000000000
--- a/src/Umbraco.Core/Components/IUmbracoUserComponent.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Umbraco.Core.Components
-{
- [RequireComponent(typeof(UmbracoCoreComponent))]
- public interface IUmbracoUserComponent : IUmbracoComponent
- { }
-}
diff --git a/src/Umbraco.Core/Components/IUserComposer.cs b/src/Umbraco.Core/Components/IUserComposer.cs
new file mode 100644
index 0000000000..59e0023635
--- /dev/null
+++ b/src/Umbraco.Core/Components/IUserComposer.cs
@@ -0,0 +1,12 @@
+namespace Umbraco.Core.Components
+{
+ ///
+ /// Represents a user .
+ ///
+ ///
+ /// All user composers require (compose after) all core composers.
+ ///
+ [ComposeAfter(typeof(ICoreComposer))]
+ public interface IUserComposer : IComposer
+ { }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs
index 0a8d9ccd52..2420e1d5bb 100644
--- a/src/Umbraco.Core/Components/ManifestWatcherComponent.cs
+++ b/src/Umbraco.Core/Components/ManifestWatcherComponent.cs
@@ -5,16 +5,24 @@ using Umbraco.Core.Manifest;
namespace Umbraco.Core.Components
{
- [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
- public class ManifestWatcherComponent : UmbracoComponentBase, IUmbracoCoreComponent
+ public sealed class ManifestWatcherComponent : IComponent
{
+ private readonly IRuntimeState _runtimeState;
+ private readonly ILogger _logger;
+
// if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for
// package.manifest chances and restarts the application on any change
private ManifestWatcher _mw;
- public void Initialize(IRuntimeState runtime, ILogger logger)
+ public ManifestWatcherComponent(IRuntimeState runtimeState, ILogger logger)
{
- if (runtime.Debug == false) return;
+ _runtimeState = runtimeState;
+ _logger = logger;
+ }
+
+ public void Initialize()
+ {
+ if (_runtimeState.Debug == false) return;
//if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false)
// return;
@@ -22,13 +30,15 @@ namespace Umbraco.Core.Components
var appPlugins = IOHelper.MapPath("~/App_Plugins/");
if (Directory.Exists(appPlugins) == false) return;
- _mw = new ManifestWatcher(logger);
+ _mw = new ManifestWatcher(_logger);
_mw.Start(Directory.GetDirectories(appPlugins));
}
- public override void Terminate()
+ public void Terminate()
{
- _mw?.Dispose();
+ if (_mw == null) return;
+
+ _mw.Dispose();
_mw = null;
}
}
diff --git a/src/Umbraco.Core/Components/ManifestWatcherComposer.cs b/src/Umbraco.Core/Components/ManifestWatcherComposer.cs
new file mode 100644
index 0000000000..b08680156b
--- /dev/null
+++ b/src/Umbraco.Core/Components/ManifestWatcherComposer.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Core.Components
+{
+ [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
+ public class ManifestWatcherComposer : ComponentComposer, ICoreComposer
+ { }
+}
diff --git a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs
index bc66dccd31..404d385680 100644
--- a/src/Umbraco.Core/Components/RelateOnCopyComponent.cs
+++ b/src/Umbraco.Core/Components/RelateOnCopyComponent.cs
@@ -6,14 +6,16 @@ using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Components
{
//TODO: This should just exist in the content service/repo!
- [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
- public sealed class RelateOnCopyComponent : UmbracoComponentBase, IUmbracoCoreComponent
+ public sealed class RelateOnCopyComponent : IComponent
{
public void Initialize()
{
ContentService.Copied += ContentServiceCopied;
}
+ public void Terminate()
+ { }
+
private static void ContentServiceCopied(IContentService sender, Events.CopyEventArgs e)
{
if (e.RelateToOriginal == false) return;
diff --git a/src/Umbraco.Core/Components/RelateOnCopyComposer.cs b/src/Umbraco.Core/Components/RelateOnCopyComposer.cs
new file mode 100644
index 0000000000..f5e9423edd
--- /dev/null
+++ b/src/Umbraco.Core/Components/RelateOnCopyComposer.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Core.Components
+{
+ [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
+ public sealed class RelateOnCopyComposer : ComponentComposer, ICoreComposer
+ { }
+}
diff --git a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs
index 8bcce50c68..6279bb98ba 100644
--- a/src/Umbraco.Core/Components/RelateOnTrashComponent.cs
+++ b/src/Umbraco.Core/Components/RelateOnTrashComponent.cs
@@ -7,8 +7,7 @@ using Umbraco.Core.Services.Implement;
namespace Umbraco.Core.Components
{
- [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
- public sealed class RelateOnTrashComponent : UmbracoComponentBase, IUmbracoCoreComponent
+ public sealed class RelateOnTrashComponent : IComponent
{
public void Initialize()
{
@@ -18,6 +17,9 @@ namespace Umbraco.Core.Components
MediaService.Trashed += MediaService_Trashed;
}
+ public void Terminate()
+ { }
+
private static void ContentService_Moved(IContentService sender, MoveEventArgs e)
{
foreach (var item in e.MoveInfoCollection.Where(x => x.OriginalPath.Contains(Constants.System.RecycleBinContent.ToInvariantString())))
diff --git a/src/Umbraco.Core/Components/RelateOnTrashComposer.cs b/src/Umbraco.Core/Components/RelateOnTrashComposer.cs
new file mode 100644
index 0000000000..5d89bc0e37
--- /dev/null
+++ b/src/Umbraco.Core/Components/RelateOnTrashComposer.cs
@@ -0,0 +1,6 @@
+namespace Umbraco.Core.Components
+{
+ [RuntimeLevel(MinLevel = RuntimeLevel.Run)]
+ public sealed class RelateOnTrashComposer : ComponentComposer, ICoreComposer
+ { }
+}
diff --git a/src/Umbraco.Core/Components/RequireComponentAttribute.cs b/src/Umbraco.Core/Components/RequireComponentAttribute.cs
deleted file mode 100644
index 2d53413f99..0000000000
--- a/src/Umbraco.Core/Components/RequireComponentAttribute.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-
-namespace Umbraco.Core.Components
-{
- ///
- /// Indicates that a component requires another component.
- ///
- ///
- /// This attribute is *not* inherited. This means that a component class inheriting from
- /// another component class does *not* inherit its requirements. However, the bootloader checks
- /// the *interfaces* of every component for their requirements, so requirements declared on
- /// interfaces are inherited by every component class implementing the interface.
- /// When targetting a class, indicates a dependency on the component which must be enabled,
- /// unless the requirement has explicitely been declared as weak (and then, only if the component
- /// is enabled).
- /// When targetting an interface, indicates a dependency on enabled components implementing
- /// the interface. It could be no component at all, unless the requirement has explicitely been
- /// declared as strong (and at least one component must be enabled).
- ///
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
- public class RequireComponentAttribute : Attribute
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The type of the required component.
- public RequireComponentAttribute(Type requiredType)
- {
- if (typeof(IUmbracoComponent).IsAssignableFrom(requiredType) == false)
- throw new ArgumentException($"Type {requiredType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}.");
- RequiredType = requiredType;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The type of the required component.
- /// A value indicating whether the requirement is weak.
- public RequireComponentAttribute(Type requiredType, bool weak)
- : this(requiredType)
- {
- Weak = weak;
- }
-
- ///
- /// Gets the required type.
- ///
- public Type RequiredType { get; }
-
- ///
- /// Gets a value indicating whether the requirement is weak.
- ///
- /// Returns true if the requirement is weak (requires the other component if it
- /// is enabled), false if the requirement is strong (requires the other component to be
- /// enabled), and null if unspecified, in which case it is strong for classes and weak for
- /// interfaces.
- public bool? Weak { get; }
- }
-}
diff --git a/src/Umbraco.Core/Components/RequiredComponentAttribute.cs b/src/Umbraco.Core/Components/RequiredComponentAttribute.cs
deleted file mode 100644
index 7895445179..0000000000
--- a/src/Umbraco.Core/Components/RequiredComponentAttribute.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System;
-
-namespace Umbraco.Core.Components
-{
- ///
- /// Indicates that a component is required by another component.
- ///
- ///
- /// fixme
- /// This attribute is *not* inherited. This means that a component class inheriting from
- /// another component class does *not* inherit its requirements. However, the bootloader checks
- /// the *interfaces* of every component for their requirements, so requirements declared on
- /// interfaces are inherited by every component class implementing the interface.
- /// When targetting a class, indicates a dependency on the component which must be enabled,
- /// unless the requirement has explicitely been declared as weak (and then, only if the component
- /// is enabled).
- /// When targetting an interface, indicates a dependency on enabled components implementing
- /// the interface. It could be no component at all, unless the requirement has explicitely been
- /// declared as strong (and at least one component must be enabled).
- ///
-
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = false)]
- public class RequiredComponentAttribute : Attribute
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// The type of the required component.
- public RequiredComponentAttribute(Type requiringType)
- {
- if (typeof(IUmbracoComponent).IsAssignableFrom(requiringType) == false)
- throw new ArgumentException($"Type {requiringType.FullName} is invalid here because it does not implement {typeof(IUmbracoComponent).FullName}.");
- RequiringType = requiringType;
- }
-
- ///
- /// Gets the required type.
- ///
- public Type RequiringType { get; }
- }
-}
diff --git a/src/Umbraco.Core/Components/UmbracoComponentBase.cs b/src/Umbraco.Core/Components/UmbracoComponentBase.cs
deleted file mode 100644
index 476c4c6d59..0000000000
--- a/src/Umbraco.Core/Components/UmbracoComponentBase.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace Umbraco.Core.Components
-{
- ///
- /// Provides a base class for implementations.
- ///
- public abstract class UmbracoComponentBase : IUmbracoComponent
- {
- ///
- public virtual void Compose(Composition composition)
- { }
-
- ///
- public virtual void Terminate()
- { }
- }
-}
diff --git a/src/Umbraco.Core/Components/UmbracoCoreComponent.cs b/src/Umbraco.Core/Components/UmbracoCoreComponent.cs
deleted file mode 100644
index 9f6709c494..0000000000
--- a/src/Umbraco.Core/Components/UmbracoCoreComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Umbraco.Core.Components
-{
- // the UmbracoCoreComponent requires all IUmbracoCoreComponent
- // all user-level components should require the UmbracoCoreComponent
-
- [RequireComponent(typeof(IUmbracoCoreComponent))]
- public class UmbracoCoreComponent : UmbracoComponentBase
- { }
-}
diff --git a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
index 3fac2d3255..41038ea4e9 100644
--- a/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
+++ b/src/Umbraco.Core/Composing/CollectionBuilderBase.cs
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Linq.Expressions;
-using LightInject;
namespace Umbraco.Core.Composing
{
@@ -14,69 +12,34 @@ namespace Umbraco.Core.Composing
/// The type of the items.
public abstract class CollectionBuilderBase : ICollectionBuilder
where TBuilder: CollectionBuilderBase
- where TCollection : IBuilderCollection
+ where TCollection : class, IBuilderCollection
{
private readonly List _types = new List();
private readonly object _locker = new object();
- private Func, TCollection> _collectionCtor;
- private ServiceRegistration[] _registrations;
-
- ///
- /// Initializes a new instance of the
- /// class with a service container.
- ///
- /// A service container.
- protected CollectionBuilderBase(IServiceContainer container)
- {
- Container = container;
- // ReSharper disable once DoNotCallOverridableMethodsInConstructor
- Initialize();
- }
-
- ///
- /// Gets the service container.
- ///
- protected IServiceContainer Container { get; }
+ private Type[] _registeredTypes;
///
/// Gets the internal list of types as an IEnumerable (immutable).
///
public IEnumerable GetTypes() => _types;
- ///
- /// Initializes a new instance of the builder.
- ///
- /// This is called by the constructor and, by default, registers the
- /// collection automatically.
- protected virtual void Initialize()
+ ///
+ public virtual void RegisterWith(IRegister register)
{
- // compile the auto-collection constructor
- var argType = typeof(IEnumerable);
- var ctorArgTypes = new[] { argType };
- var constructor = typeof(TCollection).GetConstructor(ctorArgTypes);
- if (constructor != null)
- {
- var exprArg = Expression.Parameter(argType, "items");
- var exprNew = Expression.New(constructor, exprArg);
- var expr = Expression.Lambda, TCollection>>(exprNew, exprArg);
- _collectionCtor = expr.Compile();
- }
- // else _collectionCtor remains null, assuming CreateCollection has been overriden
-
- // we just don't want to support re-registering collections here
- var registration = Container.GetAvailableService();
- if (registration != null)
- throw new InvalidOperationException("Collection builders cannot be registered once the collection itself has been registered.");
+ if (_registeredTypes != null)
+ throw new InvalidOperationException("This builder has already been registered.");
// register the collection
- Container.Register(_ => CreateCollection(), CollectionLifetime);
+ register.Register(CreateCollection, CollectionLifetime);
+
+ // register the types
+ RegisterTypes(register);
}
///
/// Gets the collection lifetime.
///
- /// Return null for transient collections.
- protected virtual ILifetime CollectionLifetime => new PerContainerLifetime();
+ protected virtual Lifetime CollectionLifetime => Lifetime.Singleton;
///
/// Configures the internal list of types.
@@ -87,8 +50,8 @@ namespace Umbraco.Core.Composing
{
lock (_locker)
{
- if (_registrations != null)
- throw new InvalidOperationException("Cannot configure a collection builder after its types have been resolved.");
+ if (_registeredTypes != null)
+ throw new InvalidOperationException("Cannot configure a collection builder after it has been registered.");
action(_types);
}
}
@@ -104,55 +67,54 @@ namespace Umbraco.Core.Composing
return types;
}
- private void RegisterTypes()
+ private void RegisterTypes(IRegister register)
{
lock (_locker)
{
- if (_registrations != null) return;
+ if (_registeredTypes != null) return;
var types = GetRegisteringTypes(_types).ToArray();
+
+ // ensure they are safe
foreach (var type in types)
EnsureType(type, "register");
- var prefix = GetType().FullName + "_";
- var i = 0;
+ // register them
foreach (var type in types)
- {
- var name = $"{prefix}{i++:00000}";
- Container.Register(typeof(TItem), type, name);
- }
+ register.Register(type);
- _registrations = Container.AvailableServices
- .Where(x => x.ServiceName.StartsWith(prefix))
- .OrderBy(x => x.ServiceName)
- .ToArray();
+ _registeredTypes = types;
}
}
///
/// Creates the collection items.
///
- /// The arguments.
/// The collection items.
- protected virtual IEnumerable CreateItems(params object[] args)
+ protected virtual IEnumerable CreateItems(IFactory factory)
{
- RegisterTypes(); // will do it only once
+ if (_registeredTypes == null)
+ throw new InvalidOperationException("Cannot create items before the collection builder has been registered.");
- var type = typeof (TItem);
- return _registrations
- .Select(x => (TItem) Container.GetInstanceOrThrow(type, x.ServiceName, x.ImplementingType, args))
+ return _registeredTypes // respect order
+ .Select(x => CreateItem(factory, x))
.ToArray(); // safe
}
+ ///
+ /// Creates a collection item.
+ ///
+ protected virtual TItem CreateItem(IFactory factory, Type itemType)
+ => (TItem) factory.GetInstance(itemType);
+
///
/// Creates a collection.
///
/// A collection.
/// Creates a new collection each time it is invoked.
- public virtual TCollection CreateCollection()
+ public virtual TCollection CreateCollection(IFactory factory)
{
- if (_collectionCtor == null) throw new InvalidOperationException("Collection auto-creation is not possible.");
- return _collectionCtor(CreateItems());
+ return factory.CreateInstance(CreateItems(factory));
}
protected Type EnsureType(Type type, string action)
diff --git a/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs b/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs
new file mode 100644
index 0000000000..ca86f623cc
--- /dev/null
+++ b/src/Umbraco.Core/Composing/Composers/ConfigurationComposer.cs
@@ -0,0 +1,25 @@
+using Umbraco.Core.Components;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Configuration.UmbracoSettings;
+
+namespace Umbraco.Core.Composing.Composers
+{
+ ///
+ /// Compose configurations.
+ ///
+ public static class ConfigurationComposer
+ {
+ public static Composition ComposeConfiguration(this Composition composition)
+ {
+ // common configurations are already registered
+ // register others
+
+ composition.RegisterUnique(factory => factory.GetInstance().Content);
+ composition.RegisterUnique(factory => factory.GetInstance().Templates);
+ composition.RegisterUnique(factory => factory.GetInstance().RequestHandler);
+ composition.RegisterUnique(factory => factory.GetInstance().Security);
+
+ return composition;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs b/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs
new file mode 100644
index 0000000000..0274b8f1a9
--- /dev/null
+++ b/src/Umbraco.Core/Composing/Composers/CoreMappingProfilesComposer.cs
@@ -0,0 +1,16 @@
+using AutoMapper;
+using Umbraco.Core.Components;
+using Umbraco.Core.Models.Identity;
+
+namespace Umbraco.Core.Composing.Composers
+
+{
+ public static class CoreMappingProfilesComposer
+ {
+ public static Composition ComposeCoreMappingProfiles(this Composition composition)
+ {
+ composition.Register();
+ return composition;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs
new file mode 100644
index 0000000000..4c598f27e4
--- /dev/null
+++ b/src/Umbraco.Core/Composing/Composers/FileSystemsComposer.cs
@@ -0,0 +1,98 @@
+using Umbraco.Core.Components;
+using Umbraco.Core.IO;
+using Umbraco.Core.IO.MediaPathSchemes;
+
+namespace Umbraco.Core.Composing.Composers
+{
+ public static class FileSystemsComposer
+ {
+ /*
+ * HOW TO REPLACE THE MEDIA UNDERLYING FILESYSTEM
+ * ----------------------------------------------
+ *
+ * Create a component and use it to modify the composition by adding something like:
+ *
+ * composition.RegisterUniqueFor(...);
+ *
+ * and register whatever supporting filesystem you like.
+ *
+ *
+ * HOW TO IMPLEMENT MY OWN FILESYSTEM
+ * ----------------------------------
+ *
+ * Create your filesystem class:
+ *
+ * public class MyFileSystem : FileSystemWrapper
+ * {
+ * public MyFileSystem(IFileSystem innerFileSystem)
+ * : base(innerFileSystem)
+ * { }
+ * }
+ *
+ * The ctor can have more parameters, that will be resolved by the container.
+ *
+ * Register your filesystem, in a component:
+ *
+ * composition.RegisterFileSystem();
+ *
+ * Register the underlying filesystem:
+ *
+ * composition.RegisterUniqueFor(...);
+ *
+ * And that's it, you can inject MyFileSystem wherever it's needed.
+ *
+ *
+ * You can also declare a filesystem interface:
+ *
+ * public interface IMyFileSystem : IFileSystem
+ * { }
+ *
+ * Make the class implement the interface, then
+ * register your filesystem, in a component:
+ *
+ * composition.RegisterFileSystem();
+ * composition.RegisterUniqueFor(...);
+ *
+ * And that's it, you can inject IMyFileSystem wherever it's needed.
+ *
+ *
+ * WHAT IS SHADOWING
+ * -----------------
+ *
+ * Shadowing is the technology used for Deploy to implement some sort of
+ * transaction-management on top of filesystems. The plumbing explained above,
+ * compared to creating your own physical filesystem, ensures that your filesystem
+ * would participate into such transactions.
+ *
+ *
+ */
+
+ public static Composition ComposeFileSystems(this Composition composition)
+ {
+ // register FileSystems, which manages all filesystems
+ // it needs to be registered (not only the interface) because it provides additional
+ // functionality eg for scoping, and is injected in the scope provider - whereas the
+ // interface is really for end-users to get access to filesystems.
+ composition.RegisterUnique(factory => factory.CreateInstance(factory));
+
+ // register IFileSystems, which gives access too all filesystems
+ composition.RegisterUnique(factory => factory.GetInstance());
+
+ // register the scheme for media paths
+ composition.RegisterUnique();
+
+ // register the IMediaFileSystem implementation
+ composition.RegisterFileSystem();
+
+ // register the supporting filesystems provider
+ composition.Register(factory => new SupportingFileSystems(factory), Lifetime.Singleton);
+
+ // register the IFileSystem supporting the IMediaFileSystem
+ // THIS IS THE ONLY THING THAT NEEDS TO CHANGE, IN ORDER TO REPLACE THE UNDERLYING FILESYSTEM
+ // and, SupportingFileSystem.For() returns the underlying filesystem
+ composition.SetMediaFileSystem(() => new PhysicalFileSystem("~/media"));
+
+ return composition;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs
new file mode 100644
index 0000000000..62b92081c1
--- /dev/null
+++ b/src/Umbraco.Core/Composing/Composers/RepositoriesComposer.cs
@@ -0,0 +1,54 @@
+using Umbraco.Core.Components;
+using Umbraco.Core.Persistence.Repositories;
+using Umbraco.Core.Persistence.Repositories.Implement;
+
+namespace Umbraco.Core.Composing.Composers
+{
+ ///
+ /// Composes repositories.
+ ///
+ public static class RepositoriesComposer
+ {
+ public static Composition ComposeRepositories(this Composition composition)
+ {
+ // repositories
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+
+ return composition;
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
new file mode 100644
index 0000000000..1b77aaa7d6
--- /dev/null
+++ b/src/Umbraco.Core/Composing/Composers/ServicesComposer.cs
@@ -0,0 +1,96 @@
+using System;
+using System.IO;
+using System.Linq;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Components;
+using Umbraco.Core.Events;
+using Umbraco.Core.IO;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Services;
+using Umbraco.Core.Services.Implement;
+
+namespace Umbraco.Core.Composing.Composers
+{
+ public static class ServicesComposer
+ {
+ public static Composition ComposeServices(this Composition composition)
+ {
+ // register a transient messages factory, which will be replaced by the web
+ // boot manager when running in a web context
+ composition.RegisterUnique();
+
+ // register the service context
+ composition.RegisterUnique();
+
+ // register the special idk map
+ composition.RegisterUnique();
+
+ // register the services
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+ composition.Register(SourcesFactory);
+ composition.RegisterUnique(factory => new LocalizedTextService(
+ factory.GetInstance>(),
+ factory.GetInstance()));
+
+ //TODO: These are replaced in the web project - we need to declare them so that
+ // something is wired up, just not sure this is very nice but will work for now.
+ composition.RegisterUnique();
+ composition.RegisterUnique();
+
+ return composition;
+ }
+
+ private static LocalizedTextServiceFileSources SourcesFactory(IFactory container)
+ {
+ var mainLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Umbraco + "/config/lang/"));
+ var appPlugins = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins));
+ var configLangFolder = new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config + "/lang/"));
+
+ var pluginLangFolders = appPlugins.Exists == false
+ ? Enumerable.Empty()
+ : appPlugins.GetDirectories()
+ .SelectMany(x => x.GetDirectories("Lang"))
+ .SelectMany(x => x.GetFiles("*.xml", SearchOption.TopDirectoryOnly))
+ .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 5)
+ .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, false));
+
+ //user defined langs that overwrite the default, these should not be used by plugin creators
+ var userLangFolders = configLangFolder.Exists == false
+ ? Enumerable.Empty()
+ : configLangFolder
+ .GetFiles("*.user.xml", SearchOption.TopDirectoryOnly)
+ .Where(x => Path.GetFileNameWithoutExtension(x.FullName).Length == 10)
+ .Select(x => new LocalizedTextServiceSupplementaryFileSource(x, true));
+
+ return new LocalizedTextServiceFileSources(
+ container.GetInstance(),
+ container.GetInstance().RuntimeCache,
+ mainLangFolder,
+ pluginLangFolders.Concat(userLangFolders));
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Composing/CompositionExtensions.cs b/src/Umbraco.Core/Composing/CompositionExtensions.cs
new file mode 100644
index 0000000000..2307d757c9
--- /dev/null
+++ b/src/Umbraco.Core/Composing/CompositionExtensions.cs
@@ -0,0 +1,69 @@
+using Umbraco.Core.Cache;
+using Umbraco.Core.Components;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Persistence;
+
+namespace Umbraco.Core.Composing
+{
+ ///
+ /// Provides extension methods to the class.
+ ///
+ public static class CompositionExtensions
+ {
+ #region Essentials
+
+ ///
+ /// Registers essential services.
+ ///
+ public static void RegisterEssentials(this Composition composition,
+ ILogger logger, IProfiler profiler, IProfilingLogger profilingLogger,
+ IMainDom mainDom,
+ CacheHelper appCaches,
+ IUmbracoDatabaseFactory databaseFactory,
+ TypeLoader typeLoader,
+ IRuntimeState state)
+ {
+ composition.RegisterUnique(logger);
+ composition.RegisterUnique(profiler);
+ composition.RegisterUnique(profilingLogger);
+ composition.RegisterUnique(mainDom);
+ composition.RegisterUnique(appCaches);
+ composition.RegisterUnique(factory => factory.GetInstance().RuntimeCache);
+ composition.RegisterUnique(databaseFactory);
+ composition.RegisterUnique(factory => factory.GetInstance().SqlContext);
+ composition.RegisterUnique(typeLoader);
+ composition.RegisterUnique(state);
+ }
+
+ #endregion
+
+ #region Unique
+
+ ///
+ /// Registers a unique service as its own implementation.
+ ///
+ public static void RegisterUnique(this Composition composition)
+ => composition.RegisterUnique(typeof(TService), typeof(TService));
+
+ ///
+ /// Registers a unique service with an implementation type.
+ ///
+ public static void RegisterUnique(this Composition composition)
+ => composition.RegisterUnique(typeof(TService), typeof(TImplementing));
+
+ ///
+ /// Registers a unique service with an implementation type, for a target.
+ ///
+ public static void RegisterUniqueFor(this Composition composition)
+ where TService : class
+ => composition.RegisterUniqueFor(typeof(TImplementing));
+
+ ///
+ /// Registers a unique service with an implementing instance.
+ ///
+ public static void RegisterUnique(this Composition composition, TService instance)
+ => composition.RegisterUnique(typeof(TService), instance);
+
+ #endregion
+ }
+}
diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs
deleted file mode 100644
index 82912163b6..0000000000
--- a/src/Umbraco.Core/Composing/CompositionRoots/ConfigurationCompositionRoot.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using LightInject;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Configuration.UmbracoSettings;
-
-namespace Umbraco.Core.Composing.CompositionRoots
-{
- ///
- /// Sets up IoC container for Umbraco configuration classes
- ///
- public sealed class ConfigurationCompositionRoot : ICompositionRoot
- {
- public void Compose(IServiceRegistry container)
- {
- container.Register(factory => UmbracoConfig.For.UmbracoSettings());
- container.Register(factory => factory.GetInstance().Content);
- container.Register(factory => factory.GetInstance().Templates);
- container.Register(factory => factory.GetInstance().RequestHandler);
- container.Register(factory => UmbracoConfig.For.GlobalSettings());
-
- // fixme - other sections we need to add?
- }
- }
-}
diff --git a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs
deleted file mode 100644
index 6b55a4af7e..0000000000
--- a/src/Umbraco.Core/Composing/CompositionRoots/CoreMappingProfilesCompositionRoot.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using LightInject;
-using Umbraco.Core.Models.Identity;
-
-namespace Umbraco.Core.Composing.CompositionRoots
-{
- public sealed class CoreMappingProfilesCompositionRoot : ICompositionRoot
- {
- public void Compose(IServiceRegistry container)
- {
- container.Register();
- }
- }
-}
diff --git a/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs
deleted file mode 100644
index 9c36bf5cec..0000000000
--- a/src/Umbraco.Core/Composing/CompositionRoots/RepositoryCompositionRoot.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using LightInject;
-using Umbraco.Core.Cache;
-using Umbraco.Core.Persistence.Repositories;
-using Umbraco.Core.Persistence.Repositories.Implement;
-
-namespace Umbraco.Core.Composing.CompositionRoots
-{
- ///
- /// Sets the IoC container for the umbraco data layer/repositories/sql/database/etc...
- ///
- public sealed class RepositoryCompositionRoot : ICompositionRoot
- {
- public const string DisabledCache = "DisabledCache";
-
- public void Compose(IServiceRegistry container)
- {
- // register cache helpers
- // the main cache helper is registered by CoreBootManager and is used by most repositories
- // the disabled one is used by those repositories that have an annotated ctor parameter
- container.RegisterSingleton(factory => CacheHelper.CreateDisabledCacheHelper(), DisabledCache);
-
- // resolve ctor dependency from GetInstance() runtimeArguments, if possible - 'factory' is
- // the container, 'info' describes the ctor argument, and 'args' contains the args that
- // were passed to GetInstance() - use first arg if it is the right type,
- //
- // for ...
- //container.RegisterConstructorDependency((factory, info, args) =>
- //{
- // if (info.Member.DeclaringType != typeof(EntityContainerRepository)) return default;
- // return args.Length > 0 && args[0] is Guid guid ? guid : default;
- //});
-
- // register repositories
- // repos depend on various things,
- // some repositories have an annotated ctor parameter to pick the right cache helper
-
- // repositories
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
-
- // repositories that depend on a filesystem
- // these have an annotated ctor parameter to pick the right file system
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- }
- }
-}
diff --git a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs b/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs
deleted file mode 100644
index 92b8139a04..0000000000
--- a/src/Umbraco.Core/Composing/CompositionRoots/ServicesCompositionRoot.cs
+++ /dev/null
@@ -1,92 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using LightInject;
-using Umbraco.Core.Cache;
-using Umbraco.Core.Events;
-using Umbraco.Core.IO;
-using Umbraco.Core.Logging;
-using Umbraco.Core.Services;
-using Umbraco.Core.Services.Implement;
-
-namespace Umbraco.Core.Composing.CompositionRoots
-{
- public sealed class ServicesCompositionRoot : ICompositionRoot
- {
- public void Compose(IServiceRegistry container)
- {
- // register a transient messages factory, which will be replaced by the web
- // boot manager when running in a web context
- container.RegisterSingleton();
-
- // register the service context
- container.RegisterSingleton();
-
- // register the special idk map
- container.RegisterSingleton();
-
- // register the services
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton();
- container.RegisterSingleton