Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/ab5820-webprofiler-aspnetcore

# Conflicts:
#	src/Umbraco.Web.UI.NetCore/Startup.cs
This commit is contained in:
Bjarke Berg
2020-03-24 19:38:44 +01:00
42 changed files with 910 additions and 250 deletions

2
.gitignore vendored
View File

@@ -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/*

View File

@@ -14,7 +14,7 @@ namespace Umbraco.Configuration
public AspNetCoreConfigsFactory(IConfiguration configuration)
{
_configuration = configuration;
_configuration = configuration ?? throw new System.ArgumentNullException(nameof(configuration));
}
public Configs Create()

View File

@@ -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...
}
}
}

View File

@@ -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;
}

View File

@@ -1,7 +1,6 @@
using System;
using Umbraco.Core.Logging;
namespace Umbraco.Tests.TestHelpers
namespace Umbraco.Core.Logging
{
public class ConsoleLogger : ILogger
{

View File

@@ -2,6 +2,7 @@
namespace Umbraco.Core.Logging
{
/// <summary>
/// Defines the logging service.
/// </summary>

View File

@@ -15,6 +15,7 @@ namespace Umbraco.Web
public UriUtility(IHostingEnvironment hostingEnvironment)
{
if (hostingEnvironment is null) throw new ArgumentNullException(nameof(hostingEnvironment));
ResetAppDomainAppVirtualPath(hostingEnvironment);
}

View File

@@ -0,0 +1,20 @@
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.UseServiceProviderFactory(new UmbracoServiceProviderFactory());
}
}
}

View File

@@ -16,11 +16,13 @@ namespace Umbraco.Core.Composing.LightInject
/// <summary>
/// Initializes a new instance of the <see cref="LightInjectContainer"/> with a LightInject container.
/// </summary>
protected LightInjectContainer(ServiceContainer container)
public LightInjectContainer(ServiceContainer container)
{
Container = container;
Container = ConfigureContainer(container);
}
//TODO: The Create methods can die when net framework is gone
/// <summary>
/// Creates a new instance of the <see cref="LightInjectContainer"/> class.
/// </summary>
@@ -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
/// <summary>
/// Gets the LightInject container.
/// </summary>
protected ServiceContainer Container { get; }
public ServiceContainer Container { get; }
/// <inheritdoc cref="IRegister"/>
/// <inheritdoc cref="IFactory"/>

View File

@@ -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
{
/// <summary>
/// Creates the container.
/// </summary>
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";

View File

@@ -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
{
/// <summary>
/// Used to create Umbraco's container and cross-wire it up before the applicaton starts
/// </summary>
public class UmbracoServiceProviderFactory : IServiceProviderFactory<IServiceContainer>
{
public UmbracoServiceProviderFactory(ServiceContainer container)
{
_container = new LightInjectContainer(container);
}
/// <summary>
/// Creates an ASP.NET Core compatible service container
/// </summary>
/// <returns></returns>
public static ServiceContainer CreateServiceContainer() => new ServiceContainer(ContainerOptions.Default.Clone().WithMicrosoftSettings().WithAspNetCoreSettings());
/// <summary>
/// Default ctor for use in Host Builder configuration
/// </summary>
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;
/// <summary>
/// When the empty ctor is used this returns if this factory is active
/// </summary>
public static bool IsActive { get; private set; }
/// <summary>
/// When the empty ctor is used this returns the created IRegister
/// </summary>
public static IRegister UmbracoContainer { get; private set; }
/// <summary>
/// Create the container with the required settings for aspnetcore3
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public IServiceContainer CreateBuilder(IServiceCollection services)
{
_services = services;
return _container.Container;
}
/// <summary>
/// This cross-wires the container just before the application calls "Configure"
/// </summary>
/// <param name="containerBuilder"></param>
/// <returns></returns>
public IServiceProvider CreateServiceProvider(IServiceContainer containerBuilder)
{
var provider = containerBuilder.CreateServiceProvider(_services);
return provider;
}
}
}

View File

@@ -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();

View File

@@ -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<string, DbProviderFactory> _getFactory;
public SqlServerDbProviderFactoryCreator(string defaultProviderName, Func<string, DbProviderFactory> 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");
}
}
}

View File

@@ -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
/// <summary>
/// Gets the <see cref="ITypeFinder"/>
/// </summary>
protected ITypeFinder TypeFinder { get; private set; }
protected ITypeFinder TypeFinder { get; }
/// <summary>
/// Gets the <see cref="IIOHelper"/>
@@ -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<Type> GetComposerTypes(TypeLoader typeLoader)
=> typeLoader.GetTypes<IComposer>();
/// <summary>
/// Gets a <see cref="ITypeFinder"/>
/// </summary>
/// <returns></returns>
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()));
/// <summary>
/// Gets the application caches.
/// </summary>

View File

@@ -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;
}

View File

@@ -9,8 +9,11 @@
<PackageReference Include="HtmlAgilityPack" Version="1.8.14" />
<PackageReference Include="LightInject" Version="6.3.2" />
<PackageReference Include="LightInject.Annotation" Version="1.1.0" />
<PackageReference Include="LightInject.Microsoft.DependencyInjection" Version="3.3.0" />
<PackageReference Include="LightInject.Microsoft.Hosting" Version="1.2.0" />
<PackageReference Include="Markdown" Version="2.2.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" />
@@ -57,6 +60,9 @@
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Tests.Benchmarks</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Umbraco.Tests.Integration</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>

View File

@@ -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);
}
}
}

View File

@@ -35,7 +35,7 @@ using ServiceMap = System.Collections.Generic.Dictionary<System.Type, System.Col
http://twitter.com/bernhardrichter
******************************************************************************/
namespace Umbraco.Tests.Composing
namespace Umbraco.Tests.Common.Composing
{
public static class LightInjectValidation
{
@@ -110,7 +110,6 @@ Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or h
{
var registration = GetServiceRegistration(serviceMap, validationTarget);
if (registration == null)
{
if (validationTarget.ServiceType.IsFunc() || validationTarget.ServiceType.IsLazy())
{
var serviceType = validationTarget.ServiceType.GenericTypeArguments[0];
@@ -118,17 +117,13 @@ Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or h
registration = GetServiceRegistration(serviceMap, underlyingvalidationTarget);
if (registration != null)
{
return;
}
if (serviceMap.ContainsAmbiguousRegistrationFor(serviceType))
{
result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, underlyingvalidationTarget));
}
else
{
string message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType);
var message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType);
result.Add(new ValidationResult(message, ValidationSeverity.MissingDependency, underlyingvalidationTarget));
}
}
@@ -140,21 +135,14 @@ Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or h
if (registrations.Any()) return;
// strict: there has to be at least 1
string message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType);
var message = string.Format(MissingDeferredDependency, validationTarget.ServiceType, underlyingvalidationTarget.ServiceType);
result.Add(new ValidationResult(message, ValidationSeverity.MissingDependency, underlyingvalidationTarget));
}
else
{
if (serviceMap.ContainsAmbiguousRegistrationFor(validationTarget.ServiceType))
{
result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, validationTarget));
}
else
{
result.Add(new ValidationResult("", ValidationSeverity.MissingDependency, validationTarget));
}
}
}
result.Add(new ValidationResult("", ValidationSeverity.Ambiguous, validationTarget));
else
result.Add(new ValidationResult("", ValidationSeverity.MissingDependency, validationTarget));
else
{
ValidateDisposable(validationTarget, result, registration);
@@ -206,24 +194,16 @@ Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or h
private static ServiceRegistration GetServiceRegistration(ServiceMap serviceMap, ValidationTarget validationTarget)
{
if (!serviceMap.TryGetValue(validationTarget.ServiceType, out var registrations))
{
return null;
}
if (registrations.TryGetValue(string.Empty, out var registration))
{
return registration;
}
if (registrations.Count == 1)
{
return registrations.Values.First();
}
if (registrations.TryGetValue(validationTarget.ServiceName, out registration))
{
return registration;
}
return null;
}
@@ -231,22 +211,16 @@ Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or h
private static string GetLifetimeName(ILifetime lifetime)
{
if (lifetime == null)
{
return "Transient";
}
return lifetime.GetType().Name;
}
private static int GetLifespan(ILifetime lifetime)
{
if (lifetime == null)
{
return 0;
}
if (LifeSpans.TryGetValue(lifetime.GetType(), out var lifespan))
{
return lifespan;
}
return 0;
}
}
@@ -274,9 +248,7 @@ Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or h
if (serviceType.GetTypeInfo().IsGenericType && serviceType.GetTypeInfo().ContainsGenericParameters)
{
ServiceType = serviceType.GetGenericTypeDefinition();
}
}
@@ -340,9 +312,7 @@ Ensure that 'NameSpace.IBar' is registered with a lifetime that is equal to or h
public static bool ContainsAmbiguousRegistrationFor(this ServiceMap serviceMap, Type serviceType)
{
if (!serviceMap.TryGetValue(serviceType, out var registrations))
{
return false;
}
return registrations.Count > 1;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
/// </summary>
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<ILogger>(), GetHostingEnvironment(), new MainDomSemaphoreLock(Mock.Of<ILogger>(), GetHostingEnvironment()));
UriUtility = new UriUtility(GetHostingEnvironment());
SettingsForTests = new SettingsForTests();
MainDom = new SimpleMainDom();
_typeFinder = new TypeFinder(Mock.Of<ILogger>(), new DefaultUmbracoAssemblyProvider(entryAssembly));
}
public ITypeFinder GetTypeFinder()
{
var typeFinder = new TypeFinder(Mock.Of<ILogger>(),
new DefaultUmbracoAssemblyProvider(typeof(TestHelperBase).Assembly));
return typeFinder;
}
public ITypeFinder GetTypeFinder() => _typeFinder;
public TypeLoader GetMockedTypeLoader()
{
return new TypeLoader(IOHelper, Mock.Of<ITypeFinder>(), Mock.Of<IAppPolicyCache>(), new DirectoryInfo(IOHelper.MapPath("~/App_Data/TEMP")), Mock.Of<IProfilingLogger>());
}
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();
/// <summary>
/// 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()
{

View File

@@ -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<Foo>();
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<IRuntimeState>();
var umbracoDatabaseFactory = Mock.Of<IUmbracoDatabaseFactory>();
var dbProviderFactoryCreator = Mock.Of<IDbProviderFactoryCreator>();
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<Foo>();
var foo2 = lightInjectServiceProvider.GetService<Foo>();
var foo3 = umbracoContainer.GetInstance<Foo>();
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()
{
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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<IHttpContextAccessor>(x => x.HttpContext == httpContext);
_ipResolver = new AspNetIpResolver(_httpContextAccessor);
_hostEnvironment = Mock.Of<IWebHostEnvironment>(x =>
x.ApplicationName == "UmbracoIntegrationTests"
&& x.ContentRootPath == CurrentAssemblyDirectory
&& x.WebRootPath == CurrentAssemblyDirectory); // same folder for now?
_hostingEnvironment = new AspNetCoreHostingEnvironment(
SettingsForTests.GetDefaultHostingSettings(),
_hostEnvironment,
_httpContextAccessor,
Mock.Of<IHostApplicationLifetime>());
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;
}
}

