diff --git a/src/Umbraco.Core/Services/IRuntimeState.cs b/src/Umbraco.Core/Services/IRuntimeState.cs
index 4b57908ea7..fd817f1986 100644
--- a/src/Umbraco.Core/Services/IRuntimeState.cs
+++ b/src/Umbraco.Core/Services/IRuntimeState.cs
@@ -50,5 +50,10 @@ namespace Umbraco.Core
///
BootFailedException BootFailedException { get; }
+ ///
+ /// Determines the runtime level.
+ ///
+ void DetermineRuntimeLevel();
+
}
}
diff --git a/src/Umbraco.Core/SimpleMainDom.cs b/src/Umbraco.Core/SimpleMainDom.cs
index e6bdda67d7..a2ef0b8d78 100644
--- a/src/Umbraco.Core/SimpleMainDom.cs
+++ b/src/Umbraco.Core/SimpleMainDom.cs
@@ -8,11 +8,12 @@ namespace Umbraco.Core
///
/// Provides a simple implementation of .
///
- public class SimpleMainDom : IMainDom
+ public class SimpleMainDom : IMainDom, IDisposable
{
private readonly object _locko = new object();
private readonly List> _callbacks = new List>();
private bool _isStopping;
+ private bool _disposedValue;
///
public bool IsMainDom { get; private set; } = true;
@@ -59,5 +60,24 @@ namespace Umbraco.Core
IsMainDom = false;
}
}
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ Stop();
+ }
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
}
}
diff --git a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs
index b7d9cde9d1..b1254a4beb 100644
--- a/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs
+++ b/src/Umbraco.Examine.Lucene/ExamineLuceneComposer.cs
@@ -20,6 +20,7 @@ namespace Umbraco.Examine
composition.RegisterUnique();
composition.RegisterUnique();
composition.RegisterUnique();
+ composition.RegisterUnique();
}
}
}
diff --git a/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs
new file mode 100644
index 0000000000..f4946d491e
--- /dev/null
+++ b/src/Umbraco.Examine.Lucene/ILuceneDirectoryFactory.cs
@@ -0,0 +1,7 @@
+namespace Umbraco.Examine
+{
+ public interface ILuceneDirectoryFactory
+ {
+ Lucene.Net.Store.Directory CreateDirectory(string indexName);
+ }
+}
diff --git a/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs
new file mode 100644
index 0000000000..8f3bf8bf69
--- /dev/null
+++ b/src/Umbraco.Examine.Lucene/LuceneFileSystemDirectoryFactory.cs
@@ -0,0 +1,69 @@
+using Umbraco.Core.Configuration;
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Hosting;
+using Lucene.Net.Store;
+using System.IO;
+using System;
+using Examine.LuceneEngine.Directories;
+
+namespace Umbraco.Examine
+{
+
+ public class LuceneFileSystemDirectoryFactory : ILuceneDirectoryFactory
+ {
+ private readonly ITypeFinder _typeFinder;
+ private readonly IHostingEnvironment _hostingEnvironment;
+ private readonly IIndexCreatorSettings _settings;
+
+ public LuceneFileSystemDirectoryFactory(ITypeFinder typeFinder, IHostingEnvironment hostingEnvironment, IIndexCreatorSettings settings)
+ {
+ _typeFinder = typeFinder;
+ _hostingEnvironment = hostingEnvironment;
+ _settings = settings;
+ }
+
+ public Lucene.Net.Store.Directory CreateDirectory(string indexName) => CreateFileSystemLuceneDirectory(indexName);
+
+ ///
+ /// Creates a file system based Lucene with the correct locking guidelines for Umbraco
+ ///
+ ///
+ /// The folder name to store the index (single word, not a fully qualified folder) (i.e. Internal)
+ ///
+ ///
+ public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName)
+ {
+
+ var dirInfo = new DirectoryInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData), "ExamineIndexes", folderName));
+ if (!dirInfo.Exists)
+ System.IO.Directory.CreateDirectory(dirInfo.FullName);
+
+ //check if there's a configured directory factory, if so create it and use that to create the lucene dir
+ var configuredDirectoryFactory = _settings.LuceneDirectoryFactory;
+
+ if (!configuredDirectoryFactory.IsNullOrWhiteSpace())
+ {
+ //this should be a fully qualified type
+ var factoryType = _typeFinder.GetTypeByName(configuredDirectoryFactory);
+ if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory);
+ var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType);
+ return directoryFactory.CreateDirectory(dirInfo);
+ }
+
+ //no dir factory, just create a normal fs directory
+
+ var luceneDir = new SimpleFSDirectory(dirInfo);
+
+ //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain
+ //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock
+ //which simply checks the existence of the lock file
+ // The full syntax of this is: new NoPrefixSimpleFsLockFactory(dirInfo)
+ // however, we are setting the DefaultLockFactory in startup so we'll use that instead since it can be managed globally.
+ luceneDir.SetLockFactory(DirectoryFactory.DefaultLockFactory(dirInfo));
+ return luceneDir;
+
+
+ }
+ }
+}
diff --git a/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs b/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs
index 9efcdc4891..fe35e9ca0f 100644
--- a/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs
+++ b/src/Umbraco.Examine.Lucene/LuceneIndexCreator.cs
@@ -29,47 +29,6 @@ namespace Umbraco.Examine
_settings = settings;
}
- public abstract IEnumerable Create();
-
- ///
- /// Creates a file system based Lucene with the correct locking guidelines for Umbraco
- ///
- ///
- /// The folder name to store the index (single word, not a fully qualified folder) (i.e. Internal)
- ///
- ///
- public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName)
- {
-
- var dirInfo = new DirectoryInfo(Path.Combine(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.TempData), "ExamineIndexes", folderName));
- if (!dirInfo.Exists)
- System.IO.Directory.CreateDirectory(dirInfo.FullName);
-
- //check if there's a configured directory factory, if so create it and use that to create the lucene dir
- var configuredDirectoryFactory = _settings.LuceneDirectoryFactory;
-
- if (!configuredDirectoryFactory.IsNullOrWhiteSpace())
- {
- //this should be a fully qualified type
- var factoryType = _typeFinder.GetTypeByName(configuredDirectoryFactory);
- if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory);
- var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType);
- return directoryFactory.CreateDirectory(dirInfo);
- }
-
- //no dir factory, just create a normal fs directory
-
- var luceneDir = new SimpleFSDirectory(dirInfo);
-
- //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain
- //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock
- //which simply checks the existence of the lock file
- // The full syntax of this is: new NoPrefixSimpleFsLockFactory(dirInfo)
- // however, we are setting the DefaultLockFactory in startup so we'll use that instead since it can be managed globally.
- luceneDir.SetLockFactory(DirectoryFactory.DefaultLockFactory(dirInfo));
- return luceneDir;
-
-
- }
+ public abstract IEnumerable Create();
}
}
diff --git a/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs b/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs
new file mode 100644
index 0000000000..327328390b
--- /dev/null
+++ b/src/Umbraco.Examine.Lucene/LuceneRAMDirectoryFactory.cs
@@ -0,0 +1,24 @@
+using Lucene.Net.Store;
+using System;
+
+namespace Umbraco.Examine
+{
+ public class LuceneRAMDirectoryFactory : ILuceneDirectoryFactory
+ {
+ public LuceneRAMDirectoryFactory()
+ {
+
+ }
+
+ public Lucene.Net.Store.Directory CreateDirectory(string indexName) => new RandomIdRAMDirectory();
+
+ private class RandomIdRAMDirectory : RAMDirectory
+ {
+ private readonly string _lockId = Guid.NewGuid().ToString();
+ public override string GetLockId()
+ {
+ return _lockId;
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs b/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs
index 3cf7d6d386..4bb14a3a08 100644
--- a/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs
+++ b/src/Umbraco.Examine.Lucene/UmbracoIndexesCreator.cs
@@ -12,6 +12,7 @@ using Umbraco.Core.IO;
namespace Umbraco.Examine
{
+
///
/// Creates the indexes used by Umbraco
///
@@ -28,7 +29,8 @@ namespace Umbraco.Examine
IUmbracoIndexConfig umbracoIndexConfig,
IHostingEnvironment hostingEnvironment,
IRuntimeState runtimeState,
- IIndexCreatorSettings settings) : base(typeFinder, hostingEnvironment, settings)
+ IIndexCreatorSettings settings,
+ ILuceneDirectoryFactory directoryFactory) : base(typeFinder, hostingEnvironment, settings)
{
ProfilingLogger = profilingLogger ?? throw new System.ArgumentNullException(nameof(profilingLogger));
LanguageService = languageService ?? throw new System.ArgumentNullException(nameof(languageService));
@@ -37,11 +39,13 @@ namespace Umbraco.Examine
UmbracoIndexConfig = umbracoIndexConfig;
HostingEnvironment = hostingEnvironment ?? throw new System.ArgumentNullException(nameof(hostingEnvironment));
RuntimeState = runtimeState ?? throw new System.ArgumentNullException(nameof(runtimeState));
+ DirectoryFactory = directoryFactory;
}
protected IProfilingLogger ProfilingLogger { get; }
protected IHostingEnvironment HostingEnvironment { get; }
protected IRuntimeState RuntimeState { get; }
+ protected ILuceneDirectoryFactory DirectoryFactory { get; }
protected ILocalizationService LanguageService { get; }
protected IPublicAccessService PublicAccessService { get; }
protected IMemberService MemberService { get; }
@@ -65,7 +69,7 @@ namespace Umbraco.Examine
{
var index = new UmbracoContentIndex(
Constants.UmbracoIndexes.InternalIndexName,
- CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.InternalIndexPath),
+ DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.InternalIndexPath),
new UmbracoFieldDefinitionCollection(),
new CultureInvariantWhitespaceAnalyzer(),
ProfilingLogger,
@@ -81,7 +85,7 @@ namespace Umbraco.Examine
{
var index = new UmbracoContentIndex(
Constants.UmbracoIndexes.ExternalIndexName,
- CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.ExternalIndexPath),
+ DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.ExternalIndexPath),
new UmbracoFieldDefinitionCollection(),
new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30),
ProfilingLogger,
@@ -97,7 +101,7 @@ namespace Umbraco.Examine
var index = new UmbracoMemberIndex(
Constants.UmbracoIndexes.MembersIndexName,
new UmbracoFieldDefinitionCollection(),
- CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.MembersIndexPath),
+ DirectoryFactory.CreateDirectory(Constants.UmbracoIndexes.MembersIndexPath),
new CultureInvariantWhitespaceAnalyzer(),
ProfilingLogger,
HostingEnvironment,
diff --git a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs
index 88ae80bf8b..b2ded07034 100644
--- a/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs
+++ b/src/Umbraco.Infrastructure/CompositionExtensions_Essentials.cs
@@ -31,6 +31,7 @@ namespace Umbraco.Core
TypeLoader typeLoader,
IRuntimeState state,
ITypeFinder typeFinder,
+
IIOHelper ioHelper,
IUmbracoVersion umbracoVersion,
IDbProviderFactoryCreator dbProviderFactoryCreator,
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
index 2f9766e5c2..ff86f87569 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
@@ -5,6 +5,7 @@ using System.IO;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
+using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
@@ -23,7 +24,8 @@ namespace Umbraco.Core.Runtime
{
private ComponentCollection _components;
private IFactory _factory;
- private readonly RuntimeState _state;
+ // runtime state, this instance will get replaced again once the essential services are available to run the check
+ private RuntimeState _state = RuntimeState.Booting();
private readonly IUmbracoBootPermissionChecker _umbracoBootPermissionChecker;
private readonly IRequestCache _requestCache;
private readonly IGlobalSettings _globalSettings;
@@ -61,14 +63,6 @@ namespace Umbraco.Core.Runtime
_globalSettings = Configs.Global();
_connectionStrings = configs.ConnectionStrings();
-
- // runtime state
- // beware! must use '() => _factory.GetInstance()' and NOT '_factory.GetInstance'
- // as the second one captures the current value (null) and therefore fails
- _state = new RuntimeState(Configs.Global(), UmbracoVersion)
- {
- Level = RuntimeLevel.Boot
- };
}
///
@@ -88,7 +82,7 @@ namespace Umbraco.Core.Runtime
///
/// Gets the profiling logger.
///
- protected IProfilingLogger ProfilingLogger { get; private set; }
+ public IProfilingLogger ProfilingLogger { get; private set; }
///
/// Gets the
@@ -100,8 +94,8 @@ namespace Umbraco.Core.Runtime
///
protected IIOHelper IOHelper { get; }
protected IHostingEnvironment HostingEnvironment { get; }
- protected Configs Configs { get; }
- protected IUmbracoVersion UmbracoVersion { get; }
+ public Configs Configs { get; }
+ public IUmbracoVersion UmbracoVersion { get; }
///
public IRuntimeState State => _state;
@@ -164,31 +158,36 @@ namespace Umbraco.Core.Runtime
try
{
-
-
// run handlers
- RuntimeOptions.DoRuntimeBoot(ProfilingLogger);
+ OnRuntimeBoot();
// application caches
- var appCaches = GetAppCaches();
+ var appCaches = CreateAppCaches();
// database factory
- var databaseFactory = GetDatabaseFactory();
+ var databaseFactory = CreateDatabaseFactory();
// type finder/loader
var typeLoader = new TypeLoader(TypeFinder, appCaches.RuntimeCache, new DirectoryInfo(HostingEnvironment.LocalTempPath), ProfilingLogger);
+ // re-create the state object with the essential services
+ _state = new RuntimeState(Configs.Global(), UmbracoVersion, databaseFactory, Logger);
+
// create the composition
composition = new Composition(register, typeLoader, ProfilingLogger, _state, Configs, IOHelper, appCaches);
+
composition.RegisterEssentials(Logger, Profiler, ProfilingLogger, MainDom, appCaches, databaseFactory, typeLoader, _state, TypeFinder, IOHelper, UmbracoVersion, DbProviderFactoryCreator, HostingEnvironment, BackOfficeInfo);
// register ourselves (TODO: Should we put this in RegisterEssentials?)
composition.Register(_ => this, Lifetime.Singleton);
+ // run handlers
+ OnRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory);
+
try
{
// determine our runtime level
- DetermineRuntimeLevel(databaseFactory, ProfilingLogger);
+ DetermineRuntimeLevel(databaseFactory);
}
finally
{
@@ -243,9 +242,6 @@ namespace Umbraco.Core.Runtime
// throws if not full-trust
_umbracoBootPermissionChecker.ThrowIfNotPermissions();
- // run handlers
- RuntimeOptions.DoRuntimeEssentials(_factory);
-
var hostingEnvironmentLifetime = _factory.TryGetInstance();
if (hostingEnvironmentLifetime == null)
throw new InvalidOperationException($"An instance of {typeof(IApplicationShutdownRegistry)} could not be resolved from the container, ensure that one if registered in your runtime before calling {nameof(IRuntime)}.{nameof(Start)}");
@@ -257,7 +253,6 @@ namespace Umbraco.Core.Runtime
_components = _factory.GetInstance();
_components.Initialize();
-
// now (and only now) is the time to switch over to perWebRequest scopes.
// up until that point we may not have a request, and scoped services would
// fail to resolve - but we run Initialize within a factory scope - and then,
@@ -313,31 +308,29 @@ namespace Umbraco.Core.Runtime
}
}
- // internal for tests
- internal void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, IProfilingLogger profilingLogger)
+ private void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory)
{
- using (var timer = profilingLogger.DebugDuration("Determining runtime level.", "Determined."))
+ using var timer = ProfilingLogger.DebugDuration("Determining runtime level.", "Determined.");
+
+ try
{
- try
- {
- _state.DetermineRuntimeLevel(databaseFactory, profilingLogger);
+ _state.DetermineRuntimeLevel();
- profilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason);
+ ProfilingLogger.Debug("Runtime level: {RuntimeLevel} - {RuntimeLevelReason}", _state.Level, _state.Reason);
- if (_state.Level == RuntimeLevel.Upgrade)
- {
- profilingLogger.Debug("Configure database factory for upgrades.");
- databaseFactory.ConfigureForUpgrade();
- }
- }
- catch
+ if (_state.Level == RuntimeLevel.Upgrade)
{
- _state.Level = RuntimeLevel.BootFailed;
- _state.Reason = RuntimeLevelReason.BootFailedOnException;
- timer?.Fail();
- throw;
+ ProfilingLogger.Debug("Configure database factory for upgrades.");
+ databaseFactory.ConfigureForUpgrade();
}
}
+ catch
+ {
+ _state.Level = RuntimeLevel.BootFailed;
+ _state.Reason = RuntimeLevelReason.BootFailedOnException;
+ timer?.Fail();
+ throw;
+ }
}
private IEnumerable ResolveComposerTypes(TypeLoader typeLoader)
@@ -374,9 +367,9 @@ namespace Umbraco.Core.Runtime
=> typeLoader.GetTypes();
///
- /// Gets the application caches.
+ /// Creates the application caches.
///
- protected virtual AppCaches GetAppCaches()
+ protected virtual AppCaches CreateAppCaches()
{
// need the deep clone runtime cache provider to ensure entities are cached properly, ie
// are cloned in and cloned out - no request-based cache here since no web-based context,
@@ -400,14 +393,33 @@ namespace Umbraco.Core.Runtime
=> null;
///
- /// Gets the database factory.
+ /// Creates the database factory.
///
/// This is strictly internal, for tests only.
- protected internal virtual IUmbracoDatabaseFactory GetDatabaseFactory()
+ protected internal virtual IUmbracoDatabaseFactory CreateDatabaseFactory()
=> new UmbracoDatabaseFactory(Logger, _globalSettings, _connectionStrings, new Lazy(() => _factory.GetInstance()), DbProviderFactoryCreator);
#endregion
+ #region Events
+
+ protected void OnRuntimeBoot()
+ {
+ RuntimeOptions.DoRuntimeBoot(ProfilingLogger);
+ RuntimeBooting?.Invoke(this, ProfilingLogger);
+ }
+
+ protected void OnRuntimeEssentials(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory)
+ {
+ RuntimeOptions.DoRuntimeEssentials(composition, appCaches, typeLoader, databaseFactory);
+ RuntimeEssentials?.Invoke(this, new RuntimeEssentialsEventArgs(composition, appCaches, typeLoader, databaseFactory));
+ }
+
+ public event TypedEventHandler RuntimeBooting;
+ public event TypedEventHandler RuntimeEssentials;
+
+ #endregion
+
}
}
diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs
new file mode 100644
index 0000000000..78d068cb9c
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Runtime/RuntimeEssentialsEventArgs.cs
@@ -0,0 +1,23 @@
+using System;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Persistence;
+
+namespace Umbraco.Core.Runtime
+{
+ public class RuntimeEssentialsEventArgs : EventArgs
+ {
+ public RuntimeEssentialsEventArgs(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory)
+ {
+ Composition = composition;
+ AppCaches = appCaches;
+ TypeLoader = typeLoader;
+ DatabaseFactory = databaseFactory;
+ }
+
+ public Composition Composition { get; }
+ public AppCaches AppCaches { get; }
+ public TypeLoader TypeLoader { get; }
+ public IUmbracoDatabaseFactory DatabaseFactory { get; }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/RuntimeOptions.cs b/src/Umbraco.Infrastructure/RuntimeOptions.cs
index 562a7e3c5c..23abd474a4 100644
--- a/src/Umbraco.Infrastructure/RuntimeOptions.cs
+++ b/src/Umbraco.Infrastructure/RuntimeOptions.cs
@@ -16,7 +16,7 @@ namespace Umbraco.Core
public static class RuntimeOptions
{
private static List> _onBoot;
- private static List> _onEssentials;
+ private static List> _onEssentials;
///
/// Executes the RuntimeBoot handlers.
@@ -33,13 +33,13 @@ namespace Umbraco.Core
///
/// Executes the RuntimeEssentials handlers.
///
- internal static void DoRuntimeEssentials(IFactory factory)
+ internal static void DoRuntimeEssentials(Composition composition, AppCaches appCaches, TypeLoader typeLoader, IUmbracoDatabaseFactory databaseFactory)
{
if (_onEssentials== null)
return;
foreach (var action in _onEssentials)
- action(factory);
+ action(composition, appCaches, typeLoader, databaseFactory);
}
///
@@ -64,10 +64,10 @@ namespace Umbraco.Core
/// essential things (AppCaches, a TypeLoader, and a database factory) but
/// before anything else.
///
- public static void OnRuntimeEssentials(Action action)
+ public static void OnRuntimeEssentials(Action action)
{
if (_onEssentials == null)
- _onEssentials = new List>();
+ _onEssentials = new List>();
_onEssentials.Add(action);
}
}
diff --git a/src/Umbraco.Infrastructure/RuntimeState.cs b/src/Umbraco.Infrastructure/RuntimeState.cs
index 4a33291314..9b88430080 100644
--- a/src/Umbraco.Infrastructure/RuntimeState.cs
+++ b/src/Umbraco.Infrastructure/RuntimeState.cs
@@ -1,10 +1,8 @@
using System;
using System.Threading;
using Semver;
-using Umbraco.Core.Collections;
using Umbraco.Core.Configuration;
using Umbraco.Core.Exceptions;
-using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Migrations.Upgrade;
using Umbraco.Core.Persistence;
@@ -18,14 +16,27 @@ namespace Umbraco.Core
{
private readonly IGlobalSettings _globalSettings;
private readonly IUmbracoVersion _umbracoVersion;
+ private readonly IUmbracoDatabaseFactory _databaseFactory;
+ private readonly ILogger _logger;
+
+ ///
+ /// The initial
+ ///
+ public static RuntimeState Booting() => new RuntimeState() { Level = RuntimeLevel.Boot };
+
+ private RuntimeState()
+ {
+ }
///
/// Initializes a new instance of the class.
///
- public RuntimeState(IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion)
+ public RuntimeState(IGlobalSettings globalSettings, IUmbracoVersion umbracoVersion, IUmbracoDatabaseFactory databaseFactory, ILogger logger)
{
_globalSettings = globalSettings;
_umbracoVersion = umbracoVersion;
+ _databaseFactory = databaseFactory;
+ _logger = logger;
}
@@ -53,16 +64,14 @@ namespace Umbraco.Core
///
public BootFailedException BootFailedException { get; internal set; }
- ///
- /// Determines the runtime level.
- ///
- public void DetermineRuntimeLevel(IUmbracoDatabaseFactory databaseFactory, ILogger logger)
+ ///
+ public void DetermineRuntimeLevel()
{
- if (databaseFactory.Configured == false)
+ if (_databaseFactory.Configured == false)
{
// local version *does* match code version, but the database is not configured
// install - may happen with Deploy/Cloud/etc
- logger.Debug("Database is not configured, need to install Umbraco.");
+ _logger.Debug("Database is not configured, need to install Umbraco.");
Level = RuntimeLevel.Install;
Reason = RuntimeLevelReason.InstallNoDatabase;
return;
@@ -75,16 +84,16 @@ namespace Umbraco.Core
var tries = _globalSettings.InstallMissingDatabase ? 2 : 5;
for (var i = 0;;)
{
- connect = databaseFactory.CanConnect;
+ connect = _databaseFactory.CanConnect;
if (connect || ++i == tries) break;
- logger.Debug("Could not immediately connect to database, trying again.");
+ _logger.Debug("Could not immediately connect to database, trying again.");
Thread.Sleep(1000);
}
if (connect == false)
{
// cannot connect to configured database, this is bad, fail
- logger.Debug("Could not connect to database.");
+ _logger.Debug("Could not connect to database.");
if (_globalSettings.InstallMissingDatabase)
{
@@ -108,12 +117,12 @@ namespace Umbraco.Core
bool noUpgrade;
try
{
- noUpgrade = EnsureUmbracoUpgradeState(databaseFactory, logger);
+ noUpgrade = EnsureUmbracoUpgradeState(_databaseFactory, _logger);
}
catch (Exception e)
{
// can connect to the database but cannot check the upgrade state... oops
- logger.Warn(e, "Could not check the upgrade state.");
+ _logger.Warn(e, "Could not check the upgrade state.");
if (_globalSettings.InstallEmptyDatabase)
{
@@ -145,7 +154,7 @@ namespace Umbraco.Core
// although the files version matches the code version, the database version does not
// which means the local files have been upgraded but not the database - need to upgrade
- logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco.");
+ _logger.Debug("Has not reached the final upgrade step, need to upgrade Umbraco.");
Level = RuntimeLevel.Upgrade;
Reason = RuntimeLevelReason.UpgradeMigrations;
}
diff --git a/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs
index a5850719ab..3539993358 100644
--- a/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs
+++ b/src/Umbraco.Infrastructure/Scheduling/SchedulerComponent.cs
@@ -18,7 +18,7 @@ using Umbraco.Web.Routing;
namespace Umbraco.Web.Scheduling
{
- internal sealed class SchedulerComponent : IComponent
+ public sealed class SchedulerComponent : IComponent
{
private const int DefaultDelayMilliseconds = 180000; // 3 mins
private const int OneMinuteMilliseconds = 60000;
diff --git a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs
index 1946e2041b..d18480eb82 100644
--- a/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs
+++ b/src/Umbraco.Infrastructure/Search/BackgroundIndexRebuilder.cs
@@ -12,7 +12,7 @@ namespace Umbraco.Web.Search
///
/// Utility to rebuild all indexes on a background thread
///
- public sealed class BackgroundIndexRebuilder
+ public class BackgroundIndexRebuilder
{
private static readonly object RebuildLocker = new object();
private readonly IndexRebuilder _indexRebuilder;
@@ -34,7 +34,7 @@ namespace Umbraco.Web.Search
///
///
///
- public void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0)
+ public virtual void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0)
{
// TODO: need a way to disable rebuilding on startup
diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs
index 85a463ddfa..cb8b6618ee 100644
--- a/src/Umbraco.Tests.Common/TestHelperBase.cs
+++ b/src/Umbraco.Tests.Common/TestHelperBase.cs
@@ -47,13 +47,6 @@ namespace Umbraco.Tests.Common
public Configs GetConfigs() => GetConfigsFactory().Create();
- public IRuntimeState GetRuntimeState()
- {
- return new RuntimeState(
- Mock.Of(),
- GetUmbracoVersion());
- }
-
public abstract IBackOfficeInfo GetBackOfficeInfo();
public IConfigsFactory GetConfigsFactory() => new ConfigsFactory();
diff --git a/src/Umbraco.Tests.Integration/ContainerTests.cs b/src/Umbraco.Tests.Integration/ContainerTests.cs
index 2098b7241e..3ba3c31d03 100644
--- a/src/Umbraco.Tests.Integration/ContainerTests.cs
+++ b/src/Umbraco.Tests.Integration/ContainerTests.cs
@@ -83,7 +83,7 @@ namespace Umbraco.Tests.Integration
// it means the container won't be disposed, and maybe other services? not sure.
// In cases where we use it can we use IConfigureOptions? https://andrewlock.net/access-services-inside-options-and-startup-using-configureoptions/
- var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory);
+ var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory);
IHostApplicationLifetime lifetime1 = null;
diff --git a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs b/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs
deleted file mode 100644
index edbdcd0722..0000000000
--- a/src/Umbraco.Tests.Integration/Extensions/ApplicationBuilderExtensions.cs
+++ /dev/null
@@ -1,126 +0,0 @@
-using System;
-using System.Data.Common;
-using System.Data.SqlClient;
-using System.IO;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using NUnit.Framework;
-using Umbraco.Core;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Logging;
-using Umbraco.Core.Persistence;
-using Umbraco.Tests.Integration.Testing;
-using Umbraco.Tests.Testing;
-
-namespace Umbraco.Tests.Integration.Extensions
-{
- public static class ApplicationBuilderExtensions
- {
- ///
- /// Creates a LocalDb instance to use for the test
- ///
- ///
- ///
- ///
- ///
- ///
- public static IApplicationBuilder UseTestLocalDb(this IApplicationBuilder app,
- string workingDirectory,
- UmbracoIntegrationTest integrationTest, out string connectionString)
- {
- connectionString = null;
- var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
-
- // get the currently set db options
- var testOptions = TestOptionAttributeBase.GetTestOptions();
-
- if (testOptions.Database == UmbracoTestOptions.Database.None)
- return app;
-
- // need to manually register this factory
- DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
-
- if (!Directory.Exists(dbFilePath))
- Directory.CreateDirectory(dbFilePath);
-
- var db = UmbracoIntegrationTest.GetOrCreate(dbFilePath,
- app.ApplicationServices.GetRequiredService(),
- app.ApplicationServices.GetRequiredService(),
- app.ApplicationServices.GetRequiredService());
-
-
- switch (testOptions.Database)
- {
- case UmbracoTestOptions.Database.NewSchemaPerTest:
-
- // New DB + Schema
- var newSchemaDbId = db.AttachSchema();
-
- // Add teardown callback
- integrationTest.OnTestTearDown(() => db.Detach(newSchemaDbId));
-
- // We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
- var dbFactory = app.ApplicationServices.GetRequiredService();
- if (!dbFactory.Configured)
- {
- dbFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
- }
-
- // In the case that we've initialized the schema, it means that we are installed so we'll want to ensure that
- // the runtime state is configured correctly so we'll force update the configuration flag and re-run the
- // runtime state checker.
- // TODO: This wouldn't be required if we don't store the Umbraco version in config
-
- // right now we are an an 'Install' state
- var runtimeState = (RuntimeState)app.ApplicationServices.GetRequiredService();
- Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level);
-
- // dynamically change the config status
- var umbVersion = app.ApplicationServices.GetRequiredService();
- var config = app.ApplicationServices.GetRequiredService();
- config[Constants.Configuration.ConfigGlobalPrefix + "ConfigurationStatus"] = umbVersion.SemanticVersion.ToString();
-
- // re-run the runtime level check
- var profilingLogger = app.ApplicationServices.GetRequiredService();
- runtimeState.DetermineRuntimeLevel(dbFactory, profilingLogger);
-
- Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level);
-
- break;
- case UmbracoTestOptions.Database.NewEmptyPerTest:
-
- var newEmptyDbId = db.AttachEmpty();
-
- // Add teardown callback
- integrationTest.OnTestTearDown(() => db.Detach(newEmptyDbId));
-
-
- break;
- case UmbracoTestOptions.Database.NewSchemaPerFixture:
-
- // New DB + Schema
- var newSchemaFixtureDbId = db.AttachSchema();
-
- // Add teardown callback
- integrationTest.OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId));
-
- break;
- case UmbracoTestOptions.Database.NewEmptyPerFixture:
-
- throw new NotImplementedException();
-
- //// Add teardown callback
- //integrationTest.OnFixtureTearDown(() => db.Detach());
-
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null);
- }
- connectionString = db.ConnectionString;
- return app;
- }
- }
-
-
-}
diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
index a6cd372411..7faa4dac34 100644
--- a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
+++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
@@ -19,6 +19,7 @@ using Umbraco.Core.Runtime;
using Umbraco.Tests.Common;
using Umbraco.Web.Common.AspNetCore;
using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment;
+using Microsoft.Extensions.FileProviders;
namespace Umbraco.Tests.Integration.Implementations
{
@@ -39,11 +40,17 @@ namespace Umbraco.Tests.Integration.Implementations
_httpContextAccessor = Mock.Of(x => x.HttpContext == httpContext);
_ipResolver = new AspNetCoreIpResolver(_httpContextAccessor);
+ var contentRoot = Assembly.GetExecutingAssembly().GetRootDirectorySafe();
var hostEnvironment = new Mock();
- hostEnvironment.Setup(x => x.ApplicationName).Returns("UmbracoIntegrationTests");
- hostEnvironment.Setup(x => x.ContentRootPath)
- .Returns(() => Assembly.GetExecutingAssembly().GetRootDirectorySafe());
+ // this must be the assembly name for the WebApplicationFactory to work
+ hostEnvironment.Setup(x => x.ApplicationName).Returns(GetType().Assembly.GetName().Name);
+ hostEnvironment.Setup(x => x.ContentRootPath).Returns(() => contentRoot);
+ hostEnvironment.Setup(x => x.ContentRootFileProvider).Returns(() => new PhysicalFileProvider(contentRoot));
hostEnvironment.Setup(x => x.WebRootPath).Returns(() => WorkingDirectory);
+ hostEnvironment.Setup(x => x.WebRootFileProvider).Returns(() => new PhysicalFileProvider(WorkingDirectory));
+ // we also need to expose it as the obsolete interface since netcore's WebApplicationFactory casts it
+ hostEnvironment.As();
+
_hostEnvironment = hostEnvironment.Object;
_hostingLifetime = new AspNetCoreApplicationShutdownRegistry(Mock.Of());
diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs
index bd7684fcae..b41ef4ac02 100644
--- a/src/Umbraco.Tests.Integration/RuntimeTests.cs
+++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs
@@ -88,7 +88,7 @@ namespace Umbraco.Tests.Integration
[Test]
public async Task AddUmbracoCore()
{
- var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory);
+ var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory);
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
@@ -128,7 +128,7 @@ namespace Umbraco.Tests.Integration
[Test]
public async Task UseUmbracoCore()
{
- var umbracoContainer = UmbracoIntegrationTest.GetUmbracoContainer(out var serviceProviderFactory);
+ var umbracoContainer = UmbracoIntegrationTest.CreateUmbracoContainer(out var serviceProviderFactory);
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
diff --git a/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs
new file mode 100644
index 0000000000..08cd49bd81
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/TestServerTest/TestAuthHandler.cs
@@ -0,0 +1,40 @@
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Umbraco.Core;
+using Umbraco.Core.BackOffice;
+using Umbraco.Core.Mapping;
+using Umbraco.Core.Models.Membership;
+using Umbraco.Core.Services;
+using Umbraco.Web.Common.Security;
+
+namespace Umbraco.Tests.Integration.TestServerTest
+{
+ public class TestAuthHandler : AuthenticationHandler
+ {
+ private readonly BackOfficeSignInManager _backOfficeSignInManager;
+
+ private readonly BackOfficeIdentityUser _fakeUser;
+ public TestAuthHandler(IOptionsMonitor options,
+ ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, BackOfficeSignInManager backOfficeSignInManager, IUserService userService, UmbracoMapper umbracoMapper)
+ : base(options, logger, encoder, clock)
+ {
+ _backOfficeSignInManager = backOfficeSignInManager;
+
+ var user = userService.GetUserById(Constants.Security.SuperUserId);
+ _fakeUser = umbracoMapper.Map(user);
+ _fakeUser.SecurityStamp = "Needed";
+ }
+
+ protected override async Task HandleAuthenticateAsync()
+ {
+
+ var principal = await _backOfficeSignInManager.CreateUserPrincipalAsync(_fakeUser);
+ var ticket = new AuthenticationTicket(principal, "Test");
+
+ return AuthenticateResult.Success(ticket);
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs
new file mode 100644
index 0000000000..7c919e3894
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoBuilderExtensions.cs
@@ -0,0 +1,40 @@
+using System;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Composing.LightInject;
+using Umbraco.Core.Runtime;
+using Umbraco.Extensions;
+using Umbraco.Tests.Integration.Implementations;
+using Umbraco.Tests.Integration.Testing;
+using Umbraco.Web.Common.Builder;
+
+namespace Umbraco.Tests.Integration.TestServerTest
+{
+ public static class UmbracoBuilderExtensions
+ {
+ ///
+ /// Uses a test version of Umbraco Core with a test IRuntime
+ ///
+ ///
+ ///
+ public static IUmbracoBuilder WithTestCore(this IUmbracoBuilder builder, TestHelper testHelper, LightInjectContainer container,
+ Action dbInstallEventHandler)
+ {
+ return builder.AddWith(nameof(global::Umbraco.Web.Common.Builder.UmbracoBuilderExtensions.WithCore),
+ () =>
+ {
+ builder.Services.AddUmbracoCore(
+ builder.WebHostEnvironment,
+ container,
+ typeof(UmbracoBuilderExtensions).Assembly,
+ NoAppCache.Instance,
+ testHelper.GetLoggingConfiguration(),
+ // TODO: Yep that's extremely ugly
+ (a, b, c, d, e, f, g, h, i, j) =>
+ UmbracoIntegrationTest.CreateTestRuntime(a, b, c, d, e, f, g, h, i, j,
+ testHelper.MainDom, // SimpleMainDom
+ dbInstallEventHandler), // DB Installation event handler
+ out _);
+ });
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
index c5f413e24f..18a5836223 100644
--- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
+++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoTestServerTestBase.cs
@@ -2,22 +2,26 @@
using System;
using System.Linq.Expressions;
using System.Net.Http;
-using System.Reflection;
-using Examine;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Routing;
+using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using NUnit.Framework;
using Umbraco.Composing;
-using Umbraco.Core.Scoping;
-using Umbraco.Examine;
+using Umbraco.Core;
using Umbraco.Extensions;
using Umbraco.Tests.Integration.Testing;
using Umbraco.Tests.Testing;
using Umbraco.Web;
+using Umbraco.Web.Common.Builder;
using Umbraco.Web.Common.Controllers;
-
+using Umbraco.Web.Editors;
+using Microsoft.Extensions.Hosting;
namespace Umbraco.Tests.Integration.TestServerTest
{
@@ -25,37 +29,56 @@ namespace Umbraco.Tests.Integration.TestServerTest
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console, Boot = false)]
public abstract class UmbracoTestServerTestBase : UmbracoIntegrationTest
{
-
[SetUp]
- public void SetUp()
+ public override Task Setup()
{
- Factory = new UmbracoWebApplicationFactory(TestDBConnectionString);
- Client = Factory.CreateClient(new WebApplicationFactoryClientOptions(){
- AllowAutoRedirect = false
+ InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = null;
+ InMemoryConfiguration["Umbraco:CMS:Hosting:Debug"] = "true";
+
+ // create new WebApplicationFactory specifying 'this' as the IStartup instance
+ var factory = new UmbracoWebApplicationFactory(CreateHostBuilder);
+
+ // additional host configuration for web server integration tests
+ Factory = factory.WithWebHostBuilder(builder =>
+ {
+ // Executes after the standard ConfigureServices method
+ builder.ConfigureTestServices(services =>
+ {
+ services.AddAuthentication("Test").AddScheme("Test", options => { });
+ });
});
+
+ Client = Factory.CreateClient(new WebApplicationFactoryClientOptions
+ {
+ AllowAutoRedirect = false
+ });
+
LinkGenerator = Factory.Services.GetRequiredService();
- ExecuteExamineIndexOperationsInSync();
+ return Task.CompletedTask;
}
- private void ExecuteExamineIndexOperationsInSync()
+ public override IHostBuilder CreateHostBuilder()
{
- var examineManager = Factory.Services.GetRequiredService();
+ var builder = base.CreateHostBuilder();
+ builder.ConfigureWebHost(builder =>
+ {
+ // need to configure the IWebHostEnvironment too
+ builder.ConfigureServices((c, s) => {
+ c.HostingEnvironment = TestHelper.GetWebHostEnvironment();
+ });
- foreach (var index in examineManager.Indexes)
- {
- if (index is UmbracoExamineIndex umbracoExamineIndex)
- {
- umbracoExamineIndex.ProcessNonAsync();
- }
- }
+ // call startup
+ builder.Configure(app =>
+ {
+ Services = app.ApplicationServices;
+ Configure(app);
+ });
+ })
+ .UseEnvironment(Environments.Development);
+ return builder;
}
- ///
- /// Get the service from the underlying container that is also used by the .
- ///
- protected T GetRequiredService() => Factory.Services.GetRequiredService();
-
///
/// Prepare a url before using .
/// This returns the url but also sets the HttpContext.request into to use this url.
@@ -85,13 +108,14 @@ namespace Umbraco.Tests.Integration.TestServerTest
return url;
}
- protected HttpClient Client { get; set; }
- protected LinkGenerator LinkGenerator { get; set; }
- protected UmbracoWebApplicationFactory Factory { get; set; }
+ protected HttpClient Client { get; private set; }
+ protected LinkGenerator LinkGenerator { get; private set; }
+ protected WebApplicationFactory Factory { get; private set; }
[TearDown]
- public void TearDown()
+ public override void TearDown()
{
+ base.TearDown();
Factory.Dispose();
@@ -99,7 +123,40 @@ namespace Umbraco.Tests.Integration.TestServerTest
{
Current.IsInitialized = false;
}
-
}
+
+ #region IStartup
+
+ public override void ConfigureServices(IServiceCollection services)
+ {
+ //services.AddSingleton(TestHelper.DbProviderFactoryCreator);
+ //var webHostEnvironment = TestHelper.GetWebHostEnvironment();
+ //services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment);
+
+ var umbracoBuilder = services.AddUmbraco(TestHelper.GetWebHostEnvironment(), Configuration);
+ umbracoBuilder
+ .WithConfiguration()
+ .WithTestCore(TestHelper, UmbracoContainer, UseTestLocalDb) // This is the important one!
+ .WithWebComponents()
+ .WithRuntimeMinifier()
+ .WithBackOffice()
+ .WithBackOfficeIdentity()
+ //.WithMiniProfiler()
+ .WithMvcAndRazor(mvcBuilding: mvcBuilder =>
+ {
+ mvcBuilder.AddApplicationPart(typeof(ContentController).Assembly);
+ })
+ .WithWebServer()
+ .Build();
+ }
+
+ public override void Configure(IApplicationBuilder app)
+ {
+ app.UseUmbraco();
+ }
+
+ #endregion
+
+
}
}
diff --git a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs
index ea2b64c790..251f155d2c 100644
--- a/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs
+++ b/src/Umbraco.Tests.Integration/TestServerTest/UmbracoWebApplicationFactory.cs
@@ -1,97 +1,23 @@
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Security.Claims;
-using System.Text.Encodings.Web;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Hosting;
+using System;
using Microsoft.AspNetCore.Mvc.Testing;
-using Microsoft.AspNetCore.TestHost;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using Umbraco.Core;
-using Umbraco.Core.BackOffice;
-using Umbraco.Core.Composing;
-using Umbraco.Core.Configuration;
-using Umbraco.Core.Mapping;
-using Umbraco.Core.Models.Membership;
-using Umbraco.Core.Services;
-using Umbraco.Web.Common.Security;
-using Umbraco.Web.UI.NetCore;
namespace Umbraco.Tests.Integration.TestServerTest
{
- public class UmbracoWebApplicationFactory : CustomWebApplicationFactory
+
+ public class UmbracoWebApplicationFactory : WebApplicationFactory where TStartup : class
{
- public UmbracoWebApplicationFactory(string testDbConnectionString) :base(testDbConnectionString)
- {
- }
- }
+ private readonly Func _createHostBuilder;
- public abstract class CustomWebApplicationFactory : WebApplicationFactory where TStartup : class
- {
- private readonly string _testDbConnectionString;
-
- protected CustomWebApplicationFactory(string testDbConnectionString)
+ ///
+ /// Constructor to create a new WebApplicationFactory
+ ///
+ /// Method to create the IHostBuilder
+ public UmbracoWebApplicationFactory(Func createHostBuilder)
{
- _testDbConnectionString = testDbConnectionString;
+ _createHostBuilder = createHostBuilder;
}
- protected override void ConfigureWebHost(IWebHostBuilder builder)
- {
- base.ConfigureWebHost(builder);
-
- builder.ConfigureTestServices(services =>
- {
- services.AddAuthentication("Test").AddScheme("Test", options => {});
- });
-
- builder.ConfigureAppConfiguration(x =>
- {
- x.AddInMemoryCollection(new Dictionary()
- {
- ["ConnectionStrings:"+ Constants.System.UmbracoConnectionName] = _testDbConnectionString,
- ["Umbraco:CMS:Hosting:Debug"] = "true",
- });
- });
-
- }
-
- protected override IHostBuilder CreateHostBuilder()
- {
-
- var builder = base.CreateHostBuilder();
- builder.UseUmbraco();
- return builder;
- }
- }
-
- public class TestAuthHandler : AuthenticationHandler
- {
- private readonly BackOfficeSignInManager _backOfficeSignInManager;
-
- private readonly BackOfficeIdentityUser _fakeUser;
- public TestAuthHandler(IOptionsMonitor options,
- ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, BackOfficeSignInManager backOfficeSignInManager, IUserService userService, UmbracoMapper umbracoMapper)
- : base(options, logger, encoder, clock)
- {
- _backOfficeSignInManager = backOfficeSignInManager;
-
- var user = userService.GetUserById(Constants.Security.SuperUserId);
- _fakeUser = umbracoMapper.Map(user);
- _fakeUser.SecurityStamp = "Needed";
- }
-
- protected override async Task HandleAuthenticateAsync()
- {
-
- var principal = await _backOfficeSignInManager.CreateUserPrincipalAsync(_fakeUser);
- var ticket = new AuthenticationTicket(principal, "Test");
-
- return AuthenticateResult.Success(ticket);
- }
+ protected override IHostBuilder CreateHostBuilder() => _createHostBuilder();
}
}
diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs
new file mode 100644
index 0000000000..69819c9bef
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComponent.cs
@@ -0,0 +1,43 @@
+using Examine;
+using Examine.LuceneEngine.Providers;
+using Umbraco.Core.Composing;
+using Umbraco.Examine;
+
+namespace Umbraco.Tests.Integration.Testing
+{
+ ///
+ /// A component to customize some services to work nicely with integration tests
+ ///
+ public class IntegrationTestComponent : IComponent
+ {
+ private readonly IExamineManager _examineManager;
+
+ public IntegrationTestComponent(IExamineManager examineManager)
+ {
+ _examineManager = examineManager;
+ }
+
+ public void Initialize()
+ {
+ ConfigureExamineIndexes();
+ }
+
+ public void Terminate()
+ {
+ }
+
+ ///
+ /// Configure all indexes to run sync (non-backbround threads) and to use RAMDirectory
+ ///
+ private void ConfigureExamineIndexes()
+ {
+ foreach (var index in _examineManager.Indexes)
+ {
+ if (index is LuceneIndex luceneIndex)
+ {
+ luceneIndex.ProcessNonAsync();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs
index 844877b1fd..97482f9482 100644
--- a/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs
+++ b/src/Umbraco.Tests.Integration/Testing/IntegrationTestComposer.cs
@@ -1,7 +1,15 @@
using Moq;
using Umbraco.Core;
using Umbraco.Core.Composing;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Hosting;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Services;
using Umbraco.Core.WebAssets;
+using Umbraco.Examine;
+using Umbraco.Web.PublishedCache.NuCache;
+using Umbraco.Web.Scheduling;
+using Umbraco.Web.Search;
namespace Umbraco.Tests.Integration.Testing
{
@@ -13,11 +21,36 @@ namespace Umbraco.Tests.Integration.Testing
/// This is a IUserComposer so that it runs after all core composers
///
[RuntimeLevel(MinLevel = RuntimeLevel.Boot)]
- public class IntegrationTestComposer : IUserComposer
+ public class IntegrationTestComposer : ComponentComposer
{
- public void Compose(Composition composition)
+ public override void Compose(Composition composition)
{
+ base.Compose(composition);
+
+ composition.Components().Remove();
+ composition.RegisterUnique();
composition.RegisterUnique(factory => Mock.Of());
+
+ // we don't want persisted nucache files in tests
+ composition.Register(factory => new PublishedSnapshotServiceOptions { IgnoreLocalDb = true });
+
+ // ensure all lucene indexes are using RAM directory (no file system)
+ composition.RegisterUnique();
}
+
+ // replace the default so there is no background index rebuilder
+ private class TestBackgroundIndexRebuilder : BackgroundIndexRebuilder
+ {
+ public TestBackgroundIndexRebuilder(IMainDom mainDom, IProfilingLogger logger, IApplicationShutdownRegistry hostingEnvironment, IndexRebuilder indexRebuilder)
+ : base(mainDom, logger, hostingEnvironment, indexRebuilder)
+ {
+ }
+
+ public override void RebuildIndexes(bool onlyEmptyIndexes, int waitMilliseconds = 0)
+ {
+ // noop
+ }
+ }
+
}
}
diff --git a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
index 89cbfa992d..ee03e2a146 100644
--- a/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
+++ b/src/Umbraco.Tests.Integration/Testing/UmbracoIntegrationTest.cs
@@ -3,7 +3,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
using NUnit.Framework;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
@@ -22,9 +21,19 @@ using Umbraco.Extensions;
using Umbraco.Tests.Testing;
using Umbraco.Web;
using ILogger = Umbraco.Core.Logging.ILogger;
+using Umbraco.Core.Runtime;
+using Umbraco.Core;
+using Moq;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using System.Data.SqlClient;
+using System.Data.Common;
+using System.IO;
namespace Umbraco.Tests.Integration.Testing
{
+
///
/// Abstract class for integration tests
///
@@ -35,7 +44,7 @@ namespace Umbraco.Tests.Integration.Testing
[NonParallelizable]
public abstract class UmbracoIntegrationTest
{
- public static LightInjectContainer GetUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory)
+ public static LightInjectContainer CreateUmbracoContainer(out UmbracoServiceProviderFactory serviceProviderFactory)
{
var container = UmbracoServiceProviderFactory.CreateServiceContainer();
serviceProviderFactory = new UmbracoServiceProviderFactory(container, false);
@@ -43,6 +52,191 @@ namespace Umbraco.Tests.Integration.Testing
return umbracoContainer;
}
+ private Action _testTeardown = null;
+ private Action _fixtureTeardown = null;
+
+ public void OnTestTearDown(Action tearDown) => _testTeardown = tearDown;
+
+ public void OnFixtureTearDown(Action tearDown) => _fixtureTeardown = tearDown;
+
+ [OneTimeTearDown]
+ public void FixtureTearDown() => _fixtureTeardown?.Invoke();
+
+ [TearDown]
+ public virtual void TearDown() => _testTeardown?.Invoke();
+
+ [SetUp]
+ public virtual async Task Setup()
+ {
+ var hostBuilder = CreateHostBuilder();
+ var host = await hostBuilder.StartAsync();
+ Services = host.Services;
+ var app = new ApplicationBuilder(host.Services);
+ Configure(app);
+ }
+
+ #region Generic Host Builder and Runtime
+
+ ///
+ /// Create the Generic Host and execute startup ConfigureServices/Configure calls
+ ///
+ ///
+ public virtual IHostBuilder CreateHostBuilder()
+ {
+ UmbracoContainer = CreateUmbracoContainer(out var serviceProviderFactory);
+ _serviceProviderFactory = serviceProviderFactory;
+
+ var hostBuilder = Host.CreateDefaultBuilder()
+ // IMPORTANT: We Cannot use UseStartup, there's all sorts of threads about this with testing. Although this can work
+ // if you want to setup your tests this way, it is a bit annoying to do that as the WebApplicationFactory will
+ // create separate Host instances. So instead of UseStartup, we just call ConfigureServices/Configure ourselves,
+ // and in the case of the UmbracoTestServerTestBase it will use the ConfigureWebHost to Configure the IApplicationBuilder directly.
+ //.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(GetType()); })
+ .UseUmbraco(_serviceProviderFactory)
+ .ConfigureAppConfiguration((context, configBuilder) =>
+ {
+ context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
+ Configuration = context.Configuration;
+ configBuilder.AddInMemoryCollection(InMemoryConfiguration);
+ })
+ .ConfigureServices((hostContext, services) =>
+ {
+ ConfigureServices(services);
+ });
+ return hostBuilder;
+ }
+
+ ///
+ /// Creates a instance for testing and registers an event handler for database install
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public CoreRuntime CreateTestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper,
+ ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
+ ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator)
+ {
+ return CreateTestRuntime(configs,
+ umbracoVersion,
+ ioHelper,
+ logger,
+ profiler,
+ hostingEnvironment,
+ backOfficeInfo,
+ typeFinder,
+ requestCache,
+ dbProviderFactoryCreator,
+ TestHelper.MainDom, // SimpleMainDom
+ UseTestLocalDb // DB Installation event handler
+ );
+ }
+
+ ///
+ /// Creates a instance for testing and registers an event handler for database install
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The event handler used for DB installation
+ ///
+ public static CoreRuntime CreateTestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper,
+ ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
+ ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator,
+ IMainDom mainDom, Action eventHandler)
+ {
+ var runtime = new CoreRuntime(
+ configs,
+ umbracoVersion,
+ ioHelper,
+ logger,
+ profiler,
+ Mock.Of(),
+ hostingEnvironment,
+ backOfficeInfo,
+ dbProviderFactoryCreator,
+ mainDom,
+ typeFinder,
+ requestCache);
+
+ runtime.RuntimeEssentials += (sender, args) => eventHandler(sender, args);
+
+ return runtime;
+ }
+
+ #endregion
+
+ #region IStartup
+
+ public virtual void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton(TestHelper.DbProviderFactoryCreator);
+ var webHostEnvironment = TestHelper.GetWebHostEnvironment();
+ services.AddRequiredNetCoreServices(TestHelper, webHostEnvironment);
+
+ // Add it!
+ services.AddUmbracoConfiguration(Configuration);
+ services.AddUmbracoCore(webHostEnvironment, UmbracoContainer, GetType().Assembly, NoAppCache.Instance, TestHelper.GetLoggingConfiguration(), CreateTestRuntime, out _);
+ services.AddUmbracoWebComponents();
+ services.AddUmbracoRuntimeMinifier(Configuration);
+ services.AddUmbracoBackOffice();
+ services.AddUmbracoBackOfficeIdentity();
+
+ services.AddMvc();
+
+ services.AddSingleton(new ConsoleLogger(new MessageTemplates()));
+
+ CustomTestSetup(services);
+ }
+
+ public virtual void Configure(IApplicationBuilder app)
+ {
+ Services.GetRequiredService().EnsureUmbracoContext();
+
+ // get the currently set ptions
+ var testOptions = TestOptionAttributeBase.GetTestOptions();
+ if (testOptions.Boot)
+ {
+ app.UseUmbracoCore();
+ }
+ }
+
+ #endregion
+
+ #region LocalDb
+
+
+ private static readonly object _dbLocker = new object();
+ private static LocalDbTestDatabase _dbInstance;
+
+ ///
+ /// Event handler for the to install the database
+ ///
+ ///
+ ///
+ protected void UseTestLocalDb(CoreRuntime sender, RuntimeEssentialsEventArgs args)
+ {
+ // This will create a db, install the schema and ensure the app is configured to run
+ InstallTestLocalDb(args.DatabaseFactory, sender.ProfilingLogger, sender.Configs.Global(), sender.State, TestHelper.WorkingDirectory, out var connectionString);
+ TestDBConnectionString = connectionString;
+ InMemoryConfiguration["ConnectionStrings:" + Constants.System.UmbracoConnectionName] = TestDBConnectionString;
+ }
+
///
/// Get or create an instance of
///
@@ -54,7 +248,7 @@ namespace Umbraco.Tests.Integration.Testing
///
/// There must only be ONE instance shared between all tests in a session
///
- public static LocalDbTestDatabase GetOrCreate(string filesPath, ILogger logger, IGlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory)
+ private static LocalDbTestDatabase GetOrCreateDatabase(string filesPath, ILogger logger, IGlobalSettings globalSettings, IUmbracoDatabaseFactory dbFactory)
{
lock (_dbLocker)
{
@@ -68,86 +262,104 @@ namespace Umbraco.Tests.Integration.Testing
}
}
- private static readonly object _dbLocker = new object();
- private static LocalDbTestDatabase _dbInstance;
-
- private Action _testTeardown = null;
- private Action _fixtureTeardown = null;
-
- public void OnTestTearDown(Action tearDown)
+ ///
+ /// Creates a LocalDb instance to use for the test
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void InstallTestLocalDb(
+ IUmbracoDatabaseFactory databaseFactory, IProfilingLogger logger, IGlobalSettings globalSettings,
+ IRuntimeState runtimeState, string workingDirectory, out string connectionString)
{
- _testTeardown = tearDown;
- }
+ connectionString = null;
+ var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
- public void OnFixtureTearDown(Action tearDown)
- {
- _fixtureTeardown = tearDown;
- }
-
- [OneTimeTearDown]
- public void FixtureTearDown()
- {
- _fixtureTeardown?.Invoke();
- }
-
- [TearDown]
- public void TearDown()
- {
- _testTeardown?.Invoke();
- }
-
- [SetUp]
- public async Task Setup()
- {
- var umbracoContainer = GetUmbracoContainer(out var serviceProviderFactory);
- var testHelper = new TestHelper();
// get the currently set db options
var testOptions = TestOptionAttributeBase.GetTestOptions();
- var hostBuilder = new HostBuilder()
- .UseUmbraco(serviceProviderFactory)
- .ConfigureServices((hostContext, services) =>
- {
- services.AddSingleton(testHelper.DbProviderFactoryCreator);
- var webHostEnvironment = testHelper.GetWebHostEnvironment();
- services.AddRequiredNetCoreServices(testHelper, webHostEnvironment);
+ if (testOptions.Database == UmbracoTestOptions.Database.None)
+ return;
- // Add it!
- services.AddUmbracoConfiguration(hostContext.Configuration);
- services.AddUmbracoCore(webHostEnvironment, umbracoContainer, GetType().Assembly, NoAppCache.Instance, testHelper.GetLoggingConfiguration(), out _);
- services.AddUmbracoWebComponents();
- services.AddUmbracoRuntimeMinifier(hostContext.Configuration);
- services.AddUmbracoBackOffice();
- services.AddUmbracoBackOfficeIdentity();
+ // need to manually register this factory
+ DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
- services.AddMvc();
+ if (!Directory.Exists(dbFilePath))
+ Directory.CreateDirectory(dbFilePath);
+ var db = GetOrCreateDatabase(dbFilePath, logger, globalSettings, databaseFactory);
- services.AddSingleton(new ConsoleLogger(new MessageTemplates()));
-
- CustomTestSetup(services);
- });
-
- var host = await hostBuilder.StartAsync();
- var app = new ApplicationBuilder(host.Services);
- Services = app.ApplicationServices;
-
- Services.GetRequiredService().EnsureUmbracoContext();
- // This will create a db, install the schema and ensure the app is configured to run
- app.UseTestLocalDb(testHelper.WorkingDirectory, this, out var connectionString);
- TestDBConnectionString = connectionString;
-
- if (testOptions.Boot)
+ switch (testOptions.Database)
{
- app.UseUmbracoCore();
- }
+ case UmbracoTestOptions.Database.NewSchemaPerTest:
+ // New DB + Schema
+ var newSchemaDbId = db.AttachSchema();
+
+ // Add teardown callback
+ OnTestTearDown(() => db.Detach(newSchemaDbId));
+
+ // We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
+ if (!databaseFactory.Configured)
+ {
+ databaseFactory.Configure(db.ConnectionString, Constants.DatabaseProviders.SqlServer);
+ }
+
+ // re-run the runtime level check
+ runtimeState.DetermineRuntimeLevel();
+
+ Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level);
+
+ break;
+ case UmbracoTestOptions.Database.NewEmptyPerTest:
+
+ var newEmptyDbId = db.AttachEmpty();
+
+ // Add teardown callback
+ OnTestTearDown(() => db.Detach(newEmptyDbId));
+
+
+ break;
+ case UmbracoTestOptions.Database.NewSchemaPerFixture:
+
+ // New DB + Schema
+ var newSchemaFixtureDbId = db.AttachSchema();
+
+ // Add teardown callback
+ OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbId));
+
+ break;
+ case UmbracoTestOptions.Database.NewEmptyPerFixture:
+
+ throw new NotImplementedException();
+
+ //// Add teardown callback
+ //integrationTest.OnFixtureTearDown(() => db.Detach());
+
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null);
+ }
+ connectionString = db.ConnectionString;
}
- protected T GetRequiredService() => Services.GetRequiredService();
+ #endregion
#region Common services
+ protected LightInjectContainer UmbracoContainer { get; private set; }
+ private UmbracoServiceProviderFactory _serviceProviderFactory;
+
+ protected virtual T GetRequiredService() => Services.GetRequiredService();
+
+ public Dictionary InMemoryConfiguration { get; } = new Dictionary();
+
+ public IConfiguration Configuration { get; protected set; }
+
+ public TestHelper TestHelper = new TestHelper();
+
protected string TestDBConnectionString { get; private set; }
protected virtual Action CustomTestSetup => services => { };
@@ -155,7 +367,7 @@ namespace Umbraco.Tests.Integration.Testing
///
/// Returns the DI container
///
- protected IServiceProvider Services { get; private set; }
+ protected IServiceProvider Services { get; set; }
///
/// Returns the
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj
index 072f15bcd1..ab11207df2 100644
--- a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj
+++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj
@@ -30,6 +30,7 @@
+
diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
index 0165e7714f..e86c5702e3 100644
--- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
+++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs
@@ -32,7 +32,7 @@ namespace Umbraco.Tests.Routing
//create the module
var logger = Mock.Of();
var globalSettings = TestObjects.GetGlobalSettings();
- var runtime = new RuntimeState(globalSettings, UmbracoVersion);
+ var runtime = Umbraco.Core.RuntimeState.Booting();
_module = new UmbracoInjectedModule
(
diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
index f63c56b64e..c338cf8a50 100644
--- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
+++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
@@ -125,7 +125,7 @@ namespace Umbraco.Tests.Runtimes
// must override the database factory
// else BootFailedException because U cannot connect to the configured db
- protected internal override IUmbracoDatabaseFactory GetDatabaseFactory()
+ protected internal override IUmbracoDatabaseFactory CreateDatabaseFactory()
{
var mock = new Mock();
mock.Setup(x => x.Configured).Returns(true);
diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
index ac0ff3f9f9..bf26b2167c 100644
--- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
+++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
@@ -73,7 +73,7 @@ namespace Umbraco.Tests.Runtimes
var mainDom = new SimpleMainDom();
var umbracoVersion = TestHelper.GetUmbracoVersion();
var backOfficeInfo = TestHelper.GetBackOfficeInfo();
- var runtimeState = new RuntimeState(null, umbracoVersion);
+ var runtimeState = new RuntimeState(globalSettings, umbracoVersion, databaseFactory, logger);
var configs = TestHelper.GetConfigs();
var variationContextAccessor = TestHelper.VariationContextAccessor;
@@ -87,7 +87,7 @@ namespace Umbraco.Tests.Runtimes
var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder, NoAppCache.Instance);
// determine actual runtime level
- runtimeState.DetermineRuntimeLevel(databaseFactory, logger);
+ runtimeState.DetermineRuntimeLevel();
Console.WriteLine(runtimeState.Level);
// going to be Install BUT we want to force components to be there (nucache etc)
runtimeState.Level = RuntimeLevel.Run;
diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs
index 9aeb668518..5c968f2f90 100644
--- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs
@@ -78,8 +78,6 @@ namespace Umbraco.Tests.TestHelpers
public static Configs GetConfigs() => _testHelperInternal.GetConfigs();
- public static IRuntimeState GetRuntimeState() => _testHelperInternal.GetRuntimeState();
-
public static IBackOfficeInfo GetBackOfficeInfo() => _testHelperInternal.GetBackOfficeInfo();
public static IConfigsFactory GetConfigsFactory() => _testHelperInternal.GetConfigsFactory();
diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs
similarity index 79%
rename from src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs
rename to src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs
index 282668e4e6..3cba66812e 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeApplicationBuilderExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeApplicationBuilderExtensions.cs
@@ -7,7 +7,7 @@ using Umbraco.Web.BackOffice.Security;
namespace Umbraco.Extensions
{
- public static class UmbracoBackOfficeApplicationBuilderExtensions
+ public static class BackOfficeApplicationBuilderExtensions
{
public static IApplicationBuilder UseUmbraco(this IApplicationBuilder app)
{
@@ -44,11 +44,6 @@ namespace Umbraco.Extensions
app.UseUmbracoRuntimeMinification();
- // Important we handle image manipulations before the static files, otherwise the querystring is just ignored.
- // TODO: Since we are dependent on these we need to register them but what happens when we call this multiple times since we are dependent on this for UseUmbracoWebsite too?
- app.UseImageSharp();
- app.UseStaticFiles();
-
app.UseMiddleware();
return app;
diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs
similarity index 64%
rename from src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs
rename to src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs
index fc3efab5e0..f135a72293 100644
--- a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBackOfficeServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/Extensions/BackOfficeServiceCollectionExtensions.cs
@@ -20,51 +20,9 @@ using Umbraco.Web.Common.Security;
namespace Umbraco.Extensions
{
- public static class UmbracoBackOfficeServiceCollectionExtensions
+
+ public static class BackOfficeServiceCollectionExtensions
{
- public static IServiceCollection AddUmbraco(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config)
- {
- if (services == null) throw new ArgumentNullException(nameof(services));
-
- // TODO: We will need to decide on if we want to use the ServiceBasedControllerActivator to create our controllers
- // or use the default IControllerActivator: DefaultControllerActivator (which doesn't directly use the container to resolve controllers)
- // This will affect whether we need to explicitly register controllers in the container like we do today in v8.
- // What we absolutely must do though is make sure we explicitly opt-in to using one or the other *always* for our controllers instead of
- // relying on a global configuration set by a user since if a custom IControllerActivator is used for our own controllers we may not
- // guarantee it will work. And then... is that even possible?
-
- // TODO: we will need to simplify this and prob just have a one or 2 main method that devs call which call all other required methods,
- // but for now we'll just be explicit with all of them
- services.AddUmbracoConfiguration(config);
- services.AddUmbracoCore(webHostEnvironment, out var factory);
- services.AddUmbracoWebComponents();
- services.AddUmbracoRuntimeMinifier(config);
- services.AddUmbracoBackOffice();
- services.AddUmbracoBackOfficeIdentity();
- services.AddMiniProfiler(options =>
- {
- options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile
- });
-
- //We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set.
- //But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay.
- services.AddControllersWithViews().AddRazorRuntimeCompilation();
-
-
- // If using Kestrel: https://stackoverflow.com/a/55196057
- services.Configure(options =>
- {
- options.AllowSynchronousIO = true;
- });
-
- services.Configure(options =>
- {
- options.AllowSynchronousIO = true;
- });
-
- return services;
- }
-
///
/// Adds the services required for running the Umbraco back office
///
@@ -105,7 +63,7 @@ namespace Umbraco.Extensions
// Configure the options specifically for the UmbracoBackOfficeIdentityOptions instance
services.ConfigureOptions();
services.ConfigureOptions();
- }
+ }
private static IdentityBuilder BuildUmbracoBackOfficeIdentity(this IServiceCollection services)
{
diff --git a/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs
new file mode 100644
index 0000000000..71325d70d4
--- /dev/null
+++ b/src/Umbraco.Web.BackOffice/Extensions/UmbracoBuilderExtensions.cs
@@ -0,0 +1,28 @@
+using Umbraco.Web.Common.Builder;
+
+namespace Umbraco.Extensions
+{
+ public static class UmbracoBuilderExtensions
+ {
+ public static void BuildWithAllBackOfficeComponents(this IUmbracoBuilder builder)
+ {
+ builder
+ .WithConfiguration()
+ .WithCore()
+ .WithWebComponents()
+ .WithRuntimeMinifier()
+ .WithBackOffice()
+ .WithBackOfficeIdentity()
+ .WithMiniProfiler()
+ .WithMvcAndRazor()
+ .WithWebServer()
+ .Build();
+ }
+
+ public static IUmbracoBuilder WithBackOffice(this IUmbracoBuilder builder)
+ => builder.AddWith(nameof(WithBackOffice), () => builder.Services.AddUmbracoBackOffice());
+
+ public static IUmbracoBuilder WithBackOfficeIdentity(this IUmbracoBuilder builder)
+ => builder.AddWith(nameof(WithBackOfficeIdentity), () => builder.Services.AddUmbracoBackOfficeIdentity());
+ }
+}
diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
index 2547117531..1c66edc2ce 100644
--- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
+++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs b/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs
new file mode 100644
index 0000000000..2de7d2d285
--- /dev/null
+++ b/src/Umbraco.Web.Common/Builder/IUmbracoBuilder.cs
@@ -0,0 +1,18 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Text;
+
+namespace Umbraco.Web.Common.Builder
+{
+
+ public interface IUmbracoBuilder
+ {
+ IServiceCollection Services { get; }
+ IWebHostEnvironment WebHostEnvironment { get; }
+ IConfiguration Config { get; }
+ IUmbracoBuilder AddWith(string key, Action add);
+ void Build();
+ }
+}
diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs
new file mode 100644
index 0000000000..3efb1e74f5
--- /dev/null
+++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilder.cs
@@ -0,0 +1,37 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+
+namespace Umbraco.Web.Common.Builder
+{
+ public class UmbracoBuilder : IUmbracoBuilder
+ {
+ private readonly Dictionary _registrations = new Dictionary();
+
+ public UmbracoBuilder(IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config)
+ {
+ Services = services;
+ WebHostEnvironment = webHostEnvironment;
+ Config = config;
+ }
+
+ public IServiceCollection Services { get; }
+ public IWebHostEnvironment WebHostEnvironment { get; }
+ public IConfiguration Config { get; }
+
+ public IUmbracoBuilder AddWith(string key, Action add)
+ {
+ if (_registrations.ContainsKey(key)) return this;
+ _registrations[key] = add;
+ return this;
+ }
+
+ public void Build()
+ {
+ foreach (var a in _registrations)
+ a.Value();
+ }
+ }
+}
diff --git a/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs
new file mode 100644
index 0000000000..dd91a2cca9
--- /dev/null
+++ b/src/Umbraco.Web.Common/Builder/UmbracoBuilderExtensions.cs
@@ -0,0 +1,70 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Server.Kestrel.Core;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using Umbraco.Extensions;
+
+namespace Umbraco.Web.Common.Builder
+{
+ // TODO: We could add parameters to configure each of these for flexibility
+ public static class UmbracoBuilderExtensions
+ {
+ public static IUmbracoBuilder AddUmbraco(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config)
+ {
+ if (services is null) throw new ArgumentNullException(nameof(services));
+ if (webHostEnvironment is null) throw new ArgumentNullException(nameof(webHostEnvironment));
+ if (config is null) throw new ArgumentNullException(nameof(config));
+
+ var builder = new UmbracoBuilder(services, webHostEnvironment, config);
+ return builder;
+ }
+
+ public static IUmbracoBuilder WithConfiguration(this IUmbracoBuilder builder)
+ => builder.AddWith(nameof(WithConfiguration), () => builder.Services.AddUmbracoConfiguration(builder.Config));
+
+ public static IUmbracoBuilder WithCore(this IUmbracoBuilder builder)
+ => builder.AddWith(nameof(WithCore), () => builder.Services.AddUmbracoCore(builder.WebHostEnvironment, out _));
+
+ public static IUmbracoBuilder WithMiniProfiler(this IUmbracoBuilder builder)
+ => builder.AddWith(nameof(WithMiniProfiler), () =>
+ builder.Services.AddMiniProfiler(options =>
+ {
+ options.ShouldProfile = request => false; // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile
+ }));
+
+ public static IUmbracoBuilder WithMvcAndRazor(this IUmbracoBuilder builder, Action mvcOptions = null, Action mvcBuilding = null)
+ => builder.AddWith(nameof(WithMvcAndRazor), () =>
+ {
+ // TODO: We need to figure out if we can work around this because calling AddControllersWithViews modifies the global app and order is very important
+ // this will directly affect developers who need to call that themselves.
+ //We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set.
+ //But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay.
+ var mvcBuilder = builder.Services.AddControllersWithViews(mvcOptions).AddRazorRuntimeCompilation();
+ mvcBuilding?.Invoke(mvcBuilder);
+ });
+
+ public static IUmbracoBuilder WithRuntimeMinifier(this IUmbracoBuilder builder)
+ => builder.AddWith(nameof(WithRuntimeMinifier), () => builder.Services.AddUmbracoRuntimeMinifier(builder.Config));
+
+ public static IUmbracoBuilder WithWebComponents(this IUmbracoBuilder builder)
+ => builder.AddWith(nameof(WithWebComponents), () => builder.Services.AddUmbracoWebComponents());
+
+ public static IUmbracoBuilder WithWebServer(this IUmbracoBuilder builder)
+ => builder.AddWith(nameof(WithWebServer), () =>
+ {
+ // TODO: We need to figure out why thsi is needed and fix those endpoints to not need them, we don't want to change global things
+ // If using Kestrel: https://stackoverflow.com/a/55196057
+ builder.Services.Configure(options =>
+ {
+ options.AllowSynchronousIO = true;
+ });
+ builder.Services.Configure(options =>
+ {
+ options.AllowSynchronousIO = true;
+ });
+ });
+ }
+}
diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
index d18275ca7f..f4ae6ed5e5 100644
--- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs
@@ -178,6 +178,31 @@ namespace Umbraco.Extensions
IRequestCache requestCache,
ILoggingConfiguration loggingConfiguration,
out IFactory factory)
+ => services.AddUmbracoCore(webHostEnvironment, umbContainer, entryAssembly, requestCache, loggingConfiguration, GetCoreRuntime, out factory);
+
+ ///
+ /// Adds the Umbraco Back Core requirements
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Delegate to create an
+ ///
+ ///
+ public static IServiceCollection AddUmbracoCore(
+ this IServiceCollection services,
+ IWebHostEnvironment webHostEnvironment,
+ IRegister umbContainer,
+ Assembly entryAssembly,
+ IRequestCache requestCache,
+ ILoggingConfiguration loggingConfiguration,
+ // TODO: Yep that's extremely ugly
+ Func getRuntime,
+ out IFactory factory)
{
if (services is null) throw new ArgumentNullException(nameof(services));
var container = umbContainer;
@@ -216,7 +241,7 @@ namespace Umbraco.Extensions
var umbracoVersion = new UmbracoVersion(globalSettings);
var typeFinder = CreateTypeFinder(logger, profiler, webHostEnvironment, entryAssembly, configs.TypeFinder());
- var coreRuntime = GetCoreRuntime(
+ var runtime = getRuntime(
configs,
umbracoVersion,
ioHelper,
@@ -228,7 +253,7 @@ namespace Umbraco.Extensions
requestCache,
dbProviderFactoryCreator);
- factory = coreRuntime.Configure(container);
+ factory = runtime.Configure(container);
return services;
}
diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
index 2c8ce61d9a..ee657e82bb 100644
--- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
+++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs
index 673ac02013..72e0d792f3 100644
--- a/src/Umbraco.Web.UI.NetCore/Startup.cs
+++ b/src/Umbraco.Web.UI.NetCore/Startup.cs
@@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Umbraco.Extensions;
+using Umbraco.Web.Common.Builder;
namespace Umbraco.Web.UI.NetCore
{
@@ -31,7 +32,8 @@ namespace Umbraco.Web.UI.NetCore
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
- services.AddUmbraco(_env, _config);
+ var umbracoBuilder = services.AddUmbraco(_env, _config);
+ umbracoBuilder.BuildWithAllBackOfficeComponents();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.