From b5bda813bb9af156fa56f0d2ff3aae03f9696d74 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 9 Nov 2018 23:33:46 -0800 Subject: [PATCH 01/19] Added support for generic host based IWebHostBuilder - This adds an implementation of IWebHostBuilder as a facade over the IHostBuilder. This removes the 2 container issue by executing the Startup.ConfigureServies and Startup.ConfigureContainer inline as part of building the IHostBuilder. - The implementation is highly compatible implementation since it exposes the same IWebHostBuilder interface. Existing extensions mostly work. - There are some caveats with this approach. - Injecting services into Startup is not extremely constrained to the services availble on HostBuilderContext. This includes the IHostingEnvironment and the IConfiguration. - IStartup is broken when using this pattern because it isn't composable. - The IStartupConfigureServicesFilter and IStartupConfigureContainer The before and after filters added in 2.1 are also broken because there's a single container (it could maybe be fixed by downcasting and doing something specific on the GenericHostBuilder instance). - Calling into IWebHostBuilder.Build will throw a NotSupportedException since this implementation is just a facade over the IHostBuilder. --- .../GenericHostApplicationLifetime.cs | 21 ++ .../GenericHost/GenericWebHostBuilder.cs | 316 ++++++++++++++++++ .../GenericWebHostServiceOptions.cs | 15 + .../GenericHost/GenericWebHostedService.cs | 200 +++++++++++ .../GenericHostWebHostBuilderExtensions.cs | 16 + .../Internal/StartupLoader.cs | 6 +- .../WebHostBuilderExtensions.cs | 18 + 7 files changed, 589 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHost/GenericHostApplicationLifetime.cs create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericHostApplicationLifetime.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericHostApplicationLifetime.cs new file mode 100644 index 00000000..a233c97f --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericHostApplicationLifetime.cs @@ -0,0 +1,21 @@ +using System.Threading; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class GenericHostApplicationLifetime : IApplicationLifetime + { + private readonly Microsoft.Extensions.Hosting.IApplicationLifetime _applicationLifetime; + public GenericHostApplicationLifetime(Microsoft.Extensions.Hosting.IApplicationLifetime applicationLifetime) + { + _applicationLifetime = applicationLifetime; + } + + public CancellationToken ApplicationStarted => _applicationLifetime.ApplicationStarted; + + public CancellationToken ApplicationStopping => _applicationLifetime.ApplicationStopping; + + public CancellationToken ApplicationStopped => _applicationLifetime.ApplicationStopped; + + public void StopApplication() => _applicationLifetime.StopApplication(); + } +} diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs new file mode 100644 index 00000000..b2e1fa78 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.ExceptionServices; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.ObjectPool; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class GenericWebHostBuilder : IWebHostBuilder + { + private readonly IHostBuilder _builder; + private readonly IConfiguration _config; + private readonly object _startupKey = new object(); + private readonly AggregateException _hostingStartupErrors; + + public GenericWebHostBuilder(IHostBuilder builder) + { + _builder = builder; + _config = new ConfigurationBuilder() + .AddEnvironmentVariables(prefix: "ASPNETCORE_") + .Build(); + + _builder.ConfigureHostConfiguration(config => + { + config.AddConfiguration(_config); + }); + + // REVIEW: This doesn't support config from the outside + var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .Build(); + + // REVIEW: Is this application name correct? + var webHostOptions = new WebHostOptions(configuration, Assembly.GetEntryAssembly()?.GetName().Name); + + if (!webHostOptions.PreventHostingStartup) + { + var exceptions = new List(); + + // Execute the hosting startup assemblies + foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase)) + { + 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); + } + } + + _builder.ConfigureServices((context, services) => + { + var webhostContext = GetWebHostBuilderContext(context); + + // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting + services.AddSingleton(webhostContext.HostingEnvironment); + services.AddSingleton(); + + services.Configure(o => + { + // Set the options + o.WebHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; + // Store and forward any startup errors + o.HostingStartupExceptions = _hostingStartupErrors; + }); + + services.AddHostedService(); + + // REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up + // We need to flow this differently + var listener = new DiagnosticListener("Microsoft.AspNetCore"); + services.TryAddSingleton(listener); + services.TryAddSingleton(listener); + + services.TryAddSingleton(); + services.TryAddScoped(); + services.TryAddSingleton(); + + // Conjure up a RequestServices + services.TryAddTransient(); + services.TryAddTransient, DefaultServiceProviderFactory>(); + + // Ensure object pooling is available everywhere. + services.TryAddSingleton(); + }); + } + + public IWebHost Build() => throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); + + public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) + { + _builder.ConfigureAppConfiguration((context, builder) => + { + var webhostBuilderContext = GetWebHostBuilderContext(context); + configureDelegate(webhostBuilderContext, builder); + }); + + return this; + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + return ConfigureServices((context, services) => configureServices(services)); + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + _builder.ConfigureServices((context, builder) => + { + var webhostBuilderContext = GetWebHostBuilderContext(context); + configureServices(webhostBuilderContext, builder); + }); + + return this; + } + + internal IWebHostBuilder UseDefaultServiceProvider(Action configure) + { + // REVIEW: This is a hack to change the builder with the HostBuilderContext, + // we're not actually using configuration here + _builder.ConfigureAppConfiguration((context, _) => + { + var webHostBuilderContext = GetWebHostBuilderContext(context); + var options = new ServiceProviderOptions(); + configure(webHostBuilderContext, options); + + // This is only fine because this runs last + _builder.UseServiceProviderFactory(new DefaultServiceProviderFactory(options)); + }); + + return this; + } + + internal IWebHostBuilder UseStartup(Type startupType) + { + _config[HostDefaults.ApplicationKey] = startupType.GetTypeInfo().Assembly.GetName().Name; + + _builder.ConfigureServices((context, services) => + { + var webHostBuilderContext = GetWebHostBuilderContext(context); + + ExceptionDispatchInfo startupError = null; + object instance = null; + ConfigureBuilder configureBuilder = null; + + try + { + // We cannot support methods that return IServiceProvider as that is terminal and we need ConfigureServices to compose + if (typeof(IStartup).IsAssignableFrom(startupType)) + { + throw new NotSupportedException($"{typeof(IStartup)} isn't supported"); + } + + instance = ActivatorUtilities.CreateInstance(new ServiceProvider(webHostBuilderContext), startupType); + context.Properties[_startupKey] = instance; + + // Startup.ConfigureServices + var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName); + var configureServices = configureServicesBuilder.Build(instance); + + configureServices(services); + + // REVIEW: We're doing this in the callback so that we have access to the hosting environment + // Startup.ConfigureContainer + var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName); + if (configureContainerBuilder.MethodInfo != null) + { + var containerType = configureContainerBuilder.GetContainerType(); + // Store the builder in the property bag + _builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder; + + var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType); + + // Get the private ConfigureContainer method on this type then close over the container type + var configureCallback = GetType().GetMethod(nameof(ConfigureContainer), BindingFlags.NonPublic | BindingFlags.Instance) + .MakeGenericMethod(containerType) + .CreateDelegate(actionType, this); + + // _builder.ConfigureContainer(ConfigureContainer); + typeof(IHostBuilder).GetMethods().First(m => m.Name == nameof(IHostBuilder.ConfigureContainer)) + .MakeGenericMethod(containerType) + .Invoke(_builder, new object[] { configureCallback }); + } + + // Resolve Configure after calling ConfigureServices and ConfigureContainer + configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName); + } + catch (Exception ex) + { + startupError = ExceptionDispatchInfo.Capture(ex); + } + + // Startup.Configure + services.Configure(options => + { + options.ConfigureApplication = app => + { + // Throw if there was any errors initializing startup + startupError?.Throw(); + + // Execute Startup.Configure + if (instance != null && configureBuilder != null) + { + configureBuilder.Build(instance)(app); + } + }; + }); + }); + + return this; + } + + private void ConfigureContainer(HostBuilderContext context, TContainer container) + { + var instance = context.Properties[_startupKey]; + var builder = (ConfigureContainerBuilder)context.Properties[typeof(ConfigureContainerBuilder)]; + builder.Build(instance)(container); + } + + internal IWebHostBuilder Configure(Action configure) + { + _builder.ConfigureServices((context, services) => + { + services.Configure(options => + { + options.ConfigureApplication = configure; + }); + }); + + return this; + } + + private WebHostBuilderContext GetWebHostBuilderContext(HostBuilderContext context) + { + if (!context.Properties.TryGetValue(typeof(WebHostBuilderContext), out var contextVal)) + { + var options = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name); + var hostingEnvironment = new HostingEnvironment(); + hostingEnvironment.Initialize(context.HostingEnvironment.ContentRootPath, options); + + var webHostBuilderContext = new WebHostBuilderContext + { + Configuration = context.Configuration, + HostingEnvironment = hostingEnvironment + }; + context.Properties[typeof(WebHostBuilderContext)] = webHostBuilderContext; + context.Properties[typeof(WebHostOptions)] = options; + return webHostBuilderContext; + } + + return (WebHostBuilderContext)contextVal; + } + + public string GetSetting(string key) + { + return _config[key]; + } + + public IWebHostBuilder UseSetting(string key, string value) + { + _config[key] = value; + return this; + } + + // This exists just so that we can use ActivatorUtilities.CreateInstance on the Startup class + private class ServiceProvider : IServiceProvider + { + private readonly WebHostBuilderContext _context; + + public ServiceProvider(WebHostBuilderContext context) + { + _context = context; + } + + public object GetService(Type serviceType) + { + // The implementation of the HostingEnvironment supports both interfaces + if (serviceType == typeof(Microsoft.AspNetCore.Hosting.IHostingEnvironment) || serviceType == typeof(IHostingEnvironment)) + { + return _context.HostingEnvironment; + } + + if (serviceType == typeof(IConfiguration)) + { + return _context.Configuration; + } + + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs new file mode 100644 index 00000000..b652c1a5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.ExceptionServices; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class GenericWebHostServiceOptions + { + public Action ConfigureApplication { get; set; } + + public WebHostOptions WebHostOptions { get; set; } + + public AggregateException HostingStartupExceptions { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs new file mode 100644 index 00000000..3ed030e6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Builder; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Hosting.Views; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.StackTrace.Sources; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class GenericWebHostService : IHostedService + { + private static readonly string DeprecatedServerUrlsKey = "server.urls"; + + public GenericWebHostService(IOptions options, + IServiceProvider services, + IServer server, + ILogger logger, + DiagnosticListener diagnosticListener, + IHttpContextFactory httpContextFactory, + IApplicationBuilderFactory applicationBuilderFactory, + IEnumerable startupFilters, + IConfiguration configuration, + IHostingEnvironment hostingEnvironment) + { + Options = options?.Value ?? throw new System.ArgumentNullException(nameof(options)); + + if (Options.ConfigureApplication == null) + { + throw new ArgumentException(nameof(Options.ConfigureApplication)); + } + + Services = services ?? throw new ArgumentNullException(nameof(services)); + Server = server ?? throw new ArgumentNullException(nameof(server)); + Logger = logger ?? throw new ArgumentNullException(nameof(logger)); + DiagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener)); + HttpContextFactory = httpContextFactory ?? throw new ArgumentNullException(nameof(httpContextFactory)); + ApplicationBuilderFactory = applicationBuilderFactory ?? throw new ArgumentNullException(nameof(applicationBuilderFactory)); + StartupFilters = startupFilters ?? throw new ArgumentNullException(nameof(startupFilters)); + Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); + HostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + public GenericWebHostServiceOptions Options { get; } + public IServiceProvider Services { get; } + public HostBuilderContext HostBuilderContext { get; } + public IServer Server { get; } + public ILogger Logger { get; } + public DiagnosticListener DiagnosticListener { get; } + public IHttpContextFactory HttpContextFactory { get; } + public IApplicationBuilderFactory ApplicationBuilderFactory { get; } + public IEnumerable StartupFilters { get; } + public IConfiguration Configuration { get; } + public IHostingEnvironment HostingEnvironment { get; } + + public async Task StartAsync(CancellationToken cancellationToken) + { + HostingEventSource.Log.HostStart(); + + var serverAddressesFeature = Server.Features?.Get(); + var addresses = serverAddressesFeature?.Addresses; + if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0) + { + var urls = Configuration[WebHostDefaults.ServerUrlsKey] ?? Configuration[DeprecatedServerUrlsKey]; + if (!string.IsNullOrEmpty(urls)) + { + serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(Configuration, WebHostDefaults.PreferHostingUrlsKey); + + foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)) + { + addresses.Add(value); + } + } + } + + RequestDelegate application = null; + + try + { + var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features); + Action configure = Options.ConfigureApplication; + + foreach (var filter in StartupFilters.Reverse()) + { + configure = filter.Configure(configure); + } + + configure(builder); + + // Build the request pipeline + application = builder.Build(); + } + catch (Exception ex) + { + Logger.ApplicationError(ex); + + if (!Options.WebHostOptions.CaptureStartupErrors) + { + throw; + } + + application = BuildErrorPageApplication(ex); + } + + var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, HttpContextFactory); + + await Server.StartAsync(httpApplication, cancellationToken); + + var serverAddresses = Server.Features.Get()?.Addresses; + if (serverAddresses != null) + { + foreach (var address in serverAddresses) + { + Logger.LogInformation("Now listening on: {address}", address); + } + } + + if (Logger.IsEnabled(LogLevel.Debug)) + { + foreach (var assembly in Options.WebHostOptions.GetFinalHostingStartupAssemblies()) + { + Logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly); + } + } + + if (Options.HostingStartupExceptions != null) + { + foreach (var exception in Options.HostingStartupExceptions.InnerExceptions) + { + Logger.HostingStartupAssemblyError(exception); + } + } + } + + private RequestDelegate BuildErrorPageApplication(Exception exception) + { + if (exception is TargetInvocationException tae) + { + exception = tae.InnerException; + } + + var showDetailedErrors = HostingEnvironment.IsDevelopment() || Options.WebHostOptions.DetailedErrors; + + var model = new ErrorPageModel + { + RuntimeDisplayName = RuntimeInformation.FrameworkDescription + }; + var systemRuntimeAssembly = typeof(System.ComponentModel.DefaultValueAttribute).GetTypeInfo().Assembly; + var assemblyVersion = new AssemblyName(systemRuntimeAssembly.FullName).Version.ToString(); + var clrVersion = assemblyVersion; + model.RuntimeArchitecture = RuntimeInformation.ProcessArchitecture.ToString(); + var currentAssembly = typeof(ErrorPage).GetTypeInfo().Assembly; + model.CurrentAssemblyVesion = currentAssembly + .GetCustomAttribute() + .InformationalVersion; + model.ClrVersion = clrVersion; + model.OperatingSystemDescription = RuntimeInformation.OSDescription; + + if (showDetailedErrors) + { + var exceptionDetailProvider = new ExceptionDetailsProvider( + HostingEnvironment.ContentRootFileProvider, + sourceCodeLineCount: 6); + + model.ErrorDetails = exceptionDetailProvider.GetDetails(exception); + } + else + { + model.ErrorDetails = new ExceptionDetails[0]; + } + + var errorPage = new ErrorPage(model); + return context => + { + context.Response.StatusCode = 500; + context.Response.Headers["Cache-Control"] = "no-cache"; + return errorPage.ExecuteAsync(context); + }; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + HostingEventSource.Log.HostStop(); + + return Server.StopAsync(cancellationToken); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs new file mode 100644 index 00000000..ea903ad7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs @@ -0,0 +1,16 @@ +using System; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Internal; + +namespace Microsoft.Extensions.Hosting +{ + public static class GenericHostWebHostBuilderExtensions + { + public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action configure) + { + var webhostBuilder = new GenericWebHostBuilder(builder); + configure(webhostBuilder); + return builder; + } + } +} diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs b/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs index d7211d39..72dab817 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/StartupLoader.cs @@ -267,19 +267,19 @@ public static Type FindStartupType(string startupAssemblyName, string environmen return type; } - private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) + internal static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); return new ConfigureBuilder(configureMethod); } - private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName) + internal static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false); return new ConfigureContainerBuilder(configureMethod); } - private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) + internal static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) { var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false) ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs index 09c7e6d9..95f5f707 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs @@ -27,6 +27,12 @@ public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action throw new ArgumentNullException(nameof(configureApp)); } + // Light up the GenericWebHostBuilder implementation + if (hostBuilder is GenericWebHostBuilder genericWebHostBuilder) + { + return genericWebHostBuilder.Configure(configureApp); + } + var startupAssemblyName = configureApp.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder @@ -49,6 +55,12 @@ public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action /// The . public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) { + // Light up the GenericWebHostBuilder implementation + if (hostBuilder is GenericWebHostBuilder genericWebHostBuilder) + { + return genericWebHostBuilder.UseStartup(startupType); + } + var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder @@ -100,6 +112,12 @@ public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hos /// The . public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hostBuilder, Action configure) { + // Light up the GenericWebHostBuilder implementation + if (hostBuilder is GenericWebHostBuilder genericWebHostBuilder) + { + return genericWebHostBuilder.UseDefaultServiceProvider(configure); + } + return hostBuilder.ConfigureServices((context, services) => { var options = new ServiceProviderOptions(); From 41ea22f552d184ac2bc54177c2606d5dda7620cd Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 10 Nov 2018 11:19:51 -0800 Subject: [PATCH 02/19] Added support for UseStartup(assemblyName) and added a single test (so far) --- .../GenericHost/GenericWebHostBuilder.cs | 107 +++++++++++------- .../GenericHostBuilderTests.cs | 64 +++++++++++ .../Microsoft.AspNetCore.Hosting.Tests.csproj | 1 + 3 files changed, 134 insertions(+), 38 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index b2e1fa78..d4cafb04 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -20,7 +20,7 @@ internal class GenericWebHostBuilder : IWebHostBuilder private readonly IHostBuilder _builder; private readonly IConfiguration _config; private readonly object _startupKey = new object(); - private readonly AggregateException _hostingStartupErrors; + private AggregateException _hostingStartupErrors; public GenericWebHostBuilder(IHostBuilder builder) { @@ -34,6 +34,71 @@ public GenericWebHostBuilder(IHostBuilder builder) config.AddConfiguration(_config); }); + ExecuteHostingStartups(); + + _builder.ConfigureServices((context, services) => + { + var webhostContext = GetWebHostBuilderContext(context); + var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; + + // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting + services.AddSingleton(webhostContext.HostingEnvironment); + services.AddSingleton(); + + services.Configure(options => + { + // Set the options + options.WebHostOptions = webHostOptions; + // Store and forward any startup errors + options.HostingStartupExceptions = _hostingStartupErrors; + }); + + services.AddHostedService(); + + // REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up + // We need to flow this differently + var listener = new DiagnosticListener("Microsoft.AspNetCore"); + services.TryAddSingleton(listener); + services.TryAddSingleton(listener); + + services.TryAddSingleton(); + services.TryAddScoped(); + services.TryAddSingleton(); + + // Conjure up a RequestServices + services.TryAddTransient(); + services.TryAddTransient, DefaultServiceProviderFactory>(); + + // Ensure object pooling is available everywhere. + services.TryAddSingleton(); + + // Support UseStartup(assemblyName) + if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly)) + { + try + { + var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName); + UseStartup(startupType); + } + catch (Exception ex) when (webHostOptions.CaptureStartupErrors) + { + var capture = ExceptionDispatchInfo.Capture(ex); + + services.Configure(options => + { + options.ConfigureApplication = app => + { + // Throw if there was any errors initializing startup + capture.Throw(); + }; + }); + } + } + }); + } + + private void ExecuteHostingStartups() + { // REVIEW: This doesn't support config from the outside var configuration = new ConfigurationBuilder() .AddEnvironmentVariables() @@ -71,42 +136,6 @@ public GenericWebHostBuilder(IHostBuilder builder) _hostingStartupErrors = new AggregateException(exceptions); } } - - _builder.ConfigureServices((context, services) => - { - var webhostContext = GetWebHostBuilderContext(context); - - // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting - services.AddSingleton(webhostContext.HostingEnvironment); - services.AddSingleton(); - - services.Configure(o => - { - // Set the options - o.WebHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; - // Store and forward any startup errors - o.HostingStartupExceptions = _hostingStartupErrors; - }); - - services.AddHostedService(); - - // REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up - // We need to flow this differently - var listener = new DiagnosticListener("Microsoft.AspNetCore"); - services.TryAddSingleton(listener); - services.TryAddSingleton(listener); - - services.TryAddSingleton(); - services.TryAddScoped(); - services.TryAddSingleton(); - - // Conjure up a RequestServices - services.TryAddTransient(); - services.TryAddTransient, DefaultServiceProviderFactory>(); - - // Ensure object pooling is available everywhere. - services.TryAddSingleton(); - }); } public IWebHost Build() => throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); @@ -162,6 +191,8 @@ internal IWebHostBuilder UseStartup(Type startupType) _builder.ConfigureServices((context, services) => { var webHostBuilderContext = GetWebHostBuilderContext(context); + var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; + ExceptionDispatchInfo startupError = null; object instance = null; @@ -209,7 +240,7 @@ internal IWebHostBuilder UseStartup(Type startupType) // Resolve Configure after calling ConfigureServices and ConfigureContainer configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName); } - catch (Exception ex) + catch (Exception ex) when (webHostOptions.CaptureStartupErrors) { startupError = ExceptionDispatchInfo.Capture(ex); } diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs new file mode 100644 index 00000000..c2c7179c --- /dev/null +++ b/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Microsoft.AspNetCore.Hosting.Tests +{ + public class GenericHostBuilderTests + { + [Fact] + public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsFalse() + { + var host = new HostBuilder() + .ConfigureWebHost(builder => + { + builder.CaptureStartupErrors(false) + .Configure(app => + { + throw new InvalidOperationException("Startup exception"); + }) + .UseServer(new TestServer()); + }) + .Build(); + + Assert.Throws(() => host.Start()); + } + + private class TestServer : IServer + { + IFeatureCollection IServer.Features { get; } + public RequestDelegate RequestDelegate { get; private set; } + + public void Dispose() { } + + public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) + { + RequestDelegate = async ctx => + { + var httpContext = application.CreateContext(ctx.Features); + try + { + await application.ProcessRequestAsync(httpContext); + } + catch (Exception ex) + { + application.DisposeContext(httpContext, ex); + throw; + } + application.DisposeContext(httpContext, null); + }; + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + } + } +} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj b/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj index 5b24d9e6..5f08fb9b 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj @@ -10,6 +10,7 @@ + From a4e2f4bfa9ea6805130b4945d8c38df5bc2dc4dc Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 10 Nov 2018 11:58:54 -0800 Subject: [PATCH 03/19] Small tweaks to fix up some comments --- .../GenericHost/GenericWebHostBuilder.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index d4cafb04..005ff20c 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -99,7 +99,9 @@ public GenericWebHostBuilder(IHostBuilder builder) private void ExecuteHostingStartups() { - // REVIEW: This doesn't support config from the outside + // REVIEW: This doesn't support arbitrary hosting configuration. We need to run this during the call to ConfigureWebHost + // not during IHostBuilder.Build(). This is because IHostingStartup.Configure mutates the builder itself and that should happen *before* + // the delegate execute, not during. var configuration = new ConfigurationBuilder() .AddEnvironmentVariables() .Build(); @@ -169,7 +171,7 @@ public IWebHostBuilder ConfigureServices(Action configure) { - // REVIEW: This is a hack to change the builder with the HostBuilderContext, + // REVIEW: This is a hack to change the builder with the HostBuilderContext in scope, // we're not actually using configuration here _builder.ConfigureAppConfiguration((context, _) => { @@ -193,7 +195,6 @@ internal IWebHostBuilder UseStartup(Type startupType) var webHostBuilderContext = GetWebHostBuilderContext(context); var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; - ExceptionDispatchInfo startupError = null; object instance = null; ConfigureBuilder configureBuilder = null; From 1cd7fd017a2020ec97f64b7cc7d7db034861d961 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 10 Nov 2018 12:42:06 -0800 Subject: [PATCH 04/19] Make IHostingStartup work and respect configuration. - Added a few more tests --- .../GenericHost/GenericWebHostBuilder.cs | 59 +++++++++--------- .../GenericHost/GenericWebHostedService.cs | 5 +- .../WebHostBuilderExtensions.cs | 25 ++++---- .../GenericHostBuilderTests.cs | 60 +++++++++++++++++++ 4 files changed, 102 insertions(+), 47 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index 005ff20c..9697805c 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -25,6 +25,7 @@ internal class GenericWebHostBuilder : IWebHostBuilder public GenericWebHostBuilder(IHostBuilder builder) { _builder = builder; + _config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") .Build(); @@ -32,9 +33,11 @@ public GenericWebHostBuilder(IHostBuilder builder) _builder.ConfigureHostConfiguration(config => { config.AddConfiguration(_config); - }); - ExecuteHostingStartups(); + // We do this super early but still late enough that we can process the configuration + // wired up by calls to UseSetting + ExecuteHostingStartups(); + }); _builder.ConfigureServices((context, services) => { @@ -99,45 +102,39 @@ public GenericWebHostBuilder(IHostBuilder builder) private void ExecuteHostingStartups() { - // REVIEW: This doesn't support arbitrary hosting configuration. We need to run this during the call to ConfigureWebHost - // not during IHostBuilder.Build(). This is because IHostingStartup.Configure mutates the builder itself and that should happen *before* - // the delegate execute, not during. - var configuration = new ConfigurationBuilder() - .AddEnvironmentVariables() - .Build(); + var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name); - // REVIEW: Is this application name correct? - var webHostOptions = new WebHostOptions(configuration, Assembly.GetEntryAssembly()?.GetName().Name); - - if (!webHostOptions.PreventHostingStartup) + if (webHostOptions.PreventHostingStartup) { - var exceptions = new List(); + return; + } - // Execute the hosting startup assemblies - foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase)) + var exceptions = new List(); + + // Execute the hosting startup assemblies + foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase)) + { + try { - try - { - var assembly = Assembly.Load(new AssemblyName(assemblyName)); + 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) + foreach (var attribute in assembly.GetCustomAttributes()) { - // 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)); + var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType); + hostingStartup.Configure(this); } } - - if (exceptions.Count > 0) + catch (Exception ex) { - _hostingStartupErrors = new AggregateException(exceptions); + // 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); + } } public IWebHost Build() => throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); @@ -188,8 +185,6 @@ internal IWebHostBuilder UseDefaultServiceProvider(Action { var webHostBuilderContext = GetWebHostBuilderContext(context); diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs index 3ed030e6..6bd14054 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -118,10 +118,9 @@ public async Task StartAsync(CancellationToken cancellationToken) await Server.StartAsync(httpApplication, cancellationToken); - var serverAddresses = Server.Features.Get()?.Addresses; - if (serverAddresses != null) + if (addresses != null) { - foreach (var address in serverAddresses) + foreach (var address in addresses) { Logger.LogInformation("Now listening on: {address}", address); } diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs index 95f5f707..43603382 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs @@ -27,23 +27,23 @@ public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action throw new ArgumentNullException(nameof(configureApp)); } + var startupAssemblyName = configureApp.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name; + + hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName); + // Light up the GenericWebHostBuilder implementation if (hostBuilder is GenericWebHostBuilder genericWebHostBuilder) { return genericWebHostBuilder.Configure(configureApp); } - var startupAssemblyName = configureApp.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name; - - return hostBuilder - .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) - .ConfigureServices(services => + return hostBuilder.ConfigureServices(services => + { + services.AddSingleton(sp => { - services.AddSingleton(sp => - { - return new DelegateStartup(sp.GetRequiredService>(), configureApp); - }); + return new DelegateStartup(sp.GetRequiredService>(), configureApp); }); + }); } @@ -55,16 +55,17 @@ public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action /// The . public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) { + var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; + + hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName); + // Light up the GenericWebHostBuilder implementation if (hostBuilder is GenericWebHostBuilder genericWebHostBuilder) { return genericWebHostBuilder.UseStartup(startupType); } - var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; - return hostBuilder - .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) .ConfigureServices(services => { if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs index c2c7179c..c9f48931 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs @@ -1,12 +1,18 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; using Xunit; namespace Microsoft.AspNetCore.Hosting.Tests @@ -31,6 +37,60 @@ public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsFalse() Assert.Throws(() => host.Start()); } + [Fact] + public void HostingStartupFromPrimaryAssemblyCanBeDisabled() + { + var host = new HostBuilder() + .ConfigureWebHost(builder => + { + builder.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true") + .Configure(app => { }) + .UseServer(new TestServer()); + }) + .Build(); + + using (host) + { + var config = host.Services.GetRequiredService(); + Assert.Null(config["testhostingstartup"]); + } + } + + [Fact] + public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue() + { + var provider = new TestLoggerProvider(); + var host = new HostBuilder() + .ConfigureWebHost(builder => + { + builder.ConfigureLogging((_, factory) => + { + factory.AddProvider(provider); + }) + .CaptureStartupErrors(true) + .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName") + .Configure(app => { }) + .UseServer(new TestServer()); + }) + .Build(); + + using (host) + { + await host.StartAsync(); + var context = provider.Sink.Writes.FirstOrDefault(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException); + Assert.NotNull(context); + } + } + + public class TestLoggerProvider : ILoggerProvider + { + public TestSink Sink { get; set; } = new TestSink(); + + public ILogger CreateLogger(string categoryName) => new TestLogger(categoryName, Sink, enabled: true); + + public void Dispose() { } + } + private class TestServer : IServer { IFeatureCollection IServer.Features { get; } From db201b78085f2f72dde09d5defb906bbd3ad4425 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 10 Nov 2018 17:39:04 -0800 Subject: [PATCH 05/19] Call HostStop after StopAsync --- .../GenericHost/GenericWebHostedService.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs index 6bd14054..2c5bccf7 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -189,11 +189,16 @@ private RequestDelegate BuildErrorPageApplication(Exception exception) }; } - public Task StopAsync(CancellationToken cancellationToken) + public async Task StopAsync(CancellationToken cancellationToken) { - HostingEventSource.Log.HostStop(); - - return Server.StopAsync(cancellationToken); + try + { + await Server.StopAsync(cancellationToken); + } + finally + { + HostingEventSource.Log.HostStop(); + } } } } \ No newline at end of file From a8cd745fa9b6213521691aba7207b70ca6be0464 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 10 Nov 2018 17:40:27 -0800 Subject: [PATCH 06/19] Renamed the IApplicationLifetime implementation --- ...cationLifetime.cs => GenericWebHostApplicationLifetime.cs} | 4 ++-- .../GenericHost/GenericWebHostBuilder.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Microsoft.AspNetCore.Hosting/GenericHost/{GenericHostApplicationLifetime.cs => GenericWebHostApplicationLifetime.cs} (76%) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericHostApplicationLifetime.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostApplicationLifetime.cs similarity index 76% rename from src/Microsoft.AspNetCore.Hosting/GenericHost/GenericHostApplicationLifetime.cs rename to src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostApplicationLifetime.cs index a233c97f..84c38900 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericHostApplicationLifetime.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostApplicationLifetime.cs @@ -2,10 +2,10 @@ namespace Microsoft.AspNetCore.Hosting.Internal { - internal class GenericHostApplicationLifetime : IApplicationLifetime + internal class GenericWebHostApplicationLifetime : IApplicationLifetime { private readonly Microsoft.Extensions.Hosting.IApplicationLifetime _applicationLifetime; - public GenericHostApplicationLifetime(Microsoft.Extensions.Hosting.IApplicationLifetime applicationLifetime) + public GenericWebHostApplicationLifetime(Microsoft.Extensions.Hosting.IApplicationLifetime applicationLifetime) { _applicationLifetime = applicationLifetime; } diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index 9697805c..8ada1d4f 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -46,7 +46,7 @@ public GenericWebHostBuilder(IHostBuilder builder) // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting services.AddSingleton(webhostContext.HostingEnvironment); - services.AddSingleton(); + services.AddSingleton(); services.Configure(options => { From e473d8d79653fb94ea1eaf7abfa5b6093ec55697 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 10 Nov 2018 17:51:04 -0800 Subject: [PATCH 07/19] Cleanup the hosted service --- .../GenericHost/GenericWebHostedService.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs index 2c5bccf7..3f762799 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -25,7 +25,6 @@ internal class GenericWebHostService : IHostedService private static readonly string DeprecatedServerUrlsKey = "server.urls"; public GenericWebHostService(IOptions options, - IServiceProvider services, IServer server, ILogger logger, DiagnosticListener diagnosticListener, @@ -35,27 +34,24 @@ public GenericWebHostService(IOptions options, IConfiguration configuration, IHostingEnvironment hostingEnvironment) { - Options = options?.Value ?? throw new System.ArgumentNullException(nameof(options)); + Options = options.Value; if (Options.ConfigureApplication == null) { throw new ArgumentException(nameof(Options.ConfigureApplication)); } - Services = services ?? throw new ArgumentNullException(nameof(services)); - Server = server ?? throw new ArgumentNullException(nameof(server)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - DiagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener)); - HttpContextFactory = httpContextFactory ?? throw new ArgumentNullException(nameof(httpContextFactory)); - ApplicationBuilderFactory = applicationBuilderFactory ?? throw new ArgumentNullException(nameof(applicationBuilderFactory)); - StartupFilters = startupFilters ?? throw new ArgumentNullException(nameof(startupFilters)); - Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); - HostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + Server = server; + Logger = logger; + DiagnosticListener = diagnosticListener; + HttpContextFactory = httpContextFactory; + ApplicationBuilderFactory = applicationBuilderFactory; + StartupFilters = startupFilters; + Configuration = configuration; + HostingEnvironment = hostingEnvironment; } public GenericWebHostServiceOptions Options { get; } - public IServiceProvider Services { get; } - public HostBuilderContext HostBuilderContext { get; } public IServer Server { get; } public ILogger Logger { get; } public DiagnosticListener DiagnosticListener { get; } From 9a4de96e79965a2f9ff0e7b4953d891818817544 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 10 Nov 2018 20:07:32 -0800 Subject: [PATCH 08/19] Make default application throw when not configured. --- .../GenericHost/GenericWebHostServiceOptions.cs | 4 +++- .../GenericHost/GenericWebHostedService.cs | 6 ------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs index b652c1a5..62716262 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs @@ -6,10 +6,12 @@ namespace Microsoft.AspNetCore.Hosting.Internal { internal class GenericWebHostServiceOptions { - public Action ConfigureApplication { get; set; } + public Action ConfigureApplication { get; set; } = DefaultApplication; public WebHostOptions WebHostOptions { get; set; } public AggregateException HostingStartupExceptions { get; set; } + + private static Action DefaultApplication => _ => throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); } } diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs index 3f762799..28d6bfae 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -35,12 +35,6 @@ public GenericWebHostService(IOptions options, IHostingEnvironment hostingEnvironment) { Options = options.Value; - - if (Options.ConfigureApplication == null) - { - throw new ArgumentException(nameof(Options.ConfigureApplication)); - } - Server = server; Logger = logger; DiagnosticListener = diagnosticListener; From 59ce1de6d32f67e92ebce6f1dceba89d979421b8 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sat, 10 Nov 2018 20:23:46 -0800 Subject: [PATCH 09/19] Throw good error if no application delegate was specified --- .../GenericWebHostServiceOptions.cs | 4 +--- .../GenericHost/GenericWebHostedService.cs | 5 ++++- .../GenericHostBuilderTests.cs | 22 +++++++++++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs index 62716262..b652c1a5 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs @@ -6,12 +6,10 @@ namespace Microsoft.AspNetCore.Hosting.Internal { internal class GenericWebHostServiceOptions { - public Action ConfigureApplication { get; set; } = DefaultApplication; + public Action ConfigureApplication { get; set; } public WebHostOptions WebHostOptions { get; set; } public AggregateException HostingStartupExceptions { get; set; } - - private static Action DefaultApplication => _ => throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); } } diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs index 28d6bfae..94f8774e 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -24,6 +24,9 @@ internal class GenericWebHostService : IHostedService { private static readonly string DeprecatedServerUrlsKey = "server.urls"; + private static readonly Action _defaultApplication = + _ => throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); + public GenericWebHostService(IOptions options, IServer server, ILogger logger, @@ -80,7 +83,7 @@ public async Task StartAsync(CancellationToken cancellationToken) try { var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features); - Action configure = Options.ConfigureApplication; + Action configure = Options.ConfigureApplication ?? _defaultApplication; foreach (var filter in StartupFilters.Reverse()) { diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs index c9f48931..da744d14 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs @@ -82,6 +82,28 @@ public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAsse } } + [Fact] + public async Task StartThrowsIfNoApplicationProvided() + { + var provider = new TestLoggerProvider(); + var host = new HostBuilder() + .ConfigureWebHost(builder => + { + builder.ConfigureLogging((_, factory) => + { + factory.AddProvider(provider); + }) + .UseServer(new TestServer()); + }) + .Build(); + + using (host) + { + var exception = await Assert.ThrowsAsync(() => host.StartAsync()); + Assert.Equal("No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via StartupAssemblyKey in the web host configuration.", exception.Message); + } + } + public class TestLoggerProvider : ILoggerProvider { public TestSink Sink { get; set; } = new TestSink(); From 6a3e77be8f9de9ab91a7b0f75e6085abfbeeece4 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 11 Nov 2018 00:20:35 -0800 Subject: [PATCH 10/19] Unify the tests for the GenericWebHostBuilder and WebHostBuilder - There's 3 failures but 2 are a known incompatiblity and the last one is a bug --- .../GenericHost/GenericWebHost.cs | 32 ++ .../GenericHost/GenericWebHostBuilder.cs | 129 ++--- .../Internal/ConfigureContainerBuilder.cs | 2 +- .../Internal/ConfigureServicesBuilder.cs | 2 +- .../Internal/WebHost.cs | 2 +- .../Fakes/StartupNoServicesNoInterface.cs | 21 + .../GenericHostBuilderTests.cs | 146 ------ .../WebHostBuilderTests.cs | 469 ++++++++++-------- 8 files changed, 394 insertions(+), 409 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs create mode 100644 test/Microsoft.AspNetCore.Hosting.Tests/Fakes/StartupNoServicesNoInterface.cs delete mode 100644 test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs new file mode 100644 index 00000000..c7084926 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal class GenericWebHost : IWebHost + { + private readonly IHost _host; + + public GenericWebHost(IHost host) + { + _host = host; + } + + public IFeatureCollection ServerFeatures => Services.GetRequiredService().Features; + + public IServiceProvider Services => _host.Services; + + public void Dispose() => _host.Dispose(); + + public void Start() => _host.Start(); + + public Task StartAsync(CancellationToken cancellationToken = default) => _host.StartAsync(cancellationToken); + + public Task StopAsync(CancellationToken cancellationToken = default) => _host.StopAsync(cancellationToken); + } +} diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index 8ada1d4f..e8785c0f 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -81,7 +81,7 @@ public GenericWebHostBuilder(IHostBuilder builder) try { var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName); - UseStartup(startupType); + UseStartup(startupType, context, services); } catch (Exception ex) when (webHostOptions.CaptureStartupErrors) { @@ -137,7 +137,11 @@ private void ExecuteHostingStartups() } } - public IWebHost Build() => throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); + public IWebHost Build() + { + // REVIEW: This makes testing easier right now + return new GenericWebHost(_builder.Build()); + } public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) { @@ -187,78 +191,83 @@ internal IWebHostBuilder UseStartup(Type startupType) { _builder.ConfigureServices((context, services) => { - var webHostBuilderContext = GetWebHostBuilderContext(context); - var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; + UseStartup(startupType, context, services); + }); - ExceptionDispatchInfo startupError = null; - object instance = null; - ConfigureBuilder configureBuilder = null; + return this; + } - try - { - // We cannot support methods that return IServiceProvider as that is terminal and we need ConfigureServices to compose - if (typeof(IStartup).IsAssignableFrom(startupType)) - { - throw new NotSupportedException($"{typeof(IStartup)} isn't supported"); - } + private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services) + { + var webHostBuilderContext = GetWebHostBuilderContext(context); + var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; - instance = ActivatorUtilities.CreateInstance(new ServiceProvider(webHostBuilderContext), startupType); - context.Properties[_startupKey] = instance; + ExceptionDispatchInfo startupError = null; + object instance = null; + ConfigureBuilder configureBuilder = null; - // Startup.ConfigureServices - var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName); - var configureServices = configureServicesBuilder.Build(instance); + try + { + // We cannot support methods that return IServiceProvider as that is terminal and we need ConfigureServices to compose + if (typeof(IStartup).IsAssignableFrom(startupType)) + { + throw new NotSupportedException($"{typeof(IStartup)} isn't supported"); + } - configureServices(services); + instance = ActivatorUtilities.CreateInstance(new ServiceProvider(webHostBuilderContext), startupType); + context.Properties[_startupKey] = instance; - // REVIEW: We're doing this in the callback so that we have access to the hosting environment - // Startup.ConfigureContainer - var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName); - if (configureContainerBuilder.MethodInfo != null) - { - var containerType = configureContainerBuilder.GetContainerType(); - // Store the builder in the property bag - _builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder; + // Startup.ConfigureServices + var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName); + var configureServices = configureServicesBuilder.Build(instance); - var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType); + configureServices(services); - // Get the private ConfigureContainer method on this type then close over the container type - var configureCallback = GetType().GetMethod(nameof(ConfigureContainer), BindingFlags.NonPublic | BindingFlags.Instance) - .MakeGenericMethod(containerType) - .CreateDelegate(actionType, this); + // REVIEW: We're doing this in the callback so that we have access to the hosting environment + // Startup.ConfigureContainer + var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName); + if (configureContainerBuilder.MethodInfo != null) + { + var containerType = configureContainerBuilder.GetContainerType(); + // Store the builder in the property bag + _builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder; - // _builder.ConfigureContainer(ConfigureContainer); - typeof(IHostBuilder).GetMethods().First(m => m.Name == nameof(IHostBuilder.ConfigureContainer)) - .MakeGenericMethod(containerType) - .Invoke(_builder, new object[] { configureCallback }); - } + var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType); - // Resolve Configure after calling ConfigureServices and ConfigureContainer - configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName); - } - catch (Exception ex) when (webHostOptions.CaptureStartupErrors) - { - startupError = ExceptionDispatchInfo.Capture(ex); + // Get the private ConfigureContainer method on this type then close over the container type + var configureCallback = GetType().GetMethod(nameof(ConfigureContainer), BindingFlags.NonPublic | BindingFlags.Instance) + .MakeGenericMethod(containerType) + .CreateDelegate(actionType, this); + + // _builder.ConfigureContainer(ConfigureContainer); + typeof(IHostBuilder).GetMethods().First(m => m.Name == nameof(IHostBuilder.ConfigureContainer)) + .MakeGenericMethod(containerType) + .Invoke(_builder, new object[] { configureCallback }); } - // Startup.Configure - services.Configure(options => + // Resolve Configure after calling ConfigureServices and ConfigureContainer + configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName); + } + catch (Exception ex) when (webHostOptions.CaptureStartupErrors) + { + startupError = ExceptionDispatchInfo.Capture(ex); + } + + // Startup.Configure + services.Configure(options => + { + options.ConfigureApplication = app => { - options.ConfigureApplication = app => - { - // Throw if there was any errors initializing startup - startupError?.Throw(); + // Throw if there was any errors initializing startup + startupError?.Throw(); - // Execute Startup.Configure - if (instance != null && configureBuilder != null) - { - configureBuilder.Build(instance)(app); - } - }; - }); + // Execute Startup.Configure + if (instance != null && configureBuilder != null) + { + configureBuilder.Build(instance)(app); + } + }; }); - - return this; } private void ConfigureContainer(HostBuilderContext context, TContainer container) @@ -285,7 +294,7 @@ private WebHostBuilderContext GetWebHostBuilderContext(HostBuilderContext contex { if (!context.Properties.TryGetValue(typeof(WebHostBuilderContext), out var contextVal)) { - var options = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name); + var options = new WebHostOptions(context.Configuration, Assembly.GetEntryAssembly()?.GetName().Name); var hostingEnvironment = new HostingEnvironment(); hostingEnvironment.Initialize(context.HostingEnvironment.ContentRootPath, options); diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureContainerBuilder.cs b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureContainerBuilder.cs index ed8d0fd0..8791a918 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureContainerBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureContainerBuilder.cs @@ -15,7 +15,7 @@ public ConfigureContainerBuilder(MethodInfo configureContainerMethod) public MethodInfo MethodInfo { get; } - public Func, Action> ConfigureContainerFilters { get; set; } + public Func, Action> ConfigureContainerFilters { get; set; } = f => f; public Action Build(object instance) => container => Invoke(instance, container); diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureServicesBuilder.cs b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureServicesBuilder.cs index 4206d0d6..cf9a6932 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureServicesBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/ConfigureServicesBuilder.cs @@ -17,7 +17,7 @@ public ConfigureServicesBuilder(MethodInfo configureServices) public MethodInfo MethodInfo { get; } - public Func, Func> StartupServiceFilters { get; set; } + public Func, Func> StartupServiceFilters { get; set; } = f => f; public Func Build(object instance) => services => Invoke(instance, services); diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs b/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs index 3764427f..0ff44958 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs @@ -192,7 +192,7 @@ private void EnsureStartup() if (_startup == null) { - throw new InvalidOperationException($"No startup configured. Please specify startup via WebHostBuilder.UseStartup, WebHostBuilder.Configure, injecting {nameof(IStartup)} or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); + throw new InvalidOperationException($"No application configured. Please specify startup via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, injecting {nameof(IStartup)} or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); } } diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/StartupNoServicesNoInterface.cs b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/StartupNoServicesNoInterface.cs new file mode 100644 index 00000000..d97cee73 --- /dev/null +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/StartupNoServicesNoInterface.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Fakes +{ + public class StartupNoServicesNoInterface + { + public void ConfigureServices(IServiceCollection services) + { + + } + + public void Configure(IApplicationBuilder app) + { + + } + } +} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs deleted file mode 100644 index da744d14..00000000 --- a/test/Microsoft.AspNetCore.Hosting.Tests/GenericHostBuilderTests.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting.Internal; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Testing; -using Xunit; - -namespace Microsoft.AspNetCore.Hosting.Tests -{ - public class GenericHostBuilderTests - { - [Fact] - public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsFalse() - { - var host = new HostBuilder() - .ConfigureWebHost(builder => - { - builder.CaptureStartupErrors(false) - .Configure(app => - { - throw new InvalidOperationException("Startup exception"); - }) - .UseServer(new TestServer()); - }) - .Build(); - - Assert.Throws(() => host.Start()); - } - - [Fact] - public void HostingStartupFromPrimaryAssemblyCanBeDisabled() - { - var host = new HostBuilder() - .ConfigureWebHost(builder => - { - builder.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true") - .Configure(app => { }) - .UseServer(new TestServer()); - }) - .Build(); - - using (host) - { - var config = host.Services.GetRequiredService(); - Assert.Null(config["testhostingstartup"]); - } - } - - [Fact] - public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue() - { - var provider = new TestLoggerProvider(); - var host = new HostBuilder() - .ConfigureWebHost(builder => - { - builder.ConfigureLogging((_, factory) => - { - factory.AddProvider(provider); - }) - .CaptureStartupErrors(true) - .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName") - .Configure(app => { }) - .UseServer(new TestServer()); - }) - .Build(); - - using (host) - { - await host.StartAsync(); - var context = provider.Sink.Writes.FirstOrDefault(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException); - Assert.NotNull(context); - } - } - - [Fact] - public async Task StartThrowsIfNoApplicationProvided() - { - var provider = new TestLoggerProvider(); - var host = new HostBuilder() - .ConfigureWebHost(builder => - { - builder.ConfigureLogging((_, factory) => - { - factory.AddProvider(provider); - }) - .UseServer(new TestServer()); - }) - .Build(); - - using (host) - { - var exception = await Assert.ThrowsAsync(() => host.StartAsync()); - Assert.Equal("No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via StartupAssemblyKey in the web host configuration.", exception.Message); - } - } - - public class TestLoggerProvider : ILoggerProvider - { - public TestSink Sink { get; set; } = new TestSink(); - - public ILogger CreateLogger(string categoryName) => new TestLogger(categoryName, Sink, enabled: true); - - public void Dispose() { } - } - - private class TestServer : IServer - { - IFeatureCollection IServer.Features { get; } - public RequestDelegate RequestDelegate { get; private set; } - - public void Dispose() { } - - public Task StartAsync(IHttpApplication application, CancellationToken cancellationToken) - { - RequestDelegate = async ctx => - { - var httpContext = application.CreateContext(ctx.Features); - try - { - await application.ProcessRequestAsync(httpContext); - } - catch (Exception ex) - { - application.DisposeContext(httpContext, ex); - throw; - } - application.DisposeContext(httpContext, null); - }; - - return Task.CompletedTask; - } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - } - } -} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs index c1244e5c..aee81d8a 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; @@ -29,22 +30,24 @@ namespace Microsoft.AspNetCore.Hosting { public class WebHostBuilderTests { - [Fact] - public void Build_honors_UseStartup_with_string() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_honors_UseStartup_with_string(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder().UseServer(new TestServer()); + builder = builder.UseServer(new TestServer()); - using (var host = (WebHost)builder.UseStartup("MyStartupAssembly").Build()) + using (var host = builder.UseStartup("MyStartupAssembly").Build()) { - Assert.Equal("MyStartupAssembly", host.Options.ApplicationName); - Assert.Equal("MyStartupAssembly", host.Options.StartupAssembly); + var options = new WebHostOptions(host.Services.GetRequiredService()); + Assert.Equal("MyStartupAssembly", options.ApplicationName); + Assert.Equal("MyStartupAssembly", options.StartupAssembly); } } - [Fact] - public async Task StartupMissing_Fallback() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task StartupMissing_Fallback(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder(); var server = new TestServer(); using (var host = builder.UseServer(server).UseStartup("MissingStartupAssembly").Build()) { @@ -53,10 +56,10 @@ public async Task StartupMissing_Fallback() } } - [Fact] - public async Task StartupStaticCtorThrows_Fallback() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task StartupStaticCtorThrows_Fallback(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder(); var server = new TestServer(); var host = builder.UseServer(server).UseStartup().Build(); using (host) @@ -66,10 +69,10 @@ public async Task StartupStaticCtorThrows_Fallback() } } - [Fact] - public async Task StartupCtorThrows_Fallback() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task StartupCtorThrows_Fallback(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder(); var server = new TestServer(); var host = builder.UseServer(server).UseStartup().Build(); using (host) @@ -79,10 +82,10 @@ public async Task StartupCtorThrows_Fallback() } } - [Fact] - public async Task StartupCtorThrows_TypeLoadException() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task StartupCtorThrows_TypeLoadException(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder(); var server = new TestServer(); var host = builder.UseServer(server).UseStartup().Build(); using (host) @@ -92,10 +95,10 @@ public async Task StartupCtorThrows_TypeLoadException() } } - [Fact] - public async Task IApplicationLifetimeRegisteredEvenWhenStartupCtorThrows_Fallback() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task IApplicationLifetimeRegisteredEvenWhenStartupCtorThrows_Fallback(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder(); var server = new TestServer(); var host = builder.UseServer(server).UseStartup().Build(); using (host) @@ -109,11 +112,12 @@ public async Task IApplicationLifetimeRegisteredEvenWhenStartupCtorThrows_Fallba } } - [Fact] - public async Task DefaultObjectPoolProvider_IsRegistered() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task DefaultObjectPoolProvider_IsRegistered(IWebHostBuilder builder) { var server = new TestServer(); - var host = CreateWebHostBuilder() + var host = builder .UseServer(server) .Configure(app => { }) .Build(); @@ -124,10 +128,10 @@ public async Task DefaultObjectPoolProvider_IsRegistered() } } - [Fact] - public async Task StartupConfigureServicesThrows_Fallback() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task StartupConfigureServicesThrows_Fallback(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder(); var server = new TestServer(); var host = builder.UseServer(server).UseStartup().Build(); using (host) @@ -137,10 +141,10 @@ public async Task StartupConfigureServicesThrows_Fallback() } } - [Fact] - public async Task StartupConfigureThrows_Fallback() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task StartupConfigureThrows_Fallback(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder(); var server = new TestServer(); var host = builder.UseServer(server).UseStartup().Build(); using (host) @@ -150,23 +154,25 @@ public async Task StartupConfigureThrows_Fallback() } } - [Fact] - public void DefaultCreatesLoggerFactory() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void DefaultCreatesLoggerFactory(IWebHostBuilder builder) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseServer(new TestServer()) .UseStartup(); - using (var host = (WebHost)hostBuilder.Build()) + using (var host = hostBuilder.Build()) { Assert.NotNull(host.Services.GetService()); } } - [Fact] - public void ConfigureDefaultServiceProvider() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void ConfigureDefaultServiceProvider(IWebHostBuilder builder) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseServer(new TestServer()) .ConfigureServices(s => { @@ -185,11 +191,12 @@ public void ConfigureDefaultServiceProvider() Assert.Throws(() => hostBuilder.Build().Start()); } - [Fact] - public void ConfigureDefaultServiceProviderWithContext() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void ConfigureDefaultServiceProviderWithContext(IWebHostBuilder builder) { var configurationCallbackCalled = false; - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseServer(new TestServer()) .ConfigureServices(s => { @@ -212,11 +219,12 @@ public void ConfigureDefaultServiceProviderWithContext() Assert.True(configurationCallbackCalled); } - [Fact] - public void MultipleConfigureLoggingInvokedInOrder() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void MultipleConfigureLoggingInvokedInOrder(IWebHostBuilder builder) { var callCount = 0; //Verify ordering - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .ConfigureLogging(loggerFactory => { Assert.Equal(0, callCount++); @@ -234,8 +242,9 @@ public void MultipleConfigureLoggingInvokedInOrder() } } - [Fact] - public async Task MultipleStartupAssembliesSpecifiedOnlyAddAssemblyOnce() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task MultipleStartupAssembliesSpecifiedOnlyAddAssemblyOnce(IWebHostBuilder builder) { var provider = new TestLoggerProvider(); var assemblyName = "RandomName"; @@ -246,7 +255,7 @@ public async Task MultipleStartupAssembliesSpecifiedOnlyAddAssemblyOnce() }; var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build(); - var builder = CreateWebHostBuilder() + builder = builder .UseConfiguration(config) .ConfigureLogging((_, factory) => { @@ -255,7 +264,7 @@ public async Task MultipleStartupAssembliesSpecifiedOnlyAddAssemblyOnce() .UseServer(new TestServer()); // Verify that there was only one exception throw rather than two. - using (var host = (WebHost)builder.Build()) + using (var host = builder.Build()) { await host.StartAsync(); var context = provider.Sink.Writes.Where(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException); @@ -267,7 +276,7 @@ public async Task MultipleStartupAssembliesSpecifiedOnlyAddAssemblyOnce() [Fact] public void HostingContextContainsAppConfigurationDuringConfigureLogging() { - var hostBuilder = new WebHostBuilder() + var hostBuilder = CreateWebHostBuilder() .ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddInMemoryCollection( new KeyValuePair[] @@ -287,7 +296,7 @@ public void HostingContextContainsAppConfigurationDuringConfigureLogging() [Fact] public void HostingContextContainsAppConfigurationDuringConfigureServices() { - var hostBuilder = new WebHostBuilder() + var hostBuilder = CreateWebHostBuilder() .ConfigureAppConfiguration((context, configBuilder) => configBuilder.AddInMemoryCollection( new KeyValuePair[] @@ -304,23 +313,25 @@ public void HostingContextContainsAppConfigurationDuringConfigureServices() using (hostBuilder.Build()) { } } - [Fact] - public void ThereIsAlwaysConfiguration() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void ThereIsAlwaysConfiguration(IWebHostBuilder builder) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseServer(new TestServer()) .UseStartup(); - using (var host = (WebHost)hostBuilder.Build()) + using (var host = hostBuilder.Build()) { Assert.NotNull(host.Services.GetService()); } } - [Fact] - public void ConfigureConfigurationSettingsPropagated() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void ConfigureConfigurationSettingsPropagated(IWebHostBuilder builder) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseSetting("key1", "value1") .ConfigureAppConfiguration((context, configBuilder) => { @@ -333,10 +344,11 @@ public void ConfigureConfigurationSettingsPropagated() using (hostBuilder.Build()) { } } - [Fact] - public void CanConfigureConfigurationAndRetrieveFromDI() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void CanConfigureConfigurationAndRetrieveFromDI(IWebHostBuilder builder) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .ConfigureAppConfiguration((_, configBuilder) => { configBuilder @@ -350,7 +362,7 @@ public void CanConfigureConfigurationAndRetrieveFromDI() .UseServer(new TestServer()) .UseStartup(); - using (var host = (WebHost)hostBuilder.Build()) + using (var host = hostBuilder.Build()) { var config = host.Services.GetService(); Assert.NotNull(config); @@ -358,10 +370,11 @@ public void CanConfigureConfigurationAndRetrieveFromDI() } } - [Fact] - public void DoNotCaptureStartupErrorsByDefault() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void DoNotCaptureStartupErrorsByDefault(IWebHostBuilder builder) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseServer(new TestServer()) .UseStartup(); @@ -386,10 +399,11 @@ public void ServiceProviderDisposedOnBuildException() Assert.True(service.Disposed); } - [Fact] - public void CaptureStartupErrorsHonored() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void CaptureStartupErrorsHonored(IWebHostBuilder builder) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .CaptureStartupErrors(false) .UseServer(new TestServer()) .UseStartup(); @@ -398,11 +412,12 @@ public void CaptureStartupErrorsHonored() Assert.Equal("A public method named 'ConfigureProduction' or 'Configure' could not be found in the 'Microsoft.AspNetCore.Hosting.Fakes.StartupBoom' type.", exception.Message); } - [Fact] - public void ConfigureServices_CanBeCalledMultipleTimes() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void ConfigureServices_CanBeCalledMultipleTimes(IWebHostBuilder builder) { var callCount = 0; // Verify ordering - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseServer(new TestServer()) .ConfigureServices(services => { @@ -425,23 +440,26 @@ public void ConfigureServices_CanBeCalledMultipleTimes() } } - [Fact] - public void CodeBasedSettingsCodeBasedOverride() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void CodeBasedSettingsCodeBasedOverride(IWebHostBuilder builder) { - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseSetting(WebHostDefaults.EnvironmentKey, "EnvA") .UseSetting(WebHostDefaults.EnvironmentKey, "EnvB") .UseServer(new TestServer()) .UseStartup(); - using (var host = (WebHost)hostBuilder.Build()) + using (var host = hostBuilder.Build()) { - Assert.Equal("EnvB", host.Options.Environment); + var options = new WebHostOptions(host.Services.GetRequiredService()); + Assert.Equal("EnvB", options.Environment); } } - [Fact] - public void CodeBasedSettingsConfigBasedOverride() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void CodeBasedSettingsConfigBasedOverride(IWebHostBuilder builder) { var settings = new Dictionary { @@ -452,20 +470,22 @@ public void CodeBasedSettingsConfigBasedOverride() .AddInMemoryCollection(settings) .Build(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseSetting(WebHostDefaults.EnvironmentKey, "EnvA") .UseConfiguration(config) .UseServer(new TestServer()) .UseStartup(); - using (var host = (WebHost)hostBuilder.Build()) + using (var host = hostBuilder.Build()) { - Assert.Equal("EnvB", host.Options.Environment); + var options = new WebHostOptions(host.Services.GetRequiredService()); + Assert.Equal("EnvB", options.Environment); } } - [Fact] - public void ConfigBasedSettingsCodeBasedOverride() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void ConfigBasedSettingsCodeBasedOverride(IWebHostBuilder builder) { var settings = new Dictionary { @@ -476,20 +496,22 @@ public void ConfigBasedSettingsCodeBasedOverride() .AddInMemoryCollection(settings) .Build(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseConfiguration(config) .UseSetting(WebHostDefaults.EnvironmentKey, "EnvB") .UseServer(new TestServer()) .UseStartup(); - using (var host = (WebHost)hostBuilder.Build()) + using (var host = hostBuilder.Build()) { - Assert.Equal("EnvB", host.Options.Environment); + var options = new WebHostOptions(host.Services.GetRequiredService()); + Assert.Equal("EnvB", options.Environment); } } - [Fact] - public void ConfigBasedSettingsConfigBasedOverride() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void ConfigBasedSettingsConfigBasedOverride(IWebHostBuilder builder) { var settings = new Dictionary { @@ -509,33 +531,35 @@ public void ConfigBasedSettingsConfigBasedOverride() .AddInMemoryCollection(overrideSettings) .Build(); - var hostBuilder = new WebHostBuilder() + var hostBuilder = builder .UseConfiguration(config) .UseConfiguration(overrideConfig) .UseServer(new TestServer()) .UseStartup(); - using (var host = (WebHost)hostBuilder.Build()) + using (var host = hostBuilder.Build()) { - Assert.Equal("EnvB", host.Options.Environment); + var options = new WebHostOptions(host.Services.GetRequiredService()); + Assert.Equal("EnvB", options.Environment); } } - [Fact] - public void UseEnvironmentIsNotOverriden() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void UseEnvironmentIsNotOverriden(IWebHostBuilder builder) { var vals = new Dictionary { { "ENV", "Dev" }, }; - var builder = new ConfigurationBuilder() + var configBuilder = new ConfigurationBuilder() .AddInMemoryCollection(vals); - var config = builder.Build(); + var config = configBuilder.Build(); var expected = "MY_TEST_ENVIRONMENT"; - using (var host = new WebHostBuilder() + using (var host = builder .UseConfiguration(config) .UseEnvironment(expected) .UseServer(new TestServer()) @@ -547,19 +571,20 @@ public void UseEnvironmentIsNotOverriden() } } - [Fact] - public void BuildAndDispose() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void BuildAndDispose(IWebHostBuilder builder) { var vals = new Dictionary { { "ENV", "Dev" }, }; - var builder = new ConfigurationBuilder() + var configBuilder = new ConfigurationBuilder() .AddInMemoryCollection(vals); - var config = builder.Build(); + var config = configBuilder.Build(); var expected = "MY_TEST_ENVIRONMENT"; - using (var host = new WebHostBuilder() + using (var host = builder .UseConfiguration(config) .UseEnvironment(expected) .UseServer(new TestServer()) @@ -567,18 +592,19 @@ public void BuildAndDispose() .Build()) { } } - [Fact] - public void UseBasePathConfiguresBasePath() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void UseBasePathConfiguresBasePath(IWebHostBuilder builder) { var vals = new Dictionary { { "ENV", "Dev" }, }; - var builder = new ConfigurationBuilder() + var configBuilder = new ConfigurationBuilder() .AddInMemoryCollection(vals); - var config = builder.Build(); + var config = configBuilder.Build(); - using (var host = new WebHostBuilder() + using (var host = builder .UseConfiguration(config) .UseContentRoot("/") .UseServer(new TestServer()) @@ -590,10 +616,11 @@ public void UseBasePathConfiguresBasePath() } } - [Fact] - public void RelativeContentRootIsResolved() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void RelativeContentRootIsResolved(IWebHostBuilder builder) { - using (var host = new WebHostBuilder() + using (var host = builder .UseContentRoot("testroot") .UseServer(new TestServer()) .UseStartup("Microsoft.AspNetCore.Hosting.Tests") @@ -610,10 +637,11 @@ public void RelativeContentRootIsResolved() } } - [Fact] - public void DefaultContentRootIsApplicationBasePath() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void DefaultContentRootIsApplicationBasePath(IWebHostBuilder builder) { - using (var host = new WebHostBuilder() + using (var host = builder .UseServer(new TestServer()) .UseStartup("Microsoft.AspNetCore.Hosting.Tests") .Build()) @@ -624,22 +652,23 @@ public void DefaultContentRootIsApplicationBasePath() } } - [Fact] - public void DefaultWebHostBuilderWithNoStartupThrows() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void DefaultWebHostBuilderWithNoStartupThrows(IWebHostBuilder builder) { - var host = new WebHostBuilder() + var host = builder .UseServer(new TestServer()); - var ex = Assert.Throws(() => host.Build()); + var ex = Assert.Throws(() => host.Build().Start()); - Assert.Contains("No startup configured.", ex.Message); + Assert.Contains("No application configured.", ex.Message); } - [Fact] - public void DefaultApplicationNameWithUseStartupOfString() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void DefaultApplicationNameWithUseStartupOfString(IWebHostBuilder builder) { - var builder = new ConfigurationBuilder(); - using (var host = new WebHostBuilder() + using (var host = builder .UseServer(new TestServer()) .UseStartup(typeof(Startup).Assembly.GetName().Name) .Build()) @@ -651,40 +680,40 @@ public void DefaultApplicationNameWithUseStartupOfString() } } - [Fact] - public void DefaultApplicationNameWithUseStartupOfT() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void DefaultApplicationNameWithUseStartupOfT(IWebHostBuilder builder) { - var builder = new ConfigurationBuilder(); - using (var host = new WebHostBuilder() + using (var host = builder .UseServer(new TestServer()) - .UseStartup() + .UseStartup() .Build()) { var hostingEnv = host.Services.GetService(); var hostingEnv2 = host.Services.GetService(); - Assert.Equal(typeof(StartupNoServices).Assembly.GetName().Name, hostingEnv.ApplicationName); - Assert.Equal(typeof(StartupNoServices).Assembly.GetName().Name, hostingEnv2.ApplicationName); + Assert.Equal(typeof(StartupNoServicesNoInterface).Assembly.GetName().Name, hostingEnv.ApplicationName); + Assert.Equal(typeof(StartupNoServicesNoInterface).Assembly.GetName().Name, hostingEnv2.ApplicationName); } } - [Fact] - public void DefaultApplicationNameWithUseStartupOfType() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void DefaultApplicationNameWithUseStartupOfType(IWebHostBuilder builder) { - var builder = new ConfigurationBuilder(); - var host = new WebHostBuilder() + var host = builder .UseServer(new TestServer()) - .UseStartup(typeof(StartupNoServices)) + .UseStartup(typeof(StartupNoServicesNoInterface)) .Build(); var hostingEnv = host.Services.GetService(); - Assert.Equal(typeof(StartupNoServices).Assembly.GetName().Name, hostingEnv.ApplicationName); + Assert.Equal(typeof(StartupNoServicesNoInterface).Assembly.GetName().Name, hostingEnv.ApplicationName); } - [Fact] - public void DefaultApplicationNameWithConfigure() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void DefaultApplicationNameWithConfigure(IWebHostBuilder builder) { - var builder = new ConfigurationBuilder(); - using (var host = new WebHostBuilder() + using (var host = builder .UseServer(new TestServer()) .Configure(app => { }) .Build()) @@ -696,10 +725,11 @@ public void DefaultApplicationNameWithConfigure() } } - [Fact] - public void Configure_SupportsNonStaticMethodDelegate() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void Configure_SupportsNonStaticMethodDelegate(IWebHostBuilder builder) { - using (var host = new WebHostBuilder() + using (var host = builder .UseServer(new TestServer()) .Configure(app => { }) .Build()) @@ -709,10 +739,11 @@ public void Configure_SupportsNonStaticMethodDelegate() } } - [Fact] - public void Configure_SupportsStaticMethodDelegate() + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public void Configure_SupportsStaticMethodDelegate(IWebHostBuilder builder) { - using (var host = new WebHostBuilder() + using (var host = builder .UseServer(new TestServer()) .Configure(StaticConfigureMethod) .Build()) @@ -736,11 +767,11 @@ public void Build_DoesNotAllowBuildingMuiltipleTimes() } } - [Fact] - public void Build_DoesNotOverrideILoggerFactorySetByConfigureServices() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_DoesNotOverrideILoggerFactorySetByConfigureServices(IWebHostBuilder builder) { var factory = new DisposableLoggerFactory(); - var builder = CreateWebHostBuilder(); var server = new TestServer(); using (var host = builder.UseServer(server) @@ -753,10 +784,11 @@ public void Build_DoesNotOverrideILoggerFactorySetByConfigureServices() } } - [Fact] - public void Build_RunsHostingStartupAssembliesIfSpecified() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_RunsHostingStartupAssembliesIfSpecified(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName) .Configure(app => { }) @@ -768,10 +800,11 @@ public void Build_RunsHostingStartupAssembliesIfSpecified() } } - [Fact] - public void Build_RunsHostingStartupRunsPrimaryAssemblyFirst() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_RunsHostingStartupRunsPrimaryAssemblyFirst(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName) .Configure(app => { }) @@ -785,31 +818,28 @@ public void Build_RunsHostingStartupRunsPrimaryAssemblyFirst() } } - [Fact] - public void Build_RunsHostingStartupAssembliesBeforeApplication() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_RunsHostingStartupAssembliesBeforeApplication(IWebHostBuilder builder) { - var startup = new StartupVerifyServiceA(); var startupAssemblyName = typeof(WebHostBuilderTests).GetTypeInfo().Assembly.GetName().Name; - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(WebHostBuilderTests).GetTypeInfo().Assembly.FullName) .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) - .ConfigureServices(services => - { - services.AddSingleton(startup); - }) + .UseStartup() .UseServer(new TestServer()); using (var host = builder.Build()) { host.Start(); + var startup = host.Services.GetRequiredService(); Assert.NotNull(startup.ServiceADescriptor); Assert.NotNull(startup.ServiceA); } } - [Fact] public async Task ExternalContainerInstanceCanBeUsedForEverything() { @@ -825,7 +855,7 @@ public async Task ExternalContainerInstanceCanBeUsedForEverything() }); }); - var host = new WebHostBuilder() + var host = CreateWebHostBuilder() .UseStartup() .UseServer(new TestServer()) .ConfigureServices(services => @@ -848,10 +878,11 @@ public async Task ExternalContainerInstanceCanBeUsedForEverything() Assert.True(disposables[1].Disposed); } - [Fact] - public void Build_HostingStartupAssemblyCanBeExcluded() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_HostingStartupAssemblyCanBeExcluded(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName) .UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName) @@ -865,10 +896,11 @@ public void Build_HostingStartupAssemblyCanBeExcluded() } } - [Fact] - public void Build_ConfigureLoggingInHostingStartupWorks() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_ConfigureLoggingInHostingStartupWorks(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .Configure(app => { @@ -878,7 +910,7 @@ public void Build_ConfigureLoggingInHostingStartupWorks() }) .UseServer(new TestServer()); - using (var host = (WebHost)builder.Build()) + using (var host = builder.Build()) { host.Start(); var sink = host.Services.GetRequiredService(); @@ -886,25 +918,27 @@ public void Build_ConfigureLoggingInHostingStartupWorks() } } - [Fact] - public void Build_ConfigureAppConfigurationInHostingStartupWorks() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_ConfigureAppConfigurationInHostingStartupWorks(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .Configure(app => { }) .UseServer(new TestServer()); - using (var host = (WebHost)builder.Build()) + using (var host = builder.Build()) { var configuration = host.Services.GetRequiredService(); Assert.Equal("value", configuration["testhostingstartup:config"]); } } - [Fact] - public void Build_DoesRunHostingStartupFromPrimaryAssemblyEvenIfNotSpecified() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_DoesRunHostingStartupFromPrimaryAssemblyEvenIfNotSpecified(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .Configure(app => { }) .UseServer(new TestServer()); @@ -914,10 +948,11 @@ public void Build_DoesRunHostingStartupFromPrimaryAssemblyEvenIfNotSpecified() } } - [Fact] - public void Build_HostingStartupFromPrimaryAssemblyCanBeDisabled() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_HostingStartupFromPrimaryAssemblyCanBeDisabled(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .UseSetting(WebHostDefaults.PreventHostingStartupKey, "true") .Configure(app => { }) .UseServer(new TestServer()); @@ -928,10 +963,11 @@ public void Build_HostingStartupFromPrimaryAssemblyCanBeDisabled() } } - [Fact] - public void Build_DoesntThrowIfUnloadableAssemblyNameInHostingStartupAssemblies() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void Build_DoesntThrowIfUnloadableAssemblyNameInHostingStartupAssemblies(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName") .Configure(app => { }) @@ -943,11 +979,12 @@ public void Build_DoesntThrowIfUnloadableAssemblyNameInHostingStartupAssemblies( } } - [Fact] - public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue(IWebHostBuilder builder) { var provider = new TestLoggerProvider(); - var builder = CreateWebHostBuilder() + builder = builder .ConfigureLogging((_, factory) => { factory.AddProvider(provider); @@ -965,10 +1002,11 @@ public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAsse } } - [Fact] - public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsTrue() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsTrue(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(true) .Configure(app => { @@ -976,7 +1014,7 @@ public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsTrue() }) .UseServer(new TestServer()); - using (var host = (WebHost)builder.Build()) + using (var host = builder.Build()) { host.Start(); var sink = host.Services.GetRequiredService(); @@ -984,12 +1022,13 @@ public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsTrue() } } - [Fact] - public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsFalse() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsFalse(IWebHostBuilder builder) { ITestSink testSink = null; - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .Configure(app => { @@ -1017,18 +1056,20 @@ public void HostingStartupTypeCtorThrowsIfNotIHosting() Assert.Throws(() => new HostingStartupAttribute(typeof(WebHostTests))); } - [Fact] - public void UseShutdownTimeoutConfiguresShutdownTimeout() + [Theory] + [MemberData(nameof(DefaultWebHostBuildersWithConfig))] + public void UseShutdownTimeoutConfiguresShutdownTimeout(IWebHostBuilder builder) { - var builder = CreateWebHostBuilder() + builder = builder .CaptureStartupErrors(false) .UseShutdownTimeout(TimeSpan.FromSeconds(102)) .Configure(app => { }) .UseServer(new TestServer()); - using (var host = (WebHost)builder.Build()) + using (var host = builder.Build()) { - Assert.Equal(TimeSpan.FromSeconds(102), host.Options.ShutdownTimeout); + var options = new WebHostOptions(host.Services.GetRequiredService()); + Assert.Equal(TimeSpan.FromSeconds(102), options.ShutdownTimeout); } } @@ -1044,9 +1085,37 @@ private IWebHostBuilder CreateWebHostBuilder() var builder = new ConfigurationBuilder() .AddInMemoryCollection(vals); var config = builder.Build(); + return new WebHostBuilder().UseConfiguration(config); } + public static TheoryData DefaultWebHostBuilders => new TheoryData + { + new WebHostBuilder(), + new GenericWebHostBuilder(new HostBuilder()) + }; + + public static TheoryData DefaultWebHostBuildersWithConfig + { + get + { + var vals = new Dictionary + { + { "DetailedErrors", "true" }, + { "captureStartupErrors", "true" } + }; + + var builder = new ConfigurationBuilder() + .AddInMemoryCollection(vals); + var config = builder.Build(); + + return new TheoryData { + new WebHostBuilder().UseConfiguration(config), + new GenericWebHostBuilder(new HostBuilder()).UseConfiguration(config) + }; + } + } + private async Task AssertResponseContains(RequestDelegate app, string expectedText) { var httpContext = new DefaultHttpContext(); @@ -1132,17 +1201,17 @@ public void Configure(IApplicationBuilder app, DisposableService disposable) } } - internal class StartupVerifyServiceA : IStartup + internal class StartupVerifyServiceA { internal ServiceA ServiceA { get; set; } internal ServiceDescriptor ServiceADescriptor { get; set; } - public IServiceProvider ConfigureServices(IServiceCollection services) + public void ConfigureServices(IServiceCollection services) { - ServiceADescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(ServiceA)); + services.AddSingleton(this); - return services.BuildServiceProvider(); + ServiceADescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(ServiceA)); } public void Configure(IApplicationBuilder app) From 2260c5204080ee8eb8fda6f56a6cfd7cbe61f746 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 11 Nov 2018 01:13:50 -0800 Subject: [PATCH 11/19] Fix ordering issues with IHostingStartup - Put callbacks in place first to run IHostingStartup modifications using a wrapper IWebHostBuilder (now we have 3 of them...). --- .../GenericHost/GenericWebHostBuilder.cs | 25 +++++++- .../HostingStartupWebHostBuilder.cs | 62 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index e8785c0f..8e4b6ad2 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -21,6 +21,8 @@ internal class GenericWebHostBuilder : IWebHostBuilder private readonly IConfiguration _config; private readonly object _startupKey = new object(); private AggregateException _hostingStartupErrors; + private HostingStartupWebHostBuilder _hostingStartupWebHostBuilder; + public GenericWebHostBuilder(IHostBuilder builder) { @@ -39,6 +41,26 @@ public GenericWebHostBuilder(IHostBuilder builder) ExecuteHostingStartups(); }); + // IHostingStartup needs to be executed before any direct methods on the builder + // so register these callbacks first + _builder.ConfigureAppConfiguration((context, configurationBuilder) => + { + if (_hostingStartupWebHostBuilder != null) + { + var webhostContext = GetWebHostBuilderContext(context); + _hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder); + } + }); + + _builder.ConfigureServices((context, services) => + { + if (_hostingStartupWebHostBuilder != null) + { + var webhostContext = GetWebHostBuilderContext(context); + _hostingStartupWebHostBuilder.ConfigureServices(webhostContext, services); + } + }); + _builder.ConfigureServices((context, services) => { var webhostContext = GetWebHostBuilderContext(context); @@ -110,6 +132,7 @@ private void ExecuteHostingStartups() } var exceptions = new List(); + _hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this); // Execute the hosting startup assemblies foreach (var assemblyName in webHostOptions.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase)) @@ -121,7 +144,7 @@ private void ExecuteHostingStartups() foreach (var attribute in assembly.GetCustomAttributes()) { var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType); - hostingStartup.Configure(this); + hostingStartup.Configure(_hostingStartupWebHostBuilder); } } catch (Exception ex) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs new file mode 100644 index 00000000..dfc2a3a2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + // We use this type to capture calls to the IWebHostBuilder so the we can properly order calls to + // to GenericHostWebHostBuilder. + internal class HostingStartupWebHostBuilder : IWebHostBuilder + { + private readonly IWebHostBuilder _builder; + private Action _configureConfiguration; + private Action _configureServices; + + public HostingStartupWebHostBuilder(IWebHostBuilder builder) + { + _builder = builder; + } + + public IWebHost Build() + { + throw new NotSupportedException(); + } + + public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) + { + _configureConfiguration += configureDelegate; + return this; + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + return ConfigureServices((context, services) => configureServices(services)); + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + _configureServices += configureServices; + return this; + } + + public string GetSetting(string key) => _builder.GetSetting(key); + + public IWebHostBuilder UseSetting(string key, string value) + { + _builder.UseSetting(key, value); + return this; + } + + public void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) + { + _configureServices?.Invoke(context, services); + } + + public void ConfigureAppConfiguration(WebHostBuilderContext context, IConfigurationBuilder builder) + { + _configureConfiguration?.Invoke(context, builder); + } + } +} From cbc20bbca3cda4a889ed838e0be887c8a4c610a0 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 11 Nov 2018 01:26:17 -0800 Subject: [PATCH 12/19] Added light up interfaces for IWebHostBuilders - Added ISupportsStartup and ISupportsDefaultServiceProvider that circumvents the default extension method logic and delegates to the type itself. --- .../GenericHost/GenericWebHostBuilder.cs | 8 +++---- .../HostingStartupWebHostBuilder.cs | 22 ++++++++++++++++--- .../GenericHost/ISupportsStartup.cs | 13 +++++++++++ .../ISupportsUseDefaultServiceProvider.cs | 12 ++++++++++ .../WebHostBuilderExtensions.cs | 14 ++++++------ 5 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsStartup.cs create mode 100644 src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsUseDefaultServiceProvider.cs diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index 8e4b6ad2..9f1a30d2 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal { - internal class GenericWebHostBuilder : IWebHostBuilder + internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider { private readonly IHostBuilder _builder; private readonly IConfiguration _config; @@ -193,7 +193,7 @@ public IWebHostBuilder ConfigureServices(Action configure) + public IWebHostBuilder UseDefaultServiceProvider(Action configure) { // REVIEW: This is a hack to change the builder with the HostBuilderContext in scope, // we're not actually using configuration here @@ -210,7 +210,7 @@ internal IWebHostBuilder UseDefaultServiceProvider(Action { @@ -300,7 +300,7 @@ private void ConfigureContainer(HostBuilderContext context, TContain builder.Build(instance)(container); } - internal IWebHostBuilder Configure(Action configure) + public IWebHostBuilder Configure(Action configure) { _builder.ConfigureServices((context, services) => { diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs index dfc2a3a2..81ea34f4 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -8,13 +9,13 @@ namespace Microsoft.AspNetCore.Hosting.Internal { // We use this type to capture calls to the IWebHostBuilder so the we can properly order calls to // to GenericHostWebHostBuilder. - internal class HostingStartupWebHostBuilder : IWebHostBuilder + internal class HostingStartupWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider { - private readonly IWebHostBuilder _builder; + private readonly GenericWebHostBuilder _builder; private Action _configureConfiguration; private Action _configureServices; - public HostingStartupWebHostBuilder(IWebHostBuilder builder) + public HostingStartupWebHostBuilder(GenericWebHostBuilder builder) { _builder = builder; } @@ -58,5 +59,20 @@ public void ConfigureAppConfiguration(WebHostBuilderContext context, IConfigurat { _configureConfiguration?.Invoke(context, builder); } + + public IWebHostBuilder UseDefaultServiceProvider(Action configure) + { + return _builder.UseDefaultServiceProvider(configure); + } + + public IWebHostBuilder Configure(Action configure) + { + return _builder.Configure(configure); + } + + public IWebHostBuilder UseStartup(Type startupType) + { + return _builder.UseStartup(startupType); + } } } diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsStartup.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsStartup.cs new file mode 100644 index 00000000..ce81dcc2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsStartup.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Builder; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal interface ISupportsStartup + { + IWebHostBuilder Configure(Action configure); + IWebHostBuilder UseStartup(Type startupType); + } +} diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsUseDefaultServiceProvider.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsUseDefaultServiceProvider.cs new file mode 100644 index 00000000..6ac05c8d --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsUseDefaultServiceProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + internal interface ISupportsUseDefaultServiceProvider + { + IWebHostBuilder UseDefaultServiceProvider(Action configure); + } +} diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs index 43603382..63dd3257 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs @@ -31,10 +31,10 @@ public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName); - // Light up the GenericWebHostBuilder implementation - if (hostBuilder is GenericWebHostBuilder genericWebHostBuilder) + // Light up the ISupportsStartup implementation + if (hostBuilder is ISupportsStartup supportsStartup) { - return genericWebHostBuilder.Configure(configureApp); + return supportsStartup.Configure(configureApp); } return hostBuilder.ConfigureServices(services => @@ -60,9 +60,9 @@ public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName); // Light up the GenericWebHostBuilder implementation - if (hostBuilder is GenericWebHostBuilder genericWebHostBuilder) + if (hostBuilder is ISupportsStartup supportsStartup) { - return genericWebHostBuilder.UseStartup(startupType); + return supportsStartup.UseStartup(startupType); } return hostBuilder @@ -114,9 +114,9 @@ public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hos public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hostBuilder, Action configure) { // Light up the GenericWebHostBuilder implementation - if (hostBuilder is GenericWebHostBuilder genericWebHostBuilder) + if (hostBuilder is ISupportsUseDefaultServiceProvider supportsDefaultServiceProvider) { - return genericWebHostBuilder.UseDefaultServiceProvider(configure); + return supportsDefaultServiceProvider.UseDefaultServiceProvider(configure); } return hostBuilder.ConfigureServices((context, services) => From 2c8a72495cea82d9660dca17b38440d374782421 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 11 Nov 2018 14:46:20 -0800 Subject: [PATCH 13/19] Added tests for incompatible behavior in GenericWebHostBuilder --- .../GenericHost/GenericWebHostBuilder.cs | 15 ++++++++--- .../GenericHostWebHostBuilderExtensions.cs | 2 +- .../WebHostBuilderTests.cs | 26 +++++++++++++++++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index 9f1a30d2..b7788fbf 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -20,13 +20,15 @@ internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISuppo private readonly IHostBuilder _builder; private readonly IConfiguration _config; private readonly object _startupKey = new object(); + private readonly bool _allowBuild; + private AggregateException _hostingStartupErrors; private HostingStartupWebHostBuilder _hostingStartupWebHostBuilder; - - public GenericWebHostBuilder(IHostBuilder builder) + public GenericWebHostBuilder(IHostBuilder builder, bool allowBuild = true) { _builder = builder; + _allowBuild = allowBuild; _config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") @@ -162,8 +164,13 @@ private void ExecuteHostingStartups() public IWebHost Build() { - // REVIEW: This makes testing easier right now - return new GenericWebHost(_builder.Build()); + if (_allowBuild) + { + // This makes testing the various implementations easy + return new GenericWebHost(_builder.Build()); + } + + throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); } public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs index ea903ad7..5c6829ba 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs @@ -8,7 +8,7 @@ public static class GenericHostWebHostBuilderExtensions { public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action configure) { - var webhostBuilder = new GenericWebHostBuilder(builder); + var webhostBuilder = new GenericWebHostBuilder(builder, allowBuild: false); configure(webhostBuilder); return builder; } diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs index aee81d8a..693ceb1c 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs @@ -878,6 +878,32 @@ public async Task ExternalContainerInstanceCanBeUsedForEverything() Assert.True(disposables[1].Disposed); } + [Fact] + public void GenericWebHostThrowsWithIStartup() + { + var builder = new GenericWebHostBuilder(new HostBuilder()) + .UseStartup(); + + var exception = Assert.Throws(() => builder.Build()); + Assert.Equal("Microsoft.AspNetCore.Hosting.IStartup isn't supported", exception.Message); + } + + [Fact] + public void GenericWebHostThrowsOnBuild() + { + var exception = Assert.Throws(() => + { + var hostBuilder = new HostBuilder() + .ConfigureWebHost(builder => + { + builder.UseStartup(); + builder.Build(); + }); + }); + + Assert.Equal("Building this implementation of IWebHostBuilder is not supported.", exception.Message); + } + [Theory] [MemberData(nameof(DefaultWebHostBuildersWithConfig))] public void Build_HostingStartupAssemblyCanBeExcluded(IWebHostBuilder builder) From 2de091e154cc33a5b3d413572826c523d98462a4 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Sun, 11 Nov 2018 20:44:39 -0800 Subject: [PATCH 14/19] Added copyright and added error message text --- .../GenericHost/GenericWebHost.cs | 5 ++++- .../GenericHost/GenericWebHostApplicationLifetime.cs | 5 ++++- .../GenericHost/GenericWebHostBuilder.cs | 5 ++++- .../GenericHost/GenericWebHostServiceOptions.cs | 6 ++++-- .../GenericHost/GenericWebHostedService.cs | 5 ++++- .../GenericHost/HostingStartupWebHostBuilder.cs | 9 +++++---- .../GenericHost/ISupportsStartup.cs | 7 ++++--- .../GenericHost/ISupportsUseDefaultServiceProvider.cs | 7 ++++--- 8 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs index c7084926..c87a9c2f 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs @@ -1,4 +1,7 @@ -using System; +// 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.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostApplicationLifetime.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostApplicationLifetime.cs index 84c38900..d9570415 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostApplicationLifetime.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostApplicationLifetime.cs @@ -1,4 +1,7 @@ -using System.Threading; +// 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.Threading; namespace Microsoft.AspNetCore.Hosting.Internal { diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index b7788fbf..008ea8af 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -1,4 +1,7 @@ -using System; +// 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.Linq; diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs index b652c1a5..715c4351 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostServiceOptions.cs @@ -1,5 +1,7 @@ -using System; -using System.Runtime.ExceptionServices; +// 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 Microsoft.AspNetCore.Builder; namespace Microsoft.AspNetCore.Hosting.Internal diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs index 94f8774e..c1774b5e 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -1,4 +1,7 @@ -using System; +// 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.Linq; diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs index 81ea34f4..52eaec81 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/HostingStartupWebHostBuilder.cs @@ -1,6 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; +// 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 Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -22,7 +23,7 @@ public HostingStartupWebHostBuilder(GenericWebHostBuilder builder) public IWebHost Build() { - throw new NotSupportedException(); + throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); } public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsStartup.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsStartup.cs index ce81dcc2..16322c8b 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsStartup.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsStartup.cs @@ -1,6 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; +// 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 Microsoft.AspNetCore.Builder; namespace Microsoft.AspNetCore.Hosting.Internal diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsUseDefaultServiceProvider.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsUseDefaultServiceProvider.cs index 6ac05c8d..bf9813cd 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsUseDefaultServiceProvider.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/ISupportsUseDefaultServiceProvider.cs @@ -1,6 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; +// 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 Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Hosting.Internal From fa82809492d55842d007fd26db981726926dd6dc Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 12 Nov 2018 09:41:13 -0800 Subject: [PATCH 15/19] Remove things --- .../GenericHost/GenericWebHostBuilder.cs | 1 - .../GenericHost/GenericWebHostedService.cs | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index 008ea8af..1da6b1e8 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -97,7 +97,6 @@ public GenericWebHostBuilder(IHostBuilder builder, bool allowBuild = true) // Conjure up a RequestServices services.TryAddTransient(); - services.TryAddTransient, DefaultServiceProviderFactory>(); // Ensure object pooling is available everywhere. services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs index c1774b5e..6bf0967f 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -25,8 +25,6 @@ namespace Microsoft.AspNetCore.Hosting.Internal { internal class GenericWebHostService : IHostedService { - private static readonly string DeprecatedServerUrlsKey = "server.urls"; - private static readonly Action _defaultApplication = _ => throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); @@ -69,7 +67,7 @@ public async Task StartAsync(CancellationToken cancellationToken) var addresses = serverAddressesFeature?.Addresses; if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0) { - var urls = Configuration[WebHostDefaults.ServerUrlsKey] ?? Configuration[DeprecatedServerUrlsKey]; + var urls = Configuration[WebHostDefaults.ServerUrlsKey]; if (!string.IsNullOrEmpty(urls)) { serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(Configuration, WebHostDefaults.PreferHostingUrlsKey); From 111d6932a07cb732b304e04025348deba4d2ed75 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Mon, 12 Nov 2018 10:43:31 -0800 Subject: [PATCH 16/19] Renamed ServiceProvider -> HostServiceProvider --- .../GenericHost/GenericWebHostBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index 1da6b1e8..43638362 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -246,7 +246,7 @@ private void UseStartup(Type startupType, HostBuilderContext context, IServiceCo throw new NotSupportedException($"{typeof(IStartup)} isn't supported"); } - instance = ActivatorUtilities.CreateInstance(new ServiceProvider(webHostBuilderContext), startupType); + instance = ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType); context.Properties[_startupKey] = instance; // Startup.ConfigureServices @@ -355,11 +355,11 @@ public IWebHostBuilder UseSetting(string key, string value) } // This exists just so that we can use ActivatorUtilities.CreateInstance on the Startup class - private class ServiceProvider : IServiceProvider + private class HostServiceProvider : IServiceProvider { private readonly WebHostBuilderContext _context; - public ServiceProvider(WebHostBuilderContext context) + public HostServiceProvider(WebHostBuilderContext context) { _context = context; } From f23af6f342eab532e862678ff7b78a32014aea5c Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Tue, 13 Nov 2018 17:45:40 -0800 Subject: [PATCH 17/19] Seperate test and production code (#1586) --- .../GenericHost/GenericWebHostBuilder.cs | 10 +-- .../GenericHostWebHostBuilderExtensions.cs | 2 +- .../Fakes}/GenericWebHost.cs | 2 +- .../Fakes/GenericWebHostBuilderWrapper.cs | 77 +++++++++++++++++++ .../WebHostBuilderTests.cs | 7 +- 5 files changed, 84 insertions(+), 14 deletions(-) rename {src/Microsoft.AspNetCore.Hosting/GenericHost => test/Microsoft.AspNetCore.Hosting.Tests/Fakes}/GenericWebHost.cs (95%) create mode 100644 test/Microsoft.AspNetCore.Hosting.Tests/Fakes/GenericWebHostBuilderWrapper.cs diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs index 43638362..6a425883 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostBuilder.cs @@ -23,15 +23,13 @@ internal class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISuppo private readonly IHostBuilder _builder; private readonly IConfiguration _config; private readonly object _startupKey = new object(); - private readonly bool _allowBuild; private AggregateException _hostingStartupErrors; private HostingStartupWebHostBuilder _hostingStartupWebHostBuilder; - public GenericWebHostBuilder(IHostBuilder builder, bool allowBuild = true) + public GenericWebHostBuilder(IHostBuilder builder) { _builder = builder; - _allowBuild = allowBuild; _config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") @@ -166,12 +164,6 @@ private void ExecuteHostingStartups() public IWebHost Build() { - if (_allowBuild) - { - // This makes testing the various implementations easy - return new GenericWebHost(_builder.Build()); - } - throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); } diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs index 5c6829ba..ea903ad7 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHostWebHostBuilderExtensions.cs @@ -8,7 +8,7 @@ public static class GenericHostWebHostBuilderExtensions { public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action configure) { - var webhostBuilder = new GenericWebHostBuilder(builder, allowBuild: false); + var webhostBuilder = new GenericWebHostBuilder(builder); configure(webhostBuilder); return builder; } diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/GenericWebHost.cs similarity index 95% rename from src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs rename to test/Microsoft.AspNetCore.Hosting.Tests/Fakes/GenericWebHost.cs index c87a9c2f..d61ce147 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHost.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/GenericWebHost.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace Microsoft.AspNetCore.Hosting.Internal +namespace Microsoft.AspNetCore.Hosting.Tests.Fakes { internal class GenericWebHost : IWebHost { diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/GenericWebHostBuilderWrapper.cs b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/GenericWebHostBuilderWrapper.cs new file mode 100644 index 00000000..3ff3aeef --- /dev/null +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/GenericWebHostBuilderWrapper.cs @@ -0,0 +1,77 @@ +// 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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Internal; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Hosting.Tests.Fakes +{ + public class GenericWebHostBuilderWrapper : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider + { + private readonly GenericWebHostBuilder _builder; + private readonly HostBuilder _hostBuilder; + + internal GenericWebHostBuilderWrapper(HostBuilder hostBuilder) + { + _builder = new GenericWebHostBuilder(hostBuilder); + _hostBuilder = hostBuilder; + } + + // This is the only one that doesn't pass through + public IWebHost Build() + { + return new GenericWebHost(_hostBuilder.Build()); + } + + public IWebHostBuilder Configure(Action configure) + { + _builder.Configure(configure); + return this; + } + + public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) + { + _builder.ConfigureAppConfiguration(configureDelegate); + return this; + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + _builder.ConfigureServices(configureServices); + return this; + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + _builder.ConfigureServices(configureServices); + return this; + } + + public string GetSetting(string key) + { + return _builder.GetSetting(key); + } + + public IWebHostBuilder UseDefaultServiceProvider(Action configure) + { + _builder.UseDefaultServiceProvider(configure); + return this; + } + + public IWebHostBuilder UseSetting(string key, string value) + { + _builder.UseSetting(key, value); + return this; + } + + public IWebHostBuilder UseStartup(Type startupType) + { + _builder.UseStartup(startupType); + return this; + } + } +} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs index 693ceb1c..169c7e02 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Hosting.Fakes; using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Tests.Fakes; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; @@ -881,7 +882,7 @@ public async Task ExternalContainerInstanceCanBeUsedForEverything() [Fact] public void GenericWebHostThrowsWithIStartup() { - var builder = new GenericWebHostBuilder(new HostBuilder()) + var builder = new GenericWebHostBuilderWrapper(new HostBuilder()) .UseStartup(); var exception = Assert.Throws(() => builder.Build()); @@ -1118,7 +1119,7 @@ private IWebHostBuilder CreateWebHostBuilder() public static TheoryData DefaultWebHostBuilders => new TheoryData { new WebHostBuilder(), - new GenericWebHostBuilder(new HostBuilder()) + new GenericWebHostBuilderWrapper(new HostBuilder()) }; public static TheoryData DefaultWebHostBuildersWithConfig @@ -1137,7 +1138,7 @@ public static TheoryData DefaultWebHostBuildersWithConfig return new TheoryData { new WebHostBuilder().UseConfiguration(config), - new GenericWebHostBuilder(new HostBuilder()).UseConfiguration(config) + new GenericWebHostBuilderWrapper(new HostBuilder()).UseConfiguration(config) }; } } From e27fb572a7b248b8c5b6129dbf2d833f394a510d Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 13 Nov 2018 18:10:18 -0800 Subject: [PATCH 18/19] Don't run startup filters if the application isn't configured --- .../GenericHost/GenericWebHostedService.cs | 11 +++-- .../WebHostBuilderTests.cs | 40 +++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs index 6bf0967f..8ec39059 100644 --- a/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs +++ b/src/Microsoft.AspNetCore.Hosting/GenericHost/GenericWebHostedService.cs @@ -25,9 +25,6 @@ namespace Microsoft.AspNetCore.Hosting.Internal { internal class GenericWebHostService : IHostedService { - private static readonly Action _defaultApplication = - _ => throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); - public GenericWebHostService(IOptions options, IServer server, ILogger logger, @@ -83,8 +80,14 @@ public async Task StartAsync(CancellationToken cancellationToken) try { + Action configure = Options.ConfigureApplication; + + if (configure == null) + { + throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); + } + var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features); - Action configure = Options.ConfigureApplication ?? _defaultApplication; foreach (var filter in StartupFilters.Reverse()) { diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs index 169c7e02..876959dd 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs @@ -1100,6 +1100,35 @@ public void UseShutdownTimeoutConfiguresShutdownTimeout(IWebHostBuilder builder) } } + [Theory] + [MemberData(nameof(DefaultWebHostBuilders))] + public async Task StartupFiltersDoNotRunIfNotApplicationConfigured(IWebHostBuilder builder) + { + var hostBuilder = builder + .ConfigureServices(services => + { + services.AddSingleton(); + }) + .UseServer(new TestServer()); + + var exception = await Assert.ThrowsAsync(async () => + { + var host = hostBuilder.Build(); + var filter = (MyStartupFilter)host.Services.GetServices().FirstOrDefault(s => s is MyStartupFilter); + Assert.NotNull(filter); + try + { + await host.StartAsync(); + } + finally + { + Assert.False(filter.Executed); + } + }); + + Assert.Contains("No application configured.", exception.Message); + } + private static void StaticConfigureMethod(IApplicationBuilder app) { } private IWebHostBuilder CreateWebHostBuilder() @@ -1153,6 +1182,17 @@ private async Task AssertResponseContains(RequestDelegate app, string expectedTe Assert.Contains(expectedText, bodyText); } + private class MyStartupFilter : IStartupFilter + { + public bool Executed { get; set; } + + public Action Configure(Action next) + { + Executed = true; + return next; + } + } + private class TestServer : IServer { IFeatureCollection IServer.Features { get; } From 458190020351d22e8ed4c7daddabf2288a7c690c Mon Sep 17 00:00:00 2001 From: David Fowler Date: Tue, 13 Nov 2018 21:10:11 -0800 Subject: [PATCH 19/19] Use new extension method in sample --- samples/GenericWebHost/Program.cs | 19 +++--- samples/GenericWebHost/WebHostExtensions.cs | 43 ------------- samples/GenericWebHost/WebHostService.cs | 62 ------------------- .../GenericWebHost/WebHostServiceOptions.cs | 11 ---- 4 files changed, 8 insertions(+), 127 deletions(-) delete mode 100644 samples/GenericWebHost/WebHostExtensions.cs delete mode 100644 samples/GenericWebHost/WebHostService.cs delete mode 100644 samples/GenericWebHost/WebHostServiceOptions.cs diff --git a/samples/GenericWebHost/Program.cs b/samples/GenericWebHost/Program.cs index 4879031f..653541ef 100644 --- a/samples/GenericWebHost/Program.cs +++ b/samples/GenericWebHost/Program.cs @@ -1,10 +1,9 @@ -using System; -using System.Net; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Hosting; namespace GenericWebHost { @@ -19,22 +18,20 @@ public static async Task Main(string[] args) config.AddJsonFile("appsettings.json", optional: true); config.AddCommandLine(args); }) - .ConfigureServices((hostContext, services) => - { - }) .UseFakeServer() - .ConfigureWebHost((hostContext, app) => + .ConfigureWebHost(builder => { - app.Run(async (context) => + builder.Configure(app => { - await context.Response.WriteAsync("Hello World!"); + app.Run(async (context) => + { + await context.Response.WriteAsync("Hello World!"); + }); }); }) .UseConsoleLifetime() .Build(); - var s = host.Services; - await host.RunAsync(); } } diff --git a/samples/GenericWebHost/WebHostExtensions.cs b/samples/GenericWebHost/WebHostExtensions.cs deleted file mode 100644 index bf5567d8..00000000 --- a/samples/GenericWebHost/WebHostExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Internal; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.ObjectPool; - -namespace GenericWebHost -{ - public static class WebHostExtensions - { - public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action configureApp) - { - return builder.ConfigureServices((bulderContext, services) => - { - services.Configure(options => - { - options.ConfigureApp = configureApp; - }); - services.AddHostedService(); - - var listener = new DiagnosticListener("Microsoft.AspNetCore"); - services.AddSingleton(listener); - services.AddSingleton(listener); - - services.AddTransient(); - services.AddScoped(); - - // Conjure up a RequestServices - services.AddTransient(); - services.AddTransient, DefaultServiceProviderFactory>(); - - // Ensure object pooling is available everywhere. - services.AddSingleton(); - }); - } - } -} diff --git a/samples/GenericWebHost/WebHostService.cs b/samples/GenericWebHost/WebHostService.cs deleted file mode 100644 index 1ac31617..00000000 --- a/samples/GenericWebHost/WebHostService.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder.Internal; -using Microsoft.AspNetCore.Hosting.Internal; -using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.AspNetCore.Hosting.Server.Features; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace GenericWebHost -{ - internal class WebHostService : IHostedService - { - public WebHostService(IOptions options, IServiceProvider services, HostBuilderContext hostBuilderContext, IServer server, - ILogger logger, DiagnosticListener diagnosticListener, IHttpContextFactory httpContextFactory) - { - Options = options?.Value ?? throw new System.ArgumentNullException(nameof(options)); - - if (Options.ConfigureApp == null) - { - throw new ArgumentException(nameof(Options.ConfigureApp)); - } - - Services = services ?? throw new ArgumentNullException(nameof(services)); - HostBuilderContext = hostBuilderContext ?? throw new ArgumentNullException(nameof(hostBuilderContext)); - Server = server ?? throw new ArgumentNullException(nameof(server)); - Logger = logger ?? throw new ArgumentNullException(nameof(logger)); - DiagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener)); - HttpContextFactory = httpContextFactory ?? throw new ArgumentNullException(nameof(httpContextFactory)); - } - - public WebHostServiceOptions Options { get; } - public IServiceProvider Services { get; } - public HostBuilderContext HostBuilderContext { get; } - public IServer Server { get; } - public ILogger Logger { get; } - public DiagnosticListener DiagnosticListener { get; } - public IHttpContextFactory HttpContextFactory { get; } - - public Task StartAsync(CancellationToken cancellationToken) - { - Server.Features.Get()?.Addresses.Add("http://localhost:5000"); - - var builder = new ApplicationBuilder(Services, Server.Features); - Options.ConfigureApp(HostBuilderContext, builder); - var app = builder.Build(); - - var httpApp = new HostingApplication(app, Logger, DiagnosticListener, HttpContextFactory); - return Server.StartAsync(httpApp, cancellationToken); - } - - public Task StopAsync(CancellationToken cancellationToken) - { - return Server.StopAsync(cancellationToken); - } - } -} \ No newline at end of file diff --git a/samples/GenericWebHost/WebHostServiceOptions.cs b/samples/GenericWebHost/WebHostServiceOptions.cs deleted file mode 100644 index 123dcf87..00000000 --- a/samples/GenericWebHost/WebHostServiceOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Hosting; - -namespace GenericWebHost -{ - public class WebHostServiceOptions - { - public Action ConfigureApp { get; internal set; } - } -} \ No newline at end of file