diff --git a/.gitignore b/.gitignore
index 12ad3299ad..8ff5a8ef25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -164,3 +164,5 @@ build/temp/
# eof
/src/Umbraco.Web.UI.Client/TESTS-*.xml
/src/ApiDocs/api/*
+/src/Umbraco.Web.UI.NetCore/wwwroot/Media/*
+/src/Umbraco.Web.UI.NetCore/wwwroot/is-cache/*
diff --git a/src/Umbraco.Configuration/AspNetCoreConfigsFactory.cs b/src/Umbraco.Configuration/AspNetCoreConfigsFactory.cs
index 1e9f7976d5..0cacab9e1d 100644
--- a/src/Umbraco.Configuration/AspNetCoreConfigsFactory.cs
+++ b/src/Umbraco.Configuration/AspNetCoreConfigsFactory.cs
@@ -14,7 +14,7 @@ namespace Umbraco.Configuration
public AspNetCoreConfigsFactory(IConfiguration configuration)
{
- _configuration = configuration;
+ _configuration = configuration ?? throw new System.ArgumentNullException(nameof(configuration));
}
public Configs Create()
diff --git a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs
index 91225ff973..9378941166 100644
--- a/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs
+++ b/src/Umbraco.Core/Composing/FindAssembliesWithReferencesTo.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Reflection;
@@ -42,7 +43,14 @@ namespace Umbraco.Core.Composing
{
foreach(var target in _targetAssemblies)
{
- referenceItems.Add(Assembly.Load(target));
+ try
+ {
+ referenceItems.Add(Assembly.Load(target));
+ }
+ catch (FileNotFoundException)
+ {
+ // occurs if we cannot load this ... for example in a test project where we aren't currently referencing Umbraco.Web, etc...
+ }
}
}
diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs
index 3abd413fd4..9eddea6477 100644
--- a/src/Umbraco.Core/IO/IOHelper.cs
+++ b/src/Umbraco.Core/IO/IOHelper.cs
@@ -17,7 +17,7 @@ namespace Umbraco.Core.IO
public IOHelper(IHostingEnvironment hostingEnvironment, IGlobalSettings globalSettings)
{
- _hostingEnvironment = hostingEnvironment;
+ _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
_globalSettings = globalSettings;
}
diff --git a/src/Umbraco.Tests/TestHelpers/ConsoleLogger.cs b/src/Umbraco.Core/Logging/ConsoleLogger.cs
similarity index 98%
rename from src/Umbraco.Tests/TestHelpers/ConsoleLogger.cs
rename to src/Umbraco.Core/Logging/ConsoleLogger.cs
index 53d6078e4b..275b8d988b 100644
--- a/src/Umbraco.Tests/TestHelpers/ConsoleLogger.cs
+++ b/src/Umbraco.Core/Logging/ConsoleLogger.cs
@@ -1,7 +1,6 @@
using System;
-using Umbraco.Core.Logging;
-namespace Umbraco.Tests.TestHelpers
+namespace Umbraco.Core.Logging
{
public class ConsoleLogger : ILogger
{
diff --git a/src/Umbraco.Core/Logging/ILogger.cs b/src/Umbraco.Core/Logging/ILogger.cs
index 4f49d0b3b4..fe7d798ebf 100644
--- a/src/Umbraco.Core/Logging/ILogger.cs
+++ b/src/Umbraco.Core/Logging/ILogger.cs
@@ -2,6 +2,7 @@
namespace Umbraco.Core.Logging
{
+
///
/// Defines the logging service.
///
diff --git a/src/Umbraco.Core/Routing/UriUtility.cs b/src/Umbraco.Core/Routing/UriUtility.cs
index 9cb6bf50f6..0c68580204 100644
--- a/src/Umbraco.Core/Routing/UriUtility.cs
+++ b/src/Umbraco.Core/Routing/UriUtility.cs
@@ -15,6 +15,7 @@ namespace Umbraco.Web
public UriUtility(IHostingEnvironment hostingEnvironment)
{
+ if (hostingEnvironment is null) throw new ArgumentNullException(nameof(hostingEnvironment));
ResetAppDomainAppVirtualPath(hostingEnvironment);
}
diff --git a/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
new file mode 100644
index 0000000000..d5355c136f
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Composing/HostBuilderExtensions.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.Hosting;
+
+namespace Umbraco.Core.Composing
+{
+ ///
+ /// Extends the to enable Umbraco to be used as the service container.
+ ///
+ public static class HostBuilderExtensions
+ {
+ ///
+ /// Assigns a custom service provider factory to use Umbraco's container
+ ///
+ ///
+ ///
+ public static IHostBuilder UseUmbraco(this IHostBuilder builder)
+ {
+ return builder.UseServiceProviderFactory(new UmbracoServiceProviderFactory());
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Composing/LightInject/LightInjectContainer.cs b/src/Umbraco.Infrastructure/Composing/LightInject/LightInjectContainer.cs
index 5200dced90..a6db0b7b2b 100644
--- a/src/Umbraco.Infrastructure/Composing/LightInject/LightInjectContainer.cs
+++ b/src/Umbraco.Infrastructure/Composing/LightInject/LightInjectContainer.cs
@@ -16,11 +16,13 @@ namespace Umbraco.Core.Composing.LightInject
///
/// Initializes a new instance of the with a LightInject container.
///
- protected LightInjectContainer(ServiceContainer container)
+ public LightInjectContainer(ServiceContainer container)
{
- Container = container;
+ Container = ConfigureContainer(container);
}
+ //TODO: The Create methods can die when net framework is gone
+
///
/// Creates a new instance of the class.
///
@@ -33,7 +35,12 @@ namespace Umbraco.Core.Composing.LightInject
protected static ServiceContainer CreateServiceContainer()
{
var container = new ServiceContainer(new ContainerOptions { EnablePropertyInjection = false });
+ ConfigureContainer(container);
+ return container;
+ }
+ private static ServiceContainer ConfigureContainer(ServiceContainer container)
+ {
// note: the block below is disabled, as it is too LightInject-specific
//
// supports annotated constructor injections
@@ -84,7 +91,7 @@ namespace Umbraco.Core.Composing.LightInject
///
/// Gets the LightInject container.
///
- protected ServiceContainer Container { get; }
+ public ServiceContainer Container { get; }
///
///
diff --git a/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs b/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs
index 8f842e14fe..835bd0b9a8 100644
--- a/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs
+++ b/src/Umbraco.Infrastructure/Composing/RegisterFactory.cs
@@ -1,14 +1,22 @@
-using System;
+using LightInject;
+using LightInject.Microsoft.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
using System.Reflection;
+using Umbraco.Core.Composing.LightInject;
using Umbraco.Core.Configuration;
namespace Umbraco.Core.Composing
{
+
///
/// Creates the container.
///
public static class RegisterFactory
{
+ //TODO: This can die when net framework is gone
+
// cannot use typeof().AssemblyQualifiedName on the web container - we don't reference it
// a normal Umbraco site should run on the web container, but an app may run on the core one
private const string CoreLightInjectContainerTypeName = "Umbraco.Core.Composing.LightInject.LightInjectContainer,Umbraco.Core";
diff --git a/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs b/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs
new file mode 100644
index 0000000000..6fd0bda61e
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Composing/UmbracoServiceProviderFactory.cs
@@ -0,0 +1,78 @@
+using LightInject;
+using LightInject.Microsoft.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using Umbraco.Core.Composing.LightInject;
+
+namespace Umbraco.Core.Composing
+{
+ ///
+ /// Used to create Umbraco's container and cross-wire it up before the applicaton starts
+ ///
+ public class UmbracoServiceProviderFactory : IServiceProviderFactory
+ {
+ public UmbracoServiceProviderFactory(ServiceContainer container)
+ {
+ _container = new LightInjectContainer(container);
+ }
+
+ ///
+ /// Creates an ASP.NET Core compatible service container
+ ///
+ ///
+ public static ServiceContainer CreateServiceContainer() => new ServiceContainer(ContainerOptions.Default.Clone().WithMicrosoftSettings().WithAspNetCoreSettings());
+
+ ///
+ /// Default ctor for use in Host Builder configuration
+ ///
+ public UmbracoServiceProviderFactory()
+ {
+ var container = CreateServiceContainer();
+ UmbracoContainer = _container = new LightInjectContainer(container);
+ IsActive = true;
+ }
+
+ // see here for orig lightinject version https://github.com/seesharper/LightInject.Microsoft.DependencyInjection/blob/412566e3f70625e6b96471db5e1f7cd9e3e1eb18/src/LightInject.Microsoft.DependencyInjection/LightInject.Microsoft.DependencyInjection.cs#L263
+ // we don't really need all that, we're manually creating our container with the correct options and that
+ // is what we'll return in CreateBuilder
+
+ IServiceCollection _services;
+ readonly LightInjectContainer _container;
+
+ internal LightInjectContainer GetContainer() => _container;
+
+ ///
+ /// When the empty ctor is used this returns if this factory is active
+ ///
+ public static bool IsActive { get; private set; }
+
+ ///
+ /// When the empty ctor is used this returns the created IRegister
+ ///
+ public static IRegister UmbracoContainer { get; private set; }
+
+ ///
+ /// Create the container with the required settings for aspnetcore3
+ ///
+ ///
+ ///
+ public IServiceContainer CreateBuilder(IServiceCollection services)
+ {
+ _services = services;
+ return _container.Container;
+ }
+
+ ///
+ /// This cross-wires the container just before the application calls "Configure"
+ ///
+ ///
+ ///
+ public IServiceProvider CreateServiceProvider(IServiceContainer containerBuilder)
+ {
+ var provider = containerBuilder.CreateServiceProvider(_services);
+ return provider;
+ }
+
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs
index 23ef0bfda5..5806bb90ec 100644
--- a/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs
+++ b/src/Umbraco.Infrastructure/Persistence/IDbProviderFactoryCreator.cs
@@ -1,9 +1,9 @@
using System.Data.Common;
-using StackExchange.Profiling.Internal;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence
{
+
public interface IDbProviderFactoryCreator
{
DbProviderFactory CreateFactory();
diff --git a/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs b/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs
new file mode 100644
index 0000000000..9c2c6273c2
--- /dev/null
+++ b/src/Umbraco.Infrastructure/Persistence/SqlServerDbProviderFactoryCreator.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Data.Common;
+using Umbraco.Core.Persistence.SqlSyntax;
+
+namespace Umbraco.Core.Persistence
+{
+ public class SqlServerDbProviderFactoryCreator : IDbProviderFactoryCreator
+ {
+ private readonly string _defaultProviderName;
+ private readonly Func _getFactory;
+
+ public SqlServerDbProviderFactoryCreator(string defaultProviderName, Func getFactory)
+ {
+ _defaultProviderName = defaultProviderName;
+ _getFactory = getFactory;
+ }
+
+ public DbProviderFactory CreateFactory() => CreateFactory(_defaultProviderName);
+
+ public DbProviderFactory CreateFactory(string providerName)
+ {
+ if (string.IsNullOrEmpty(providerName)) return null;
+ return _getFactory(providerName);
+ }
+
+ // gets the sql syntax provider that corresponds, from attribute
+ public ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName)
+ {
+ return providerName switch
+ {
+ Constants.DbProviderNames.SqlCe => throw new NotSupportedException("SqlCe is not supported"),
+ Constants.DbProviderNames.SqlServer => new SqlServerSyntaxProvider(),
+ _ => throw new InvalidOperationException($"Unknown provider name \"{providerName}\""),
+ };
+ }
+
+ public IBulkSqlInsertProvider CreateBulkSqlInsertProvider(string providerName)
+ {
+ switch (providerName)
+ {
+ case Constants.DbProviderNames.SqlCe:
+ throw new NotSupportedException("SqlCe is not supported");
+ case Constants.DbProviderNames.SqlServer:
+ return new SqlServerBulkSqlInsertProvider();
+ default:
+ return new BasicBulkSqlInsertProvider();
+ }
+ }
+
+ public void CreateDatabase()
+ {
+ throw new NotSupportedException("Embedded databases are not supported");
+ }
+ }
+}
diff --git a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
index 94497d2e13..6c1a06ab6b 100644
--- a/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/CoreRuntime.cs
@@ -41,7 +41,8 @@ namespace Umbraco.Core.Runtime
IHostingEnvironment hostingEnvironment,
IBackOfficeInfo backOfficeInfo,
IDbProviderFactoryCreator dbProviderFactoryCreator,
- IMainDom mainDom)
+ IMainDom mainDom,
+ ITypeFinder typeFinder)
{
IOHelper = ioHelper;
Configs = configs;
@@ -55,6 +56,7 @@ namespace Umbraco.Core.Runtime
Logger = logger;
MainDom = mainDom;
+ TypeFinder = typeFinder;
_globalSettings = Configs.Global();
_connectionStrings = configs.ConnectionStrings();
@@ -95,7 +97,7 @@ namespace Umbraco.Core.Runtime
///
/// Gets the
///
- protected ITypeFinder TypeFinder { get; private set; }
+ protected ITypeFinder TypeFinder { get; }
///
/// Gets the
@@ -116,11 +118,6 @@ namespace Umbraco.Core.Runtime
// create and register the essential services
// ie the bare minimum required to boot
-
- TypeFinder = GetTypeFinder();
- if (TypeFinder == null)
- throw new InvalidOperationException($"The object returned from {nameof(GetTypeFinder)} cannot be null");
-
// the boot loader boots using a container scope, so anything that is PerScope will
// be disposed after the boot loader has booted, and anything else will remain.
// note that this REQUIRES that perWebRequestScope has NOT been enabled yet, else
@@ -260,11 +257,6 @@ namespace Umbraco.Core.Runtime
return _factory;
}
- private IUmbracoVersion GetUmbracoVersion(IGlobalSettings globalSettings)
- {
- return new UmbracoVersion(globalSettings);
- }
-
protected virtual void ConfigureUnhandledException()
{
//take care of unhandled exceptions - there is nothing we can do to
@@ -371,28 +363,6 @@ namespace Umbraco.Core.Runtime
protected virtual IEnumerable GetComposerTypes(TypeLoader typeLoader)
=> typeLoader.GetTypes();
- ///
- /// Gets a
- ///
- ///
- protected virtual ITypeFinder GetTypeFinder()
- // TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
- // this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
- => new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(
- // GetEntryAssembly was actually an exposed API by request of the aspnetcore team which works in aspnet core because a website
- // in that case is essentially an exe. However in netframework there is no entry assembly, things don't really work that way since
- // the process that is running the site is iisexpress, so this returns null. The best we can do is fallback to GetExecutingAssembly()
- // which will just return Umbraco.Infrastructure (currently with netframework) and for our purposes that is OK.
- // If you are curious... There is really no way to get the entry assembly in netframework without the hosting website having it's own
- // code compiled for the global.asax which is the entry point. Because the default global.asax for umbraco websites is just a file inheriting
- // from Umbraco.Web.UmbracoApplication, the global.asax file gets dynamically compiled into a DLL in the dynamic folder (we can get an instance
- // of that, but this doesn't really help us) but the actually entry execution is still Umbraco.Web. So that is the 'highest' level entry point
- // assembly we can get and we can only get that if we put this code into the WebRuntime since the executing assembly is the 'current' one.
- // For this purpose, it doesn't matter if it's Umbraco.Web or Umbraco.Infrastructure since all assemblies are in that same path and we are
- // getting rid of netframework.
- Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()));
-
-
///
/// Gets the application caches.
///
diff --git a/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs b/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs
index 12b12616f0..2f45a3e437 100644
--- a/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs
+++ b/src/Umbraco.Infrastructure/Runtime/WebRuntime.cs
@@ -31,9 +31,10 @@ namespace Umbraco.Web.Runtime
IBackOfficeInfo backOfficeInfo,
IDbProviderFactoryCreator dbProviderFactoryCreator,
IMainDom mainDom,
+ ITypeFinder typeFinder,
IRequestCache requestCache,
IUmbracoBootPermissionChecker umbracoBootPermissionChecker):
- base(configs, umbracoVersion, ioHelper, logger, profiler ,umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom)
+ base(configs, umbracoVersion, ioHelper, logger, profiler ,umbracoBootPermissionChecker, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder)
{
_requestCache = requestCache;
}
diff --git a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
index 00f48479f8..3d99c4918f 100644
--- a/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
+++ b/src/Umbraco.Infrastructure/Umbraco.Infrastructure.csproj
@@ -9,8 +9,11 @@
+
+
+
@@ -57,6 +60,9 @@
<_Parameter1>Umbraco.Tests.Benchmarks
+
+ <_Parameter1>Umbraco.Tests.Integration
+
diff --git a/src/Umbraco.Tests.Common/Assertions.cs b/src/Umbraco.Tests.Common/Assertions.cs
new file mode 100644
index 0000000000..0f99a6a091
--- /dev/null
+++ b/src/Umbraco.Tests.Common/Assertions.cs
@@ -0,0 +1,35 @@
+using LightInject;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Umbraco.Tests.Common.Composing;
+
+namespace Umbraco.Tests.Common
+{
+ public class Assertions
+ {
+ public static void AssertContainer(ServiceContainer container, bool reportOnly = false)
+ {
+ var results = container.Validate().ToList();
+ foreach (var resultGroup in results.GroupBy(x => x.Severity).OrderBy(x => x.Key))
+ {
+ Console.WriteLine($"{resultGroup.Key}: {resultGroup.Count()}");
+ }
+
+ foreach (var resultGroup in results.GroupBy(x => x.Severity).OrderBy(x => x.Key))
+ {
+ foreach (var result in resultGroup)
+ {
+ Console.WriteLine();
+ Console.Write(result.ToText());
+ }
+ }
+
+ if (!reportOnly)
+ Assert.AreEqual(0, results.Count);
+ }
+
+ }
+}
diff --git a/src/Umbraco.Tests/Composing/LightInjectValidation.cs b/src/Umbraco.Tests.Common/Composing/LightInjectValidation.cs
similarity index 92%
rename from src/Umbraco.Tests/Composing/LightInjectValidation.cs
rename to src/Umbraco.Tests.Common/Composing/LightInjectValidation.cs
index 75062e613c..4925074b9e 100644
--- a/src/Umbraco.Tests/Composing/LightInjectValidation.cs
+++ b/src/Umbraco.Tests.Common/Composing/LightInjectValidation.cs
@@ -35,7 +35,7 @@ using ServiceMap = System.Collections.Generic.Dictionary 1;
}
}
diff --git a/src/Umbraco.Tests.Common/Composing/ValidationResultExtensions.cs b/src/Umbraco.Tests.Common/Composing/ValidationResultExtensions.cs
new file mode 100644
index 0000000000..cfd136b63c
--- /dev/null
+++ b/src/Umbraco.Tests.Common/Composing/ValidationResultExtensions.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Text;
+using Umbraco.Core;
+
+namespace Umbraco.Tests.Common.Composing
+{
+ // These are used for Light Inject container validation
+ public static class ValidationResultExtensions
+ {
+ public static string ToText(this ValidationResult result)
+ {
+ var text = new StringBuilder();
+
+ text.AppendLine($"{result.Severity}: {WordWrap(result.Message, 120)}");
+ var target = result.ValidationTarget;
+ text.Append("\tsvce: ");
+ text.Append(target.ServiceName);
+ text.Append(target.DeclaringService.ServiceType);
+ if (!target.DeclaringService.ServiceName.IsNullOrWhiteSpace())
+ {
+ text.Append(" '");
+ text.Append(target.DeclaringService.ServiceName);
+ text.Append("'");
+ }
+
+ text.Append(" (");
+ if (target.DeclaringService.Lifetime == null)
+ text.Append("Transient");
+ else
+ text.Append(target.DeclaringService.Lifetime.ToString().TrimStart("LightInject.").TrimEnd("Lifetime"));
+ text.AppendLine(")");
+ text.Append("\timpl: ");
+ text.Append(target.DeclaringService.ImplementingType);
+ text.AppendLine();
+ text.Append("\tparm: ");
+ text.Append(target.Parameter);
+ text.AppendLine();
+
+ return text.ToString();
+ }
+
+ private static string WordWrap(string text, int width)
+ {
+ int pos, next;
+ var sb = new StringBuilder();
+ var nl = Environment.NewLine;
+
+ // Lucidity check
+ if (width < 1)
+ return text;
+
+ // Parse each line of text
+ for (pos = 0; pos < text.Length; pos = next)
+ {
+ // Find end of line
+ var eol = text.IndexOf(nl, pos, StringComparison.Ordinal);
+
+ if (eol == -1)
+ next = eol = text.Length;
+ else
+ next = eol + nl.Length;
+
+ // Copy this line of text, breaking into smaller lines as needed
+ if (eol > pos)
+ {
+ do
+ {
+ var len = eol - pos;
+
+ if (len > width)
+ len = BreakLine(text, pos, width);
+
+ if (pos > 0)
+ sb.Append("\t\t");
+ sb.Append(text, pos, len);
+ sb.Append(nl);
+
+ // Trim whitespace following break
+ pos += len;
+
+ while (pos < eol && char.IsWhiteSpace(text[pos]))
+ pos++;
+
+ } while (eol > pos);
+ }
+ else sb.Append(nl); // Empty line
+ }
+
+ return sb.ToString();
+ }
+
+ private static int BreakLine(string text, int pos, int max)
+ {
+ // Find last whitespace in line
+ var i = max - 1;
+ while (i >= 0 && !char.IsWhiteSpace(text[pos + i]))
+ i--;
+ if (i < 0)
+ return max; // No whitespace found; break at maximum length
+ // Find start of whitespace
+ while (i >= 0 && char.IsWhiteSpace(text[pos + i]))
+ i--;
+ // Return length of text before whitespace
+ return i + 1;
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Common/TestHelperBase.cs b/src/Umbraco.Tests.Common/TestHelperBase.cs
index d21aff89d9..536bebee56 100644
--- a/src/Umbraco.Tests.Common/TestHelperBase.cs
+++ b/src/Umbraco.Tests.Common/TestHelperBase.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Reflection;
using Moq;
using Umbraco.Core;
using Umbraco.Core.Cache;
@@ -12,7 +13,6 @@ using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence;
-using Umbraco.Core.Runtime;
using Umbraco.Core.Serialization;
using Umbraco.Core.Strings;
using Umbraco.Core.Sync;
@@ -27,31 +27,26 @@ namespace Umbraco.Tests.Common
///
public abstract class TestHelperBase
{
- public TestHelperBase()
+ private readonly ITypeFinder _typeFinder;
+ private UriUtility _uriUtility;
+ private IIOHelper _ioHelper;
+
+ public TestHelperBase(Assembly entryAssembly)
{
- SettingsForTests = new SettingsForTests();
- IOHelper = new IOHelper(GetHostingEnvironment(), SettingsForTests.GenerateMockGlobalSettings());
- MainDom = new MainDom(Mock.Of(), GetHostingEnvironment(), new MainDomSemaphoreLock(Mock.Of(), GetHostingEnvironment()));
- UriUtility = new UriUtility(GetHostingEnvironment());
+ SettingsForTests = new SettingsForTests();
+ MainDom = new SimpleMainDom();
+ _typeFinder = new TypeFinder(Mock.Of(), new DefaultUmbracoAssemblyProvider(entryAssembly));
}
- public ITypeFinder GetTypeFinder()
- {
-
- var typeFinder = new TypeFinder(Mock.Of(),
- new DefaultUmbracoAssemblyProvider(typeof(TestHelperBase).Assembly));
- return typeFinder;
- }
+ public ITypeFinder GetTypeFinder() => _typeFinder;
public TypeLoader GetMockedTypeLoader()
{
return new TypeLoader(IOHelper, Mock.Of(), Mock.Of(), new DirectoryInfo(IOHelper.MapPath("~/App_Data/TEMP")), Mock.Of());
}
- public Configs GetConfigs()
- {
- return GetConfigsFactory().Create();
- }
+ public Configs GetConfigs() => GetConfigsFactory().Create();
+
public IRuntimeState GetRuntimeState()
{
return new RuntimeState(
@@ -67,10 +62,7 @@ namespace Umbraco.Tests.Common
public abstract IBackOfficeInfo GetBackOfficeInfo();
- public IConfigsFactory GetConfigsFactory()
- {
- return new ConfigsFactory();
- }
+ public IConfigsFactory GetConfigsFactory() => new ConfigsFactory();
///
/// Gets the current assembly directory.
@@ -95,10 +87,27 @@ namespace Umbraco.Tests.Common
public abstract IMarchal Marchal { get; }
public ICoreDebugSettings CoreDebugSettings { get; } = new CoreDebugSettings();
+ public IIOHelper IOHelper
+ {
+ get
+ {
+ if (_ioHelper == null)
+ _ioHelper = new IOHelper(GetHostingEnvironment(), SettingsForTests.GenerateMockGlobalSettings());
+ return _ioHelper;
+ }
+ }
- public IIOHelper IOHelper { get; }
public IMainDom MainDom { get; }
- public UriUtility UriUtility { get; }
+ public UriUtility UriUtility
+ {
+ get
+ {
+ if (_uriUtility == null)
+ _uriUtility = new UriUtility(GetHostingEnvironment());
+ return _uriUtility;
+ }
+ }
+
public SettingsForTests SettingsForTests { get; }
public IWebRoutingSettings WebRoutingSettings => SettingsForTests.GenerateMockWebRoutingSettings();
@@ -115,10 +124,7 @@ namespace Umbraco.Tests.Common
return relativePath.Replace("~/", CurrentAssemblyDirectory + "/");
}
- public IUmbracoVersion GetUmbracoVersion()
- {
- return new UmbracoVersion(GetConfigs().Global());
- }
+ public IUmbracoVersion GetUmbracoVersion() => new UmbracoVersion(GetConfigs().Global());
public IRegister GetRegister()
{
diff --git a/src/Umbraco.Tests.Integration/ContainerTests.cs b/src/Umbraco.Tests.Integration/ContainerTests.cs
new file mode 100644
index 0000000000..d190d1165d
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/ContainerTests.cs
@@ -0,0 +1,77 @@
+using LightInject;
+using LightInject.Microsoft.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Composing.LightInject;
+using Umbraco.Core.Configuration;
+using Umbraco.Core.Persistence;
+using Umbraco.Tests.Common;
+using Umbraco.Tests.Integration.Implementations;
+
+namespace Umbraco.Tests.Integration
+{
+
+ [TestFixture]
+ public class ContainerTests
+ {
+ [Test]
+ public void CrossWire()
+ {
+ // MSDI
+ var services = new ServiceCollection();
+ services.AddSingleton();
+ var msdiServiceProvider = services.BuildServiceProvider();
+
+ // LightInject / Umbraco
+ var container = UmbracoServiceProviderFactory.CreateServiceContainer();
+ var serviceProviderFactory = new UmbracoServiceProviderFactory(container);
+ var umbracoContainer = serviceProviderFactory.GetContainer();
+ serviceProviderFactory.CreateBuilder(services); // called during Host Builder, needed to capture services
+
+ // Dependencies needed for creating composition/register essentials
+ var testHelper = new TestHelper();
+ var runtimeState = Mock.Of();
+ var umbracoDatabaseFactory = Mock.Of();
+ var dbProviderFactoryCreator = Mock.Of();
+ var typeLoader = testHelper.GetMockedTypeLoader();
+
+ // Register in the container
+ var composition = new Composition(umbracoContainer, typeLoader,
+ testHelper.Logger, runtimeState, testHelper.GetConfigs(), testHelper.IOHelper, testHelper.AppCaches);
+ composition.RegisterEssentials(testHelper.Logger, testHelper.Profiler, testHelper.Logger, testHelper.MainDom,
+ testHelper.AppCaches, umbracoDatabaseFactory, typeLoader, runtimeState, testHelper.GetTypeFinder(),
+ testHelper.IOHelper, testHelper.GetUmbracoVersion(), dbProviderFactoryCreator);
+
+ // Cross wire - this would be called by the Host Builder at the very end of ConfigureServices
+ var lightInjectServiceProvider = serviceProviderFactory.CreateServiceProvider(umbracoContainer.Container);
+
+ // From MSDI
+ var foo1 = msdiServiceProvider.GetService();
+ var foo2 = lightInjectServiceProvider.GetService();
+ var foo3 = umbracoContainer.GetInstance();
+
+ Assert.IsNotNull(foo1);
+ Assert.IsNotNull(foo2);
+ Assert.IsNotNull(foo3);
+
+ // These are not the same because cross wiring means copying the container, not falling back to a container
+ Assert.AreNotSame(foo1, foo2);
+ // These are the same because the umbraco container wraps the light inject container
+ Assert.AreSame(foo2, foo3);
+
+ Assertions.AssertContainer(umbracoContainer.Container);
+ }
+
+ private class Foo
+ {
+ public Foo()
+ {
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Integration/Implementations/TestDbProviderFactoryCreator.cs b/src/Umbraco.Tests.Integration/Implementations/TestDbProviderFactoryCreator.cs
new file mode 100644
index 0000000000..9081c4ccb4
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/Implementations/TestDbProviderFactoryCreator.cs
@@ -0,0 +1,34 @@
+using System.Data.Common;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Persistence.SqlSyntax;
+
+namespace Umbraco.Tests.Integration.Implementations
+{
+ public class TestDbProviderFactoryCreator : IDbProviderFactoryCreator
+ {
+ public IBulkSqlInsertProvider CreateBulkSqlInsertProvider(string providerName)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void CreateDatabase()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public DbProviderFactory CreateFactory()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public DbProviderFactory CreateFactory(string providerName)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public ISqlSyntaxProvider GetSqlSyntaxProvider(string providerName)
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
new file mode 100644
index 0000000000..5254892b23
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/Implementations/TestHelper.cs
@@ -0,0 +1,80 @@
+
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Hosting;
+using Moq;
+using System.Net;
+using Umbraco.Core;
+using Umbraco.Core.Cache;
+using Umbraco.Core.Diagnostics;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Runtime;
+using Umbraco.Net;
+using Umbraco.Tests.Common;
+using Umbraco.Web.BackOffice;
+using Umbraco.Web.BackOffice.AspNetCore;
+using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment;
+
+namespace Umbraco.Tests.Integration.Implementations
+{
+
+ public class TestHelper : TestHelperBase
+ {
+ private IBackOfficeInfo _backOfficeInfo;
+ private readonly IHostingEnvironment _hostingEnvironment;
+ private readonly IIpResolver _ipResolver;
+ private readonly IWebHostEnvironment _hostEnvironment;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ public TestHelper() : base(typeof(TestHelper).Assembly)
+ {
+ var httpContext = new DefaultHttpContext();
+ httpContext.Connection.RemoteIpAddress = IPAddress.Parse("127.0.0.1");
+ _httpContextAccessor = Mock.Of(x => x.HttpContext == httpContext);
+ _ipResolver = new AspNetIpResolver(_httpContextAccessor);
+
+ _hostEnvironment = Mock.Of(x =>
+ x.ApplicationName == "UmbracoIntegrationTests"
+ && x.ContentRootPath == CurrentAssemblyDirectory
+ && x.WebRootPath == CurrentAssemblyDirectory); // same folder for now?
+
+ _hostingEnvironment = new AspNetCoreHostingEnvironment(
+ SettingsForTests.GetDefaultHostingSettings(),
+ _hostEnvironment,
+ _httpContextAccessor,
+ Mock.Of());
+
+ Logger = new ProfilingLogger(new ConsoleLogger(new MessageTemplates()), Profiler);
+ }
+
+ public IUmbracoBootPermissionChecker UmbracoBootPermissionChecker { get; } = new TestUmbracoBootPermissionChecker();
+
+ public AppCaches AppCaches { get; } = new AppCaches(NoAppCache.Instance, NoAppCache.Instance, new IsolatedCaches(type => NoAppCache.Instance));
+
+ public IProfilingLogger Logger { get; private set; }
+
+ public IProfiler Profiler { get; } = new VoidProfiler();
+
+ public IHttpContextAccessor GetHttpContextAccessor() => _httpContextAccessor;
+
+ public IWebHostEnvironment GetWebHostEnvironment() => _hostEnvironment;
+
+ public override IDbProviderFactoryCreator DbProviderFactoryCreator => new TestDbProviderFactoryCreator();
+
+ public override IBulkSqlInsertProvider BulkSqlInsertProvider => new SqlServerBulkSqlInsertProvider();
+
+ public override IMarchal Marchal { get; } = new AspNetCoreMarchal();
+
+ public override IBackOfficeInfo GetBackOfficeInfo()
+ {
+ if (_backOfficeInfo == null)
+ _backOfficeInfo = new AspNetCoreBackOfficeInfo(SettingsForTests.GetDefaultGlobalSettings(GetUmbracoVersion()));
+ return _backOfficeInfo;
+ }
+
+ public override IHostingEnvironment GetHostingEnvironment() => _hostingEnvironment;
+
+ public override IIpResolver GetIpResolver() => _ipResolver;
+ }
+}
diff --git a/src/Umbraco.Tests.Integration/Implementations/TestUmbracoBootPermissionChecker.cs b/src/Umbraco.Tests.Integration/Implementations/TestUmbracoBootPermissionChecker.cs
new file mode 100644
index 0000000000..b4f876fc66
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/Implementations/TestUmbracoBootPermissionChecker.cs
@@ -0,0 +1,11 @@
+using Umbraco.Core.Runtime;
+
+namespace Umbraco.Tests.Integration.Implementations
+{
+ public class TestUmbracoBootPermissionChecker : IUmbracoBootPermissionChecker
+ {
+ public void ThrowIfNotPermissions()
+ {
+ }
+ }
+}
diff --git a/src/Umbraco.Tests.Integration/RuntimeTests.cs b/src/Umbraco.Tests.Integration/RuntimeTests.cs
new file mode 100644
index 0000000000..24786a1591
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/RuntimeTests.cs
@@ -0,0 +1,128 @@
+using LightInject;
+using LightInject.Microsoft.DependencyInjection;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core;
+using Umbraco.Core.Composing;
+using Umbraco.Core.Logging;
+using Umbraco.Core.Runtime;
+using Umbraco.Tests.Common;
+using Umbraco.Tests.Integration.Implementations;
+using Umbraco.Web.BackOffice.AspNetCore;
+
+namespace Umbraco.Tests.Integration
+{
+ [TestFixture]
+ public class RuntimeTests
+ {
+ [Test]
+ public void BootCoreRuntime()
+ {
+ // LightInject / Umbraco
+ var container = UmbracoServiceProviderFactory.CreateServiceContainer();
+ var serviceProviderFactory = new UmbracoServiceProviderFactory(container);
+ var umbracoContainer = serviceProviderFactory.GetContainer();
+
+ // Create the core runtime
+ var testHelper = new TestHelper();
+ var coreRuntime = new CoreRuntime(testHelper.GetConfigs(), testHelper.GetUmbracoVersion(),
+ testHelper.IOHelper, testHelper.Logger, testHelper.Profiler, testHelper.UmbracoBootPermissionChecker,
+ testHelper.GetHostingEnvironment(), testHelper.GetBackOfficeInfo(), testHelper.DbProviderFactoryCreator,
+ testHelper.MainDom, testHelper.GetTypeFinder());
+
+ // boot it!
+ var factory = coreRuntime.Boot(umbracoContainer);
+
+ Assert.IsTrue(coreRuntime.MainDom.IsMainDom);
+ Assert.IsNull(coreRuntime.State.BootFailedException);
+ Assert.AreEqual(RuntimeLevel.Install, coreRuntime.State.Level);
+ Assert.IsTrue(MyComposer.IsComposed);
+ Assert.IsTrue(MyComponent.IsInit);
+ Assert.IsFalse(MyComponent.IsTerminated);
+
+ Assertions.AssertContainer(umbracoContainer.Container, reportOnly: true); // TODO Change that to false eventually when we clean up the container
+
+ coreRuntime.Terminate();
+
+ Assert.IsTrue(MyComponent.IsTerminated);
+ }
+
+ [Test]
+ public void AddUmbracoCore()
+ {
+ var testHelper = new TestHelper();
+
+ // MSDI
+ var services = new ServiceCollection();
+ // These services are required
+ services.AddSingleton(x => testHelper.GetHttpContextAccessor());
+ services.AddSingleton(x => testHelper.GetWebHostEnvironment());
+ services.AddSingleton(x => Mock.Of());
+
+ // LightInject / Umbraco
+ var container = UmbracoServiceProviderFactory.CreateServiceContainer();
+ var serviceProviderFactory = new UmbracoServiceProviderFactory(container);
+ var umbracoContainer = serviceProviderFactory.GetContainer();
+
+ // Some IConfiguration must exist in the container first
+ var configurationBuilder = new ConfigurationBuilder();
+ configurationBuilder.AddEnvironmentVariables();
+ services.AddSingleton(x => configurationBuilder.Build());
+
+ // Add it!
+ services.AddUmbracoConfiguration();
+ services.AddUmbracoCore(umbracoContainer, GetType().Assembly);
+
+ // assert results
+ var runtimeState = umbracoContainer.GetInstance();
+ var mainDom = umbracoContainer.GetInstance();
+
+ Assert.IsTrue(mainDom.IsMainDom);
+ Assert.IsNull(runtimeState.BootFailedException);
+ Assert.AreEqual(RuntimeLevel.Install, runtimeState.Level);
+ Assert.IsTrue(MyComposer.IsComposed);
+ }
+
+ [RuntimeLevel(MinLevel = RuntimeLevel.Install)]
+ public class MyComposer : IUserComposer
+ {
+ public void Compose(Composition composition)
+ {
+ composition.Components().Append();
+ IsComposed = true;
+ }
+
+ public static bool IsComposed { get; private set; }
+ }
+
+ public class MyComponent : IComponent
+ {
+ public static bool IsInit { get; private set; }
+ public static bool IsTerminated { get; private set; }
+
+ private readonly ILogger _logger;
+
+ public MyComponent(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public void Initialize()
+ {
+ IsInit = true;
+ }
+
+ public void Terminate()
+ {
+ IsTerminated = true;
+ }
+ }
+ }
+
+
+}
diff --git a/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj
new file mode 100644
index 0000000000..55b3e8cdca
--- /dev/null
+++ b/src/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj
@@ -0,0 +1,27 @@
+
+
+
+ Exe
+ netcoreapp3.1
+ false
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs
index b6b671fb3b..bc58282795 100644
--- a/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs
+++ b/src/Umbraco.Tests/Routing/RenderRouteHandlerTests.cs
@@ -50,7 +50,7 @@ namespace Umbraco.Tests.Routing
public class TestRuntime : WebRuntime
{
public TestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo)
- : base(configs, umbracoVersion, ioHelper, Mock.Of(), Mock.Of(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetRequestCache(), new AspNetUmbracoBootPermissionChecker())
+ : base(configs, umbracoVersion, ioHelper, Mock.Of(), Mock.Of(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), TestHelper.GetRequestCache(), new AspNetUmbracoBootPermissionChecker())
{
}
diff --git a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
index 3c0097f27b..92ab825ee2 100644
--- a/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
+++ b/src/Umbraco.Tests/Runtimes/CoreRuntimeTests.cs
@@ -121,15 +121,11 @@ namespace Umbraco.Tests.Runtimes
public class TestRuntime : CoreRuntime
{
public TestRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo)
- :base(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom)
+ :base(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder())
{
}
- // override because we cannot use Assembly.GetEntryAssembly in Nunit tests since that is always null
- protected override ITypeFinder GetTypeFinder()
- => new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
-
// must override the database factory
// else BootFailedException because U cannot connect to the configured db
protected internal override IUmbracoDatabaseFactory GetDatabaseFactory()
@@ -181,7 +177,6 @@ namespace Umbraco.Tests.Runtimes
public override void Terminate()
{
- ((IRegisteredObject) _mainDom).Stop(false);
base.Terminate();
}
diff --git a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
index 213074caa7..0cb3f3e2e1 100644
--- a/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
+++ b/src/Umbraco.Tests/Runtimes/StandaloneTests.cs
@@ -25,7 +25,6 @@ using Umbraco.Core.Runtime;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services;
using Umbraco.Core.Sync;
-using Umbraco.Tests.Composing;
using Umbraco.Tests.TestHelpers;
using Umbraco.Web;
using Umbraco.Web.Cache;
@@ -36,6 +35,7 @@ using Umbraco.Web.Runtime;
using File = System.IO.File;
using Current = Umbraco.Web.Composing.Current;
using Umbraco.Tests.Common;
+using Umbraco.Tests.Common.Composing;
namespace Umbraco.Tests.Runtimes
{
@@ -64,8 +64,8 @@ namespace Umbraco.Tests.Runtimes
var appCaches = AppCaches.Disabled;
var globalSettings = TestHelper.GetConfigs().Global();
var connectionStrings = TestHelper.GetConfigs().ConnectionStrings();
+ var typeFinder = TestHelper.GetTypeFinder();
var databaseFactory = new UmbracoDatabaseFactory(logger,globalSettings, connectionStrings, new Lazy(() => factory.GetInstance()), TestHelper.DbProviderFactoryCreator);
- var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var ioHelper = TestHelper.IOHelper;
var hostingEnvironment = Mock.Of();
var typeLoader = new TypeLoader(ioHelper, typeFinder, appCaches.RuntimeCache, new DirectoryInfo(ioHelper.MapPath("~/App_Data/TEMP")), profilingLogger);
@@ -75,6 +75,7 @@ namespace Umbraco.Tests.Runtimes
var runtimeState = new RuntimeState(logger, null, new Lazy(() => mainDom), new Lazy(() => factory.GetInstance()), umbracoVersion, hostingEnvironment, backOfficeInfo);
var configs = TestHelper.GetConfigs();
var variationContextAccessor = TestHelper.VariationContextAccessor;
+
// create the register and the composition
var register = TestHelper.GetRegister();
@@ -82,7 +83,8 @@ namespace Umbraco.Tests.Runtimes
composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator);
// create the core runtime and have it compose itself
- var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom);coreRuntime.Compose(composition);
+ var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder);
+ coreRuntime.Compose(composition);
// determine actual runtime level
runtimeState.DetermineRuntimeLevel(databaseFactory, logger);
@@ -276,7 +278,7 @@ namespace Umbraco.Tests.Runtimes
composition.RegisterEssentials(logger, profiler, profilingLogger, mainDom, appCaches, databaseFactory, typeLoader, runtimeState, typeFinder, ioHelper, umbracoVersion, TestHelper.DbProviderFactoryCreator);
// create the core runtime and have it compose itself
- var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom);
+ var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetUmbracoBootPermissionChecker(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, typeFinder);
coreRuntime.Compose(composition);
// get the components
@@ -315,107 +317,14 @@ namespace Umbraco.Tests.Runtimes
foreach (var result in resultGroup)
{
Console.WriteLine();
- Console.Write(ToText(result));
+ Console.Write(result.ToText());
}
Assert.AreEqual(0, results.Count);
}
- private static string ToText(ValidationResult result)
- {
- var text = new StringBuilder();
+
- text.AppendLine($"{result.Severity}: {WordWrap(result.Message, 120)}");
- var target = result.ValidationTarget;
- text.Append("\tsvce: ");
- text.Append(target.ServiceName);
- text.Append(target.DeclaringService.ServiceType);
- if (!target.DeclaringService.ServiceName.IsNullOrWhiteSpace())
- {
- text.Append(" '");
- text.Append(target.DeclaringService.ServiceName);
- text.Append("'");
- }
-
- text.Append(" (");
- if (target.DeclaringService.Lifetime == null)
- text.Append("Transient");
- else
- text.Append(target.DeclaringService.Lifetime.ToString().TrimStart("LightInject.").TrimEnd("Lifetime"));
- text.AppendLine(")");
- text.Append("\timpl: ");
- text.Append(target.DeclaringService.ImplementingType);
- text.AppendLine();
- text.Append("\tparm: ");
- text.Append(target.Parameter);
- text.AppendLine();
-
- return text.ToString();
- }
-
- private static string WordWrap(string text, int width)
- {
- int pos, next;
- var sb = new StringBuilder();
- var nl = Environment.NewLine;
-
- // Lucidity check
- if (width < 1)
- return text;
-
- // Parse each line of text
- for (pos = 0; pos < text.Length; pos = next)
- {
- // Find end of line
- var eol = text.IndexOf(nl, pos, StringComparison.Ordinal);
-
- if (eol == -1)
- next = eol = text.Length;
- else
- next = eol + nl.Length;
-
- // Copy this line of text, breaking into smaller lines as needed
- if (eol > pos)
- {
- do
- {
- var len = eol - pos;
-
- if (len > width)
- len = BreakLine(text, pos, width);
-
- if (pos > 0)
- sb.Append("\t\t");
- sb.Append(text, pos, len);
- sb.Append(nl);
-
- // Trim whitespace following break
- pos += len;
-
- while (pos < eol && char.IsWhiteSpace(text[pos]))
- pos++;
-
- } while (eol > pos);
- }
- else sb.Append(nl); // Empty line
- }
-
- return sb.ToString();
- }
-
- private static int BreakLine(string text, int pos, int max)
- {
- // Find last whitespace in line
- var i = max - 1;
- while (i >= 0 && !char.IsWhiteSpace(text[pos + i]))
- i--;
- if (i < 0)
- return max; // No whitespace found; break at maximum length
- // Find start of whitespace
- while (i >= 0 && char.IsWhiteSpace(text[pos + i]))
- i--;
- // Return length of text before whitespace
- return i + 1;
- }
+
}
}
diff --git a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs
index 579ca104ea..24dcb229c9 100644
--- a/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs
+++ b/src/Umbraco.Tests/TestHelpers/BaseWebTest.cs
@@ -33,7 +33,6 @@ namespace Umbraco.Tests.TestHelpers
protected override void Compose()
{
base.Compose();
- base.Compose();
Composition.RegisterUnique();
Composition.RegisterUnique();
diff --git a/src/Umbraco.Tests/TestHelpers/TestHelper.cs b/src/Umbraco.Tests/TestHelpers/TestHelper.cs
index 95e211db24..05d4de6e23 100644
--- a/src/Umbraco.Tests/TestHelpers/TestHelper.cs
+++ b/src/Umbraco.Tests/TestHelpers/TestHelper.cs
@@ -43,6 +43,11 @@ namespace Umbraco.Tests.TestHelpers
private static TestHelperInternal _testHelperInternal = new TestHelperInternal();
private class TestHelperInternal : TestHelperBase
{
+ public TestHelperInternal() : base(typeof(TestHelperInternal).Assembly)
+ {
+
+ }
+
public override IDbProviderFactoryCreator DbProviderFactoryCreator { get; } = new UmbracoDbProviderFactoryCreator(Constants.DbProviderNames.SqlCe);
public override IBulkSqlInsertProvider BulkSqlInsertProvider { get; } = new SqlCeBulkSqlInsertProvider();
diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj
index 29d69db0d2..12d3ae111c 100644
--- a/src/Umbraco.Tests/Umbraco.Tests.csproj
+++ b/src/Umbraco.Tests/Umbraco.Tests.csproj
@@ -120,7 +120,6 @@
-
@@ -210,7 +209,6 @@
-
diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs
index 0da6950d04..00bc894b0d 100644
--- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs
+++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreHostingEnvironment.cs
@@ -84,9 +84,10 @@ namespace Umbraco.Web.BackOffice.AspNetCore
}
}
-
+ // TODO: This may need to take into account ~/ paths which means the ApplicationVirtualPath and is this the content root or web root?
public string MapPath(string path) => Path.Combine(_webHostEnvironment.WebRootPath, path);
+ // TODO: Need to take into account 'root' here
public string ToAbsolute(string virtualPath, string root)
{
if (Uri.TryCreate(virtualPath, UriKind.Absolute, out _))
diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreIpResolver.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreIpResolver.cs
index 8f231191f2..cee43757d8 100644
--- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreIpResolver.cs
+++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreIpResolver.cs
@@ -3,7 +3,7 @@ using Umbraco.Net;
namespace Umbraco.Web.BackOffice.AspNetCore
{
- internal class AspNetIpResolver : IIpResolver
+ public class AspNetIpResolver : IIpResolver
{
private readonly IHttpContextAccessor _httpContextAccessor;
diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs
index 5470516cf7..cafb02d367 100644
--- a/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs
+++ b/src/Umbraco.Web.BackOffice/AspNetCore/AspNetCoreSessionIdResolver.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
using Umbraco.Net;
namespace Umbraco.Web.BackOffice.AspNetCore
@@ -12,6 +13,18 @@ namespace Umbraco.Web.BackOffice.AspNetCore
_httpContextAccessor = httpContextAccessor;
}
- public string SessionId => _httpContextAccessor?.HttpContext.Session?.Id;
+
+ public string SessionId
+ {
+ get
+ {
+ var httpContext = _httpContextAccessor?.HttpContext;
+ // If session isn't enabled this will throw an exception so we check
+ var sessionFeature = httpContext?.Features.Get();
+ return sessionFeature != null
+ ? httpContext?.Session?.Id
+ : "0";
+ }
+ }
}
}
diff --git a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs
index 105f238acd..b75f83e35a 100644
--- a/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs
+++ b/src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs
@@ -1,4 +1,3 @@
-using System.Configuration;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
@@ -10,20 +9,29 @@ using Umbraco.Composing;
using Umbraco.Configuration;
using Umbraco.Core;
using Umbraco.Core.Cache;
+using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging.Serilog;
+using Umbraco.Core.Persistence;
+using Umbraco.Core.Runtime;
namespace Umbraco.Web.BackOffice.AspNetCore
{
+
+
public static class UmbracoBackOfficeServiceCollectionExtensions
{
+
public static IServiceCollection AddUmbracoConfiguration(this IServiceCollection services)
{
var serviceProvider = services.BuildServiceProvider();
var configuration = serviceProvider.GetService();
+ if (configuration == null)
+ throw new InvalidOperationException($"Could not resolve {typeof(IConfiguration)} from the container");
+
var configsFactory = new AspNetCoreConfigsFactory(configuration);
var configs = configsFactory.Create();
@@ -33,10 +41,83 @@ namespace Umbraco.Web.BackOffice.AspNetCore
return services;
}
- public static IServiceCollection AddUmbracoBackOffice(this IServiceCollection services)
+
+ ///
+ /// Adds the Umbraco Back Core requirements
+ ///
+ ///
+ ///
+ ///
+ /// Must be called after all services are added to the application because we are cross-wiring the container (currently)
+ ///
+ public static IServiceCollection AddUmbracoCore(this IServiceCollection services)
+ {
+ if (!UmbracoServiceProviderFactory.IsActive)
+ throw new InvalidOperationException("Ensure to add UseUmbraco() in your Program.cs after ConfigureWebHostDefaults to enable Umbraco's service provider factory");
+
+ var umbContainer = UmbracoServiceProviderFactory.UmbracoContainer;
+
+ return services.AddUmbracoCore(umbContainer, Assembly.GetEntryAssembly());
+ }
+
+ public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IRegister umbContainer, Assembly entryAssembly)
{
services.AddSingleton();
+ CreateCompositionRoot(services);
+
+ // TODO: Get rid of this 'Current' requirement
+ var globalSettings = Current.Configs.Global();
+ var umbracoVersion = new UmbracoVersion(globalSettings);
+
+ // TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
+ // this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
+ var typeFinder = new TypeFinder(Current.Logger, new DefaultUmbracoAssemblyProvider(entryAssembly));
+
+ var coreRuntime = GetCoreRuntime(
+ Current.Configs,
+ umbracoVersion,
+ Current.IOHelper,
+ Current.Logger,
+ Current.Profiler,
+ Current.HostingEnvironment,
+ Current.BackOfficeInfo,
+ typeFinder);
+
+ var factory = coreRuntime.Boot(umbContainer);
+
+ return services;
+ }
+
+ private static IRuntime GetCoreRuntime(Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger,
+ IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
+ ITypeFinder typeFinder)
+ {
+ var connectionStringConfig = configs.ConnectionStrings()[Constants.System.UmbracoConnectionName];
+ var dbProviderFactoryCreator = new SqlServerDbProviderFactoryCreator(
+ connectionStringConfig?.ProviderName,
+ DbProviderFactories.GetFactory);
+
+ // Determine if we should use the sql main dom or the default
+ var globalSettings = configs.Global();
+ var connStrings = configs.ConnectionStrings();
+ var appSettingMainDomLock = globalSettings.MainDomLock;
+ var mainDomLock = appSettingMainDomLock == "SqlMainDomLock"
+ ? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connStrings, dbProviderFactoryCreator)
+ : new MainDomSemaphoreLock(logger, hostingEnvironment);
+
+ var mainDom = new MainDom(logger, hostingEnvironment, mainDomLock);
+
+ var coreRuntime = new CoreRuntime(configs, umbracoVersion, ioHelper, logger, profiler, new AspNetCoreBootPermissionsChecker(),
+ hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder);
+
+ return coreRuntime;
+ }
+
+ private static void CreateCompositionRoot(IServiceCollection services)
+ {
+ // TODO: This isn't the best to have to resolve the services now but to avoid this will
+ // require quite a lot of re-work.
var serviceProvider = services.BuildServiceProvider();
var httpContextAccessor = serviceProvider.GetService();
@@ -45,12 +126,8 @@ namespace Umbraco.Web.BackOffice.AspNetCore
var configuration = serviceProvider.GetService();
var configs = serviceProvider.GetService();
-
- services.CreateCompositionRoot(
- httpContextAccessor,
- webHostEnvironment,
- hostApplicationLifetime,
- configs);
+ if (configs == null)
+ throw new InvalidOperationException($"Could not resolve type {typeof(Configs)} from the container, ensure {nameof(AddUmbracoConfiguration)} is called before calling {nameof(AddUmbracoCore)}");
services.AddRuntimeMinifier(configuration);
@@ -69,11 +146,11 @@ namespace Umbraco.Web.BackOffice.AspNetCore
var coreDebug = configs.CoreDebug();
var globalSettings = configs.Global();
- var hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment,
- httpContextAccessor, hostApplicationLifetime);
+ var hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment, httpContextAccessor, hostApplicationLifetime);
var ioHelper = new IOHelper(hostingEnvironment, globalSettings);
var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment,
new AspNetCoreSessionIdResolver(httpContextAccessor),
+ // need to build a new service provider since the one already resolved above doesn't have the IRequestCache yet
() => services.BuildServiceProvider().GetService(), coreDebug, ioHelper,
new AspNetCoreMarchal());
@@ -92,5 +169,13 @@ namespace Umbraco.Web.BackOffice.AspNetCore
return services;
}
+
+ private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker
+ {
+ public void ThrowIfNotPermissions()
+ {
+ // nothing to check
+ }
+ }
}
}
diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
index a7ca2a93d6..33b36ef6e8 100644
--- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
+++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/Umbraco.Web.UI.NetCore/Program.cs b/src/Umbraco.Web.UI.NetCore/Program.cs
index bbe78907f1..1151f16be8 100644
--- a/src/Umbraco.Web.UI.NetCore/Program.cs
+++ b/src/Umbraco.Web.UI.NetCore/Program.cs
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
-
+using Umbraco.Core.Composing;
namespace Umbraco.Web.UI.BackOffice
{
@@ -15,6 +15,7 @@ namespace Umbraco.Web.UI.BackOffice
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
- .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); });
+ .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); })
+ .UseUmbraco();
}
}
diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs
index 9ef4985aea..ab8c6e021c 100644
--- a/src/Umbraco.Web.UI.NetCore/Startup.cs
+++ b/src/Umbraco.Web.UI.NetCore/Startup.cs
@@ -22,8 +22,8 @@ namespace Umbraco.Web.UI.BackOffice
public void ConfigureServices(IServiceCollection services)
{
services.AddUmbracoConfiguration();
+ services.AddUmbracoCore();
services.AddUmbracoWebsite();
- services.AddUmbracoBackOffice();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
diff --git a/src/Umbraco.Web/UmbracoApplication.cs b/src/Umbraco.Web/UmbracoApplication.cs
index b7cdc1039a..9f24da95e3 100644
--- a/src/Umbraco.Web/UmbracoApplication.cs
+++ b/src/Umbraco.Web/UmbracoApplication.cs
@@ -36,7 +36,7 @@ namespace Umbraco.Web
var requestCache = new HttpRequestAppCache(() => HttpContext.Current?.Items);
var umbracoBootPermissionChecker = new AspNetUmbracoBootPermissionChecker();
- return new WebRuntime(configs, umbracoVersion, ioHelper, logger, profiler, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, requestCache, umbracoBootPermissionChecker);
+ return new WebRuntime(configs, umbracoVersion, ioHelper, logger, profiler, hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, GetTypeFinder(), requestCache, umbracoBootPermissionChecker);
}
}
}
diff --git a/src/Umbraco.Web/UmbracoApplicationBase.cs b/src/Umbraco.Web/UmbracoApplicationBase.cs
index fe4622d8fa..ce5f1304cd 100644
--- a/src/Umbraco.Web/UmbracoApplicationBase.cs
+++ b/src/Umbraco.Web/UmbracoApplicationBase.cs
@@ -24,12 +24,10 @@ namespace Umbraco.Web
public abstract class UmbracoApplicationBase : HttpApplication
{
private IRuntime _runtime;
-
private IFactory _factory;
protected UmbracoApplicationBase()
{
-
if (!Umbraco.Composing.Current.IsInitialized)
{
var configFactory = new ConfigsFactory();
@@ -46,8 +44,8 @@ namespace Umbraco.Web
var backOfficeInfo = new AspNetBackOfficeInfo(globalSettings, ioHelper, logger, configFactory.WebRoutingSettings);
var profiler = GetWebProfiler(hostingEnvironment);
Umbraco.Composing.Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler);
+ Logger = logger;
}
-
}
private IProfiler GetWebProfiler(IHostingEnvironment hostingEnvironment)
@@ -67,13 +65,36 @@ namespace Umbraco.Web
}
protected UmbracoApplicationBase(ILogger logger, Configs configs, IIOHelper ioHelper, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo)
- {
+ {
if (!Umbraco.Composing.Current.IsInitialized)
{
+ Logger = logger;
Umbraco.Composing.Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler);
}
}
+ protected ILogger Logger { get; }
+
+ ///
+ /// Gets a
+ ///
+ ///
+ protected virtual ITypeFinder GetTypeFinder()
+ // TODO: Currently we are not passing in any TypeFinderConfig (with ITypeFinderSettings) which we should do, however
+ // this is not critical right now and would require loading in some config before boot time so just leaving this as-is for now.
+ => new TypeFinder(Logger, new DefaultUmbracoAssemblyProvider(
+ // GetEntryAssembly was actually an exposed API by request of the aspnetcore team which works in aspnet core because a website
+ // in that case is essentially an exe. However in netframework there is no entry assembly, things don't really work that way since
+ // the process that is running the site is iisexpress, so this returns null. The best we can do is fallback to GetExecutingAssembly()
+ // which will just return Umbraco.Infrastructure (currently with netframework) and for our purposes that is OK.
+ // If you are curious... There is really no way to get the entry assembly in netframework without the hosting website having it's own
+ // code compiled for the global.asax which is the entry point. Because the default global.asax for umbraco websites is just a file inheriting
+ // from Umbraco.Web.UmbracoApplication, the global.asax file gets dynamically compiled into a DLL in the dynamic folder (we can get an instance
+ // of that, but this doesn't really help us) but the actually entry execution is still Umbraco.Web. So that is the 'highest' level entry point
+ // assembly we can get and we can only get that if we put this code into the WebRuntime since the executing assembly is the 'current' one.
+ // For this purpose, it doesn't matter if it's Umbraco.Web or Umbraco.Infrastructure since all assemblies are in that same path and we are
+ // getting rid of netframework.
+ Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()));
///
/// Gets a runtime.
diff --git a/src/umbraco.sln b/src/umbraco.sln
index be363ef2e6..ca5d4d5188 100644
--- a/src/umbraco.sln
+++ b/src/umbraco.sln
@@ -121,6 +121,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.UI.NetCore", "U
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Umbraco.Web.Website", "Umbraco.Web.Website\Umbraco.Web.Website.csproj", "{5A246D54-3109-4D2B-BE7D-FC0787D126AE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Integration", "Umbraco.Tests.Integration\Umbraco.Tests.Integration.csproj", "{D6319409-777A-4BD0-93ED-B2DFD805B32C}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Common", "Umbraco.Tests.Common\Umbraco.Tests.Common.csproj", "{A499779C-1B3B-48A8-B551-458E582E6E96}"
EndProject
Global
@@ -191,6 +193,10 @@ Global
{5A246D54-3109-4D2B-BE7D-FC0787D126AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A246D54-3109-4D2B-BE7D-FC0787D126AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A246D54-3109-4D2B-BE7D-FC0787D126AE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D6319409-777A-4BD0-93ED-B2DFD805B32C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D6319409-777A-4BD0-93ED-B2DFD805B32C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D6319409-777A-4BD0-93ED-B2DFD805B32C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D6319409-777A-4BD0-93ED-B2DFD805B32C}.Release|Any CPU.Build.0 = Release|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A499779C-1B3B-48A8-B551-458E582E6E96}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -208,6 +214,7 @@ Global
{3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
{C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645}
{FB5676ED-7A69-492C-B802-E7B24144C0FC} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
+ {D6319409-777A-4BD0-93ED-B2DFD805B32C} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
{A499779C-1B3B-48A8-B551-458E582E6E96} = {B5BD12C1-A454-435E-8A46-FF4A364C0382}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution