Added support for generic host based IWebHostBuilder (#1580)

- 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.
This commit is contained in:
David Fowler 2018-11-13 21:22:30 -08:00 коммит произвёл GitHub
Родитель d7b9fd4807
Коммит cfe9b26a34
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
22 изменённых файлов: 1250 добавлений и 340 удалений

Просмотреть файл

@ -1,10 +1,9 @@
using System; using System.Threading.Tasks;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Hosting;
namespace GenericWebHost namespace GenericWebHost
{ {
@ -19,22 +18,20 @@ namespace GenericWebHost
config.AddJsonFile("appsettings.json", optional: true); config.AddJsonFile("appsettings.json", optional: true);
config.AddCommandLine(args); config.AddCommandLine(args);
}) })
.ConfigureServices((hostContext, services) =>
{
})
.UseFakeServer() .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() .UseConsoleLifetime()
.Build(); .Build();
var s = host.Services;
await host.RunAsync(); await host.RunAsync();
} }
} }

Просмотреть файл

@ -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<HostBuilderContext, IApplicationBuilder> configureApp)
{
return builder.ConfigureServices((bulderContext, services) =>
{
services.Configure<WebHostServiceOptions>(options =>
{
options.ConfigureApp = configureApp;
});
services.AddHostedService<WebHostService>();
var listener = new DiagnosticListener("Microsoft.AspNetCore");
services.AddSingleton<DiagnosticListener>(listener);
services.AddSingleton<DiagnosticSource>(listener);
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
// Conjure up a RequestServices
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
// Ensure object pooling is available everywhere.
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
});
}
}
}

Просмотреть файл

@ -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<WebHostServiceOptions> options, IServiceProvider services, HostBuilderContext hostBuilderContext, IServer server,
ILogger<WebHostService> 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<WebHostService> Logger { get; }
public DiagnosticListener DiagnosticListener { get; }
public IHttpContextFactory HttpContextFactory { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
Server.Features.Get<IServerAddressesFeature>()?.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);
}
}
}

Просмотреть файл

@ -1,11 +0,0 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
namespace GenericWebHost
{
public class WebHostServiceOptions
{
public Action<HostBuilderContext, IApplicationBuilder> ConfigureApp { get; internal set; }
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// 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
{
internal class GenericWebHostApplicationLifetime : IApplicationLifetime
{
private readonly Microsoft.Extensions.Hosting.IApplicationLifetime _applicationLifetime;
public GenericWebHostApplicationLifetime(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();
}
}

Просмотреть файл

@ -0,0 +1,376 @@
// 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;
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, ISupportsStartup, ISupportsUseDefaultServiceProvider
{
private readonly IHostBuilder _builder;
private readonly IConfiguration _config;
private readonly object _startupKey = new object();
private AggregateException _hostingStartupErrors;
private HostingStartupWebHostBuilder _hostingStartupWebHostBuilder;
public GenericWebHostBuilder(IHostBuilder builder)
{
_builder = builder;
_config = new ConfigurationBuilder()
.AddEnvironmentVariables(prefix: "ASPNETCORE_")
.Build();
_builder.ConfigureHostConfiguration(config =>
{
config.AddConfiguration(_config);
// We do this super early but still late enough that we can process the configuration
// wired up by calls to UseSetting
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);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
// Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting
services.AddSingleton(webhostContext.HostingEnvironment);
services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>();
services.Configure<GenericWebHostServiceOptions>(options =>
{
// Set the options
options.WebHostOptions = webHostOptions;
// Store and forward any startup errors
options.HostingStartupExceptions = _hostingStartupErrors;
});
services.AddHostedService<GenericWebHostService>();
// 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<DiagnosticListener>(listener);
services.TryAddSingleton<DiagnosticSource>(listener);
services.TryAddSingleton<IHttpContextFactory, HttpContextFactory>();
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
// Conjure up a RequestServices
services.TryAddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
// Ensure object pooling is available everywhere.
services.TryAddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
// Support UseStartup(assemblyName)
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
{
try
{
var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName);
UseStartup(startupType, context, services);
}
catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
{
var capture = ExceptionDispatchInfo.Capture(ex);
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.ConfigureApplication = app =>
{
// Throw if there was any errors initializing startup
capture.Throw();
};
});
}
}
});
}
private void ExecuteHostingStartups()
{
var webHostOptions = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
if (webHostOptions.PreventHostingStartup)
{
return;
}
var exceptions = new List<Exception>();
_hostingStartupWebHostBuilder = new HostingStartupWebHostBuilder(this);
// 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<HostingStartupAttribute>())
{
var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
hostingStartup.Configure(_hostingStartupWebHostBuilder);
}
}
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);
}
}
public IWebHost Build()
{
throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported.");
}
public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_builder.ConfigureAppConfiguration((context, builder) =>
{
var webhostBuilderContext = GetWebHostBuilderContext(context);
configureDelegate(webhostBuilderContext, builder);
});
return this;
}
public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
{
return ConfigureServices((context, services) => configureServices(services));
}
public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
{
_builder.ConfigureServices((context, builder) =>
{
var webhostBuilderContext = GetWebHostBuilderContext(context);
configureServices(webhostBuilderContext, builder);
});
return this;
}
public IWebHostBuilder UseDefaultServiceProvider(Action<WebHostBuilderContext, ServiceProviderOptions> configure)
{
// REVIEW: This is a hack to change the builder with the HostBuilderContext in scope,
// 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;
}
public IWebHostBuilder UseStartup(Type startupType)
{
_builder.ConfigureServices((context, services) =>
{
UseStartup(startupType, context, services);
});
return this;
}
private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services)
{
var webHostBuilderContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
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 HostServiceProvider(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<T>(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) when (webHostOptions.CaptureStartupErrors)
{
startupError = ExceptionDispatchInfo.Capture(ex);
}
// Startup.Configure
services.Configure<GenericWebHostServiceOptions>(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);
}
};
});
}
private void ConfigureContainer<TContainer>(HostBuilderContext context, TContainer container)
{
var instance = context.Properties[_startupKey];
var builder = (ConfigureContainerBuilder)context.Properties[typeof(ConfigureContainerBuilder)];
builder.Build(instance)(container);
}
public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
{
_builder.ConfigureServices((context, services) =>
{
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.ConfigureApplication = configure;
});
});
return this;
}
private WebHostBuilderContext GetWebHostBuilderContext(HostBuilderContext context)
{
if (!context.Properties.TryGetValue(typeof(WebHostBuilderContext), out var contextVal))
{
var options = new WebHostOptions(context.Configuration, 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 HostServiceProvider : IServiceProvider
{
private readonly WebHostBuilderContext _context;
public HostServiceProvider(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;
}
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// 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
{
internal class GenericWebHostServiceOptions
{
public Action<IApplicationBuilder> ConfigureApplication { get; set; }
public WebHostOptions WebHostOptions { get; set; }
public AggregateException HostingStartupExceptions { get; set; }
}
}

Просмотреть файл

@ -0,0 +1,201 @@
// 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;
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
{
public GenericWebHostService(IOptions<GenericWebHostServiceOptions> options,
IServer server,
ILogger<GenericWebHostService> logger,
DiagnosticListener diagnosticListener,
IHttpContextFactory httpContextFactory,
IApplicationBuilderFactory applicationBuilderFactory,
IEnumerable<IStartupFilter> startupFilters,
IConfiguration configuration,
IHostingEnvironment hostingEnvironment)
{
Options = options.Value;
Server = server;
Logger = logger;
DiagnosticListener = diagnosticListener;
HttpContextFactory = httpContextFactory;
ApplicationBuilderFactory = applicationBuilderFactory;
StartupFilters = startupFilters;
Configuration = configuration;
HostingEnvironment = hostingEnvironment;
}
public GenericWebHostServiceOptions Options { get; }
public IServer Server { get; }
public ILogger<GenericWebHostService> Logger { get; }
public DiagnosticListener DiagnosticListener { get; }
public IHttpContextFactory HttpContextFactory { get; }
public IApplicationBuilderFactory ApplicationBuilderFactory { get; }
public IEnumerable<IStartupFilter> StartupFilters { get; }
public IConfiguration Configuration { get; }
public IHostingEnvironment HostingEnvironment { get; }
public async Task StartAsync(CancellationToken cancellationToken)
{
HostingEventSource.Log.HostStart();
var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>();
var addresses = serverAddressesFeature?.Addresses;
if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
{
var urls = Configuration[WebHostDefaults.ServerUrlsKey];
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
{
Action<IApplicationBuilder> 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);
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);
if (addresses != null)
{
foreach (var address in addresses)
{
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<AssemblyInformationalVersionAttribute>()
.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 async Task StopAsync(CancellationToken cancellationToken)
{
try
{
await Server.StopAsync(cancellationToken);
}
finally
{
HostingEventSource.Log.HostStop();
}
}
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// 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;
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, ISupportsStartup, ISupportsUseDefaultServiceProvider
{
private readonly GenericWebHostBuilder _builder;
private Action<WebHostBuilderContext, IConfigurationBuilder> _configureConfiguration;
private Action<WebHostBuilderContext, IServiceCollection> _configureServices;
public HostingStartupWebHostBuilder(GenericWebHostBuilder builder)
{
_builder = builder;
}
public IWebHost Build()
{
throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported.");
}
public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_configureConfiguration += configureDelegate;
return this;
}
public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
{
return ConfigureServices((context, services) => configureServices(services));
}
public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> 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);
}
public IWebHostBuilder UseDefaultServiceProvider(Action<WebHostBuilderContext, ServiceProviderOptions> configure)
{
return _builder.UseDefaultServiceProvider(configure);
}
public IWebHostBuilder Configure(Action<IApplicationBuilder> configure)
{
return _builder.Configure(configure);
}
public IWebHostBuilder UseStartup(Type startupType)
{
return _builder.UseStartup(startupType);
}
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// 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
{
internal interface ISupportsStartup
{
IWebHostBuilder Configure(Action<IApplicationBuilder> configure);
IWebHostBuilder UseStartup(Type startupType);
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// 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
{
internal interface ISupportsUseDefaultServiceProvider
{
IWebHostBuilder UseDefaultServiceProvider(Action<WebHostBuilderContext, ServiceProviderOptions> configure);
}
}

Просмотреть файл

@ -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<IWebHostBuilder> configure)
{
var webhostBuilder = new GenericWebHostBuilder(builder);
configure(webhostBuilder);
return builder;
}
}
}

Просмотреть файл

@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
public MethodInfo MethodInfo { get; } public MethodInfo MethodInfo { get; }
public Func<Action<object>, Action<object>> ConfigureContainerFilters { get; set; } public Func<Action<object>, Action<object>> ConfigureContainerFilters { get; set; } = f => f;
public Action<object> Build(object instance) => container => Invoke(instance, container); public Action<object> Build(object instance) => container => Invoke(instance, container);

Просмотреть файл

@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
public MethodInfo MethodInfo { get; } public MethodInfo MethodInfo { get; }
public Func<Func<IServiceCollection, IServiceProvider>, Func<IServiceCollection, IServiceProvider>> StartupServiceFilters { get; set; } public Func<Func<IServiceCollection, IServiceProvider>, Func<IServiceCollection, IServiceProvider>> StartupServiceFilters { get; set; } = f => f;
public Func<IServiceCollection, IServiceProvider> Build(object instance) => services => Invoke(instance, services); public Func<IServiceCollection, IServiceProvider> Build(object instance) => services => Invoke(instance, services);

Просмотреть файл

@ -267,19 +267,19 @@ namespace Microsoft.AspNetCore.Hosting.Internal
return type; 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); var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
return new ConfigureBuilder(configureMethod); 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); var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);
return new ConfigureContainerBuilder(configureMethod); 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) var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);

Просмотреть файл

@ -192,7 +192,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
if (_startup == null) 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.");
} }
} }

Просмотреть файл

@ -29,15 +29,21 @@ namespace Microsoft.AspNetCore.Hosting
var startupAssemblyName = configureApp.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name; var startupAssemblyName = configureApp.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name;
return hostBuilder hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);
.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
.ConfigureServices(services => // Light up the ISupportsStartup implementation
if (hostBuilder is ISupportsStartup supportsStartup)
{
return supportsStartup.Configure(configureApp);
}
return hostBuilder.ConfigureServices(services =>
{
services.AddSingleton<IStartup>(sp =>
{ {
services.AddSingleton<IStartup>(sp => return new DelegateStartup(sp.GetRequiredService<IServiceProviderFactory<IServiceCollection>>(), configureApp);
{
return new DelegateStartup(sp.GetRequiredService<IServiceProviderFactory<IServiceCollection>>(), configureApp);
});
}); });
});
} }
@ -51,8 +57,15 @@ namespace Microsoft.AspNetCore.Hosting
{ {
var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;
hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);
// Light up the GenericWebHostBuilder implementation
if (hostBuilder is ISupportsStartup supportsStartup)
{
return supportsStartup.UseStartup(startupType);
}
return hostBuilder return hostBuilder
.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
.ConfigureServices(services => .ConfigureServices(services =>
{ {
if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
@ -100,6 +113,12 @@ namespace Microsoft.AspNetCore.Hosting
/// <returns>The <see cref="IWebHostBuilder"/>.</returns> /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ServiceProviderOptions> configure) public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ServiceProviderOptions> configure)
{ {
// Light up the GenericWebHostBuilder implementation
if (hostBuilder is ISupportsUseDefaultServiceProvider supportsDefaultServiceProvider)
{
return supportsDefaultServiceProvider.UseDefaultServiceProvider(configure);
}
return hostBuilder.ConfigureServices((context, services) => return hostBuilder.ConfigureServices((context, services) =>
{ {
var options = new ServiceProviderOptions(); var options = new ServiceProviderOptions();

Просмотреть файл

@ -0,0 +1,35 @@
// 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;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Microsoft.AspNetCore.Hosting.Tests.Fakes
{
internal class GenericWebHost : IWebHost
{
private readonly IHost _host;
public GenericWebHost(IHost host)
{
_host = host;
}
public IFeatureCollection ServerFeatures => Services.GetRequiredService<IServer>().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);
}
}

Просмотреть файл

@ -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<IApplicationBuilder> configure)
{
_builder.Configure(configure);
return this;
}
public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_builder.ConfigureAppConfiguration(configureDelegate);
return this;
}
public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
{
_builder.ConfigureServices(configureServices);
return this;
}
public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
{
_builder.ConfigureServices(configureServices);
return this;
}
public string GetSetting(string key)
{
return _builder.GetSetting(key);
}
public IWebHostBuilder UseDefaultServiceProvider(Action<WebHostBuilderContext, ServiceProviderOptions> 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;
}
}
}

Просмотреть файл

@ -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)
{
}
}
}

Просмотреть файл

@ -10,6 +10,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Hosting\Microsoft.AspNetCore.Hosting.csproj" /> <ProjectReference Include="..\..\src\Microsoft.AspNetCore.Hosting\Microsoft.AspNetCore.Hosting.csproj" />
<ProjectReference Include="..\..\src\Microsoft.Extensions.Hosting\Microsoft.Extensions.Hosting.csproj" />
<ProjectReference Include="..\TestAssets\TestStartupAssembly1\TestStartupAssembly1.csproj" /> <ProjectReference Include="..\TestAssets\TestStartupAssembly1\TestStartupAssembly1.csproj" />
</ItemGroup> </ItemGroup>

Разница между файлами не показана из-за своего большого размера Загрузить разницу