2020-03-25 20:42:02 +01:00
using System ;
2020-04-20 06:19:59 +02:00
using System.Collections ;
2020-03-25 20:42:02 +01:00
using System.Data.Common ;
2020-04-27 10:09:10 +02:00
using System.Data.SqlClient ;
2020-04-03 01:08:52 +11:00
using System.IO ;
2020-03-25 20:42:02 +01:00
using System.Reflection ;
2020-05-19 09:45:31 +02:00
using System.Runtime.InteropServices ;
2020-02-24 16:18:47 +01:00
using Microsoft.AspNetCore.Hosting ;
using Microsoft.AspNetCore.Http ;
2020-03-16 14:02:08 +01:00
using Microsoft.Extensions.Configuration ;
2020-02-18 08:32:06 +01:00
using Microsoft.Extensions.DependencyInjection ;
2020-04-22 14:23:56 +10:00
using Microsoft.Extensions.Logging ;
using Serilog ;
using Serilog.Extensions.Hosting ;
using Serilog.Extensions.Logging ;
2020-02-25 08:56:58 +01:00
using Umbraco.Composing ;
2020-03-16 14:02:08 +01:00
using Umbraco.Configuration ;
2020-03-16 19:14:04 +01:00
using Umbraco.Core ;
2020-02-24 16:18:47 +01:00
using Umbraco.Core.Cache ;
2020-03-13 18:44:58 +11:00
using Umbraco.Core.Composing ;
2020-02-24 16:18:47 +01:00
using Umbraco.Core.Configuration ;
using Umbraco.Core.IO ;
using Umbraco.Core.Logging ;
using Umbraco.Core.Logging.Serilog ;
2020-03-13 18:44:58 +11:00
using Umbraco.Core.Persistence ;
2020-05-06 20:37:03 +02:00
using Umbraco.Core.Persistence.SqlSyntax ;
2020-02-25 08:56:58 +01:00
using Umbraco.Core.Runtime ;
2020-03-31 12:22:11 +02:00
using Umbraco.Web.Common.AspNetCore ;
2020-05-12 10:21:40 +10:00
using Umbraco.Web.Common.Profiler ;
2020-02-18 08:32:06 +01:00
2020-05-07 10:08:23 +02:00
namespace Umbraco.Extensions
2020-02-18 08:32:06 +01:00
{
2020-05-12 10:21:40 +10:00
2020-03-24 14:48:32 +11:00
public static class UmbracoCoreServiceCollectionExtensions
2020-02-18 08:32:06 +01:00
{
2020-05-12 10:21:40 +10:00
/// <summary>
/// Adds SqlCe support for Umbraco
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
2020-05-06 20:37:03 +02:00
public static IServiceCollection AddUmbracoSqlCeSupport ( this IServiceCollection services )
{
try
{
var binFolder = Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) ;
if ( binFolder ! = null )
{
var dllPath = Path . Combine ( binFolder , "Umbraco.Persistance.SqlCe.dll" ) ;
2020-05-08 16:35:37 +10:00
var umbSqlCeAssembly = Assembly . LoadFrom ( dllPath ) ;
2020-05-06 20:37:03 +02:00
2020-05-08 16:35:37 +10:00
var sqlCeSyntaxProviderType = umbSqlCeAssembly . GetType ( "Umbraco.Persistance.SqlCe.SqlCeSyntaxProvider" ) ;
var sqlCeBulkSqlInsertProviderType = umbSqlCeAssembly . GetType ( "Umbraco.Persistance.SqlCe.SqlCeBulkSqlInsertProvider" ) ;
var sqlCeEmbeddedDatabaseCreatorType = umbSqlCeAssembly . GetType ( "Umbraco.Persistance.SqlCe.SqlCeEmbeddedDatabaseCreator" ) ;
2020-05-06 20:37:03 +02:00
if ( ! ( sqlCeSyntaxProviderType is null | | sqlCeBulkSqlInsertProviderType is null | | sqlCeEmbeddedDatabaseCreatorType is null ) )
{
services . AddSingleton ( typeof ( ISqlSyntaxProvider ) , sqlCeSyntaxProviderType ) ;
services . AddSingleton ( typeof ( IBulkSqlInsertProvider ) , sqlCeBulkSqlInsertProviderType ) ;
services . AddSingleton ( typeof ( IEmbeddedDatabaseCreator ) , sqlCeEmbeddedDatabaseCreatorType ) ;
}
2020-05-08 16:35:37 +10:00
var sqlCeAssembly = Assembly . LoadFrom ( Path . Combine ( binFolder , "System.Data.SqlServerCe.dll" ) ) ;
2020-05-06 20:37:03 +02:00
2020-05-08 16:35:37 +10:00
var sqlCe = sqlCeAssembly . GetType ( "System.Data.SqlServerCe.SqlCeProviderFactory" ) ;
2020-05-06 20:37:03 +02:00
if ( ! ( sqlCe is null ) )
{
2020-05-12 10:21:40 +10:00
DbProviderFactories . RegisterFactory ( Core . Constants . DbProviderNames . SqlCe , sqlCe ) ;
2020-05-06 20:37:03 +02:00
}
}
}
catch
{
// Ignore if SqlCE is not available
}
return services ;
}
2020-05-12 10:21:40 +10:00
/// <summary>
/// Adds Sql Server support for Umbraco
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
2020-05-06 20:37:03 +02:00
public static IServiceCollection AddUmbracoSqlServerSupport ( this IServiceCollection services )
{
DbProviderFactories . RegisterFactory ( Core . Constants . DbProviderNames . SqlServer , SqlClientFactory . Instance ) ;
services . AddSingleton < ISqlSyntaxProvider , SqlServerSyntaxProvider > ( ) ;
services . AddSingleton < IBulkSqlInsertProvider , SqlServerBulkSqlInsertProvider > ( ) ;
services . AddSingleton < IEmbeddedDatabaseCreator , NoopEmbeddedDatabaseCreator > ( ) ;
return services ;
}
2020-03-24 14:48:32 +11:00
/// <summary>
/// Adds the Umbraco Configuration requirements
/// </summary>
/// <param name="services"></param>
2020-03-25 18:21:44 +11:00
/// <param name="configuration"></param>
2020-03-24 14:48:32 +11:00
/// <returns></returns>
2020-03-25 18:21:44 +11:00
public static IServiceCollection AddUmbracoConfiguration ( this IServiceCollection services , IConfiguration configuration )
2020-03-17 17:56:00 +01:00
{
2020-03-25 18:21:44 +11:00
if ( configuration = = null ) throw new ArgumentNullException ( nameof ( configuration ) ) ;
2020-03-23 16:39:27 +11:00
2020-03-17 17:56:00 +01:00
var configsFactory = new AspNetCoreConfigsFactory ( configuration ) ;
var configs = configsFactory . Create ( ) ;
2020-03-19 18:43:39 +01:00
2020-03-17 17:56:00 +01:00
services . AddSingleton ( configs ) ;
return services ;
}
2020-03-13 18:44:58 +11:00
/// <summary>
2020-03-24 14:48:32 +11:00
/// Adds the Umbraco Back Core requirements
2020-03-13 18:44:58 +11:00
/// </summary>
/// <param name="services"></param>
2020-03-25 18:21:44 +11:00
/// <param name="webHostEnvironment"></param>
2020-03-13 18:44:58 +11:00
/// <returns></returns>
2020-03-25 18:21:44 +11:00
public static IServiceCollection AddUmbracoCore ( this IServiceCollection services , IWebHostEnvironment webHostEnvironment )
2020-04-01 14:19:41 +02:00
{
2020-05-12 10:21:40 +10:00
return services . AddUmbracoCore ( webHostEnvironment , out _ ) ;
2020-04-01 14:19:41 +02:00
}
/// <summary>
/// Adds the Umbraco Back Core requirements
/// </summary>
/// <param name="services"></param>
/// <param name="webHostEnvironment"></param>
/// <param name="factory"></param>
/// <returns></returns>
public static IServiceCollection AddUmbracoCore ( this IServiceCollection services , IWebHostEnvironment webHostEnvironment , out IFactory factory )
2020-03-13 18:44:58 +11:00
{
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 ;
2020-04-22 14:23:56 +10:00
var loggingConfig = new LoggingConfiguration (
Path . Combine ( webHostEnvironment . ContentRootPath , "App_Data\\Logs" ) ,
Path . Combine ( webHostEnvironment . ContentRootPath , "config\\serilog.config" ) ,
Path . Combine ( webHostEnvironment . ContentRootPath , "config\\serilog.user.config" ) ) ;
2020-04-20 06:19:59 +02:00
IHttpContextAccessor httpContextAccessor = new HttpContextAccessor ( ) ;
services . AddSingleton < IHttpContextAccessor > ( httpContextAccessor ) ;
2020-04-28 12:31:15 +02:00
var requestCache = new GenericDictionaryRequestAppCache ( ( ) = > httpContextAccessor . HttpContext ? . Items ) ;
2020-04-20 06:19:59 +02:00
2020-04-22 10:29:21 +02:00
services . AddUmbracoCore ( webHostEnvironment ,
umbContainer ,
Assembly . GetEntryAssembly ( ) ,
requestCache ,
loggingConfig ,
out factory ) ;
2020-03-25 20:42:02 +01:00
return services ;
2020-03-13 19:10:21 +11:00
}
2020-03-24 14:48:32 +11:00
/// <summary>
/// Adds the Umbraco Back Core requirements
/// </summary>
/// <param name="services"></param>
2020-03-25 18:21:44 +11:00
/// <param name="webHostEnvironment"></param>
2020-03-24 14:48:32 +11:00
/// <param name="umbContainer"></param>
/// <param name="entryAssembly"></param>
2020-04-20 06:19:59 +02:00
/// <param name="requestCache"></param>
2020-04-22 10:29:21 +02:00
/// <param name="httpContextAccessor"></param>
2020-04-22 14:23:56 +10:00
/// <param name="loggingConfiguration"></param>
2020-04-01 14:19:41 +02:00
/// <param name="factory"></param>
2020-03-24 14:48:32 +11:00
/// <returns></returns>
2020-04-22 14:23:56 +10:00
public static IServiceCollection AddUmbracoCore (
this IServiceCollection services ,
IWebHostEnvironment webHostEnvironment ,
IRegister umbContainer ,
Assembly entryAssembly ,
2020-04-20 06:19:59 +02:00
IRequestCache requestCache ,
2020-04-22 14:23:56 +10:00
ILoggingConfiguration loggingConfiguration ,
out IFactory factory )
2020-02-18 08:32:06 +01:00
{
2020-03-24 11:53:56 +11:00
if ( services is null ) throw new ArgumentNullException ( nameof ( services ) ) ;
2020-03-27 11:39:17 +01:00
var container = umbContainer ;
if ( container is null ) throw new ArgumentNullException ( nameof ( container ) ) ;
2020-03-24 11:53:56 +11:00
if ( entryAssembly is null ) throw new ArgumentNullException ( nameof ( entryAssembly ) ) ;
2020-05-08 17:30:30 +10:00
services . AddUmbracoSqlCeSupport ( ) ;
services . AddUmbracoSqlServerSupport ( ) ;
2020-05-06 20:37:03 +02:00
services . AddSingleton < IDbProviderFactoryCreator > ( x = > new DbProviderFactoryCreator (
DbProviderFactories . GetFactory ,
x . GetServices < ISqlSyntaxProvider > ( ) ,
x . GetServices < IBulkSqlInsertProvider > ( ) ,
x . GetServices < IEmbeddedDatabaseCreator > ( )
) ) ;
2020-05-08 17:47:21 +10:00
// TODO: We want to avoid pre-resolving a container as much as possible we should not
// be doing this any more than we are now. The ugly part about this is that the service
// instances resolved here won't be the same instances resolved from the container
// later once the true container is built. However! ... in the case of IDbProviderFactoryCreator
// it will be the same instance resolved later because we are re-registering this instance back
// into the container. This is not true for `Configs` but we should do that too, see comments in
// `RegisterEssentials`.
2020-04-20 06:19:59 +02:00
var serviceProvider = services . BuildServiceProvider ( ) ;
var configs = serviceProvider . GetService < Configs > ( ) ;
2020-04-29 08:56:42 +02:00
var dbProviderFactoryCreator = serviceProvider . GetRequiredService < IDbProviderFactoryCreator > ( ) ;
2020-02-24 16:18:47 +01:00
2020-04-22 10:29:21 +02:00
CreateCompositionRoot ( services ,
configs ,
webHostEnvironment ,
loggingConfiguration ,
2020-05-12 10:21:40 +10:00
out var logger , out var ioHelper , out var hostingEnvironment , out var backOfficeInfo , out var profiler ) ;
2020-03-13 19:10:21 +11:00
2020-03-24 11:53:56 +11:00
var globalSettings = configs . Global ( ) ;
2020-03-13 18:44:58 +11:00
var umbracoVersion = new UmbracoVersion ( globalSettings ) ;
2020-04-27 10:09:10 +02:00
var typeFinder = CreateTypeFinder ( logger , profiler , webHostEnvironment , entryAssembly , configs . TypeFinder ( ) ) ;
2020-03-13 18:44:58 +11:00
var coreRuntime = GetCoreRuntime (
2020-03-24 11:53:56 +11:00
configs ,
2020-03-13 18:44:58 +11:00
umbracoVersion ,
2020-03-24 11:53:56 +11:00
ioHelper ,
logger ,
profiler ,
hostingEnvironment ,
backOfficeInfo ,
2020-04-27 10:09:10 +02:00
typeFinder ,
2020-04-28 07:01:30 +02:00
requestCache ,
dbProviderFactoryCreator ) ;
2020-03-13 18:44:58 +11:00
2020-04-01 14:19:41 +02:00
factory = coreRuntime . Configure ( container ) ;
2020-03-13 18:44:58 +11:00
2020-02-18 08:32:06 +01:00
return services ;
2020-06-30 20:11:39 +02:00
}
2020-05-06 20:37:03 +02:00
2020-04-27 10:09:10 +02:00
private static ITypeFinder CreateTypeFinder ( Core . Logging . ILogger logger , IProfiler profiler , IWebHostEnvironment webHostEnvironment , Assembly entryAssembly , ITypeFinderSettings typeFinderSettings )
2020-04-03 01:08:52 +11:00
{
var runtimeHashPaths = new RuntimeHashPaths ( ) ;
runtimeHashPaths . AddFolder ( new DirectoryInfo ( Path . Combine ( webHostEnvironment . ContentRootPath , "bin" ) ) ) ;
var runtimeHash = new RuntimeHash ( new ProfilingLogger ( logger , profiler ) , runtimeHashPaths ) ;
2020-04-27 10:09:10 +02:00
return new TypeFinder ( logger , new DefaultUmbracoAssemblyProvider ( entryAssembly ) , runtimeHash , new TypeFinderConfig ( typeFinderSettings ) ) ;
2020-04-03 01:08:52 +11:00
}
2020-04-20 06:19:59 +02:00
private static IRuntime GetCoreRuntime (
2020-04-22 10:29:21 +02:00
Configs configs , IUmbracoVersion umbracoVersion , IIOHelper ioHelper , Core . Logging . ILogger logger ,
2020-03-13 19:10:21 +11:00
IProfiler profiler , Core . Hosting . IHostingEnvironment hostingEnvironment , IBackOfficeInfo backOfficeInfo ,
2020-04-28 07:01:30 +02:00
ITypeFinder typeFinder , IRequestCache requestCache , IDbProviderFactoryCreator dbProviderFactoryCreator )
2020-03-13 18:44:58 +11:00
{
// Determine if we should use the sql main dom or the default
2020-03-23 15:50:01 +11:00
var globalSettings = configs . Global ( ) ;
var connStrings = configs . ConnectionStrings ( ) ;
var appSettingMainDomLock = globalSettings . MainDomLock ;
2020-06-25 11:39:11 +02:00
2020-05-19 09:45:31 +02:00
var isLinux = RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) ;
var mainDomLock = appSettingMainDomLock = = "SqlMainDomLock" | | isLinux = = true
2020-05-29 12:27:00 +02:00
? ( IMainDomLock ) new SqlMainDomLock ( logger , globalSettings , connStrings , dbProviderFactoryCreator , hostingEnvironment )
2020-03-13 18:44:58 +11:00
: new MainDomSemaphoreLock ( logger , hostingEnvironment ) ;
2020-03-25 15:06:22 +11:00
var mainDom = new MainDom ( logger , mainDomLock ) ;
2020-03-13 18:44:58 +11:00
2020-04-20 06:19:59 +02:00
var coreRuntime = new CoreRuntime (
configs ,
umbracoVersion ,
ioHelper ,
logger ,
profiler ,
new AspNetCoreBootPermissionsChecker ( ) ,
hostingEnvironment ,
backOfficeInfo ,
dbProviderFactoryCreator ,
mainDom ,
typeFinder ,
requestCache ) ;
2020-03-13 18:44:58 +11:00
return coreRuntime ;
}
2020-02-25 08:56:58 +01:00
2020-04-22 14:23:56 +10:00
private static IServiceCollection CreateCompositionRoot (
IServiceCollection services ,
2020-04-22 10:29:21 +02:00
Configs configs ,
2020-04-22 14:23:56 +10:00
IWebHostEnvironment webHostEnvironment ,
2020-04-22 10:29:21 +02:00
ILoggingConfiguration loggingConfiguration ,
out Core . Logging . ILogger logger ,
out IIOHelper ioHelper ,
out Core . Hosting . IHostingEnvironment hostingEnvironment ,
out IBackOfficeInfo backOfficeInfo ,
out IProfiler profiler )
2020-02-24 16:18:47 +01:00
{
2020-03-23 16:39:27 +11:00
if ( configs = = null )
throw new InvalidOperationException ( $"Could not resolve type {typeof(Configs)} from the container, ensure {nameof(AddUmbracoConfiguration)} is called before calling {nameof(AddUmbracoCore)}" ) ;
2020-02-24 16:18:47 +01:00
2020-03-16 19:14:04 +01:00
var hostingSettings = configs . Hosting ( ) ;
var globalSettings = configs . Global ( ) ;
2020-02-24 16:18:47 +01:00
2020-04-03 01:08:52 +11:00
hostingEnvironment = new AspNetCoreHostingEnvironment ( hostingSettings , webHostEnvironment ) ;
2020-03-24 11:53:56 +11:00
ioHelper = new IOHelper ( hostingEnvironment , globalSettings ) ;
2020-04-22 14:23:56 +10:00
logger = AddLogger ( services , hostingEnvironment , loggingConfiguration ) ;
2020-03-12 16:56:39 +01:00
2020-03-24 14:47:10 +11:00
backOfficeInfo = new AspNetCoreBackOfficeInfo ( globalSettings ) ;
2020-04-22 15:23:51 +10:00
profiler = GetWebProfiler ( hostingEnvironment ) ;
2020-02-24 16:18:47 +01:00
2020-03-16 14:02:08 +01:00
return services ;
2020-02-24 16:18:47 +01:00
}
2020-03-24 10:51:53 +01:00
2020-04-22 14:23:56 +10:00
/// <summary>
/// Create and configure the logger
/// </summary>
/// <param name="hostingEnvironment"></param>
private static Core . Logging . ILogger AddLogger ( IServiceCollection services , Core . Hosting . IHostingEnvironment hostingEnvironment , ILoggingConfiguration loggingConfiguration )
{
// Create a serilog logger
var logger = SerilogLogger . CreateWithDefaultConfiguration ( hostingEnvironment , loggingConfiguration ) ;
// Wire up all the bits that serilog needs. We need to use our own code since the Serilog ext methods don't cater to our needs since
// we don't want to use the global serilog `Log` object and we don't have our own ILogger implementation before the HostBuilder runs which
2020-04-22 10:29:21 +02:00
// is the only other option that these ext methods allow.
2020-04-22 14:23:56 +10:00
// I have created a PR to make this nicer https://github.com/serilog/serilog-extensions-hosting/pull/19 but we'll need to wait for that.
// Also see : https://github.com/serilog/serilog-extensions-hosting/blob/dev/src/Serilog.Extensions.Hosting/SerilogHostBuilderExtensions.cs
services . AddSingleton < ILoggerFactory > ( services = > new SerilogLoggerFactory ( logger . SerilogLog , false ) ) ;
2020-04-22 10:29:21 +02:00
// This won't (and shouldn't) take ownership of the logger.
2020-04-22 14:23:56 +10:00
services . AddSingleton ( logger . SerilogLog ) ;
// Registered to provide two services...
var diagnosticContext = new DiagnosticContext ( logger . SerilogLog ) ;
// Consumed by e.g. middleware
services . AddSingleton ( diagnosticContext ) ;
// Consumed by user code
services . AddSingleton < IDiagnosticContext > ( diagnosticContext ) ;
return logger ;
}
2020-04-22 15:23:51 +10:00
private static IProfiler GetWebProfiler ( Umbraco . Core . Hosting . IHostingEnvironment hostingEnvironment )
2020-03-25 05:39:25 +01:00
{
// create and start asap to profile boot
if ( ! hostingEnvironment . IsDebugMode )
{
// should let it be null, that's how MiniProfiler is meant to work,
// but our own IProfiler expects an instance so let's get one
return new VoidProfiler ( ) ;
}
2020-04-22 15:23:51 +10:00
var webProfiler = new WebProfiler ( ) ;
2020-03-29 22:35:52 +02:00
webProfiler . StartBoot ( ) ;
2020-03-25 05:39:25 +01:00
return webProfiler ;
}
2020-05-12 10:21:40 +10:00
2020-03-13 18:44:58 +11:00
private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker
{
public void ThrowIfNotPermissions ( )
{
// nothing to check
}
}
2020-03-24 14:48:32 +11:00
2020-02-18 08:32:06 +01:00
}
2020-04-20 06:19:59 +02:00
2020-02-18 08:32:06 +01:00
}