Execute IHostingStart instances in the primary assembly (#1033)

* #1000 Execute IHostingStart instances in the primary assembly
Also make the sample app runnable.
Add an opt-out flag, more tests
This commit is contained in:
Chris R 2017-04-25 07:30:11 -07:00 committed by GitHub
parent d2816d14ab
commit 8377d226f1
17 changed files with 228 additions and 33 deletions

View File

@ -1,7 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.4
VisualStudioVersion = 15.0.26418.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E0497F39-AFFB-4819-A116-E39E361915AB}"
EndProject
@ -31,6 +30,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hostin
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Hosting.TestSites", "test\Microsoft.AspNetCore.Hosting.TestSites\Microsoft.AspNetCore.Hosting.TestSites.csproj", "{542D4600-B232-4B17-A08C-E31EBFA0D74E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestStartupAssembly1", "test\TestStartupAssembly1\TestStartupAssembly1.csproj", "{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -165,6 +166,18 @@ Global
{542D4600-B232-4B17-A08C-E31EBFA0D74E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{542D4600-B232-4B17-A08C-E31EBFA0D74E}.Release|x86.ActiveCfg = Release|Any CPU
{542D4600-B232-4B17-A08C-E31EBFA0D74E}.Release|x86.Build.0 = Release|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Debug|Any CPU.Build.0 = Debug|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Debug|x86.ActiveCfg = Debug|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Debug|x86.Build.0 = Debug|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Release|Any CPU.ActiveCfg = Release|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Release|Any CPU.Build.0 = Release|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Release|x86.ActiveCfg = Release|Any CPU
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -181,5 +194,6 @@ Global
{03148731-EA95-40A2-BAE8-A12315EA1748} = {E0497F39-AFFB-4819-A116-E39E361915AB}
{FC578F4E-171C-4F82-B301-3ABF6318D082} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
{542D4600-B232-4B17-A08C-E31EBFA0D74E} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
{39D3B138-37DB-4D03-A5A0-3F2B02EFC671} = {FEB39027-9158-4DE2-997F-7ADAEF8188D0}
EndGlobalSection
EndGlobal

View File

@ -1,5 +1,6 @@
<Project>
<ItemGroup>
<ExcludeFromTest Include="$(RepositoryRoot)test\Microsoft.AspNetCore.Hosting.TestSites\Microsoft.AspNetCore.Hosting.TestSites.csproj" />
<ExcludeFromTest Include="$(RepositoryRoot)test\TestStartupAssembly1\TestStartupAssembly1.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,32 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
namespace SampleStartups
{
// We can't reference real servers in this sample without creating a circular repo dependency.
// This fake server lets us at least run the code.
public class FakeServer : IServer
{
public IFeatureCollection Features => new FeatureCollection();
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) => Task.CompletedTask;
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public void Dispose()
{
}
}
public static class FakeServerWebHostBuliderExtensions
{
public static IWebHostBuilder UseFakeServer(this IWebHostBuilder builder)
{
return builder.ConfigureServices(services => services.AddSingleton<IServer, FakeServer>());
}
}
}

View File

@ -4,6 +4,8 @@
<PropertyGroup>
<TargetFrameworks>net46;netcoreapp2.0</TargetFrameworks>
<StartupObject>SampleStartups.StartupInjection</StartupObject>
<OutputType>exe</OutputType>
</PropertyGroup>
<ItemGroup>

View File

@ -34,6 +34,7 @@ namespace SampleStartups
var host = new WebHostBuilder()
.UseConfiguration(config)
.UseFakeServer()
.UseStartup<StartupBlockingOnStart>()
.Build();

View File

@ -25,6 +25,7 @@ namespace SampleStartups
var host = new WebHostBuilder()
.UseConfiguration(config)
.UseFakeServer()
.UseStartup<StartupConfigureAddresses>()
.UseUrls("http://localhost:5000", "http://localhost:5001")
.Build();

View File

@ -32,6 +32,7 @@ namespace SampleStartups
{
_host = new WebHostBuilder()
//.UseKestrel()
.UseFakeServer()
.UseStartup<StartupExternallyControlled>()
.Start(_urls.ToArray());
}

View File

@ -22,7 +22,8 @@ namespace SampleStartups
var host = new WebHostBuilder()
.UseConfiguration(config) // Default set of configurations to use, may be subsequently overridden
//.UseKestrel()
//.UseKestrel()
.UseFakeServer()
.UseContentRoot(Directory.GetCurrentDirectory()) // Override the content root with the current directory
.UseUrls("http://*:1000", "https://*:902")
.UseEnvironment("Development")

View File

@ -21,7 +21,8 @@ namespace SampleStartups
public static void Main(string[] args)
{
var host = new WebHostBuilder()
//.UseKestrel()
//.UseKestrel()
.UseFakeServer()
.UseStartup<StartupHelloWorld>()
.Build();

View File

@ -0,0 +1,69 @@
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
// HostingStartup's in the primary assembly are run automatically.
[assembly: HostingStartup(typeof(SampleStartups.StartupInjection))]
namespace SampleStartups
{
public class StartupInjection : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.UseStartup<InjectedStartup>();
}
// Entry point for the application.
public static void Main(string[] args)
{
var host = new WebHostBuilder()
//.UseKestrel()
.UseFakeServer()
// Each of these three sets ApplicationName to the current assembly, which is needed in order to
// scan the assembly for HostingStartupAttributes.
// .UseSetting(WebHostDefaults.ApplicationKey, "SampleStartups")
// .Configure(_ => { })
.UseStartup<NormalStartup>()
.Build();
host.Run();
}
}
public class NormalStartup
{
public void ConfigureServices(IServiceCollection services)
{
Console.WriteLine("NormalStartup.ConfigureServices");
}
public void Configure(IApplicationBuilder app)
{
Console.WriteLine("NormalStartup.Configure");
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
public class InjectedStartup
{
public void ConfigureServices(IServiceCollection services)
{
Console.WriteLine("InjectedStartup.ConfigureServices");
}
public void Configure(IApplicationBuilder app)
{
Console.WriteLine("InjectedStartup.Configure");
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
}

View File

@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Hosting
public static readonly string ServerUrlsKey = "urls";
public static readonly string ContentRootKey = "contentRoot";
public static readonly string PreferHostingUrlsKey = "preferHostingUrls";
public static readonly string PreventHostingStartupKey = "preventHostingStartup";
public static readonly string ShutdownTimeoutKey = "shutdownTimeoutSeconds";
}

View File

@ -26,7 +26,10 @@ namespace Microsoft.AspNetCore.Hosting.Internal
Environment = configuration[WebHostDefaults.EnvironmentKey];
WebRoot = configuration[WebHostDefaults.WebRootKey];
ContentRootPath = configuration[WebHostDefaults.ContentRootKey];
HostingStartupAssemblies = configuration[WebHostDefaults.HostingStartupAssembliesKey]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
PreventHostingStartup = ParseBool(configuration, WebHostDefaults.PreventHostingStartupKey);
// Search the primary assembly and configured assemblies.
HostingStartupAssemblies = $"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}"
.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
PreferHostingUrls = ParseBool(configuration, WebHostDefaults.PreferHostingUrlsKey);
var timeout = configuration[WebHostDefaults.ShutdownTimeoutKey];
@ -39,6 +42,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
public string ApplicationName { get; set; }
public bool PreventHostingStartup { get; set; }
public IReadOnlyList<string> HostingStartupAssemblies { get; set; }
public bool DetailedErrors { get; set; }

View File

@ -302,36 +302,39 @@ namespace Microsoft.AspNetCore.Hosting
services.AddSingleton(loggerFactory);
_context.LoggerFactory = loggerFactory;
var exceptions = new List<Exception>();
// Execute the hosting startup assemblies
foreach (var assemblyName in _options.HostingStartupAssemblies)
if (!_options.PreventHostingStartup)
{
try
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var exceptions = new List<Exception>();
foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
// Execute the hosting startup assemblies
foreach (var assemblyName in _options.HostingStartupAssemblies)
{
try
{
var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
hostingStartup.Configure(this);
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));
}
}
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)
if (exceptions.Count > 0)
{
throw hostingStartupErrors;
hostingStartupErrors = new AggregateException(exceptions);
// Throw directly if we're not capturing startup errors
if (!_options.CaptureStartupErrors)
{
throw hostingStartupErrors;
}
}
}

View File

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

View File

@ -833,13 +833,30 @@ namespace Microsoft.AspNetCore.Hosting
{
var builder = CreateWebHostBuilder()
.CaptureStartupErrors(false)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(WebHostBuilderTests).GetTypeInfo().Assembly.FullName)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
.Configure(app => { })
.UseServer(new TestServer());
using (var host = builder.Build())
{
Assert.Equal("1", builder.GetSetting("testhostingstartup"));
Assert.Equal("1", builder.GetSetting("testhostingstartup1"));
}
}
[Fact]
public void Build_RunsHostingStartupRunsPrimaryAssemblyFirst()
{
var builder = CreateWebHostBuilder()
.CaptureStartupErrors(false)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
.Configure(app => { })
.UseServer(new TestServer());
using (var host = builder.Build())
{
Assert.Equal("0", builder.GetSetting("testhostingstartup"));
Assert.Equal("1", builder.GetSetting("testhostingstartup1"));
Assert.Equal("01", builder.GetSetting("testhostingstartup_chain"));
}
}
@ -871,7 +888,6 @@ namespace Microsoft.AspNetCore.Hosting
{
var builder = CreateWebHostBuilder()
.CaptureStartupErrors(false)
.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(WebHostBuilderTests).GetTypeInfo().Assembly.FullName)
.Configure(app =>
{
var loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();
@ -888,12 +904,26 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void Build_DoesNotRunHostingStartupAssembliesDoNotRunIfNotSpecified()
public void Build_DoesRunHostingStartupFromPrimaryAssemblyEvenIfNotSpecified()
{
var builder = CreateWebHostBuilder()
.Configure(app => { })
.UseServer(new TestServer());
using (builder.Build())
{
Assert.Equal("0", builder.GetSetting("testhostingstartup"));
}
}
[Fact]
public void Build_HostingStartupFromPrimaryAssemblyCanBeDisabled()
{
var builder = CreateWebHostBuilder()
.UseSetting(WebHostDefaults.PreventHostingStartupKey, "true")
.Configure(app => { })
.UseServer(new TestServer());
using (builder.Build())
{
Assert.Null(builder.GetSetting("testhostingstartup"));
@ -1027,7 +1057,8 @@ namespace Microsoft.AspNetCore.Hosting
public void Configure(IWebHostBuilder builder)
{
var loggerProvider = new TestLoggerProvider();
builder.UseSetting("testhostingstartup", "1")
builder.UseSetting("testhostingstartup", "0")
.UseSetting("testhostingstartup_chain", builder.GetSetting("testhostingstartup_chain") + "0")
.ConfigureServices(services => services.AddSingleton<ServiceA>())
.ConfigureServices(services => services.AddSingleton<ITestSink>(loggerProvider.Sink))
.ConfigureLogging(lf => lf.AddProvider(loggerProvider));

View File

@ -0,0 +1,18 @@
// 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 Microsoft.AspNetCore.Hosting;
[assembly: HostingStartup(typeof(TestStartupAssembly1.TestHostingStartup1))]
namespace TestStartupAssembly1
{
public class TestHostingStartup1 : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
builder.UseSetting("testhostingstartup1", "1");
builder.UseSetting("testhostingstartup_chain", builder.GetSetting("testhostingstartup_chain") + "1");
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\build\common.props" />
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net46</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Hosting.Abstractions\Microsoft.AspNetCore.Hosting.Abstractions.csproj" />
</ItemGroup>
</Project>