Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/AB5822-smidge-implementation

# Conflicts:
#	src/Umbraco.Web.BackOffice/AspNetCore/UmbracoBackOfficeServiceCollectionExtensions.cs
This commit is contained in:
Bjarke Berg
2020-03-25 13:31:27 +01:00
42 changed files with 905 additions and 239 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,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<IConfiguration>();
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)
/// <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>();
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<IHttpContextAccessor>();
@@ -45,12 +126,8 @@ namespace Umbraco.Web.BackOffice.AspNetCore
var configuration = serviceProvider.GetService<IConfiguration>();
var configs = serviceProvider.GetService<Configs>();
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<IRequestCache>(), 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
}
}
}
}

View File

@@ -13,6 +13,7 @@
<ItemGroup>
<PackageReference Include="Smidge" Version="3.1.0" />
<PackageReference Include="Smidge.Nuglify" Version="2.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
</ItemGroup>
<ItemGroup>

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

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

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
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