using System; using System.Collections.Generic; 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 Umbraco.Core; using Umbraco.Core.Builder; 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.Infrastructure.HostedServices; using Umbraco.Infrastructure.HostedServices.ServerRegistration; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Profiler; using ConnectionStrings = Umbraco.Core.Configuration.Models.ConnectionStrings; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Extensions { public static class UmbracoCoreServiceCollectionExtensions { /// /// Adds the Umbraco Back Core requirements /// /// /// /// /// /// /// /// Weird hack for tests, won't exist much longer /// /// Shouldn't exist public static IUmbracoBuilder AddUmbracoCore( this IUmbracoBuilder builder, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, AppCaches appCaches, ILoggingConfiguration loggingConfiguration, IConfiguration configuration, //TODO: Yep that's extremely ugly Action configureSomeMoreBits) { if (builder is null) throw new ArgumentNullException(nameof(builder)); if (entryAssembly is null) throw new ArgumentNullException(nameof(entryAssembly)); builder.Services.AddLazySupport(); // Add service session // This can be overwritten by the user by adding their own call to AddSession // since the last call of AddSession take precedence builder.Services.AddSession(options => { options.Cookie.Name = "UMB_SESSION"; options.Cookie.HttpOnly = true; }); var syntaxProviders = new List(); var insertProviders = new List(); var databaseCreators = new List(); // Add supported databases builder.Services.AddUmbracoSqlCeSupport(syntaxProviders, insertProviders, databaseCreators); builder.Services.AddUmbracoSqlServerSupport(syntaxProviders, insertProviders, databaseCreators); var dbProviderFactoryCreator = new DbProviderFactoryCreator( DbProviderFactories.GetFactory, syntaxProviders, insertProviders, databaseCreators); builder.Services.AddSingleton(dbProviderFactoryCreator); // TODO: We should not be doing this at all. var serviceProvider = builder.Services.BuildServiceProvider(); // Switching to IOptions vs IOptionsMonitor was rejected previously as it prevents setting IsDebug true without a restart var hostingSettings = serviceProvider.GetService>(); // <--- We are now building ServiceProvider just for this line var hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment); var ioHelper = new IOHelper(hostingEnvironment); var profiler = GetWebProfiler(hostingEnvironment); builder.Services.AddUnique(); builder.Services.AddLogger(loggingConfiguration, configuration); var loggerFactory = builder.BuilderLoggerFactory; var typeFinderSettings = builder.Config.GetSection(Core.Constants.Configuration.ConfigTypeFinder).Get() ?? new TypeFinderSettings(); var typeFinder = CreateTypeFinder(loggerFactory, profiler, webHostEnvironment, entryAssembly, Options.Create(typeFinderSettings)); var globalSettings = builder.Config.GetSection(Core.Constants.Configuration.ConfigGlobal).Get() ?? new GlobalSettings(); var connectionStrings = builder.Config.GetSection("ConnectionStrings").Get(opt => opt.BindNonPublicProperties = true) ?? new ConnectionStrings(); configureSomeMoreBits( builder, globalSettings, connectionStrings, new UmbracoVersion(), ioHelper, profiler, hostingEnvironment, typeFinder, appCaches, dbProviderFactoryCreator); builder.AddComposers(); return builder; } public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) { var composerTypes = builder.TypeLoader.GetTypes(); var enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); new Composers(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger()).Compose(); return builder; } /// /// Adds SqlCe support for Umbraco /// private static IServiceCollection AddUmbracoSqlCeSupport( this IServiceCollection services, ICollection syntaxProviders, ICollection insertProviders, ICollection databaseCreators) { 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); syntaxProviders.Add((ISqlSyntaxProvider)Activator.CreateInstance(sqlCeSyntaxProviderType)); insertProviders.Add((IBulkSqlInsertProvider)Activator.CreateInstance(sqlCeBulkSqlInsertProviderType)); databaseCreators.Add((IEmbeddedDatabaseCreator)Activator.CreateInstance(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, ICollection syntaxProviders, ICollection insertProviders, ICollection databaseCreators) { DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); var syntaxProvider = new SqlServerSyntaxProvider(); var insertProvider = new SqlServerBulkSqlInsertProvider(); var databaseCreator = new NoopEmbeddedDatabaseCreator(); services.AddSingleton(syntaxProvider); services.AddSingleton(insertProvider); services.AddSingleton(databaseCreator); syntaxProviders.Add(syntaxProvider); insertProviders.Add(insertProvider); databaseCreators.Add(databaseCreator); return services; } /// /// Adds hosted services for Umbraco. /// /// /// public static IServiceCollection AddUmbracoHostedServices(this IServiceCollection services) { services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); services.AddHostedService(); return services; } /// /// Adds HTTP clients for Umbraco. /// /// /// public static IServiceCollection AddUmbracoHttpClients(this IServiceCollection services) { services.AddHttpClient(); return services; } private static ITypeFinder CreateTypeFinder(ILoggerFactory loggerFactory, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, IOptions 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)); } internal static void ConfigureSomeMorebits( IUmbracoBuilder builder, GlobalSettings globalSettings, ConnectionStrings connectionStrings, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, IProfiler profiler, IHostingEnvironment hostingEnvironment, 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(builder.BuilderLoggerFactory.CreateLogger(), builder.BuilderLoggerFactory, globalSettings, connectionStrings, dbProviderFactoryCreator, hostingEnvironment) : new MainDomSemaphoreLock(builder.BuilderLoggerFactory.CreateLogger(), hostingEnvironment); var mainDom = new MainDom(builder.BuilderLoggerFactory.CreateLogger(), mainDomLock); var logger = builder.BuilderLoggerFactory.CreateLogger(); var profilingLogger = new ProfilingLogger(logger, profiler); AppDomain.CurrentDomain.SetData("DataDirectory", hostingEnvironment?.MapPathContentRoot(Constants.SystemDirectories.Data)); // application environment AppDomain.CurrentDomain.UnhandledException += (_, args) => { var exception = (Exception)args.ExceptionObject; var isTerminating = args.IsTerminating; // always true? var msg = "Unhandled exception in AppDomain"; if (isTerminating) msg += " (terminating)"; msg += "."; logger.LogError(exception, msg); }; // TODO: Don't do this, UmbracoBuilder ctor should handle it... builder.TypeLoader = new TypeLoader(typeFinder, appCaches.RuntimeCache, new DirectoryInfo(hostingEnvironment.LocalTempPath), builder.BuilderLoggerFactory.CreateLogger(), profilingLogger); builder.Services.AddUnique(new AspNetCoreBootPermissionsChecker()); builder.Services.AddUnique(profiler); builder.Services.AddUnique(profilingLogger); builder.Services.AddUnique(mainDom); builder.Services.AddUnique(appCaches); builder.Services.AddUnique(appCaches.RequestCache); builder.Services.AddUnique(builder.TypeLoader); builder.Services.AddUnique(typeFinder); builder.Services.AddUnique(ioHelper); builder.Services.AddUnique(umbracoVersion); builder.Services.AddUnique(dbProviderFactoryCreator); builder.Services.AddUnique(hostingEnvironment); builder.Services.AddUnique(); // after bootstrapping we let the container wire up for us. builder.Services.AddUnique(); builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext); builder.Services.AddUnique(factory => factory.GetRequiredService().BulkSqlInsertProvider); // re-create the state object with the essential services builder.Services.AddUnique(); } /// /// Create and configure the logger /// /// public static IServiceCollection AddLogger( this IServiceCollection services, ILoggingConfiguration loggingConfiguration, IConfiguration configuration) { // Create a serilog logger var logger = SerilogLogger.CreateWithDefaultConfiguration(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); }); // 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); return services; } 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 } } } }