View File

@@ -0,0 +1,11 @@
using Umbraco.Core.Runtime;
namespace Umbraco.Tests.Integration.Implementations
{
public class TestUmbracoBootPermissionChecker : IUmbracoBootPermissionChecker
{
public void ThrowIfNotPermissions()
{
}
}
}

View File

@@ -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<IHttpContextAccessor>(x => testHelper.GetHttpContextAccessor());
services.AddSingleton<IWebHostEnvironment>(x => testHelper.GetWebHostEnvironment());
services.AddSingleton<IHostApplicationLifetime>(x => Mock.Of<IHostApplicationLifetime>());
// 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<IConfiguration>(x => configurationBuilder.Build());
// Add it!
services.AddUmbracoConfiguration();
services.AddUmbracoCore(umbracoContainer, GetType().Assembly);
// assert results
var runtimeState = umbracoContainer.GetInstance<IRuntimeState>();
var mainDom = umbracoContainer.GetInstance<IMainDom>();
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<MyComponent>();
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;
}
}
}
}

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="LightInject.Microsoft.DependencyInjection" Version="3.3.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="nunit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj" />
<ProjectReference Include="..\Umbraco.Tests.Common\Umbraco.Tests.Common.csproj" />
<ProjectReference Include="..\Umbraco.Web.BackOffice\Umbraco.Web.BackOffice.csproj" />
</ItemGroup>
</Project>

