using System; using System.Collections; using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.IO; using System.Reflection; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Hosting; using Serilog.Extensions.Logging; using Smidge; using Smidge.Nuglify; using Umbraco.Composing; using Umbraco.Configuration; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Logging.Serilog; using Umbraco.Core.Persistence; using Umbraco.Core.Runtime; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.Runtime.Profiler; namespace Umbraco.Web.Common.Extensions { public static class UmbracoCoreServiceCollectionExtensions { /// /// Adds the Umbraco Configuration requirements /// /// /// /// public static IServiceCollection AddUmbracoConfiguration(this IServiceCollection services, IConfiguration configuration) { if (configuration == null) throw new ArgumentNullException(nameof(configuration)); var configsFactory = new AspNetCoreConfigsFactory(configuration); var configs = configsFactory.Create(); services.AddSingleton(configs); return services; } /// /// Adds the Umbraco Back Core requirements /// /// /// /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment) { return services.AddUmbracoCore(webHostEnvironment,out _); } /// /// Adds the Umbraco Back Core requirements /// /// /// /// /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment, 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, "App_Data\\Logs"), Path.Combine(webHostEnvironment.ContentRootPath, "config\\serilog.config"), Path.Combine(webHostEnvironment.ContentRootPath, "config\\serilog.user.config")); IHttpContextAccessor httpContextAccessor = new HttpContextAccessor(); services.AddSingleton(httpContextAccessor); var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext?.Items); services.AddUmbracoCore(webHostEnvironment, umbContainer, Assembly.GetEntryAssembly(), requestCache, loggingConfig, out factory); return services; } /// /// Adds the Umbraco Back Core requirements /// /// /// /// /// /// /// /// /// /// public static IServiceCollection AddUmbracoCore( this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IRegister umbContainer, Assembly entryAssembly, IRequestCache requestCache, ILoggingConfiguration loggingConfiguration, 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)); var serviceProvider = services.BuildServiceProvider(); var configs = serviceProvider.GetService(); var dbProviderFactoryCreator = serviceProvider.GetRequiredService(); CreateCompositionRoot(services, configs, webHostEnvironment, loggingConfiguration, out var logger, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler); var globalSettings = configs.Global(); var umbracoVersion = new UmbracoVersion(globalSettings); var typeFinder = CreateTypeFinder(logger, profiler, webHostEnvironment, entryAssembly, configs.TypeFinder()); RegisterDatabaseTypes(typeFinder); var coreRuntime = GetCoreRuntime( configs, umbracoVersion, ioHelper, logger, profiler, hostingEnvironment, backOfficeInfo, typeFinder, requestCache, dbProviderFactoryCreator); factory = coreRuntime.Configure(container); return services; } private static void RegisterDatabaseTypes(ITypeFinder typeFinder) { // need to manually register this factory DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); var sqlCe = typeFinder.GetTypeByName("System.Data.SqlServerCe.SqlCeProviderFactory"); if (!(sqlCe is null)) { DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlCe, sqlCe ); } } private static ITypeFinder CreateTypeFinder(Core.Logging.ILogger logger, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, ITypeFinderSettings typeFinderSettings) { var runtimeHashPaths = new RuntimeHashPaths(); runtimeHashPaths.AddFolder(new DirectoryInfo(Path.Combine(webHostEnvironment.ContentRootPath, "bin"))); var runtimeHash = new RuntimeHash(new ProfilingLogger(logger, profiler), runtimeHashPaths); return new TypeFinder(logger, new DefaultUmbracoAssemblyProvider(entryAssembly), runtimeHash, new TypeFinderConfig(typeFinderSettings)); } private static IRuntime GetCoreRuntime( Configs configs, IUmbracoVersion umbracoVersion, IIOHelper ioHelper, Core.Logging.ILogger logger, IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo, ITypeFinder typeFinder, IRequestCache requestCache, IDbProviderFactoryCreator dbProviderFactoryCreator) { // 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, mainDomLock); var coreRuntime = new CoreRuntime( configs, umbracoVersion, ioHelper, logger, profiler, new AspNetCoreBootPermissionsChecker(), hostingEnvironment, backOfficeInfo, dbProviderFactoryCreator, mainDom, typeFinder, requestCache); return coreRuntime; } private static IServiceCollection CreateCompositionRoot( IServiceCollection services, Configs configs, IWebHostEnvironment webHostEnvironment, ILoggingConfiguration loggingConfiguration, out Core.Logging.ILogger logger, out IIOHelper ioHelper, out Core.Hosting.IHostingEnvironment hostingEnvironment, out IBackOfficeInfo backOfficeInfo, out IProfiler profiler) { if (configs == null) throw new InvalidOperationException($"Could not resolve type {typeof(Configs)} from the container, ensure {nameof(AddUmbracoConfiguration)} is called before calling {nameof(AddUmbracoCore)}"); var hostingSettings = configs.Hosting(); var globalSettings = configs.Global(); hostingEnvironment = new AspNetCoreHostingEnvironment(hostingSettings, webHostEnvironment); ioHelper = new IOHelper(hostingEnvironment, globalSettings); logger = AddLogger(services, hostingEnvironment, loggingConfiguration); backOfficeInfo = new AspNetCoreBackOfficeInfo(globalSettings); profiler = GetWebProfiler(hostingEnvironment); return services; } /// /// Create and configure the logger /// /// 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 // 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.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); return logger; } public static IServiceCollection AddUmbracoRuntimeMinifier(this IServiceCollection services, IConfiguration configuration) { services.AddSmidge(configuration.GetSection(Core.Constants.Configuration.ConfigRuntimeMinification)); services.AddSmidgeNuglify(); 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 } } } }