using System.Data.Common; using System.Net.Http.Headers; using System.Reflection; using Dazinator.Extensions.FileProviders.GlobPatternFilter; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection.Infrastructure; 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.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Blocks; using Umbraco.Cms.Core.Cache; using Umbraco.Cms.Core.Composing; using Umbraco.Cms.Core.Configuration; using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Diagnostics; using Umbraco.Cms.Core.Extensions; using Umbraco.Cms.Core.Hosting; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Logging; using Umbraco.Cms.Core.Net; using Umbraco.Cms.Core.Notifications; using Umbraco.Cms.Core.Persistence.Repositories; using Umbraco.Cms.Core.Security; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Templates; using Umbraco.Cms.Core.Web; using Umbraco.Cms.Infrastructure.BackgroundJobs; using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs; using Umbraco.Cms.Infrastructure.BackgroundJobs.Jobs.ServerRegistration; using Umbraco.Cms.Infrastructure.DependencyInjection; using Umbraco.Cms.Infrastructure.HostedServices; using Umbraco.Cms.Infrastructure.Migrations.Install; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.SqlSyntax; using Umbraco.Cms.Web.Common; using Umbraco.Cms.Web.Common.ApplicationModels; using Umbraco.Cms.Web.Common.AspNetCore; using Umbraco.Cms.Web.Common.Blocks; using Umbraco.Cms.Web.Common.Configuration; using Umbraco.Cms.Web.Common.Controllers; using Umbraco.Cms.Web.Common.DependencyInjection; using Umbraco.Cms.Web.Common.FileProviders; using Umbraco.Cms.Web.Common.Localization; using Umbraco.Cms.Web.Common.Middleware; using Umbraco.Cms.Web.Common.ModelBinders; using Umbraco.Cms.Web.Common.Mvc; using Umbraco.Cms.Web.Common.Profiler; using Umbraco.Cms.Web.Common.Repositories; using Umbraco.Cms.Web.Common.Security; using Umbraco.Cms.Web.Common.Templates; using Umbraco.Cms.Web.Common.UmbracoContext; using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; namespace Umbraco.Extensions; // TODO: We could add parameters to configure each of these for flexibility /// /// Extension methods for for the common Umbraco functionality /// public static partial 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)); } // Setup static application logging ASAP (e.g. during configure services). // Will log to SilentLogger until Serilog.Log.Logger is setup. StaticApplicationLogging.Initialize(new SerilogLoggerFactory()); // The DataDirectory is used to resolve database file paths (directly supported by SQL CE and manually replaced for LocalDB) AppDomain.CurrentDomain.SetData( "DataDirectory", webHostEnvironment.MapPathContentRoot(Constants.SystemDirectories.Data)); // Manually create and register the HttpContextAccessor. In theory this should not be registered // again by the user but if that is the case it's not the end of the world since HttpContextAccessor // is just based on AsyncLocal, see https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http/src/HttpContextAccessor.cs IHttpContextAccessor httpContextAccessor = new HttpContextAccessor(); services.AddSingleton(httpContextAccessor); var requestCache = new HttpContextRequestAppCache(httpContextAccessor); var appCaches = AppCaches.Create(requestCache); services.ConfigureOptions(); services.ConfigureOptions(); IProfiler profiler = GetWebProfiler(config, httpContextAccessor); services.AddLogger(webHostEnvironment, config); ILoggerFactory loggerFactory = new SerilogLoggerFactory(); TypeLoader typeLoader = services.AddTypeLoader(Assembly.GetEntryAssembly(), loggerFactory, config); IHostingEnvironment tempHostingEnvironment = GetTemporaryHostingEnvironment(webHostEnvironment, config); return new UmbracoBuilder(services, config, typeLoader, loggerFactory, profiler, appCaches, tempHostingEnvironment); } /// /// Adds core Umbraco services /// /// /// This will not add any composers/components /// public static IUmbracoBuilder AddUmbracoCore(this IUmbracoBuilder builder) { if (builder is null) { throw new ArgumentNullException(nameof(builder)); } // Add ASP.NET specific services builder.Services.AddUnique(); builder.Services.AddUnique(sp => ActivatorUtilities.CreateInstance( sp, sp.GetRequiredService())); builder.Services.AddSingleton(); builder.Services.TryAddEnumerable(ServiceDescriptor .Singleton()); // WebRootFileProviderFactory is just a wrapper around the IWebHostEnvironment.WebRootFileProvider, // therefore no need to register it as singleton builder.Services.AddSingleton(); builder.Services.AddSingleton(); // Must be added here because DbProviderFactories is netstandard 2.1 so cannot exist in Infra for now builder.Services.AddSingleton(factory => new DbProviderFactoryCreator( DbProviderFactories.GetFactory, factory.GetServices(), factory.GetServices(), factory.GetServices(), factory.GetServices(), factory.GetServices())); builder.AddCoreInitialServices(); builder.AddTelemetryProviders(); // aspnet app lifetime mgmt builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddTransient(); return builder; } /// /// Add Umbraco recurring background jobs /// public static IUmbracoBuilder AddRecurringBackgroundJobs(this IUmbracoBuilder builder) { // Add background jobs builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddRecurringBackgroundJob(); builder.Services.AddSingleton(RecurringBackgroundJobHostedService.CreateHostedServiceFactory); builder.Services.AddHostedService(); builder.Services.AddHostedService(); return builder; } /// /// Adds the Umbraco request profiler /// public static IUmbracoBuilder AddUmbracoProfiler(this IUmbracoBuilder builder) { builder.Services.AddSingleton(); builder.Services.AddMiniProfiler(); builder.Services.ConfigureOptions(); builder.Services.AddSingleton(); builder.AddNotificationHandler(); return builder; } private static IUmbracoBuilder AddHttpClients(this IUmbracoBuilder builder) { builder.Services.AddHttpClient(); builder.Services.AddHttpClient(Constants.HttpClients.IgnoreCertificateErrors) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, }); builder.Services.AddHttpClient(Constants.HttpClients.WebhookFiring, (services, client) => { var productVersion = services.GetRequiredService().SemanticVersion.ToSemanticStringWithoutBuild(); client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.HttpClients.Headers.UserAgentProductName, productVersion)); }); 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. IMvcBuilder mvcBuilder = builder.Services.AddControllersWithViews(); if (builder.Config.GetRuntimeMode() != RuntimeMode.Production) { mvcBuilder.AddRazorRuntimeCompilation(); } mvcBuilding?.Invoke(mvcBuilder); return builder; } /// /// Adds all web based services required for Umbraco to run /// 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.ConfigureOptions(); builder.Services.AddApiVersioning().AddApiExplorer(); builder.Services.ConfigureOptions(); builder.Services.ConfigureOptions(); builder.Services.TryAddEnumerable(ServiceDescriptor .Transient()); // AspNetCore specific services builder.Services.AddUnique(); builder.AddNotificationHandler(); builder.AddNotificationHandler(); // Password hasher builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddTransient(); builder.Services.AddUnique(); builder.Services.AddMultipleUnique(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddSingleton(); // register the umbraco context factory builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddUnique(); builder.Services.AddUnique(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.AddHttpClients(); 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; } private static IProfiler GetWebProfiler(IConfiguration config, IHttpContextAccessor httpContextAccessor) { var isDebug = config.GetValue($"{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 NoopProfiler(); } var webProfiler = new WebProfiler(httpContextAccessor); 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) { HostingSettings hostingSettings = config.GetSection(Constants.Configuration.ConfigHosting).Get() ?? new HostingSettings(); var wrappedHostingSettings = new OptionsMonitorAdapter(hostingSettings); WebRoutingSettings webRoutingSettings = config.GetSection(Constants.Configuration.ConfigWebRouting).Get() ?? new WebRoutingSettings(); var wrappedWebRoutingSettings = new OptionsMonitorAdapter(webRoutingSettings); return new AspNetCoreHostingEnvironment( wrappedHostingSettings, wrappedWebRoutingSettings, webHostEnvironment); } }