View File

@@ -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<ILogger>(), Mock.Of<IProfiler>(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetRequestCache(), new AspNetUmbracoBootPermissionChecker())
: base(configs, umbracoVersion, ioHelper, Mock.Of<ILogger>(), Mock.Of<IProfiler>(), hostingEnvironment, backOfficeInfo, TestHelper.DbProviderFactoryCreator, TestHelper.MainDom, TestHelper.GetTypeFinder(), TestHelper.GetRequestCache(), new AspNetUmbracoBootPermissionChecker())
{
}

View File

@@ -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();
}

View File

@@ -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<IMapperCollection>(() => factory.GetInstance<IMapperCollection>()), TestHelper.DbProviderFactoryCreator);
var typeFinder = new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(GetType().Assembly));
var ioHelper = TestHelper.IOHelper;
var hostingEnvironment = Mock.Of<IHostingEnvironment>();
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<IMainDom>(() => mainDom), new Lazy<IServerRegistrar>(() => factory.GetInstance<IServerRegistrar>()), 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;
}
}
}

View File

@@ -33,7 +33,6 @@ namespace Umbraco.Tests.TestHelpers
protected override void Compose()
{
base.Compose();
base.Compose();
Composition.RegisterUnique<IPublishedValueFallback, PublishedValueFallback>();
Composition.RegisterUnique<IProfilingLogger, ProfilingLogger>();

View File

@@ -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();

View File

@@ -120,7 +120,6 @@
<Compile Include="Clr\ReflectionUtilitiesTests.cs" />
<Compile Include="Collections\OrderedHashSetTests.cs" />
<Compile Include="Composing\CompositionTests.cs" />
<Compile Include="Composing\LightInjectValidation.cs" />
<Compile Include="Composing\ContainerConformingTests.cs" />
<Compile Include="Configurations\GlobalSettingsTests.cs" />
<Compile Include="CoreThings\CallContextTests.cs" />
@@ -210,7 +209,6 @@
<Compile Include="Services\CachedDataTypeServiceTests.cs" />
<Compile Include="Services\ConsentServiceTests.cs" />
<Compile Include="Services\SectionServiceTests.cs" />
<Compile Include="TestHelpers\ConsoleLogger.cs" />
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingExtensions.cs" />
<Compile Include="TestHelpers\ControllerTesting\AuthenticateEverythingMiddleware.cs" />
<Compile Include="TestHelpers\ControllerTesting\SpecificAssemblyResolver.cs" />

View File

@@ -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 _))

