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);
}
}
}