diff --git a/Hosting.sln b/Hosting.sln
index 5fd76b63ed..09f0ae3c38 100644
--- a/Hosting.sln
+++ b/Hosting.sln
@@ -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
diff --git a/build/repo.props b/build/repo.props
index 9bbb94d668..2e289dd92d 100644
--- a/build/repo.props
+++ b/build/repo.props
@@ -1,5 +1,6 @@
+
diff --git a/samples/SampleStartups/FakeServer.cs b/samples/SampleStartups/FakeServer.cs
new file mode 100644
index 0000000000..a43790c352
--- /dev/null
+++ b/samples/SampleStartups/FakeServer.cs
@@ -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(IHttpApplication 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());
+ }
+ }
+}
diff --git a/samples/SampleStartups/SampleStartups.csproj b/samples/SampleStartups/SampleStartups.csproj
index 3e29e7642d..23f037440d 100644
--- a/samples/SampleStartups/SampleStartups.csproj
+++ b/samples/SampleStartups/SampleStartups.csproj
@@ -4,6 +4,8 @@
net46;netcoreapp2.0
+ SampleStartups.StartupInjection
+ exe
diff --git a/samples/SampleStartups/StartupBlockingOnStart.cs b/samples/SampleStartups/StartupBlockingOnStart.cs
index 1bdecf438a..c46010e18b 100644
--- a/samples/SampleStartups/StartupBlockingOnStart.cs
+++ b/samples/SampleStartups/StartupBlockingOnStart.cs
@@ -34,6 +34,7 @@ namespace SampleStartups
var host = new WebHostBuilder()
.UseConfiguration(config)
+ .UseFakeServer()
.UseStartup()
.Build();
diff --git a/samples/SampleStartups/StartupConfigureAddresses.cs b/samples/SampleStartups/StartupConfigureAddresses.cs
index 3fe6c20d0b..8413c47c90 100644
--- a/samples/SampleStartups/StartupConfigureAddresses.cs
+++ b/samples/SampleStartups/StartupConfigureAddresses.cs
@@ -25,6 +25,7 @@ namespace SampleStartups
var host = new WebHostBuilder()
.UseConfiguration(config)
+ .UseFakeServer()
.UseStartup()
.UseUrls("http://localhost:5000", "http://localhost:5001")
.Build();
diff --git a/samples/SampleStartups/StartupExternallyControlled.cs b/samples/SampleStartups/StartupExternallyControlled.cs
index 218624bd65..4fa9e55b87 100644
--- a/samples/SampleStartups/StartupExternallyControlled.cs
+++ b/samples/SampleStartups/StartupExternallyControlled.cs
@@ -32,6 +32,7 @@ namespace SampleStartups
{
_host = new WebHostBuilder()
//.UseKestrel()
+ .UseFakeServer()
.UseStartup()
.Start(_urls.ToArray());
}
diff --git a/samples/SampleStartups/StartupFullControl.cs b/samples/SampleStartups/StartupFullControl.cs
index a9254e482b..0f0207564a 100644
--- a/samples/SampleStartups/StartupFullControl.cs
+++ b/samples/SampleStartups/StartupFullControl.cs
@@ -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")
diff --git a/samples/SampleStartups/StartupHelloWorld.cs b/samples/SampleStartups/StartupHelloWorld.cs
index a2f806f661..88a77cfc6c 100644
--- a/samples/SampleStartups/StartupHelloWorld.cs
+++ b/samples/SampleStartups/StartupHelloWorld.cs
@@ -21,7 +21,8 @@ namespace SampleStartups
public static void Main(string[] args)
{
var host = new WebHostBuilder()
- //.UseKestrel()
+ //.UseKestrel()
+ .UseFakeServer()
.UseStartup()
.Build();
diff --git a/samples/SampleStartups/StartupInjection.cs b/samples/SampleStartups/StartupInjection.cs
new file mode 100644
index 0000000000..381d621a10
--- /dev/null
+++ b/samples/SampleStartups/StartupInjection.cs
@@ -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();
+ }
+
+ // 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()
+ .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!");
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostDefaults.cs b/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostDefaults.cs
index a7762b113e..cf0edb62b8 100644
--- a/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostDefaults.cs
+++ b/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostDefaults.cs
@@ -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";
}
diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/WebHostOptions.cs b/src/Microsoft.AspNetCore.Hosting/Internal/WebHostOptions.cs
index 2102706069..0a5ca14cf9 100644
--- a/src/Microsoft.AspNetCore.Hosting/Internal/WebHostOptions.cs
+++ b/src/Microsoft.AspNetCore.Hosting/Internal/WebHostOptions.cs
@@ -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 HostingStartupAssemblies { get; set; }
public bool DetailedErrors { get; set; }
diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs
index 336cec3f40..5ae258f924 100644
--- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs
+++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs
@@ -302,36 +302,39 @@ namespace Microsoft.AspNetCore.Hosting
services.AddSingleton(loggerFactory);
_context.LoggerFactory = loggerFactory;
- var exceptions = new List();
-
- // 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();
- foreach (var attribute in assembly.GetCustomAttributes())
+ // 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())
+ {
+ 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;
+ }
}
}
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 37912abcaa..7de02b4777 100644
--- a/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj
+++ b/test/Microsoft.AspNetCore.Hosting.Tests/Microsoft.AspNetCore.Hosting.Tests.csproj
@@ -13,6 +13,7 @@
+
diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs
index c2eb9c4364..6993805d92 100644
--- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs
+++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs
@@ -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();
@@ -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())
.ConfigureServices(services => services.AddSingleton(loggerProvider.Sink))
.ConfigureLogging(lf => lf.AddProvider(loggerProvider));
diff --git a/test/TestStartupAssembly1/TestHostingStartup1.cs b/test/TestStartupAssembly1/TestHostingStartup1.cs
new file mode 100644
index 0000000000..e8519c83c3
--- /dev/null
+++ b/test/TestStartupAssembly1/TestHostingStartup1.cs
@@ -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");
+ }
+ }
+}
diff --git a/test/TestStartupAssembly1/TestStartupAssembly1.csproj b/test/TestStartupAssembly1/TestStartupAssembly1.csproj
new file mode 100644
index 0000000000..f2b53ef9f3
--- /dev/null
+++ b/test/TestStartupAssembly1/TestStartupAssembly1.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+
+ netcoreapp2.0;net46
+
+
+
+
+
+
+
\ No newline at end of file