View File

@@ -3,7 +3,7 @@ using Umbraco.Net;
namespace Umbraco.Web.BackOffice.AspNetCore
{
internal class AspNetIpResolver : IIpResolver
public class AspNetIpResolver : IIpResolver
{
private readonly IHttpContextAccessor _httpContextAccessor;

View File

@@ -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<ISessionFeature>();
return sessionFeature != null
? httpContext?.Session?.Id
: "0";
}
}
}
}

View File

@@ -1,3 +1,6 @@
using System;
using System.Data.Common;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
@@ -7,20 +10,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<IConfiguration>();
if (configuration == null)
throw new InvalidOperationException($"Could not resolve {typeof(IConfiguration)} from the container");
var configsFactory = new AspNetCoreConfigsFactory(configuration);
var configs = configsFactory.Create();
@@ -30,44 +42,102 @@ namespace Umbraco.Web.BackOffice.AspNetCore
return services;
}
public static IServiceCollection AddUmbracoBackOffice(this IServiceCollection services)
/// <summary>
/// Adds the Umbraco Back Core requirements
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
/// <remarks>
/// Must be called after all services are added to the application because we are cross-wiring the container (currently)
/// </remarks>
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<IHttpContextAccessor, HttpContextAccessor>();
var serviceProvider = services.BuildServiceProvider();
CreateCompositionRoot(services);
var httpContextAccessor = serviceProvider.GetService<IHttpContextAccessor>();
var webHostEnvironment = serviceProvider.GetService<IWebHostEnvironment>();
var hostApplicationLifetime = serviceProvider.GetService<IHostApplicationLifetime>();
// TODO: Get rid of this 'Current' requirement
var globalSettings = Current.Configs.Global();
var umbracoVersion = new UmbracoVersion(globalSettings);
var configs = serviceProvider.GetService<Configs>();
// 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));
services.CreateCompositionRoot(
httpContextAccessor,
webHostEnvironment,
hostApplicationLifetime,
configs);
var coreRuntime = GetCoreRuntime(
Current.Configs,
umbracoVersion,
Current.IOHelper,
Current.Logger,
Current.Profiler,
Current.HostingEnvironment,
Current.BackOfficeInfo,
typeFinder);
var factory = coreRuntime.Boot(umbContainer);
return services;
}
public static IServiceCollection CreateCompositionRoot(
this IServiceCollection services,
IHttpContextAccessor httpContextAccessor,
IWebHostEnvironment webHostEnvironment,
IHostApplicationLifetime hostApplicationLifetime,
Configs configs)
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.GetRequiredService<IHttpContextAccessor>();
var webHostEnvironment = serviceProvider.GetRequiredService<IWebHostEnvironment>();
var hostApplicationLifetime = serviceProvider.GetRequiredService<IHostApplicationLifetime>();
var configs = serviceProvider.GetService<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)}");
var hostingSettings = configs.Hosting();
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<IRequestCache>(), coreDebug, ioHelper,
new AspNetCoreMarchal());
@@ -75,8 +145,14 @@ namespace Umbraco.Web.BackOffice.AspNetCore
var profiler = new LogProfiler(logger);
Current.Initialize(logger, configs, ioHelper, hostingEnvironment, backOfficeInfo, profiler);
}
return services;
private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker
{
public void ThrowIfNotPermissions()
{
// nothing to check
}
}
}
}

View File

@@ -10,6 +10,10 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Configuration\Umbraco.Configuration.csproj" />
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj" />

View File

@@ -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<Startup>(); });
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.UseUmbraco();
}
}

View File

@@ -25,8 +25,8 @@ namespace Umbraco.Web.UI.BackOffice
services.AddUmbracoConfiguration();
services.AddUmbracoRequest();
services.AddUmbracoCore();
services.AddUmbracoWebsite();
services.AddUmbracoBackOffice();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@@ -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);
}
}
}

View File

@@ -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; }
/// <summary>
/// Gets a <see cref="ITypeFinder"/>
/// </summary>
/// <returns></returns>
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()));
/// <summary>
/// Gets a runtime.

View File

@@ -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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Web.Common", "Umbraco.Web.Common\Umbraco.Web.Common.csproj", "{839D3048-9718-4907-BDE0-7CD63D364383}"
@@ -193,6 +195,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
@@ -214,6 +220,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