Merge pull request #9556 from rustybox/netcore/feature/core-component-cleanup-low-hanging-fruit

Netcore: composer / component cleanup low hanging fruit
This commit is contained in:
Bjarke Berg
2020-12-18 15:40:50 +01:00
committed by GitHub
30 changed files with 391 additions and 416 deletions

1
.gitignore vendored
View File

@@ -191,6 +191,7 @@ src/Umbraco.Web.UI/Umbraco/telemetrics-id.umb
/src/Umbraco.Web.UI.NetCore/App_Data/Smidge/Cache/*
/src/Umbraco.Web.UI.NetCore/umbraco/logs
src/Umbraco.Tests.Integration/umbraco/Data/
src/Umbraco.Tests.Integration/umbraco/logs/
src/Umbraco.Tests.Integration/Views/

View File

@@ -1,6 +1,7 @@
// Copyright (c) Umbraco.
// See LICENSE for more details.
using System;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Events;
@@ -26,5 +27,22 @@ namespace Umbraco.Core.DependencyInjection
builder.Services.AddTransient(typeof(INotificationHandler<TNotification>), typeof(TNotificationHandler));
return builder;
}
/// <summary>
/// Registers a notification handler against the Umbraco service collection.
/// </summary>
/// <typeparam name="TNotification">The type of notification.</typeparam>
/// <typeparam name="TNotificationHandler">The type of notificiation handler.</typeparam>
/// <param name="builder">The Umbraco builder.</param>
/// <param name="factory">Factory method</param>
/// <returns>The <see cref="IUmbracoBuilder"/>.</returns>
public static IUmbracoBuilder AddNotificationHandler<TNotification, TNotificationHandler>(this IUmbracoBuilder builder, Func<IServiceProvider, TNotificationHandler> factory)
where TNotificationHandler : class, INotificationHandler<TNotification>
where TNotification : INotification
{
// Register the handler as transient. This ensures that anything can be injected into it.
builder.Services.AddTransient(typeof(INotificationHandler<TNotification>), factory);
return builder;
}
}
}

View File

@@ -0,0 +1,16 @@
namespace Umbraco.Core.Events
{
public class UmbracoApplicationStarting : INotification
{
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoApplicationStarting"/> class.
/// </summary>
/// <param name="runtimeLevel">The runtime level</param>
public UmbracoApplicationStarting(RuntimeLevel runtimeLevel) => RuntimeLevel = runtimeLevel;
/// <summary>
/// Gets the runtime level of execution.
/// </summary>
public RuntimeLevel RuntimeLevel { get; }
}
}

View File

@@ -0,0 +1,4 @@
namespace Umbraco.Core.Events
{
public class UmbracoApplicationStopping : INotification { }
}

View File

@@ -1,22 +1,15 @@
using System;
using Microsoft.Extensions.Hosting;
namespace Umbraco.Core
{
/// <summary>
/// Defines the Umbraco runtime.
/// </summary>
public interface IRuntime
public interface IRuntime : IHostedService
{
/// <summary>
/// Gets the runtime state.
/// </summary>
IRuntimeState State { get; }
void Start();
/// <summary>
/// Terminates the runtime.
/// </summary>
void Terminate();
}
}

View File

@@ -16,6 +16,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.8" />
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.8" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />

View File

@@ -1,51 +0,0 @@
using System.IO;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Composing;
using Umbraco.Core.Hosting;
using Umbraco.Core.Manifest;
using Umbraco.Net;
namespace Umbraco.Core.Compose
{
public sealed class ManifestWatcherComponent : IComponent
{
private readonly IHostingEnvironment _hosting;
private readonly ILoggerFactory _loggerFactory;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
// 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 ManifestWatcherComponent(IHostingEnvironment hosting, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime)
{
_hosting = hosting;
_loggerFactory = loggerFactory;
_hostingEnvironment = hostingEnvironment;
_umbracoApplicationLifetime = umbracoApplicationLifetime;
}
public void Initialize()
{
if (_hosting.IsDebugMode == false) return;
//if (ApplicationContext.Current.IsConfigured == false || GlobalSettings.DebugMode == false)
// return;
var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins);
if (Directory.Exists(appPlugins) == false) return;
_mw = new ManifestWatcher(_loggerFactory.CreateLogger<ManifestWatcher>(), _umbracoApplicationLifetime);
_mw.Start(Directory.GetDirectories(appPlugins));
}
public void Terminate()
{
if (_mw == null) return;
_mw.Dispose();
_mw = null;
}
}
}

View File

@@ -1,7 +0,0 @@
using Umbraco.Core.Composing;
namespace Umbraco.Core.Compose
{
public class ManifestWatcherComposer : ComponentComposer<ManifestWatcherComponent>, ICoreComposer
{ }
}

View File

@@ -1,20 +0,0 @@
using Microsoft.Extensions.Hosting;
namespace Umbraco.Core.Composing
{
/// <summary>
/// Extends the <see cref="IHostBuilder"/> to enable Umbraco to be used as the service container.
/// </summary>
public static class HostBuilderExtensions
{
/// <summary>
/// Assigns a custom service provider factory to use Umbraco's container
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IHostBuilder UseUmbraco(this IHostBuilder builder)
{
return builder;
}
}
}

View File

@@ -1,10 +1,10 @@
using System;
using System;
using Examine;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Composing.CompositionExtensions;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Grid;
@@ -62,15 +62,17 @@ using Umbraco.Web.Templates;
using Umbraco.Web.Trees;
using TextStringValueConverter = Umbraco.Core.PropertyEditors.ValueConverters.TextStringValueConverter;
namespace Umbraco.Core.Runtime
namespace Umbraco.Infrastructure.Runtime
{
// core's initial composer composes before all core composers
[ComposeBefore(typeof(ICoreComposer))]
public class CoreInitialComposer : ComponentComposer<CoreInitialComponent>
public static class CoreInitialServices
{
public override void Compose(IUmbracoBuilder builder)
public static IUmbracoBuilder AddCoreInitialServices(this IUmbracoBuilder builder)
{
base.Compose(builder);
builder.AddNotificationHandler<UmbracoApplicationStarting, EssentialDirectoryCreator>();
builder.Services.AddSingleton<ManifestWatcher>();
builder.AddNotificationHandler<UmbracoApplicationStarting, ManifestWatcher>(factory => factory.GetRequiredService<ManifestWatcher>());
builder.AddNotificationHandler<UmbracoApplicationStopping, ManifestWatcher>(factory => factory.GetRequiredService<ManifestWatcher>());
// composers
builder
@@ -302,7 +304,7 @@ namespace Umbraco.Core.Runtime
// register *all* checks, except those marked [HideFromTypeFinder] of course
builder.Services.AddUnique<IMarkdownToHtmlConverter, MarkdownToHtmlConverter>();
builder.HealthChecks()
.Add(() => builder.TypeLoader.GetTypes<HealthCheck.HealthCheck>());
.Add(() => builder.TypeLoader.GetTypes<Core.HealthCheck.HealthCheck>());
builder.WithCollectionBuilder<HealthCheckNotificationMethodCollectionBuilder>()
.Add(() => builder.TypeLoader.GetTypes<IHealthCheckNotificationMethod>());
@@ -382,7 +384,10 @@ namespace Umbraco.Core.Runtime
builder.Services.AddUnique<MediaPermissions>();
builder.Services.AddUnique<IImageDimensionExtractor, ImageDimensionExtractor>();
builder.Services.AddUnique<PackageDataInstallation>();
return builder;
}
}
}

View File

@@ -1,7 +1,10 @@
using System;
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
@@ -10,71 +13,102 @@ namespace Umbraco.Infrastructure.Runtime
{
public class CoreRuntime : IRuntime
{
public IRuntimeState State { get; }
private readonly ILogger<CoreRuntime> _logger;
private readonly ILoggerFactory _loggerFactory;
private readonly ComponentCollection _components;
private readonly IApplicationShutdownRegistry _applicationShutdownRegistry;
private readonly IProfilingLogger _profilingLogger;
private readonly IMainDom _mainDom;
private readonly IUmbracoDatabaseFactory _databaseFactory;
private readonly IEventAggregator _eventAggregator;
private readonly IHostingEnvironment _hostingEnvironment;
public CoreRuntime(
ILogger<CoreRuntime> logger,
ILoggerFactory loggerFactory,
IRuntimeState state,
ComponentCollection components,
IApplicationShutdownRegistry applicationShutdownRegistry,
IProfilingLogger profilingLogger,
IMainDom mainDom,
IUmbracoDatabaseFactory databaseFactory)
IUmbracoDatabaseFactory databaseFactory,
IEventAggregator eventAggregator,
IHostingEnvironment hostingEnvironment)
{
State = state;
_logger = logger;
_loggerFactory = loggerFactory;
_components = components;
_applicationShutdownRegistry = applicationShutdownRegistry;
_profilingLogger = profilingLogger;
_mainDom = mainDom;
_databaseFactory = databaseFactory;
}
_eventAggregator = eventAggregator;
_hostingEnvironment = hostingEnvironment;
public void Start()
_logger = _loggerFactory.CreateLogger<CoreRuntime>();
}
/// <summary>
/// Gets the state of the Umbraco runtime.
/// </summary>
public IRuntimeState State { get; }
/// <inheritdoc/>
public async Task StartAsync(CancellationToken cancellationToken)
{
StaticApplicationLogging.Initialize(_loggerFactory);
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
{
var exception = (Exception)args.ExceptionObject;
var isTerminating = args.IsTerminating; // always true?
var msg = "Unhandled exception in AppDomain";
if (isTerminating) msg += " (terminating)";
if (isTerminating)
{
msg += " (terminating)";
}
msg += ".";
_logger.LogError(exception, msg);
};
AppDomain.CurrentDomain.SetData("DataDirectory", _hostingEnvironment?.MapPathContentRoot(Core.Constants.SystemDirectories.Data));
DetermineRuntimeLevel();
if (State.Level <= RuntimeLevel.BootFailed)
{
throw new InvalidOperationException($"Cannot start the runtime if the runtime level is less than or equal to {RuntimeLevel.BootFailed}");
}
var hostingEnvironmentLifetime = _applicationShutdownRegistry;
IApplicationShutdownRegistry hostingEnvironmentLifetime = _applicationShutdownRegistry;
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)}");
{
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(StartAsync)}");
}
// acquire the main domain - if this fails then anything that should be registered with MainDom will not operate
AcquireMainDom();
await _eventAggregator.PublishAsync(new UmbracoApplicationStarting(State.Level), cancellationToken);
// create & initialize the components
_components.Initialize();
}
public void Terminate()
public async Task StopAsync(CancellationToken cancellationToken)
{
_components.Terminate();
await _eventAggregator.PublishAsync(new UmbracoApplicationStopping(), cancellationToken);
StaticApplicationLogging.Initialize(null);
}
private void AcquireMainDom()
{
using (var timer = _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired."))
using (DisposableTimer timer = _profilingLogger.DebugDuration<CoreRuntime>("Acquiring MainDom.", "Acquired."))
{
try
{
@@ -90,7 +124,7 @@ namespace Umbraco.Infrastructure.Runtime
private void DetermineRuntimeLevel()
{
using var timer = _profilingLogger.DebugDuration<CoreRuntime>("Determining runtime level.", "Determined.");
using DisposableTimer timer = _profilingLogger.DebugDuration<CoreRuntime>("Determining runtime level.", "Determined.");
try
{

View File

@@ -1,25 +1,28 @@
using Microsoft.Extensions.Options;
using Umbraco.Core.Composing;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
namespace Umbraco.Core.Runtime
namespace Umbraco.Infrastructure.Runtime
{
public class CoreInitialComponent : IComponent
public class EssentialDirectoryCreator : INotificationHandler<UmbracoApplicationStarting>
{
private readonly IIOHelper _ioHelper;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly GlobalSettings _globalSettings;
public CoreInitialComponent(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IOptions<GlobalSettings> globalSettings)
public EssentialDirectoryCreator(IIOHelper ioHelper, IHostingEnvironment hostingEnvironment, IOptions<GlobalSettings> globalSettings)
{
_ioHelper = ioHelper;
_hostingEnvironment = hostingEnvironment;
_globalSettings = globalSettings.Value;
}
public void Initialize()
public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
{
// ensure we have some essential directories
// every other component can then initialize safely
@@ -28,9 +31,8 @@ namespace Umbraco.Core.Runtime
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MvcViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.PartialViews));
_ioHelper.EnsurePathExists(_hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.MacroPartials));
}
public void Terminate()
{ }
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,65 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Hosting;
using Umbraco.Net;
namespace Umbraco.Infrastructure.Runtime
{
public sealed class ManifestWatcher :
INotificationHandler<UmbracoApplicationStarting>,
INotificationHandler<UmbracoApplicationStopping>
{
private readonly IHostingEnvironment _hosting;
private readonly ILoggerFactory _loggerFactory;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IUmbracoApplicationLifetime _umbracoApplicationLifetime;
// if configured and in debug mode, a ManifestWatcher watches App_Plugins folders for
// package.manifest chances and restarts the application on any change
private Core.Manifest.ManifestWatcher _mw;
public ManifestWatcher(IHostingEnvironment hosting, ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment, IUmbracoApplicationLifetime umbracoApplicationLifetime)
{
_hosting = hosting;
_loggerFactory = loggerFactory;
_hostingEnvironment = hostingEnvironment;
_umbracoApplicationLifetime = umbracoApplicationLifetime;
}
public Task HandleAsync(UmbracoApplicationStarting notification, CancellationToken cancellationToken)
{
if (_hosting.IsDebugMode == false)
{
return Task.CompletedTask;
}
var appPlugins = _hostingEnvironment.MapPathContentRoot(Constants.SystemDirectories.AppPlugins);
if (Directory.Exists(appPlugins) == false)
{
return Task.CompletedTask;
}
_mw = new Core.Manifest.ManifestWatcher(_loggerFactory.CreateLogger<Core.Manifest.ManifestWatcher>(), _umbracoApplicationLifetime);
_mw.Start(Directory.GetDirectories(appPlugins));
return Task.CompletedTask;
}
public Task HandleAsync(UmbracoApplicationStopping notification, CancellationToken cancellationToken)
{
if (_mw == null)
{
return Task.CompletedTask;
}
_mw.Dispose();
_mw = null;
return Task.CompletedTask;
}
}
}

View File

@@ -135,16 +135,16 @@ namespace Umbraco.Web.PublishedCache.NuCache
_entitySerializer = entitySerializer;
_publishedModelFactory = publishedModelFactory;
// we always want to handle repository events, configured or not
// assuming no repository event will trigger before the whole db is ready
// (ideally we'd have Upgrading.App vs Upgrading.Data application states...)
InitializeRepositoryEvents();
_lifeTime.ApplicationInit += OnApplicationInit;
}
internal void OnApplicationInit(object sender, EventArgs e)
{
// we always want to handle repository events, configured or not
// assuming no repository event will trigger before the whole db is ready
// (ideally we'd have Upgrading.App vs Upgrading.Data application states...)
InitializeRepositoryEvents();
// however, the cache is NOT available until we are configured, because loading
// content (and content types) from database cannot be consistent (see notes in "Handle
// Notifications" region), so

View File

@@ -33,54 +33,6 @@ namespace Umbraco.Tests.Integration
MyComposer.Reset();
}
/// <summary>
/// Calling AddUmbracoCore to configure the container
/// </summary>
[Test]
public async Task AddUmbracoCore()
{
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
.UseUmbraco()
.ConfigureServices((hostContext, services) =>
{
var webHostEnvironment = testHelper.GetWebHostEnvironment();
services.AddSingleton(testHelper.DbProviderFactoryCreator);
services.AddRequiredNetCoreServices(testHelper, webHostEnvironment);
// Add it!
var typeLoader = services.AddTypeLoader(
GetType().Assembly,
webHostEnvironment,
testHelper.GetHostingEnvironment(),
testHelper.ConsoleLoggerFactory,
AppCaches.NoCache,
hostContext.Configuration,
testHelper.Profiler);
var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader, testHelper.ConsoleLoggerFactory);
builder.Services.AddUnique<AppCaches>(AppCaches.NoCache);
builder.AddConfiguration();
builder.AddUmbracoCore();
});
var host = await hostBuilder.StartAsync();
var app = new ApplicationBuilder(host.Services);
// assert results
var runtimeState = app.ApplicationServices.GetRequiredService<IRuntimeState>();
var mainDom = app.ApplicationServices.GetRequiredService<IMainDom>();
Assert.IsFalse(mainDom.IsMainDom); // We haven't "Started" the runtime yet
Assert.IsNull(runtimeState.BootFailedException);
Assert.IsFalse(MyComponent.IsInit); // We haven't "Started" the runtime yet
await host.StopAsync();
Assert.IsFalse(MyComponent.IsTerminated); // we didn't "Start" the runtime so nothing was registered for shutdown
}
/// <summary>
/// Calling AddUmbracoCore to configure the container and UseUmbracoCore to start the runtime
/// </summary>
@@ -91,7 +43,6 @@ namespace Umbraco.Tests.Integration
var testHelper = new TestHelper();
var hostBuilder = new HostBuilder()
.UseUmbraco()
.ConfigureServices((hostContext, services) =>
{
var webHostEnvironment = testHelper.GetWebHostEnvironment();
@@ -109,11 +60,12 @@ namespace Umbraco.Tests.Integration
hostContext.Configuration,
testHelper.Profiler);
var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader, testHelper.ConsoleLoggerFactory);
var builder = new UmbracoBuilder(services, hostContext.Configuration, typeLoader,
testHelper.ConsoleLoggerFactory);
builder.Services.AddUnique<AppCaches>(AppCaches.NoCache);
builder.AddConfiguration()
.AddUmbracoCore()
.Build();
.AddUmbracoCore()
.Build();
services.AddRouting(); // LinkGenerator
});

View File

@@ -1,8 +1,6 @@

using System;
using System.Linq.Expressions;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -11,7 +9,6 @@ using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Extensions;
@@ -22,8 +19,6 @@ using Umbraco.Core.DependencyInjection;
using Umbraco.Web.Common.Controllers;
using Microsoft.Extensions.Hosting;
using Umbraco.Core.Cache;
using Umbraco.Core.Persistence;
using Umbraco.Core.Runtime;
using Umbraco.Web.BackOffice.Controllers;
namespace Umbraco.Tests.Integration.TestServerTest
@@ -39,7 +34,7 @@ namespace Umbraco.Tests.Integration.TestServerTest
InMemoryConfiguration["Umbraco:CMS:Hosting:Debug"] = "true";
// create new WebApplicationFactory specifying 'this' as the IStartup instance
var factory = new UmbracoWebApplicationFactory<UmbracoTestServerTestBase>(CreateHostBuilder);
var factory = new UmbracoWebApplicationFactory<UmbracoTestServerTestBase>(CreateHostBuilder, BeforeHostStart);
// additional host configuration for web server integration tests
Factory = factory.WithWebHostBuilder(builder =>
@@ -75,11 +70,10 @@ namespace Umbraco.Tests.Integration.TestServerTest
// call startup
builder.Configure(app =>
{
UseTestLocalDb(app.ApplicationServices);
Services = app.ApplicationServices;
Configure(app);
});
}).UseEnvironment(Environments.Development);
}).UseEnvironment(Environments.Development);
return builder;
}
@@ -119,15 +113,6 @@ namespace Umbraco.Tests.Integration.TestServerTest
protected LinkGenerator LinkGenerator { get; private set; }
protected WebApplicationFactory<UmbracoTestServerTestBase> Factory { get; private set; }
[TearDown]
public override void TearDown()
{
base.TearDown();
base.TerminateCoreRuntime();
Factory.Dispose();
}
#region IStartup
public override void ConfigureServices(IServiceCollection services)
@@ -162,7 +147,5 @@ namespace Umbraco.Tests.Integration.TestServerTest
}
#endregion
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.Hosting;
@@ -8,16 +8,30 @@ namespace Umbraco.Tests.Integration.TestServerTest
public class UmbracoWebApplicationFactory<TStartup> : WebApplicationFactory<TStartup> where TStartup : class
{
private readonly Func<IHostBuilder> _createHostBuilder;
private readonly Action<IHost> _beforeStart;
/// <summary>
/// Constructor to create a new WebApplicationFactory
/// </summary>
/// <param name="createHostBuilder">Method to create the IHostBuilder</param>
public UmbracoWebApplicationFactory(Func<IHostBuilder> createHostBuilder)
/// <param name="beforeStart">Method to perform an action before IHost starts</param>
public UmbracoWebApplicationFactory(Func<IHostBuilder> createHostBuilder, Action<IHost> beforeStart = null)
{
_createHostBuilder = createHostBuilder;
_beforeStart = beforeStart;
}
protected override IHostBuilder CreateHostBuilder() => _createHostBuilder();
protected override IHost CreateHost(IHostBuilder builder)
{
IHost host = builder.Build();
_beforeStart?.Invoke(host);
host.Start();
return host;
}
}
}

View File

@@ -17,9 +17,11 @@ namespace Umbraco.Tests.Integration.Testing
{
protected ILoggerFactory _loggerFactory;
protected IUmbracoDatabaseFactory _databaseFactory;
protected IEnumerable<TestDbMeta> _testDatabases;
protected UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands;
protected IList<TestDbMeta> _testDatabases;
protected const int _threadCount = 2;
protected UmbracoDatabase.CommandInfo[] _cachedDatabaseInitCommands = new UmbracoDatabase.CommandInfo[0];
protected BlockingCollection<TestDbMeta> _prepareQueue;
protected BlockingCollection<TestDbMeta> _readySchemaQueue;
@@ -92,46 +94,52 @@ namespace Umbraco.Tests.Integration.Testing
});
}
protected void RebuildSchema(IDbCommand command, TestDbMeta meta)
private void RebuildSchema(IDbCommand command, TestDbMeta meta)
{
if (_cachedDatabaseInitCommands != null)
lock (_cachedDatabaseInitCommands)
{
foreach (var dbCommand in _cachedDatabaseInitCommands)
if (!_cachedDatabaseInitCommands.Any())
{
if (dbCommand.Text.StartsWith("SELECT "))
{
continue;
}
command.CommandText = dbCommand.Text;
command.Parameters.Clear();
foreach (var parameterInfo in dbCommand.Parameters)
{
AddParameter(command, parameterInfo);
}
command.ExecuteNonQuery();
RebuildSchemaFirstTime(command, meta);
return;
}
}
else
foreach (var dbCommand in _cachedDatabaseInitCommands)
{
_databaseFactory.Configure(meta.ConnectionString, Core.Constants.DatabaseProviders.SqlServer);
using (var database = (UmbracoDatabase)_databaseFactory.CreateDatabase())
if (dbCommand.Text.StartsWith("SELECT "))
{
database.LogCommands = true;
continue;
}
using (var transaction = database.GetTransaction())
{
var schemaCreator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger<DatabaseSchemaCreator>(), _loggerFactory, new UmbracoVersion());
schemaCreator.InitializeDatabaseSchema();
command.CommandText = dbCommand.Text;
command.Parameters.Clear();
transaction.Complete();
foreach (var parameterInfo in dbCommand.Parameters)
{
AddParameter(command, parameterInfo);
}
_cachedDatabaseInitCommands = database.Commands.ToArray();
}
command.ExecuteNonQuery();
}
}
private void RebuildSchemaFirstTime(IDbCommand command, TestDbMeta meta)
{
_databaseFactory.Configure(meta.ConnectionString, Core.Constants.DatabaseProviders.SqlServer);
using (var database = (UmbracoDatabase)_databaseFactory.CreateDatabase())
{
database.LogCommands = true;
using (var transaction = database.GetTransaction())
{
var schemaCreator = new DatabaseSchemaCreator(database, _loggerFactory.CreateLogger<DatabaseSchemaCreator>(), _loggerFactory, new UmbracoVersion());
schemaCreator.InitializeDatabaseSchema();
transaction.Complete();
_cachedDatabaseInitCommands = database.Commands.ToArray();
}
}
}

View File

@@ -19,8 +19,6 @@ namespace Umbraco.Tests.Integration.Testing
private static LocalDb.Instance _localDbInstance;
private static string _filesPath;
private const int _threadCount = 2;
public static LocalDbTestDatabase Instance { get; private set; }
//It's internal because `Umbraco.Core.Persistence.LocalDb` is internal
@@ -64,13 +62,17 @@ namespace Umbraco.Tests.Integration.Testing
var tempName = Guid.NewGuid().ToString("N");
_localDbInstance.CreateDatabase(tempName, _filesPath);
_localDbInstance.DetachDatabase(tempName);
_prepareQueue = new BlockingCollection<TestDbMeta>();
_readySchemaQueue = new BlockingCollection<TestDbMeta>();
_readyEmptyQueue = new BlockingCollection<TestDbMeta>();
foreach (var meta in _testDatabases)
for (var i = 0; i < _testDatabases.Count; i++)
{
_localDb.CopyDatabaseFiles(tempName, _filesPath, targetDatabaseName: meta.Name, overwrite: true, delete: false);
var meta = _testDatabases[i];
var isLast = i == _testDatabases.Count - 1;
_localDb.CopyDatabaseFiles(tempName, _filesPath, targetDatabaseName: meta.Name, overwrite: true, delete: isLast);
meta.ConnectionString = _localDbInstance.GetAttachedConnectionString(meta.Name, _filesPath);
_prepareQueue.Add(meta);
}
@@ -85,7 +87,9 @@ namespace Umbraco.Tests.Integration.Testing
public void Finish()
{
if (_prepareQueue == null)
{
return;
}
_prepareQueue.CompleteAdding();
while (_prepareQueue.TryTake(out _))
@@ -100,14 +104,18 @@ namespace Umbraco.Tests.Integration.Testing
{ }
if (_filesPath == null)
{
return;
}
var filename = Path.Combine(_filesPath, DatabaseName).ToUpper();
foreach (var database in _localDbInstance.GetDatabases())
{
if (database.StartsWith(filename))
{
_localDbInstance.DropDatabase(database);
}
}
foreach (var file in Directory.EnumerateFiles(_filesPath))

View File

@@ -17,8 +17,6 @@ namespace Umbraco.Tests.Integration.Testing
private readonly string _masterConnectionString;
public const string DatabaseName = "UmbracoTests";
private const int _threadCount = 2;
public static SqlDeveloperTestDatabase Instance { get; private set; }
public SqlDeveloperTestDatabase(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory databaseFactory, string masterConnectionString)

View File

@@ -1,4 +1,5 @@
using System;
using System.IO;
using Microsoft.Extensions.Logging;
using Umbraco.Core.Persistence;
@@ -8,13 +9,20 @@ namespace Umbraco.Tests.Integration.Testing
{
public static ITestDatabase Create(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
{
return string.IsNullOrEmpty(Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString"))
? CreateLocalDb(filesPath, loggerFactory, dbFactory.Create())
: CreateSqlDeveloper(loggerFactory, dbFactory.Create());
var connectionString = Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString");
return string.IsNullOrEmpty(connectionString)
? CreateLocalDb(filesPath, loggerFactory, dbFactory)
: CreateSqlDeveloper(loggerFactory, dbFactory, connectionString);
}
private static ITestDatabase CreateLocalDb(string filesPath, ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
private static ITestDatabase CreateLocalDb(string filesPath, ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory)
{
if (!Directory.Exists(filesPath))
{
Directory.CreateDirectory(filesPath);
}
var localDb = new LocalDb();
if (!localDb.IsAvailable)
@@ -22,22 +30,21 @@ namespace Umbraco.Tests.Integration.Testing
throw new InvalidOperationException("LocalDB is not available.");
}
return new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory);
return new LocalDbTestDatabase(loggerFactory, localDb, filesPath, dbFactory.Create());
}
private static ITestDatabase CreateSqlDeveloper(ILoggerFactory loggerFactory, IUmbracoDatabaseFactory dbFactory)
private static ITestDatabase CreateSqlDeveloper(ILoggerFactory loggerFactory, TestUmbracoDatabaseFactoryProvider dbFactory, string connectionString)
{
// NOTE: Example setup for Linux box.
// $ export SA_PASSWORD=Foobar123!
// $ export UmbracoIntegrationTestConnectionString="Server=localhost,1433;User Id=sa;Password=$SA_PASSWORD;"
// $ docker run -e 'ACCEPT_EULA=Y' -e "SA_PASSWORD=$SA_PASSWORD" -e 'MSSQL_PID=Developer' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2017-latest-ubuntu
var connectionString = Environment.GetEnvironmentVariable("UmbracoIntegrationTestConnectionString");
if (string.IsNullOrEmpty(connectionString))
{
throw new InvalidOperationException("ENV: UmbracoIntegrationTestConnectionString is not set");
}
return new SqlDeveloperTestDatabase(loggerFactory, dbFactory, connectionString);
return new SqlDeveloperTestDatabase(loggerFactory, dbFactory.Create(), connectionString);
}
}
}

View File

@@ -71,9 +71,14 @@ namespace Umbraco.Tests.Integration.Testing
foreach (var a in _testTeardown)
a();
}
_testTeardown = null;
FirstTestInFixture = false;
FirstTestInSession = false;
// Ensure CoreRuntime stopped (now it's a HostedService)
IHost host = Services.GetRequiredService<IHost>();
host.StopAsync().GetAwaiter().GetResult();
}
[TearDown]
@@ -94,14 +99,18 @@ namespace Umbraco.Tests.Integration.Testing
InMemoryConfiguration[Constants.Configuration.ConfigGlobal + ":" + nameof(GlobalSettings.InstallEmptyDatabase)] = "true";
var hostBuilder = CreateHostBuilder();
var host = hostBuilder.Start();
IHost host = hostBuilder.Build();
BeforeHostStart(host);
host.Start();
Services = host.Services;
var app = new ApplicationBuilder(host.Services);
Configure(app);
}
OnFixtureTearDown(() => host.Dispose());
protected void BeforeHostStart(IHost host)
{
Services = host.Services;
UseTestDatabase(Services);
}
#region Generic Host Builder and Runtime
@@ -110,13 +119,21 @@ namespace Umbraco.Tests.Integration.Testing
{
try
{
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
switch (testOptions.Logger)
switch (TestOptions.Logger)
{
case UmbracoTestOptions.Logger.Mock:
return NullLoggerFactory.Instance;
case UmbracoTestOptions.Logger.Serilog:
return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddSerilog(); });
return Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
{
var path = Path.Combine(TestHelper.WorkingDirectory, "logs", "umbraco_integration_tests_.txt");
Log.Logger = new LoggerConfiguration()
.WriteTo.File(path, rollingInterval: RollingInterval.Day)
.CreateLogger();
builder.AddSerilog(Log.Logger);
});
case UmbracoTestOptions.Logger.Console:
return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { builder.AddConsole(); });
}
@@ -141,7 +158,6 @@ namespace Umbraco.Tests.Integration.Testing
// 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()
.ConfigureAppConfiguration((context, configBuilder) =>
{
context.HostingEnvironment = TestHelper.GetWebHostEnvironment();
@@ -152,8 +168,15 @@ namespace Umbraco.Tests.Integration.Testing
})
.ConfigureServices((hostContext, services) =>
{
services.AddTransient(_ => CreateLoggerFactory());
ConfigureServices(services);
services.AddUnique(CreateLoggerFactory());
if (!TestOptions.Boot)
{
// If boot is false, we don't want the CoreRuntime hosted service to start
// So we replace it with a Mock
services.AddUnique(Mock.Of<IRuntime>());
}
});
return hostBuilder;
}
@@ -213,29 +236,13 @@ namespace Umbraco.Tests.Integration.Testing
public virtual void Configure(IApplicationBuilder app)
{
UseTestLocalDb(app.ApplicationServices);
//get the currently set options
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
if (testOptions.Boot)
if (TestOptions.Boot)
{
Services.GetRequiredService<IBackOfficeSecurityFactory>().EnsureBackOfficeSecurity();
Services.GetRequiredService<IUmbracoContextFactory>().EnsureUmbracoContext();
app.UseUmbracoCore(); // Takes 200 ms
OnTestTearDown(TerminateCoreRuntime);
}
}
/// <remarks>
/// Some IComponents hook onto static events (e.g. Published in ContentService)
/// If these fire after the components host has been shutdown, errors can occur.
/// If CoreRuntime.Start() is called We also need to de-register the events.
/// </remarks>
protected void TerminateCoreRuntime()
{
Services.GetRequiredService<IRuntime>().Terminate();
StaticApplicationLogging.Initialize(null);
app.UseUmbracoCore(); // This no longer starts CoreRuntime, it's very fast
}
#endregion
@@ -246,25 +253,20 @@ namespace Umbraco.Tests.Integration.Testing
private static ITestDatabase _dbInstance;
private static TestDbMeta _fixtureDbMeta;
protected void UseTestLocalDb(IServiceProvider serviceProvider)
protected void UseTestDatabase(IServiceProvider serviceProvider)
{
var state = serviceProvider.GetRequiredService<IRuntimeState>();
var testDatabaseFactoryProvider = serviceProvider.GetRequiredService<TestUmbracoDatabaseFactoryProvider>();
var databaseFactory = serviceProvider.GetRequiredService<IUmbracoDatabaseFactory>();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
// This will create a db, install the schema and ensure the app is configured to run
InstallTestLocalDb(testDatabaseFactoryProvider, databaseFactory, serviceProvider.GetRequiredService<ILoggerFactory>(), state, TestHelper.WorkingDirectory);
SetupTestDatabase(testDatabaseFactoryProvider, databaseFactory, loggerFactory, state, TestHelper.WorkingDirectory);
}
/// <summary>
/// Get or create an instance of <see cref="LocalDbTestDatabase"/>
/// Get or create an instance of <see cref="ITestDatabase"/>
/// </summary>
/// <param name="filesPath"></param>
/// <param name="logger"></param>
/// <param name="loggerFactory"></param>
/// <param name="globalSettings"></param>
/// <param name="dbFactory"></param>
/// <returns></returns>
/// <remarks>
/// There must only be ONE instance shared between all tests in a session
/// </remarks>
@@ -273,9 +275,12 @@ namespace Umbraco.Tests.Integration.Testing
lock (_dbLocker)
{
if (_dbInstance != null)
{
return _dbInstance;
}
_dbInstance = TestDatabaseFactory.Create(filesPath, loggerFactory, dbFactory);
return _dbInstance;
}
}
@@ -283,65 +288,47 @@ namespace Umbraco.Tests.Integration.Testing
/// <summary>
/// Creates a LocalDb instance to use for the test
/// </summary>
private void InstallTestLocalDb(
private void SetupTestDatabase(
TestUmbracoDatabaseFactoryProvider testUmbracoDatabaseFactoryProvider,
IUmbracoDatabaseFactory databaseFactory,
ILoggerFactory loggerFactory,
IRuntimeState runtimeState,
string workingDirectory)
{
var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
// get the currently set db options
var testOptions = TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
if (testOptions.Database == UmbracoTestOptions.Database.None)
if (TestOptions.Database == UmbracoTestOptions.Database.None)
{
return;
}
// need to manually register this factory
DbProviderFactories.RegisterFactory(Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance);
if (!Directory.Exists(dbFilePath))
Directory.CreateDirectory(dbFilePath);
var dbFilePath = Path.Combine(workingDirectory, "LocalDb");
var db = GetOrCreateDatabase(dbFilePath, loggerFactory, testUmbracoDatabaseFactoryProvider);
switch (testOptions.Database)
switch (TestOptions.Database)
{
case UmbracoTestOptions.Database.NewSchemaPerTest:
// New DB + Schema
var newSchemaDbMeta = db.AttachSchema();
TestDbMeta newSchemaDbMeta = db.AttachSchema();
// Add teardown callback
OnTestTearDown(() => db.Detach(newSchemaDbMeta));
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(newSchemaDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
// re-run the runtime level check
runtimeState.DetermineRuntimeLevel();
ConfigureTestDatabaseFactory(newSchemaDbMeta, databaseFactory, runtimeState);
Assert.AreEqual(RuntimeLevel.Run, runtimeState.Level);
break;
case UmbracoTestOptions.Database.NewEmptyPerTest:
var newEmptyDbMeta = db.AttachEmpty();
TestDbMeta newEmptyDbMeta = db.AttachEmpty();
// Add teardown callback
OnTestTearDown(() => db.Detach(newEmptyDbMeta));
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(newEmptyDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
// re-run the runtime level check
runtimeState.DetermineRuntimeLevel();
ConfigureTestDatabaseFactory(newEmptyDbMeta, databaseFactory, runtimeState);
Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level);
@@ -353,21 +340,14 @@ namespace Umbraco.Tests.Integration.Testing
if (FirstTestInFixture)
{
// New DB + Schema
var newSchemaFixtureDbMeta = db.AttachSchema();
TestDbMeta newSchemaFixtureDbMeta = db.AttachSchema();
_fixtureDbMeta = newSchemaFixtureDbMeta;
// Add teardown callback
OnFixtureTearDown(() => db.Detach(newSchemaFixtureDbMeta));
}
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(_fixtureDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
// re-run the runtime level check
runtimeState.DetermineRuntimeLevel();
ConfigureTestDatabaseFactory(_fixtureDbMeta, databaseFactory, runtimeState);
break;
case UmbracoTestOptions.Database.NewEmptyPerFixture:
@@ -377,29 +357,40 @@ namespace Umbraco.Tests.Integration.Testing
if (FirstTestInFixture)
{
// New DB + Schema
var newEmptyFixtureDbMeta = db.AttachEmpty();
TestDbMeta newEmptyFixtureDbMeta = db.AttachEmpty();
_fixtureDbMeta = newEmptyFixtureDbMeta;
// Add teardown callback
OnFixtureTearDown(() => db.Detach(newEmptyFixtureDbMeta));
}
// We must re-configure our current factory since attaching a new LocalDb from the pool changes connection strings
if (!databaseFactory.Configured)
{
databaseFactory.Configure(_fixtureDbMeta.ConnectionString, Constants.DatabaseProviders.SqlServer);
}
ConfigureTestDatabaseFactory(_fixtureDbMeta, databaseFactory, runtimeState);
break;
default:
throw new ArgumentOutOfRangeException(nameof(testOptions), testOptions, null);
throw new ArgumentOutOfRangeException(nameof(TestOptions), TestOptions, null);
}
}
private void ConfigureTestDatabaseFactory(TestDbMeta meta, IUmbracoDatabaseFactory factory, IRuntimeState state)
{
ILogger<UmbracoIntegrationTest> log = Services.GetRequiredService<ILogger<UmbracoIntegrationTest>>();
log.LogInformation($"ConfigureTestDatabaseFactory - Using test database: [{meta.Name}] - IsEmpty: [{meta.IsEmpty}]");
// It's just been pulled from container and wasn't used to create test database
Assert.IsFalse(factory.Configured);
factory.Configure(meta.ConnectionString, Constants.DatabaseProviders.SqlServer);
state.DetermineRuntimeLevel();
log.LogInformation($"ConfigureTestDatabaseFactory - Determined RuntimeLevel: [{state.Level}]");
}
#endregion
#region Common services
protected UmbracoTestAttribute TestOptions => TestOptionAttributeBase.GetTestOptions<UmbracoTestAttribute>();
protected virtual T GetRequiredService<T>() => Services.GetRequiredService<T>();
public Dictionary<string, string> InMemoryConfiguration { get; } = new Dictionary<string, string>();
@@ -429,7 +420,6 @@ namespace Umbraco.Tests.Integration.Testing
/// Returns the <see cref="ILoggerFactory"/>
/// </summary>
protected ILoggerFactory LoggerFactory => Services.GetRequiredService<ILoggerFactory>();
protected AppCaches AppCaches => Services.GetRequiredService<AppCaches>();
protected IIOHelper IOHelper => Services.GetRequiredService<IIOHelper>();
protected IShortStringHelper ShortStringHelper => Services.GetRequiredService<IShortStringHelper>();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -10,10 +10,12 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
using Umbraco.Net;
using Umbraco.Tests.Common.Builders;
using Umbraco.Tests.Integration.Testing;
using Umbraco.Tests.Testing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.PublishedCache.NuCache;
namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
{
@@ -510,7 +512,9 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
{
// one simple content type, variant, with both variant and invariant properties
// can change it to invariant and back
GetRequiredService<IPublishedSnapshotService>(); //hack to ensure events are initialized
//hack to ensure events are initialized
(GetRequiredService<IPublishedSnapshotService>() as PublishedSnapshotService)?.OnApplicationInit(null, null);
CreateFrenchAndEnglishLangs();
var contentType = CreateContentType(ContentVariation.Culture);
@@ -599,7 +603,9 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
// one simple content type, invariant
// can change it to variant and back
// can then switch one property to variant
GetRequiredService<IPublishedSnapshotService>(); //hack to ensure events are initialized
//hack to ensure events are initialized
(GetRequiredService<IPublishedSnapshotService>() as PublishedSnapshotService)?.OnApplicationInit(null, null);
var globalSettings = new GlobalSettings();
@@ -690,7 +696,9 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
GetRequiredService<IPublishedSnapshotService>(); //hack to ensure events are initialized
//hack to ensure events are initialized
(GetRequiredService<IPublishedSnapshotService>() as PublishedSnapshotService)?.OnApplicationInit(null, null);
CreateFrenchAndEnglishLangs();
var contentType = CreateContentType(ContentVariation.Culture);
@@ -967,8 +975,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
// can change the composing content type to invariant and back
// can change the composed content type to invariant and back
GetRequiredService<IPublishedSnapshotService>(); //hack to ensure events are initialized
//hack to ensure events are initialized
(GetRequiredService<IPublishedSnapshotService>() as PublishedSnapshotService)?.OnApplicationInit(null, null);
CreateFrenchAndEnglishLangs();
var composing = CreateContentType(ContentVariation.Culture, "composing");
@@ -1064,7 +1072,8 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
// can change the composing content type to invariant and back
// can change the variant composed content type to invariant and back
GetRequiredService<IPublishedSnapshotService>(); //hack to ensure events are initialized
//hack to ensure events are initialized
(GetRequiredService<IPublishedSnapshotService>() as PublishedSnapshotService)?.OnApplicationInit(null, null);
CreateFrenchAndEnglishLangs();
var composing = CreateContentType(ContentVariation.Culture, "composing");

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
@@ -31,7 +32,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
[TestFixture]
[Apartment(ApartmentState.STA)]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
public class ThreadSafetyServiceTest : UmbracoIntegrationTest
{
private IContentService ContentService => GetRequiredService<IContentService>();
@@ -98,13 +99,15 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
if (Environment.GetEnvironmentVariable("UMBRACO_TMP") != null)
Assert.Ignore("Do not run on VSTS.");
var log = GetRequiredService<ILogger<ThreadSafetyServiceTest>>();
// the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton
var contentService = (ContentService)ContentService;
var threads = new List<Thread>();
var exceptions = new List<Exception>();
Debug.WriteLine("Starting...");
log.LogInformation("Starting...");
var done = TraceLocks();
@@ -114,12 +117,12 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
{
try
{
Debug.WriteLine("[{0}] Running...", Thread.CurrentThread.ManagedThreadId);
log.LogInformation("[{0}] Running...", Thread.CurrentThread.ManagedThreadId);
var name1 = "test-" + Guid.NewGuid();
var content1 = contentService.Create(name1, -1, "umbTextpage");
Debug.WriteLine("[{0}] Saving content #1.", Thread.CurrentThread.ManagedThreadId);
log.LogInformation("[{0}] Saving content #1.", Thread.CurrentThread.ManagedThreadId);
Save(contentService, content1);
Thread.Sleep(100); //quick pause for maximum overlap!
@@ -127,7 +130,7 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
var name2 = "test-" + Guid.NewGuid();
var content2 = contentService.Create(name2, -1, "umbTextpage");
Debug.WriteLine("[{0}] Saving content #2.", Thread.CurrentThread.ManagedThreadId);
log.LogInformation("[{0}] Saving content #2.", Thread.CurrentThread.ManagedThreadId);
Save(contentService, content2);
}
catch (Exception e)
@@ -139,16 +142,16 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
}
// start all threads
Debug.WriteLine("Starting threads");
log.LogInformation("Starting threads");
threads.ForEach(x => x.Start());
// wait for all to complete
Debug.WriteLine("Joining threads");
log.LogInformation("Joining threads");
threads.ForEach(x => x.Join());
done.Set();
Debug.WriteLine("Checking exceptions");
log.LogInformation("Checking exceptions");
if (exceptions.Count == 0)
{
//now look up all items, there should be 40!
@@ -166,13 +169,16 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
{
if (Environment.GetEnvironmentVariable("UMBRACO_TMP") != null)
Assert.Ignore("Do not run on VSTS.");
var log = GetRequiredService<ILogger<ThreadSafetyServiceTest>>();
// mimick the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton
var mediaService = (MediaService)MediaService;
var threads = new List<Thread>();
var exceptions = new List<Exception>();
Debug.WriteLine("Starting...");
log.LogInformation("Starting...");
var done = TraceLocks();
@@ -182,18 +188,18 @@ namespace Umbraco.Tests.Integration.Umbraco.Infrastructure.Services
{
try
{
Debug.WriteLine("[{0}] Running...", Thread.CurrentThread.ManagedThreadId);
log.LogInformation("[{0}] Running...", Thread.CurrentThread.ManagedThreadId);
var name1 = "test-" + Guid.NewGuid();
var media1 = mediaService.CreateMedia(name1, -1, Constants.Conventions.MediaTypes.Folder);
Debug.WriteLine("[{0}] Saving media #1.", Thread.CurrentThread.ManagedThreadId);
log.LogInformation("[{0}] Saving media #1.", Thread.CurrentThread.ManagedThreadId);
Save(mediaService, media1);
Thread.Sleep(100); //quick pause for maximum overlap!
var name2 = "test-" + Guid.NewGuid();
var media2 = mediaService.CreateMedia(name2, -1, Constants.Conventions.MediaTypes.Folder);
Debug.WriteLine("[{0}] Saving media #2.", Thread.CurrentThread.ManagedThreadId);
log.LogInformation("[{0}] Saving media #2.", Thread.CurrentThread.ManagedThreadId);
Save(mediaService, media2);
}
catch (Exception e)

View File

@@ -143,11 +143,14 @@ namespace Umbraco.Core.DependencyInjection
builder.Services.AddUnique<IUmbracoDatabase>(factory => factory.GetRequiredService<IUmbracoDatabaseFactory>().CreateDatabase());
builder.Services.AddUnique<ISqlContext>(factory => factory.GetRequiredService<IUmbracoDatabaseFactory>().SqlContext);
builder.Services.AddUnique<IUmbracoVersion, UmbracoVersion>();
builder.Services.AddUnique<IRuntime, CoreRuntime>();
builder.Services.AddUnique<IRuntimeState, RuntimeState>();
builder.Services.AddUnique<IHostingEnvironment, AspNetCoreHostingEnvironment>();
builder.Services.AddUnique<IMainDom, MainDom>();
builder.Services.AddUnique<IRuntime, CoreRuntime>();
builder.Services.AddHostedService<IRuntime>(factory => factory.GetRequiredService<IRuntime>());
builder.AddCoreInitialServices();
builder.AddComposers();
return builder;

View File

@@ -1,13 +1,11 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using Smidge;
using Smidge.Nuglify;
using StackExchange.Profiling;
using Umbraco.Core;
using Umbraco.Core.Hosting;
using Umbraco.Infrastructure.Logging.Serilog.Enrichers;
using Umbraco.Web.Common.Middleware;
@@ -23,9 +21,9 @@ namespace Umbraco.Extensions
/// <returns></returns>
public static bool UmbracoCanBoot(this IApplicationBuilder app)
{
var runtime = app.ApplicationServices.GetRequiredService<IRuntime>();
var state = app.ApplicationServices.GetRequiredService<IRuntimeState>();
// can't continue if boot failed
return runtime.State.Level > RuntimeLevel.BootFailed;
return state.Level > RuntimeLevel.BootFailed;
}
/// <summary>
@@ -39,25 +37,10 @@ namespace Umbraco.Extensions
if (!app.UmbracoCanBoot()) return app;
var hostingEnvironment = app.ApplicationServices.GetRequiredService<IHostingEnvironment>();
AppDomain.CurrentDomain.SetData("DataDirectory", hostingEnvironment?.MapPathContentRoot(Core.Constants.SystemDirectories.Data));
var runtime = app.ApplicationServices.GetRequiredService<IRuntime>();
// Register a listener for application shutdown in order to terminate the runtime
var hostLifetime = app.ApplicationServices.GetRequiredService<IApplicationShutdownRegistry>();
var runtimeShutdown = new CoreRuntimeShutdown(runtime, hostLifetime);
hostLifetime.RegisterObject(runtimeShutdown);
// Register our global threadabort enricher for logging
var threadAbortEnricher = app.ApplicationServices.GetRequiredService<ThreadAbortExceptionEnricher>();
LogContext.Push(threadAbortEnricher); // NOTE: We are not in a using clause because we are not removing it, it is on the global context
StaticApplicationLogging.Initialize(app.ApplicationServices.GetRequiredService<ILoggerFactory>());
// Start the runtime!
runtime.Start();
return app;
}
@@ -121,33 +104,6 @@ namespace Umbraco.Extensions
return app;
}
/// <summary>
/// Ensures the runtime is shutdown when the application is shutting down
/// </summary>
private class CoreRuntimeShutdown : IRegisteredObject
{
public CoreRuntimeShutdown(IRuntime runtime, IApplicationShutdownRegistry hostLifetime)
{
_runtime = runtime;
_hostLifetime = hostLifetime;
}
private bool _completed = false;
private readonly IRuntime _runtime;
private readonly IApplicationShutdownRegistry _hostLifetime;
public void Stop(bool immediate)
{
if (!_completed)
{
_completed = true;
_runtime.Terminate();
_hostLifetime.UnregisterObject(this);
}
}
}
}
}

View File

@@ -1,22 +1,17 @@
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Diagnostics;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Runtime;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Net;
using Umbraco.Web.Common.AspNetCore;
using Umbraco.Web.Common.Controllers;
using Umbraco.Web.Common.Formatters;
using Umbraco.Web.Common.Install;
using Umbraco.Web.Common.Lifetime;
using Umbraco.Web.Common.Macros;
@@ -38,7 +33,6 @@ namespace Umbraco.Web.Common.Runtime
/// Adds/replaces AspNetCore specific services
/// </summary>
[ComposeBefore(typeof(ICoreComposer))]
[ComposeAfter(typeof(CoreInitialComposer))]
public class AspNetCoreComposer : ComponentComposer<AspNetCoreComponent>, IComposer
{
public override void Compose(IUmbracoBuilder builder)

View File

@@ -20,7 +20,6 @@ namespace Umbraco.Web.UI.NetCore
{
x.ClearProviders();
})
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.UseUmbraco();
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
}
}

View File

@@ -1,26 +1,19 @@
using System.Web.Mvc;
using System.Web.Security;
using Microsoft.AspNet.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core;
using Umbraco.Core.DependencyInjection;
using Umbraco.Core.Composing;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Templates;
using Umbraco.Core.Runtime;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Web.Composing.CompositionExtensions;
using Umbraco.Web.Macros;
using Umbraco.Web.Mvc;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Security;
using Umbraco.Web.Security.Providers;
namespace Umbraco.Web.Runtime
{
// web's initial composer composes after core's, and before all core composers
[ComposeAfter(typeof(CoreInitialComposer))]
[ComposeBefore(typeof(ICoreComposer))]
public sealed class WebInitialComposer : ComponentComposer<WebInitialComponent>
{

View File

@@ -46,7 +46,6 @@ namespace Umbraco.Web
protected UmbracoApplicationBase()
{
HostingSettings hostingSettings = null;
GlobalSettings globalSettings = null;
SecuritySettings securitySettings = null;
@@ -60,8 +59,6 @@ namespace Umbraco.Web
var backOfficeInfo = new AspNetBackOfficeInfo(globalSettings, ioHelper, _loggerFactory.CreateLogger<AspNetBackOfficeInfo>(), Options.Create(webRoutingSettings));
var profiler = GetWebProfiler(hostingEnvironment);
StaticApplicationLogging.Initialize(_loggerFactory);
Logger = NullLogger<UmbracoApplicationBase>.Instance;
}
private IProfiler GetWebProfiler(IHostingEnvironment hostingEnvironment)
@@ -87,7 +84,6 @@ namespace Umbraco.Web
_loggerFactory = loggerFactory;
Logger = logger;
StaticApplicationLogging.Initialize(_loggerFactory);
}
protected ILogger<UmbracoApplicationBase> Logger { get; }
@@ -189,7 +185,6 @@ namespace Umbraco.Web
LogContext.Push(new HttpRequestIdEnricher(_factory.GetRequiredService<IRequestCache>()));
_runtime = _factory.GetRequiredService<IRuntime>();
_runtime.Start();
}
// called by ASP.NET (auto event wireup) once per app domain
@@ -237,7 +232,6 @@ namespace Umbraco.Web
{
if (_runtime != null)
{
_runtime.Terminate();
_runtime.DisposeIfDisposable();
_runtime = null;