Add support for executing IHostingStartup in specified assemblies (#961)
* Add support for executing IHostingStartup in specified assemblies - Assemblies that are specified in the "hostingStartupAssemblies" configuration (; delimited) setting can specify assemblies that use an assembly level attribute (HostingStartupAttribute) to specify a type that implements IHostingStartup. This allows hosting environments to extend the IWebHostBuilder with platform specific behavior before the application runs. - Added tests - Log errors that occur during load and execution of the IHostingStartup when capture startup errors is off. This happens on start of the application. - Added debug logging on startup to print out the hosted startup assemblies hosting processed #951
This commit is contained in:
parent
387e2d8ad1
commit
ddb1bfeb20
|
|
@ -0,0 +1,40 @@
|
||||||
|
// 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.Reflection;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Hosting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Marker attribute indicating an implementation of <see cref="IHostingStartup"/> that will be loaded and executed when building an <see cref="IWebHost"/>.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)]
|
||||||
|
public sealed class HostingStartupAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs the <see cref="HostingStartupAttribute"/> with the specified type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hostingStartupType">A type that implements <see cref="IHostingStartup"/>.</param>
|
||||||
|
public HostingStartupAttribute(Type hostingStartupType)
|
||||||
|
{
|
||||||
|
if (hostingStartupType == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(hostingStartupType));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeof(IHostingStartup).GetTypeInfo().IsAssignableFrom(hostingStartupType.GetTypeInfo()))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($@"""{hostingStartupType}"" does not implement {typeof(IHostingStartup)}.", nameof(hostingStartupType));
|
||||||
|
}
|
||||||
|
|
||||||
|
HostingStartupType = hostingStartupType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The implementation of <see cref="IHostingStartup"/> that should be loaded when
|
||||||
|
/// starting an application.
|
||||||
|
/// </summary>
|
||||||
|
public Type HostingStartupType { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Hosting
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents platform specific configuration that will be applied to a <see cref="IWebHostBuilder"/> when building an <see cref="IWebHost"/>.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHostingStartup
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Configure the <see cref="IWebHostBuilder"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Configure is intended to be called before user code, allowing a user to overwrite any changes made.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
void Configure(IWebHostBuilder builder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,8 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
{
|
{
|
||||||
public static readonly string ApplicationKey = "applicationName";
|
public static readonly string ApplicationKey = "applicationName";
|
||||||
public static readonly string StartupAssemblyKey = "startupAssembly";
|
public static readonly string StartupAssemblyKey = "startupAssembly";
|
||||||
|
public static readonly string HostingStartupAssembliesKey = "hostingStartupAssemblies";
|
||||||
|
|
||||||
public static readonly string DetailedErrorsKey = "detailedErrors";
|
public static readonly string DetailedErrorsKey = "detailedErrors";
|
||||||
public static readonly string EnvironmentKey = "environment";
|
public static readonly string EnvironmentKey = "environment";
|
||||||
public static readonly string WebRootKey = "webroot";
|
public static readonly string WebRootKey = "webroot";
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,14 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
exception: exception);
|
exception: exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void HostingStartupAssemblyError(this ILogger logger, Exception exception)
|
||||||
|
{
|
||||||
|
logger.ApplicationError(
|
||||||
|
eventId: LoggerEventIds.HostingStartupAssemblyException,
|
||||||
|
message: "Hosting startup assembly exception",
|
||||||
|
exception: exception);
|
||||||
|
}
|
||||||
|
|
||||||
public static void ApplicationError(this ILogger logger, EventId eventId, string message, Exception exception)
|
public static void ApplicationError(this ILogger logger, EventId eventId, string message, Exception exception)
|
||||||
{
|
{
|
||||||
var reflectionTypeLoadException = exception as ReflectionTypeLoadException;
|
var reflectionTypeLoadException = exception as ReflectionTypeLoadException;
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
public const int ApplicationStoppedException = 8;
|
public const int ApplicationStoppedException = 8;
|
||||||
public const int HostedServiceStartException = 9;
|
public const int HostedServiceStartException = 9;
|
||||||
public const int HostedServiceStopException = 10;
|
public const int HostedServiceStopException = 10;
|
||||||
|
public const int HostingStartupAssemblyException = 11;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
private readonly IServiceProvider _hostingServiceProvider;
|
private readonly IServiceProvider _hostingServiceProvider;
|
||||||
private readonly WebHostOptions _options;
|
private readonly WebHostOptions _options;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly AggregateException _hostingStartupErrors;
|
||||||
|
|
||||||
private IServiceProvider _applicationServices;
|
private IServiceProvider _applicationServices;
|
||||||
private RequestDelegate _application;
|
private RequestDelegate _application;
|
||||||
|
|
@ -47,7 +48,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
IServiceCollection appServices,
|
IServiceCollection appServices,
|
||||||
IServiceProvider hostingServiceProvider,
|
IServiceProvider hostingServiceProvider,
|
||||||
WebHostOptions options,
|
WebHostOptions options,
|
||||||
IConfiguration config)
|
IConfiguration config,
|
||||||
|
AggregateException hostingStartupErrors)
|
||||||
{
|
{
|
||||||
if (appServices == null)
|
if (appServices == null)
|
||||||
{
|
{
|
||||||
|
|
@ -65,6 +67,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_hostingStartupErrors = hostingStartupErrors;
|
||||||
_options = options;
|
_options = options;
|
||||||
_applicationServiceCollection = appServices;
|
_applicationServiceCollection = appServices;
|
||||||
_hostingServiceProvider = hostingServiceProvider;
|
_hostingServiceProvider = hostingServiceProvider;
|
||||||
|
|
@ -118,6 +121,26 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
_hostedServiceExecutor.Start();
|
_hostedServiceExecutor.Start();
|
||||||
|
|
||||||
_logger.Started();
|
_logger.Started();
|
||||||
|
|
||||||
|
// REVIEW: Is this the right place to log these errors?
|
||||||
|
if (_hostingStartupErrors != null)
|
||||||
|
{
|
||||||
|
foreach (var exception in _hostingStartupErrors.InnerExceptions)
|
||||||
|
{
|
||||||
|
_logger.HostingStartupAssemblyError(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If there were no errors then just log the fact that we did load hosting startup assemblies.
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
foreach (var assembly in _options.HostingStartupAssemblies)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureApplicationServices()
|
private void EnsureApplicationServices()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Hosting.Internal
|
namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
|
|
@ -26,10 +27,13 @@ namespace Microsoft.AspNetCore.Hosting.Internal
|
||||||
Environment = configuration[WebHostDefaults.EnvironmentKey];
|
Environment = configuration[WebHostDefaults.EnvironmentKey];
|
||||||
WebRoot = configuration[WebHostDefaults.WebRootKey];
|
WebRoot = configuration[WebHostDefaults.WebRootKey];
|
||||||
ContentRootPath = configuration[WebHostDefaults.ContentRootKey];
|
ContentRootPath = configuration[WebHostDefaults.ContentRootKey];
|
||||||
|
HostingStartupAssemblies = configuration[WebHostDefaults.HostingStartupAssembliesKey]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ApplicationName { get; set; }
|
public string ApplicationName { get; set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<string> HostingStartupAssemblies { get; set; }
|
||||||
|
|
||||||
public bool DetailedErrors { get; set; }
|
public bool DetailedErrors { get; set; }
|
||||||
|
|
||||||
public bool CaptureStartupErrors { get; set; }
|
public bool CaptureStartupErrors { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
|
Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
|
||||||
}
|
}
|
||||||
|
|
||||||
var hostingServices = BuildCommonServices();
|
var hostingServices = BuildCommonServices(out var hostingStartupErrors);
|
||||||
var applicationServices = hostingServices.Clone();
|
var applicationServices = hostingServices.Clone();
|
||||||
var hostingServiceProvider = hostingServices.BuildServiceProvider();
|
var hostingServiceProvider = hostingServices.BuildServiceProvider();
|
||||||
|
|
||||||
|
|
@ -168,15 +168,18 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
applicationServices,
|
applicationServices,
|
||||||
hostingServiceProvider,
|
hostingServiceProvider,
|
||||||
_options,
|
_options,
|
||||||
_config);
|
_config,
|
||||||
|
hostingStartupErrors);
|
||||||
|
|
||||||
host.Initialize();
|
host.Initialize();
|
||||||
|
|
||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IServiceCollection BuildCommonServices()
|
private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
|
||||||
{
|
{
|
||||||
|
hostingStartupErrors = null;
|
||||||
|
|
||||||
_options = new WebHostOptions(_config);
|
_options = new WebHostOptions(_config);
|
||||||
|
|
||||||
var appEnvironment = PlatformServices.Default.Application;
|
var appEnvironment = PlatformServices.Default.Application;
|
||||||
|
|
@ -200,6 +203,39 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
services.AddSingleton(_loggerFactory);
|
services.AddSingleton(_loggerFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var exceptions = new List<Exception>();
|
||||||
|
|
||||||
|
// Execute the hosting startup assemblies
|
||||||
|
foreach (var assemblyName in _options.HostingStartupAssemblies)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assembly = Assembly.Load(new AssemblyName(assemblyName));
|
||||||
|
|
||||||
|
foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Throw directly if we're not capturing startup errors
|
||||||
|
if (!_options.CaptureStartupErrors)
|
||||||
|
{
|
||||||
|
throw hostingStartupErrors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var configureLogging in _configureLoggingDelegates)
|
foreach (var configureLogging in _configureLoggingDelegates)
|
||||||
{
|
{
|
||||||
configureLogging(_loggerFactory);
|
configureLogging(_loggerFactory);
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Owin" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Owin" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(AspNetCoreVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="$(AspNetCoreVersion)" />
|
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="$(AspNetCoreVersion)" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting.Fakes;
|
using Microsoft.AspNetCore.Hosting.Fakes;
|
||||||
using Microsoft.AspNetCore.Hosting.Internal;
|
using Microsoft.AspNetCore.Hosting.Internal;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
|
|
@ -16,10 +18,13 @@ using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
|
using Microsoft.Extensions.Logging.Testing;
|
||||||
using Microsoft.Extensions.ObjectPool;
|
using Microsoft.Extensions.ObjectPool;
|
||||||
using Microsoft.Extensions.PlatformAbstractions;
|
using Microsoft.Extensions.PlatformAbstractions;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
[assembly: HostingStartup(typeof(WebHostBuilderTests.TestHostingStartup))]
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Hosting
|
namespace Microsoft.AspNetCore.Hosting
|
||||||
{
|
{
|
||||||
public class WebHostBuilderTests
|
public class WebHostBuilderTests
|
||||||
|
|
@ -588,6 +593,121 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
Assert.Equal(factory, factoryFromHost);
|
Assert.Equal(factory, factoryFromHost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_RunsHostingStartupAssembliesIfSpecified()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder()
|
||||||
|
.CaptureStartupErrors(false)
|
||||||
|
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(WebHostBuilderTests).GetTypeInfo().Assembly.FullName)
|
||||||
|
.Configure(app => { })
|
||||||
|
.UseServer(new TestServer());
|
||||||
|
|
||||||
|
var host = (WebHost)builder.Build();
|
||||||
|
|
||||||
|
Assert.Equal("1", builder.GetSetting("testhostingstartup"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_RunsHostingStartupAssembliesBeforeApplication()
|
||||||
|
{
|
||||||
|
var startup = new StartupVerifyServiceA();
|
||||||
|
var startupAssemblyName = typeof(WebHostBuilderTests).GetTypeInfo().Assembly.GetName().Name;
|
||||||
|
|
||||||
|
var builder = CreateWebHostBuilder()
|
||||||
|
.CaptureStartupErrors(false)
|
||||||
|
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(WebHostBuilderTests).GetTypeInfo().Assembly.FullName)
|
||||||
|
.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddSingleton<IStartup>(startup);
|
||||||
|
})
|
||||||
|
.UseServer(new TestServer());
|
||||||
|
|
||||||
|
var host = (WebHost)builder.Build();
|
||||||
|
|
||||||
|
Assert.NotNull(startup.ServiceADescriptor);
|
||||||
|
Assert.NotNull(startup.ServiceA);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_ConfigureLoggingInHostingStartupWorks()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder()
|
||||||
|
.CaptureStartupErrors(false)
|
||||||
|
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(WebHostBuilderTests).GetTypeInfo().Assembly.FullName)
|
||||||
|
.Configure(app =>
|
||||||
|
{
|
||||||
|
var loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();
|
||||||
|
var logger = loggerFactory.CreateLogger(nameof(WebHostBuilderTests));
|
||||||
|
logger.LogInformation("From startup");
|
||||||
|
})
|
||||||
|
.UseServer(new TestServer());
|
||||||
|
|
||||||
|
var host = (WebHost)builder.Build();
|
||||||
|
var sink = host.Services.GetRequiredService<ITestSink>();
|
||||||
|
Assert.True(sink.Writes.Any(w => w.State.ToString() == "From startup"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_DoesNotRunHostingStartupAssembliesDoNotRunIfNotSpecified()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder()
|
||||||
|
.Configure(app => { })
|
||||||
|
.UseServer(new TestServer());
|
||||||
|
|
||||||
|
var host = (WebHost)builder.Build();
|
||||||
|
|
||||||
|
Assert.Null(builder.GetSetting("testhostingstartup"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_ThrowsIfUnloadableAssemblyNameInHostingStartupAssemblies()
|
||||||
|
{
|
||||||
|
var builder = CreateWebHostBuilder()
|
||||||
|
.CaptureStartupErrors(false)
|
||||||
|
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName")
|
||||||
|
.Configure(app => { })
|
||||||
|
.UseServer(new TestServer());
|
||||||
|
|
||||||
|
var ex = Assert.Throws<AggregateException>(() => (WebHost)builder.Build());
|
||||||
|
Assert.IsType<InvalidOperationException>(ex.InnerExceptions[0]);
|
||||||
|
Assert.IsType<FileNotFoundException>(ex.InnerExceptions[0].InnerException);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue()
|
||||||
|
{
|
||||||
|
var provider = new TestLoggerProvider();
|
||||||
|
var builder = CreateWebHostBuilder()
|
||||||
|
.ConfigureLogging(factory =>
|
||||||
|
{
|
||||||
|
factory.AddProvider(provider);
|
||||||
|
})
|
||||||
|
.CaptureStartupErrors(true)
|
||||||
|
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName")
|
||||||
|
.Configure(app => { })
|
||||||
|
.UseServer(new TestServer());
|
||||||
|
|
||||||
|
using (var host = builder.Build())
|
||||||
|
{
|
||||||
|
host.Start();
|
||||||
|
var context = provider.Sink.Writes.FirstOrDefault(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException);
|
||||||
|
Assert.NotNull(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HostingStartupTypeCtorThrowsIfNull()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentNullException>(() => new HostingStartupAttribute(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HostingStartupTypeCtorThrowsIfNotIHosting()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentException>(() => new HostingStartupAttribute(typeof(WebHostTests)));
|
||||||
|
}
|
||||||
|
|
||||||
private static void StaticConfigureMethod(IApplicationBuilder app)
|
private static void StaticConfigureMethod(IApplicationBuilder app)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
@ -643,6 +763,52 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class StartupVerifyServiceA : IStartup
|
||||||
|
{
|
||||||
|
internal ServiceA ServiceA { get; set; }
|
||||||
|
|
||||||
|
internal ServiceDescriptor ServiceADescriptor { get; set; }
|
||||||
|
|
||||||
|
public IServiceProvider ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
ServiceADescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(ServiceA));
|
||||||
|
|
||||||
|
return services.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
ServiceA = app.ApplicationServices.GetService<ServiceA>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestHostingStartup : IHostingStartup
|
||||||
|
{
|
||||||
|
public void Configure(IWebHostBuilder builder)
|
||||||
|
{
|
||||||
|
var loggerProvider = new TestLoggerProvider();
|
||||||
|
builder.UseSetting("testhostingstartup", "1")
|
||||||
|
.ConfigureServices(services => services.AddSingleton<ServiceA>())
|
||||||
|
.ConfigureServices(services => services.AddSingleton<ITestSink>(loggerProvider.Sink))
|
||||||
|
.ConfigureLogging(lf => lf.AddProvider(loggerProvider));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestLoggerProvider : ILoggerProvider
|
||||||
|
{
|
||||||
|
public TestSink Sink { get; set; } = new TestSink();
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new TestLogger(categoryName, Sink, enabled: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ServiceC
|
private class ServiceC
|
||||||
{
|
{
|
||||||
public ServiceC(ServiceD serviceD)
|
public ServiceC(ServiceD serviceD)
|
||||||
|
|
@ -651,17 +817,17 @@ namespace Microsoft.AspNetCore.Hosting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ServiceD
|
internal class ServiceD
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ServiceA
|
internal class ServiceA
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ServiceB
|
internal class ServiceB
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue