using System; using System.Data.Common; using System.Data.SqlClient; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog; using Serilog.Extensions.Hosting; using Serilog.Extensions.Logging; using Umbraco.Composing; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Models; using Umbraco.Core.Configuration.Models.Validation; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Runtime; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Profiler; using ConnectionStrings = Umbraco.Core.Configuration.Models.ConnectionStrings; using CoreDebugSettings = Umbraco.Core.Configuration.Models.CoreDebugSettings; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; using ILogger = Microsoft.Extensions.Logging.ILogger; namespace Umbraco.Extensions { public static class UmbracoCoreServiceCollectionExtensions { /// /// Adds SqlCe support for Umbraco /// /// /// 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"); var umbSqlCeAssembly = Assembly.LoadFrom(dllPath); var sqlCeSyntaxProviderType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeSyntaxProvider"); var sqlCeBulkSqlInsertProviderType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeBulkSqlInsertProvider"); var sqlCeEmbeddedDatabaseCreatorType = umbSqlCeAssembly.GetType("Umbraco.Persistance.SqlCe.SqlCeEmbeddedDatabaseCreator"); 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); } var sqlCeAssembly = Assembly.LoadFrom(Path.Combine(binFolder, "System.Data.SqlServerCe.dll")); var sqlCe = sqlCeAssembly.GetType("System.Data.SqlServerCe.SqlCeProviderFactory"); if (!(sqlCe is null)) { DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlCe, sqlCe); } } } catch { // Ignore if SqlCE is not available } return services; } /// /// Adds Sql Server support for Umbraco /// /// /// public static IServiceCollection AddUmbracoSqlServerSupport(this IServiceCollection services) { DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); return services; } /// /// Adds the Umbraco Back Core requirements /// /// /// /// /// /// /// /// /// /// /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, AppCaches appCaches, ILoggingConfiguration loggingConfiguration, IConfiguration configuration, out IFactory factory) => services.AddUmbracoCore(webHostEnvironment, umbContainer, entryAssembly, appCaches, loggingConfiguration, configuration, GetCoreRuntime, out factory); /// /// Adds the Umbraco Configuration requirements /// /// /// /// public static IServiceCollection AddUmbracoConfiguration(this IServiceCollection services, IConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); services.AddSingleton, ContentSettingsValidator>(); services.AddSingleton, GlobalSettingsValidator>(); services.AddSingleton, RequestHandlerSettingsValidator>(); services.Configure(configuration.GetSection(Constants.Configuration.ConfigActiveDirectory)); services.Configure(configuration.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true); services.Configure(configuration.GetSection(Constants.Configuration.ConfigContent)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigCoreDebug)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigExceptionFilter)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigGlobal)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigHealthChecks)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigHosting)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigImaging)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigExamine)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigKeepAlive)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigLogging)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigMemberPassword)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigModelsBuilder), o => o.BindNonPublicProperties = true); services.Configure(configuration.GetSection(Constants.Configuration.ConfigNuCache)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigRequestHandler)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigRuntime)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigSecurity)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigTours)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigTypeFinder)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigUserPassword)); services.Configure(configuration.GetSection(Constants.Configuration.ConfigWebRouting)); return services; } /// /// Adds the Umbraco Back Core requirements /// /// /// /// /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration configuration) { return services.AddUmbracoCore(webHostEnvironment, configuration, out _); } /// /// Adds the Umbraco Back Core requirements /// /// /// /// /// /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration configuration, out IFactory factory) { 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; var loggingConfig = new LoggingConfiguration( Path.Combine(webHostEnvironment.ContentRootPath, "umbraco", "logs")); IHttpContextAccessor httpContextAccessor = new HttpContextAccessor(); services.AddSingleton(httpContextAccessor); services.AddSingleton(loggingConfig); var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext?.Items); var appCaches = new AppCaches( new DeepCloneAppCache(new ObjectCacheAppCache()), requestCache, new IsolatedCaches(type => new DeepCloneAppCache(new ObjectCacheAppCache()))); services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), appCaches, loggingConfig, configuration, GetCoreRuntime, out factory); return services; } /// /// Adds the Umbraco Back Core requirements /// /// /// /// /// /// /// /// /// Delegate to create an /// /// public static IServiceCollection AddUmbracoCore( this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, AppCaches appCaches, ILoggingConfiguration loggingConfiguration, IConfiguration configuration, //TODO: Yep that's extremely ugly Func getRuntime, out IFactory factory) { if (services is null) throw new ArgumentNullException(nameof(services)); var container = umbContainer; if (container is null) throw new ArgumentNullException(nameof(container)); if (entryAssembly is null) throw new ArgumentNullException(nameof(entryAssembly)); // Add supported databases services.AddUmbracoSqlCeSupport(); services.AddUmbracoSqlServerSupport(); services.AddSingleton(x => new DbProviderFactoryCreator( DbProviderFactories.GetFactory, x.GetServices(), x.GetServices(), x.GetServices() )); // 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`. var serviceProvider = services.BuildServiceProvider(); var globalSettings = serviceProvider.GetService>(); var connectionStrings = serviceProvider.GetService>(); var hostingSettings = serviceProvider.GetService>(); var typeFinderSettings = serviceProvider.GetService>(); var loggerFactory = serviceProvider.GetService(); var dbProviderFactoryCreator = serviceProvider.GetRequiredService(); CreateCompositionRoot(services, globalSettings, hostingSettings, webHostEnvironment, loggingConfiguration, configuration, out var logger, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler); var umbracoVersion = new UmbracoVersion(); var typeFinder = CreateTypeFinder(serviceProvider.GetService(), profiler, webHostEnvironment, entryAssembly, typeFinderSettings); var coreRuntime = getRuntime( globalSettings.CurrentValue, connectionStrings.Value, umbracoVersion, ioHelper, logger, loggerFactory, profiler, hostingEnvironment, backOfficeInfo, typeFinder, appCaches, dbProviderFactoryCreator); factory = coreRuntime.Configure(container); services.Configure(hostingSettings => { hostingSettings.Debug = false; }); return services; } private static ITypeFinder CreateTypeFinder(ILoggerFactory loggerFactory, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, IOptionsMonitor typeFinderSettings) { var runtimeHashPaths = new RuntimeHashPaths(); runtimeHashPaths.AddFolder(new DirectoryInfo(Path.Combine(webHostEnvironment.ContentRootPath, "bin"))); var runtimeHash = new RuntimeHash(new ProfilingLogger(loggerFactory.CreateLogger("RuntimeHash"), profiler), runtimeHashPaths); return new TypeFinder(loggerFactory.CreateLogger(), new DefaultUmbracoAssemblyProvider(entryAssembly), runtimeHash, new TypeFinderConfig(typeFinderSettings)); } private static IRuntime GetCoreRuntime( GlobalSettings globalSettings, ConnectionStrings connectionStrings, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, ILogger logger, ILoggerFactory loggerFactory, IProfiler profiler, IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, ITypeFinder typeFinder, AppCaches appCaches, IDbProviderFactoryCreator dbProviderFactoryCreator) { // Determine if we should use the sql main dom or the default var appSettingMainDomLock = globalSettings.MainDomLock; var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" || isWindows == false ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbProviderFactoryCreator, hostingEnvironment) : new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment); var mainDom = new MainDom(loggerFactory.CreateLogger(), mainDomLock); var coreRuntime = new CoreRuntime( globalSettings, connectionStrings, umbracoVersion, ioHelper, logger, loggerFactory, profiler, new AspNetCoreBootPermissionsChecker(), hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder, appCaches); return coreRuntime; } private static IServiceCollection CreateCompositionRoot( IServiceCollection services, IOptionsMonitor globalSettings, IOptionsMonitor hostingSettings, IWebHostEnvironment webHostEnvironment, ILoggingConfiguration loggingConfiguration, IConfiguration configuration, out ILogger logger, out IIOHelper ioHelper, out Core.Hosting.IHostingEnvironment hostingEnvironment, out IBackOfficeInfo backOfficeInfo, out IProfiler profiler) { if (globalSettings == null) throw new InvalidOperationException($"Could not resolve type {typeof(GlobalSettings)} from the container, ensure {nameof(AddUmbracoConfiguration)} is called before calling {nameof(AddUmbracoCore)}"); hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment); ioHelper = new IOHelper(hostingEnvironment); logger = AddLogger(services, hostingEnvironment, loggingConfiguration, configuration); backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); profiler = GetWebProfiler(hostingEnvironment); return services; } /// /// Create and configure the logger /// /// private static ILogger AddLogger(IServiceCollection services, Core.Hosting.IHostingEnvironment hostingEnvironment, ILoggingConfiguration loggingConfiguration, IConfiguration configuration) { // Create a serilog logger var logger = SerilogLogger.CreateWithDefaultConfiguration(hostingEnvironment, loggingConfiguration, configuration); // This is nessasary to pick up all the loggins to MS ILogger. Log.Logger = logger.SerilogLog; // 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 // is the only other option that these ext methods allow. // 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.AddLogging(configure => { configure.AddSerilog(logger.SerilogLog, false); }); //services.AddSingleton(services => new SerilogLoggerFactory(logger.SerilogLog, false)); // // This won't (and shouldn't) take ownership of the logger. // 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(diagnosticContext); var serviceProvider = services.BuildServiceProvider(); return serviceProvider.GetRequiredService().CreateLogger("Generic Logger"); } private static IProfiler GetWebProfiler(Umbraco.Core.Hosting.IHostingEnvironment hostingEnvironment) { // 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(); } var webProfiler = new WebProfiler(); webProfiler.StartBoot(); return webProfiler; } private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker { public void ThrowIfNotPermissions() { // nothing to check } } } }