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.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog; using Smidge; using Smidge.Nuglify; 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.DependencyInjection; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Runtime; using Umbraco.Extensions; using Umbraco.Infrastructure.DependencyInjection; using Umbraco.Infrastructure.HostedServices; using Umbraco.Infrastructure.HostedServices.ServerRegistration; using Umbraco.Infrastructure.Runtime; using Umbraco.Web.Cache; using Umbraco.Web.Common.ApplicationModels; using Umbraco.Web.Common.AspNetCore; using Umbraco.Web.Common.DependencyInjection; using Umbraco.Web.Common.Profiler; using Umbraco.Web.Telemetry; using IHostingEnvironment = Umbraco.Core.Hosting.IHostingEnvironment; namespace Umbraco.Web.Common.DependencyInjection { // TODO: We could add parameters to configure each of these for flexibility /// /// Extension methods for for the common Umbraco functionality /// public static class UmbracoBuilderExtensions { /// /// Creates an and registers basic Umbraco services /// public static IUmbracoBuilder AddUmbraco( this IServiceCollection services, IWebHostEnvironment webHostEnvironment, IConfiguration config) { if (services is null) { throw new ArgumentNullException(nameof(services)); } if (config is null) { throw new ArgumentNullException(nameof(config)); } // TODO: Should some/all of these registrations be moved directly into UmbracoBuilder? IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config); var loggingDir = tempHostingEnvironment.MapPathContentRoot(Core.Constants.SystemDirectories.LogFiles); var loggingConfig = new LoggingConfiguration(loggingDir); services.AddLogger(tempHostingEnvironment, loggingConfig, config); IHttpContextAccessor httpContextAccessor = new HttpContextAccessor(); services.AddSingleton(httpContextAccessor); var requestCache = new GenericDictionaryRequestAppCache(() => httpContextAccessor.HttpContext?.Items); var appCaches = AppCaches.Create(requestCache); services.AddUnique(appCaches); IProfiler profiler = GetWebProfiler(config); services.AddUnique(profiler); ILoggerFactory loggerFactory = LoggerFactory.Create(cfg => cfg.AddSerilog(Log.Logger, false)); TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), webHostEnvironment, tempHostingEnvironment, loggerFactory, appCaches, config, profiler); return new UmbracoBuilder(services, config, typeLoader, loggerFactory); } /// /// Adds core Umbraco services /// public static IUmbracoBuilder AddUmbracoCore(this IUmbracoBuilder builder) { if (builder is null) { throw new ArgumentNullException(nameof(builder)); } builder.Services.AddLazySupport(); // Add supported databases builder.AddUmbracoSqlCeSupport(); builder.AddUmbracoSqlServerSupport(); builder.Services.AddSingleton(factory => new DbProviderFactoryCreator( DbProviderFactories.GetFactory, factory.GetServices(), factory.GetServices(), factory.GetServices() )); builder.Services.AddUnique(factory => { var globalSettings = factory.GetRequiredService>().Value; var connectionStrings = factory.GetRequiredService>().Value; var hostingEnvironment = factory.GetRequiredService(); var dbCreator = factory.GetRequiredService(); var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); var loggerFactory = factory.GetRequiredService(); return globalSettings.MainDomLock.Equals("SqlMainDomLock") || isWindows == false ? (IMainDomLock)new SqlMainDomLock(loggerFactory.CreateLogger(), loggerFactory, globalSettings, connectionStrings, dbCreator, hostingEnvironment) : new MainDomSemaphoreLock(loggerFactory.CreateLogger(), hostingEnvironment); }); builder.Services.AddUnique(factory => { var hostingEnvironment = factory.GetRequiredService(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { return new IOHelperLinux(hostingEnvironment); } if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { return new IOHelperOSX(hostingEnvironment); } return new IOHelperWindows(hostingEnvironment); } ); builder.Services.AddUnique(factory => factory.GetRequiredService().RuntimeCache); builder.Services.AddUnique(factory => factory.GetRequiredService().RequestCache); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(factory => factory.GetRequiredService().CreateDatabase()); builder.Services.AddUnique(factory => factory.GetRequiredService().SqlContext); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddHostedService(factory => factory.GetRequiredService()); builder.AddCoreInitialServices(); builder.AddComposers(); return builder; } /// /// Adds Umbraco composers for plugins /// public static IUmbracoBuilder AddComposers(this IUmbracoBuilder builder) { IEnumerable composerTypes = builder.TypeLoader.GetTypes(); IEnumerable enableDisable = builder.TypeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute)); new Composers(builder, composerTypes, enableDisable, builder.BuilderLoggerFactory.CreateLogger()).Compose(); return builder; } /// /// Add Umbraco configuration services and options /// public static IUmbracoBuilder AddConfiguration(this IUmbracoBuilder builder) { // Register configuration validators. builder.Services.AddSingleton, ContentSettingsValidator>(); builder.Services.AddSingleton, GlobalSettingsValidator>(); builder.Services.AddSingleton, HealthChecksSettingsValidator>(); builder.Services.AddSingleton, RequestHandlerSettingsValidator>(); // Register configuration sections. builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigActiveDirectory)); builder.Services.Configure(builder.Config.GetSection("ConnectionStrings"), o => o.BindNonPublicProperties = true); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigContent)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigCoreDebug)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigExceptionFilter)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigGlobal)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigHealthChecks)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigHosting)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigImaging)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigExamine)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigKeepAlive)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigLogging)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigMemberPassword)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigModelsBuilder), o => o.BindNonPublicProperties = true); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigNuCache)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigRequestHandler)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigRuntime)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigSecurity)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigTours)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigTypeFinder)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigUserPassword)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigWebRouting)); builder.Services.Configure(builder.Config.GetSection(Core.Constants.Configuration.ConfigPlugins)); return builder; } /// /// Add Umbraco hosted services /// public static IUmbracoBuilder AddHostedServices(this IUmbracoBuilder builder) { builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); return builder; } // TODO: Not sure this needs to exist and/or be public? public static IUmbracoBuilder AddHttpClients(this IUmbracoBuilder builder) { builder.Services.AddHttpClient(); return builder; } /// /// Adds mini profiler services for Umbraco /// public static IUmbracoBuilder AddMiniProfiler(this IUmbracoBuilder builder) { builder.Services.AddMiniProfiler(options => // WebProfiler determine and start profiling. We should not use the MiniProfilerMiddleware to also profile options.ShouldProfile = request => false); return builder; } public static IUmbracoBuilder AddMvcAndRazor(this IUmbracoBuilder builder, Action mvcBuilding = null) { // TODO: We need to figure out if we can work around this because calling AddControllersWithViews modifies the global app and order is very important // this will directly affect developers who need to call that themselves. // We need to have runtime compilation of views when using umbraco. We could consider having only this when a specific config is set. // But as far as I can see, there are still precompiled views, even when this is activated, so maybe it is okay. IMvcBuilder mvcBuilder = builder.Services .AddControllersWithViews() .AddRazorRuntimeCompilation(); mvcBuilding?.Invoke(mvcBuilder); return builder; } /// /// Add runtime minifier support for Umbraco /// public static IUmbracoBuilder AddRuntimeMinifier(this IUmbracoBuilder builder) { builder.Services.AddSmidge(builder.Config.GetSection(Core.Constants.Configuration.ConfigRuntimeMinification)); builder.Services.AddSmidgeNuglify(); return builder; } public static IUmbracoBuilder AddWebComponents(this IUmbracoBuilder builder) { // 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; }); builder.Services.ConfigureOptions(); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.TryAddEnumerable(ServiceDescriptor.Transient()); builder.Services.AddUmbracoImageSharp(builder.Config); return builder; } // TODO: Does this need to exist and/or be public? public static IUmbracoBuilder AddWebServer(this IUmbracoBuilder builder) { // TODO: We need to figure out why this is needed and fix those endpoints to not need them, we don't want to change global things // If using Kestrel: https://stackoverflow.com/a/55196057 builder.Services.Configure(options => { options.AllowSynchronousIO = true; }); builder.Services.Configure(options => { options.AllowSynchronousIO = true; }); return builder; } /// /// Adds SqlCe support for Umbraco /// private static IUmbracoBuilder AddUmbracoSqlCeSupport(this IUmbracoBuilder builder) { 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)) { builder.Services.AddSingleton(typeof(ISqlSyntaxProvider), sqlCeSyntaxProviderType); builder.Services.AddSingleton(typeof(IBulkSqlInsertProvider), sqlCeBulkSqlInsertProviderType); builder.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 builder; } /// /// Adds Sql Server support for Umbraco /// private static IUmbracoBuilder AddUmbracoSqlServerSupport(this IUmbracoBuilder builder) { DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); return builder; } private static IProfiler GetWebProfiler(IConfiguration config) { var isDebug = config.GetValue($"{Core.Constants.Configuration.ConfigHosting}:Debug"); // create and start asap to profile boot if (!isDebug) { // 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; } /// /// HACK: returns an AspNetCoreHostingEnvironment that doesn't monitor changes to configuration.
/// We require this to create a TypeLoader during ConfigureServices.
/// Instances returned from this method shouldn't be registered in the service collection. ///
private static IHostingEnvironment GetTemporaryHostingEnvironment(IWebHostEnvironment webHostEnvironment, IConfiguration config) { var hostingSettings = config.GetSection(Core.Constants.Configuration.ConfigHosting).Get() ?? new HostingSettings(); var wrappedHostingSettings = new OptionsMonitorAdapter(hostingSettings); return new AspNetCoreHostingEnvironment(wrappedHostingSettings, webHostEnvironment); } } }