// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.ExceptionServices; using Microsoft.AspNetCore.Hosting.Builder; using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Hosting { /// /// A builder for /// public class WebHostBuilder : IWebHostBuilder { private readonly IHostingEnvironment _hostingEnvironment; private readonly List> _configureServicesDelegates; private IConfiguration _config; private WebHostOptions _options; private WebHostBuilderContext _context; private bool _webHostBuilt; private List> _configureAppConfigurationBuilderDelegates; /// /// Initializes a new instance of the class. /// public WebHostBuilder() { _hostingEnvironment = new HostingEnvironment(); _configureServicesDelegates = new List>(); _configureAppConfigurationBuilderDelegates = new List>(); _config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") .Build(); if (string.IsNullOrEmpty(GetSetting(WebHostDefaults.EnvironmentKey))) { // Try adding legacy environment keys, never remove these. UseSetting(WebHostDefaults.EnvironmentKey, Environment.GetEnvironmentVariable("Hosting:Environment") ?? Environment.GetEnvironmentVariable("ASPNET_ENV")); } if (string.IsNullOrEmpty(GetSetting(WebHostDefaults.ServerUrlsKey))) { // Try adding legacy url key, never remove this. UseSetting(WebHostDefaults.ServerUrlsKey, Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS")); } _context = new WebHostBuilderContext { Configuration = _config }; } /// /// Get the setting value from the configuration. /// /// The key of the setting to look up. /// The value the setting currently contains. public string GetSetting(string key) { return _config[key]; } /// /// Add or replace a setting in the configuration. /// /// The key of the setting to add or replace. /// The value of the setting to add or replace. /// The . public IWebHostBuilder UseSetting(string key, string value) { _config[key] = value; return this; } /// /// Adds a delegate for configuring additional services for the host or web application. This may be called /// multiple times. /// /// A delegate for configuring the . /// The . public IWebHostBuilder ConfigureServices(Action configureServices) { if (configureServices == null) { throw new ArgumentNullException(nameof(configureServices)); } return ConfigureServices((_ , services) => configureServices(services)); } /// /// Adds a delegate for configuring additional services for the host or web application. This may be called /// multiple times. /// /// A delegate for configuring the . /// The . public IWebHostBuilder ConfigureServices(Action configureServices) { if (configureServices == null) { throw new ArgumentNullException(nameof(configureServices)); } _configureServicesDelegates.Add(configureServices); return this; } /// /// Adds a delegate for configuring the that will construct an . /// /// The delegate for configuring the that will be used to construct an . /// The . /// /// The and on the are uninitialized at this stage. /// The is pre-populated with the settings of the . /// public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) { if (configureDelegate == null) { throw new ArgumentNullException(nameof(configureDelegate)); } _configureAppConfigurationBuilderDelegates.Add(configureDelegate); return this; } /// /// Builds the required services and an which hosts a web application. /// public IWebHost Build() { if (_webHostBuilt) { throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance); } _webHostBuilt = true; // Warn about deprecated environment variables if (Environment.GetEnvironmentVariable("Hosting:Environment") != null) { Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'"); } if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null) { Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'"); } if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null) { Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'"); } var hostingServices = BuildCommonServices(out var hostingStartupErrors); var applicationServices = hostingServices.Clone(); var hostingServiceProvider = hostingServices.BuildServiceProvider(); AddApplicationServices(applicationServices, hostingServiceProvider); var host = new WebHost( applicationServices, hostingServiceProvider, _options, _config, hostingStartupErrors); host.Initialize(); return host; } private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors) { hostingStartupErrors = null; _options = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name); if (!_options.PreventHostingStartup) { var exceptions = new List(); // Execute the hosting startup assemblies foreach (var assemblyName in _options.HostingStartupAssemblies) { try { var assembly = Assembly.Load(new AssemblyName(assemblyName)); foreach (var attribute in assembly.GetCustomAttributes()) { var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType); hostingStartup.Configure(this); } } catch (Exception ex) { // Capture any errors that happen during startup exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex)); } } if (exceptions.Count > 0) { hostingStartupErrors = new AggregateException(exceptions); } } var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, AppContext.BaseDirectory); // Initialize the hosting environment _hostingEnvironment.Initialize(contentRootPath, _options); _context.HostingEnvironment = _hostingEnvironment; var services = new ServiceCollection(); services.AddSingleton(_hostingEnvironment); services.AddSingleton(_context); var builder = new ConfigurationBuilder() .SetBasePath(_hostingEnvironment.ContentRootPath) .AddInMemoryCollection(_config.AsEnumerable()); foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates) { configureAppConfiguration(_context, builder); } var configuration = builder.Build(); services.AddSingleton(configuration); _context.Configuration = configuration; var listener = new DiagnosticListener("Microsoft.AspNetCore"); services.AddSingleton(listener); services.AddSingleton(listener); services.AddTransient(); services.AddTransient(); services.AddScoped(); services.AddOptions(); services.AddLogging(); // Conjure up a RequestServices services.AddTransient(); services.AddTransient, DefaultServiceProviderFactory>(); // Ensure object pooling is available everywhere. services.AddSingleton(); if (!string.IsNullOrEmpty(_options.StartupAssembly)) { try { var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName); if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { services.AddSingleton(typeof(IStartup), startupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService(); var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName); return new ConventionBasedStartup(methods); }); } } catch (Exception ex) { var capture = ExceptionDispatchInfo.Capture(ex); services.AddSingleton(_ => { capture.Throw(); return null; }); } } foreach (var configureServices in _configureServicesDelegates) { configureServices(_context, services); } return services; } private void AddApplicationServices(IServiceCollection services, IServiceProvider hostingServiceProvider) { // We are forwarding services from hosting contrainer so hosting container // can still manage their lifetime (disposal) shared instances with application services. // NOTE: This code overrides original services lifetime. Instances would always be singleton in // application container. var listener = hostingServiceProvider.GetService(); services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticListener), listener)); services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticSource), listener)); } private string ResolveContentRootPath(string contentRootPath, string basePath) { if (string.IsNullOrEmpty(contentRootPath)) { return basePath; } if (Path.IsPathRooted(contentRootPath)) { return contentRootPath; } return Path.Combine(Path.GetFullPath(basePath), contentRootPath); } } }