diff --git a/samples/SampleStartups/StartupBlockingOnStart.cs b/samples/SampleStartups/StartupBlockingOnStart.cs index 39db2830c4..fdbc094d1b 100644 --- a/samples/SampleStartups/StartupBlockingOnStart.cs +++ b/samples/SampleStartups/StartupBlockingOnStart.cs @@ -35,8 +35,9 @@ namespace SampleStartups .UseStartup() .Build(); - using (application.Start()) + using (application) { + application.Start(); Console.ReadLine(); } } diff --git a/samples/SampleStartups/StartupConfigureAddresses.cs b/samples/SampleStartups/StartupConfigureAddresses.cs index 0fff30e2f8..00df99135b 100644 --- a/samples/SampleStartups/StartupConfigureAddresses.cs +++ b/samples/SampleStartups/StartupConfigureAddresses.cs @@ -32,12 +32,9 @@ namespace SampleStartups var application = new WebApplicationBuilder() .UseConfiguration(config) .UseStartup() + .UseUrls("http://localhost:5000", "http://localhost:5001") .Build(); - var addresses = application.GetAddresses(); - addresses.Add("http://localhost:5000"); - addresses.Add("http://localhost:5001"); - application.Run(); } } diff --git a/samples/SampleStartups/StartupExternallyControlled.cs b/samples/SampleStartups/StartupExternallyControlled.cs index dc7ce9d8b4..538e97decd 100644 --- a/samples/SampleStartups/StartupExternallyControlled.cs +++ b/samples/SampleStartups/StartupExternallyControlled.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; @@ -10,8 +11,8 @@ namespace SampleStartups { public class StartupExternallyControlled { - private readonly IWebApplication _host; - private IDisposable _application; + private IWebApplication _application; + private readonly List _urls = new List(); // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940 @@ -30,15 +31,13 @@ namespace SampleStartups public StartupExternallyControlled() { - _host = new WebApplicationBuilder().UseStartup().Build(); - - // Clear all configured addresses - _host.GetAddresses().Clear(); } public void Start() { - _application = _host.Start(); + _application = new WebApplicationBuilder() + .UseStartup() + .Start(_urls.ToArray()); } public void Stop() @@ -48,9 +47,7 @@ namespace SampleStartups public void AddUrl(string url) { - var addresses = _host.GetAddresses(); - - addresses.Add(url); + _urls.Add(url); } } } diff --git a/samples/SampleStartups/StartupFullControl.cs b/samples/SampleStartups/StartupFullControl.cs index 82c32791d6..1279bfb1a0 100644 --- a/samples/SampleStartups/StartupFullControl.cs +++ b/samples/SampleStartups/StartupFullControl.cs @@ -14,14 +14,11 @@ namespace SampleStartups public static void Main(string[] args) { var application = new WebApplicationBuilder() - .UseServerFactory("Microsoft.AspNet.Server.Kestrel") // Set the server manually + .UseServer("Microsoft.AspNet.Server.Kestrel") // Set the server manually .UseApplicationBasePath(Directory.GetCurrentDirectory()) // Override the application base with the current directory + .UseUrls("http://*:1000", "https://*:902") .UseEnvironment("Development") .UseWebRoot("public") - .ConfigureLogging(loggerFactory => - { - loggerFactory.AddProvider(new MyHostLoggerProvider()); - }) .ConfigureServices(services => { // Configure services that the application can see diff --git a/src/Microsoft.AspNet.Hosting/IWebApplication.cs b/src/Microsoft.AspNet.Hosting.Abstractions/IWebApplication.cs similarity index 91% rename from src/Microsoft.AspNet.Hosting/IWebApplication.cs rename to src/Microsoft.AspNet.Hosting.Abstractions/IWebApplication.cs index 0db082a45f..cfafc97ee5 100644 --- a/src/Microsoft.AspNet.Hosting/IWebApplication.cs +++ b/src/Microsoft.AspNet.Hosting.Abstractions/IWebApplication.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Hosting /// /// Represents a configured web application /// - public interface IWebApplication + public interface IWebApplication : IDisposable { /// /// The exposed by the configured server. @@ -25,6 +25,6 @@ namespace Microsoft.AspNet.Hosting /// Starts listening on the configured addresses. /// /// - IDisposable Start(); + void Start(); } } diff --git a/src/Microsoft.AspNet.Hosting.Abstractions/IWebApplicationBuilder.cs b/src/Microsoft.AspNet.Hosting.Abstractions/IWebApplicationBuilder.cs new file mode 100644 index 0000000000..e92288a572 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting.Abstractions/IWebApplicationBuilder.cs @@ -0,0 +1,28 @@ +// 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.AspNet.Builder; +using Microsoft.AspNet.Hosting.Server; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.Hosting +{ + public interface IWebApplicationBuilder + { + IWebApplication Build(); + + IWebApplicationBuilder UseConfiguration(IConfiguration configuration); + + IWebApplicationBuilder UseServer(IServerFactory factory); + + IWebApplicationBuilder UseStartup(Type startupType); + + IWebApplicationBuilder ConfigureServices(Action configureServices); + + IWebApplicationBuilder Configure(Action configureApplication); + + IWebApplicationBuilder UseSetting(string key, string value); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting.Abstractions/project.json b/src/Microsoft.AspNet.Hosting.Abstractions/project.json index 087bc75058..f6a665e878 100644 --- a/src/Microsoft.AspNet.Hosting.Abstractions/project.json +++ b/src/Microsoft.AspNet.Hosting.Abstractions/project.json @@ -12,7 +12,9 @@ "dependencies": { "Microsoft.AspNet.Http.Abstractions": "1.0.0-*", "Microsoft.AspNet.FileProviders.Abstractions": "1.0.0-*", - "Microsoft.Extensions.Configuration.Abstractions": "1.0.0-*" + "Microsoft.AspNet.Hosting.Server.Abstractions": "1.0.0-*", + "Microsoft.Extensions.Configuration.Abstractions": "1.0.0-*", + "Microsoft.Extensions.DependencyInjection.Abstractions": "1.0.0-*" }, "frameworks": { "net451": {}, diff --git a/src/Microsoft.AspNet.Hosting.WindowsServices/WebApplicationService.cs b/src/Microsoft.AspNet.Hosting.WindowsServices/WebApplicationService.cs index a50d0d76a7..4cecc5705a 100644 --- a/src/Microsoft.AspNet.Hosting.WindowsServices/WebApplicationService.cs +++ b/src/Microsoft.AspNet.Hosting.WindowsServices/WebApplicationService.cs @@ -13,7 +13,6 @@ namespace Microsoft.AspNet.Hosting.WindowsServices public class WebApplicationService : ServiceBase { private IWebApplication _application; - private IDisposable _applicationShutdown; private bool _stopRequestedByWindows; /// @@ -41,7 +40,7 @@ namespace Microsoft.AspNet.Hosting.WindowsServices } }); - _applicationShutdown = _application.Start(); + _application.Start(); OnStarted(); } @@ -50,7 +49,7 @@ namespace Microsoft.AspNet.Hosting.WindowsServices { _stopRequestedByWindows = true; OnStopping(); - _applicationShutdown?.Dispose(); + _application?.Dispose(); OnStopped(); } diff --git a/src/Microsoft.AspNet.Hosting/Internal/IncludedConfigurationProvider.cs b/src/Microsoft.AspNet.Hosting/Internal/IncludedConfigurationProvider.cs new file mode 100644 index 0000000000..5feb23e761 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/Internal/IncludedConfigurationProvider.cs @@ -0,0 +1,39 @@ +// 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.Configuration; + +namespace Microsoft.AspNet.Hosting.Internal +{ + // TODO: Remove this once https://github.com/aspnet/Configuration/pull/349 gets merged + internal class IncludedConfigurationProvider : ConfigurationProvider + { + public IncludedConfigurationProvider(IConfiguration source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + int pathStart = 0; + var section = source as IConfigurationSection; + if (section != null) + { + pathStart = section.Path.Length + 1; + } + foreach (var child in source.GetChildren()) + { + AddSection(child, pathStart); + } + } + + private void AddSection(IConfigurationSection section, int pathStart) + { + Data.Add(section.Path.Substring(pathStart), section.Value); + foreach (var child in section.GetChildren()) + { + AddSection(child, pathStart); + } + } + } +} diff --git a/src/Microsoft.AspNet.Hosting/Internal/WebApplication.cs b/src/Microsoft.AspNet.Hosting/Internal/WebApplication.cs index 0014537a7b..170a7f26cd 100644 --- a/src/Microsoft.AspNet.Hosting/Internal/WebApplication.cs +++ b/src/Microsoft.AspNet.Hosting/Internal/WebApplication.cs @@ -32,6 +32,7 @@ namespace Microsoft.AspNet.Hosting.Internal private IServiceProvider _applicationServices; private RequestDelegate _application; + private ILogger _logger; // Only one of these should be set internal string StartupAssemblyName { get; set; } @@ -41,7 +42,7 @@ namespace Microsoft.AspNet.Hosting.Internal // Only one of these should be set internal IServerFactory ServerFactory { get; set; } internal string ServerFactoryLocation { get; set; } - internal IServer Server { get; set; } + private IServer Server { get; set; } public WebApplication( IServiceCollection appServices, @@ -97,29 +98,20 @@ namespace Microsoft.AspNet.Hosting.Internal } } - public virtual IDisposable Start() + public virtual void Start() { Initialize(); - var logger = _applicationServices.GetRequiredService>(); + _logger = _applicationServices.GetRequiredService>(); var diagnosticSource = _applicationServices.GetRequiredService(); var httpContextFactory = _applicationServices.GetRequiredService(); - logger.Starting(); + _logger.Starting(); - Server.Start(new HostingApplication(_application, logger, diagnosticSource, httpContextFactory)); + Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory)); _applicationLifetime.NotifyStarted(); - logger.Started(); - - return new Disposable(() => - { - logger.Shutdown(); - _applicationLifetime.StopApplication(); - Server.Dispose(); - _applicationLifetime.NotifyStopped(); - (_applicationServices as IDisposable)?.Dispose(); - }); + _logger.Started(); } private void EnsureApplicationServices() @@ -248,6 +240,15 @@ namespace Microsoft.AspNet.Hosting.Internal } } + public void Dispose() + { + _logger?.Shutdown(); + _applicationLifetime.StopApplication(); + Server?.Dispose(); + _applicationLifetime.NotifyStopped(); + (_applicationServices as IDisposable)?.Dispose(); + } + private class Disposable : IDisposable { private Action _dispose; diff --git a/src/Microsoft.AspNet.Hosting/Internal/WebApplicationOptions.cs b/src/Microsoft.AspNet.Hosting/Internal/WebApplicationOptions.cs index ef76bcdb8f..1019c866ec 100644 --- a/src/Microsoft.AspNet.Hosting/Internal/WebApplicationOptions.cs +++ b/src/Microsoft.AspNet.Hosting/Internal/WebApplicationOptions.cs @@ -27,6 +27,7 @@ namespace Microsoft.AspNet.Hosting.Internal Environment = configuration[WebApplicationConfiguration.EnvironmentKey] ?? configuration[OldEnvironmentKey]; ServerFactoryLocation = configuration[WebApplicationConfiguration.ServerKey]; WebRoot = configuration[WebApplicationConfiguration.WebRootKey]; + ApplicationBasePath = configuration[WebApplicationConfiguration.ApplicationBaseKey]; } public string Application { get; set; } @@ -41,6 +42,8 @@ namespace Microsoft.AspNet.Hosting.Internal public string WebRoot { get; set; } + public string ApplicationBasePath { get; set; } + private static bool ParseBool(IConfiguration configuration, string key) { return string.Equals("true", configuration[key], StringComparison.OrdinalIgnoreCase) diff --git a/src/Microsoft.AspNet.Hosting/WebApplicationBuilder.cs b/src/Microsoft.AspNet.Hosting/WebApplicationBuilder.cs index bec3ee4381..236b5c8392 100644 --- a/src/Microsoft.AspNet.Hosting/WebApplicationBuilder.cs +++ b/src/Microsoft.AspNet.Hosting/WebApplicationBuilder.cs @@ -2,6 +2,7 @@ // 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.Runtime.Versioning; using Microsoft.AspNet.Builder; @@ -16,10 +17,11 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.PlatformAbstractions; +using Microsoft.Extensions.Primitives; namespace Microsoft.AspNet.Hosting { - public class WebApplicationBuilder + public class WebApplicationBuilder : IWebApplicationBuilder { private readonly IHostingEnvironment _hostingEnvironment; private readonly ILoggerFactory _loggerFactory; @@ -28,19 +30,15 @@ namespace Microsoft.AspNet.Hosting private WebApplicationOptions _options; private Action _configureServices; - private string _environmentName; - private string _webRoot; - private string _applicationBasePath; // Only one of these should be set private StartupMethods _startup; private Type _startupType; - private string _startupAssemblyName; // Only one of these should be set - private string _serverFactoryLocation; private IServerFactory _serverFactory; - private IServer _server; + + private Dictionary _settings = new Dictionary(StringComparer.OrdinalIgnoreCase); public WebApplicationBuilder() { @@ -48,62 +46,19 @@ namespace Microsoft.AspNet.Hosting _loggerFactory = new LoggerFactory(); } - public WebApplicationBuilder UseConfiguration(IConfiguration configuration) + public IWebApplicationBuilder UseSetting(string key, string value) + { + _settings[key] = value; + return this; + } + + public IWebApplicationBuilder UseConfiguration(IConfiguration configuration) { _config = configuration; return this; } - public WebApplicationBuilder UseApplicationBasePath(string applicationBasePath) - { - _applicationBasePath = applicationBasePath; - return this; - } - - public WebApplicationBuilder UseEnvironment(string environment) - { - if (environment == null) - { - throw new ArgumentNullException(nameof(environment)); - } - - _environmentName = environment; - return this; - } - - public WebApplicationBuilder UseWebRoot(string webRoot) - { - if (webRoot == null) - { - throw new ArgumentNullException(nameof(webRoot)); - } - _webRoot = webRoot; - return this; - } - - public WebApplicationBuilder UseServer(IServer server) - { - if (server == null) - { - throw new ArgumentNullException(nameof(server)); - } - - _server = server; - return this; - } - - public WebApplicationBuilder UseServerFactory(string assemblyName) - { - if (assemblyName == null) - { - throw new ArgumentNullException(nameof(assemblyName)); - } - - _serverFactoryLocation = assemblyName; - return this; - } - - public WebApplicationBuilder UseServerFactory(IServerFactory factory) + public IWebApplicationBuilder UseServer(IServerFactory factory) { if (factory == null) { @@ -114,18 +69,7 @@ namespace Microsoft.AspNet.Hosting return this; } - public WebApplicationBuilder UseStartup(string startupAssemblyName) - { - if (startupAssemblyName == null) - { - throw new ArgumentNullException(nameof(startupAssemblyName)); - } - - _startupAssemblyName = startupAssemblyName; - return this; - } - - public WebApplicationBuilder UseStartup(Type startupType) + public IWebApplicationBuilder UseStartup(Type startupType) { if (startupType == null) { @@ -136,18 +80,13 @@ namespace Microsoft.AspNet.Hosting return this; } - public WebApplicationBuilder UseStartup() where TStartup : class - { - return UseStartup(typeof(TStartup)); - } - - public WebApplicationBuilder ConfigureServices(Action configureServices) + public IWebApplicationBuilder ConfigureServices(Action configureServices) { _configureServices = configureServices; return this; } - public WebApplicationBuilder Configure(Action configureApp) + public IWebApplicationBuilder Configure(Action configureApp) { if (configureApp == null) { @@ -158,7 +97,7 @@ namespace Microsoft.AspNet.Hosting return this; } - public WebApplicationBuilder ConfigureLogging(Action configureLogging) + public IWebApplicationBuilder ConfigureLogging(Action configureLogging) { configureLogging(_loggerFactory); return this; @@ -173,29 +112,19 @@ namespace Microsoft.AspNet.Hosting var appEnvironment = hostingContainer.GetRequiredService(); var startupLoader = hostingContainer.GetRequiredService(); - _config = _config ?? WebApplicationConfiguration.GetDefault(); - _options = new WebApplicationOptions(_config); - // Initialize the hosting environment - _options.WebRoot = _webRoot ?? _options.WebRoot; _hostingEnvironment.Initialize(appEnvironment.ApplicationBasePath, _options, _config); - if (!string.IsNullOrEmpty(_environmentName)) - { - _hostingEnvironment.EnvironmentName = _environmentName; - } - var application = new WebApplication(hostingServices, startupLoader, _options, _config); // Only one of these should be set, but they are used in priority - application.Server = _server; application.ServerFactory = _serverFactory; - application.ServerFactoryLocation = _options.ServerFactoryLocation ?? _serverFactoryLocation; + application.ServerFactoryLocation = _options.ServerFactoryLocation; // Only one of these should be set, but they are used in priority application.Startup = _startup; application.StartupType = _startupType; - application.StartupAssemblyName = _startupAssemblyName ?? _options.Application; + application.StartupAssemblyName = _options.Application; application.Initialize(); @@ -204,6 +133,17 @@ namespace Microsoft.AspNet.Hosting private IServiceCollection BuildHostingServices() { + // Apply the configuration settings + var configuration = _config ?? WebApplicationConfiguration.GetDefault(); + + var mergedConfiguration = new ConfigurationBuilder() + .Add(new IncludedConfigurationProvider(configuration)) + .AddInMemoryCollection(_settings) + .Build(); + + _config = mergedConfiguration; + _options = new WebApplicationOptions(_config); + var services = new ServiceCollection(); services.AddSingleton(_hostingEnvironment); services.AddSingleton(_loggerFactory); @@ -230,9 +170,9 @@ namespace Microsoft.AspNet.Hosting if (defaultPlatformServices.Application != null) { var appEnv = defaultPlatformServices.Application; - if (!string.IsNullOrEmpty(_applicationBasePath)) + if (!string.IsNullOrEmpty(_options.ApplicationBasePath)) { - appEnv = new WrappedApplicationEnvironment(_applicationBasePath, appEnv); + appEnv = new WrappedApplicationEnvironment(_options.ApplicationBasePath, appEnv); } services.TryAddSingleton(appEnv); diff --git a/src/Microsoft.AspNet.Hosting/WebApplicationBuilderExtensions.cs b/src/Microsoft.AspNet.Hosting/WebApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..e49ba7f4ed --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/WebApplicationBuilderExtensions.cs @@ -0,0 +1,148 @@ +// 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.AspNet.Hosting.Server; +using Microsoft.AspNet.Server.Features; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNet.Hosting +{ + public static class WebApplicationBuilderExtensions + { + private static readonly string ServerUrlsSeparator = ";"; + + public static IWebApplicationBuilder UseStartup(this IWebApplicationBuilder applicationBuilder) where TStartup : class + { + return applicationBuilder.UseStartup(typeof(TStartup)); + } + + public static IWebApplicationBuilder UseServer(this IWebApplicationBuilder applicationBuilder, string assemblyName) + { + if (assemblyName == null) + { + throw new ArgumentNullException(nameof(assemblyName)); + } + + return applicationBuilder.UseSetting(WebApplicationConfiguration.ServerKey, assemblyName); + } + + public static IWebApplicationBuilder UseServer(this IWebApplicationBuilder applicationBuilder, IServer server) + { + if (server == null) + { + throw new ArgumentNullException(nameof(server)); + } + + return applicationBuilder.UseServer(new ServerFactory(server)); + } + + public static IWebApplicationBuilder UseApplicationBasePath(this IWebApplicationBuilder applicationBuilder, string applicationBasePath) + { + if (applicationBasePath == null) + { + throw new ArgumentNullException(nameof(applicationBasePath)); + } + + return applicationBuilder.UseSetting(WebApplicationConfiguration.ApplicationBaseKey, applicationBasePath); + } + + public static IWebApplicationBuilder UseEnvironment(this IWebApplicationBuilder applicationBuilder, string environment) + { + if (environment == null) + { + throw new ArgumentNullException(nameof(environment)); + } + + return applicationBuilder.UseSetting(WebApplicationConfiguration.EnvironmentKey, environment); + } + + public static IWebApplicationBuilder UseWebRoot(this IWebApplicationBuilder applicationBuilder, string webRoot) + { + if (webRoot == null) + { + throw new ArgumentNullException(nameof(webRoot)); + } + + return applicationBuilder.UseSetting(WebApplicationConfiguration.WebRootKey, webRoot); + } + + public static IWebApplicationBuilder UseUrls(this IWebApplicationBuilder applicationBuilder, params string[] urls) + { + if (urls == null) + { + throw new ArgumentNullException(nameof(urls)); + } + + return applicationBuilder.UseSetting(WebApplicationConfiguration.ServerUrlsKey, string.Join(ServerUrlsSeparator, urls)); + } + + public static IWebApplicationBuilder UseStartup(this IWebApplicationBuilder applicationBuilder, string startupAssemblyName) + { + if (startupAssemblyName == null) + { + throw new ArgumentNullException(nameof(startupAssemblyName)); + } + + return applicationBuilder.UseSetting(WebApplicationConfiguration.ApplicationKey, startupAssemblyName); + } + + public static IWebApplication Start(this IWebApplicationBuilder applicationBuilder, params string[] urls) + { + var application = applicationBuilder.UseUrls(urls).Build(); + application.Start(); + return application; + } + + /// + /// Runs a web application and block the calling thread until host shutdown. + /// + /// + public static void Run(this IWebApplication application) + { + using (application) + { + application.Start(); + + var hostingEnvironment = application.Services.GetService(); + var applicationLifetime = application.Services.GetService(); + + Console.WriteLine("Hosting environment: " + hostingEnvironment.EnvironmentName); + + var serverAddresses = application.ServerFeatures.Get()?.Addresses; + if (serverAddresses != null) + { + foreach (var address in serverAddresses) + { + Console.WriteLine("Now listening on: " + address); + } + } + + Console.WriteLine("Application started. Press Ctrl+C to shut down."); + + Console.CancelKeyPress += (sender, eventArgs) => + { + applicationLifetime.StopApplication(); + + // Don't terminate the process immediately, wait for the Main thread to exit gracefully. + eventArgs.Cancel = true; + }; + + applicationLifetime.ApplicationStopping.WaitHandle.WaitOne(); + } + } + + private class ServerFactory : IServerFactory + { + private readonly IServer _server; + + public ServerFactory(IServer server) + { + _server = server; + } + + public IServer CreateServer(IConfiguration configuration) => _server; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/WebApplicationConfiguration.cs b/src/Microsoft.AspNet.Hosting/WebApplicationConfiguration.cs index 0d71dafc2d..49eff15003 100644 --- a/src/Microsoft.AspNet.Hosting/WebApplicationConfiguration.cs +++ b/src/Microsoft.AspNet.Hosting/WebApplicationConfiguration.cs @@ -4,12 +4,14 @@ namespace Microsoft.AspNet.Hosting { public class WebApplicationConfiguration { - public static readonly string ApplicationKey = "app"; + public static readonly string ApplicationKey = "application"; public static readonly string DetailedErrorsKey = "detailedErrors"; public static readonly string EnvironmentKey = "environment"; public static readonly string ServerKey = "server"; public static readonly string WebRootKey = "webroot"; public static readonly string CaptureStartupErrorsKey = "captureStartupErrors"; + public static readonly string ServerUrlsKey = "server.urls"; + public static readonly string ApplicationBaseKey = "applicationBase"; public static readonly string HostingJsonFile = "hosting.json"; public static readonly string EnvironmentVariablesPrefix = "ASPNET_"; diff --git a/src/Microsoft.AspNet.Hosting/WebApplicationExtensions.cs b/src/Microsoft.AspNet.Hosting/WebApplicationExtensions.cs deleted file mode 100644 index a0b0f0b3f6..0000000000 --- a/src/Microsoft.AspNet.Hosting/WebApplicationExtensions.cs +++ /dev/null @@ -1,60 +0,0 @@ -// 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 Microsoft.AspNet.Http.Features; -using Microsoft.AspNet.Server.Features; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNet.Hosting -{ - public static class WebApplicationExtensions - { - /// - /// Retruns the server addresses the web application is going to listen on. - /// - /// - /// An which the addresses the server will listen to - public static ICollection GetAddresses(this IWebApplication application) - { - return application.ServerFeatures.Get()?.Addresses; - } - - /// - /// Runs a web application and block the calling thread until host shutdown. - /// - /// - public static void Run(this IWebApplication application) - { - using (application.Start()) - { - var hostingEnvironment = application.Services.GetService(); - var applicationLifetime = application.Services.GetService(); - - Console.WriteLine("Hosting environment: " + hostingEnvironment.EnvironmentName); - - var serverAddresses = application.GetAddresses(); - if (serverAddresses != null) - { - foreach (var address in serverAddresses) - { - Console.WriteLine("Now listening on: " + address); - } - } - - Console.WriteLine("Application started. Press Ctrl+C to shut down."); - - Console.CancelKeyPress += (sender, eventArgs) => - { - applicationLifetime.StopApplication(); - - // Don't terminate the process immediately, wait for the Main thread to exit gracefully. - eventArgs.Cancel = true; - }; - - applicationLifetime.ApplicationStopping.WaitHandle.WaitOne(); - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TestHost/TestServer.cs b/src/Microsoft.AspNet.TestHost/TestServer.cs index 51980617cf..38da4fd213 100644 --- a/src/Microsoft.AspNet.TestHost/TestServer.cs +++ b/src/Microsoft.AspNet.TestHost/TestServer.cs @@ -16,14 +16,15 @@ namespace Microsoft.AspNet.TestHost { private const string DefaultEnvironmentName = "Development"; private const string ServerName = nameof(TestServer); - private IDisposable _appInstance; + private IWebApplication _appInstance; private bool _disposed = false; private IHttpApplication _application; - public TestServer(WebApplicationBuilder builder) + public TestServer(IWebApplicationBuilder builder) { var application = builder.UseServer(this).Build(); - _appInstance = application.Start(); + application.Start(); + _appInstance = application; } public Uri BaseAddress { get; set; } = new Uri("http://localhost/"); @@ -59,8 +60,11 @@ namespace Microsoft.AspNet.TestHost public void Dispose() { - _disposed = true; - _appInstance.Dispose(); + if (!_disposed) + { + _disposed = true; + _appInstance.Dispose(); + } } void IServer.Start(IHttpApplication application) diff --git a/test/Microsoft.AspNet.Hosting.Tests/WebApplicationBuilderTests.cs b/test/Microsoft.AspNet.Hosting.Tests/WebApplicationBuilderTests.cs index 06f912ac17..a71b92bbae 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/WebApplicationBuilderTests.cs +++ b/test/Microsoft.AspNet.Hosting.Tests/WebApplicationBuilderTests.cs @@ -36,8 +36,9 @@ namespace Microsoft.AspNet.Hosting var builder = CreateWebApplicationBuilder(); var server = new TestServer(); var application = builder.UseServer(server).UseStartup("MissingStartupAssembly").Build(); - using (application.Start()) + using (application) { + application.Start(); await AssertResponseContains(server.RequestDelegate, "MissingStartupAssembly"); } } @@ -48,8 +49,9 @@ namespace Microsoft.AspNet.Hosting var builder = CreateWebApplicationBuilder(); var server = new TestServer(); var application = builder.UseServer(server).UseStartup().Build(); - using (application.Start()) + using (application) { + application.Start(); await AssertResponseContains(server.RequestDelegate, "Exception from static constructor"); } } @@ -60,8 +62,9 @@ namespace Microsoft.AspNet.Hosting var builder = CreateWebApplicationBuilder(); var server = new TestServer(); var application = builder.UseServer(server).UseStartup().Build(); - using (application.Start()) + using (application) { + application.Start(); await AssertResponseContains(server.RequestDelegate, "Exception from constructor"); } } @@ -72,8 +75,9 @@ namespace Microsoft.AspNet.Hosting var builder = CreateWebApplicationBuilder(); var server = new TestServer(); var application = builder.UseServer(server).UseStartup().Build(); - using (application.Start()) + using (application) { + application.Start(); await AssertResponseContains(server.RequestDelegate, "Message from the LoaderException"); } } @@ -84,8 +88,9 @@ namespace Microsoft.AspNet.Hosting var builder = CreateWebApplicationBuilder(); var server = new TestServer(); var application = builder.UseServer(server).UseStartup().Build(); - using (application.Start()) + using (application) { + application.Start(); var service = application.Services.GetServices(); Assert.NotNull(service); await AssertResponseContains(server.RequestDelegate, "Exception from constructor"); @@ -98,8 +103,9 @@ namespace Microsoft.AspNet.Hosting var builder = CreateWebApplicationBuilder(); var server = new TestServer(); var application = builder.UseServer(server).UseStartup().Build(); - using (application.Start()) + using (application) { + application.Start(); await AssertResponseContains(server.RequestDelegate, "Exception from ConfigureServices"); } } @@ -110,8 +116,9 @@ namespace Microsoft.AspNet.Hosting var builder = CreateWebApplicationBuilder(); var server = new TestServer(); var application = builder.UseServer(server).UseStartup().Build(); - using (application.Start()) + using (application) { + application.Start(); await AssertResponseContains(server.RequestDelegate, "Exception from Configure"); } } @@ -138,6 +145,28 @@ namespace Microsoft.AspNet.Hosting Assert.Equal(expected, application.Services.GetService().EnvironmentName); } + [Fact] + public void BuildAndDispose() + { + var vals = new Dictionary + { + { "ENV", "Dev" }, + }; + var builder = new ConfigurationBuilder() + .AddInMemoryCollection(vals); + var config = builder.Build(); + + var expected = "MY_TEST_ENVIRONMENT"; + var application = new WebApplicationBuilder() + .UseConfiguration(config) + .UseEnvironment(expected) + .UseServer(new TestServer()) + .UseStartup("Microsoft.AspNet.Hosting.Tests") + .Build(); + + application.Dispose(); + } + [Fact] public void UseBasePathConfiguresBasePath() { @@ -159,7 +188,7 @@ namespace Microsoft.AspNet.Hosting Assert.Equal("/foo/bar", application.Services.GetService().ApplicationBasePath); } - private WebApplicationBuilder CreateWebApplicationBuilder() + private IWebApplicationBuilder CreateWebApplicationBuilder() { var vals = new Dictionary { diff --git a/test/Microsoft.AspNet.Hosting.Tests/WebApplicationConfigurationsTests.cs b/test/Microsoft.AspNet.Hosting.Tests/WebApplicationConfigurationsTests.cs index 3e935c68cd..cd7dbdd569 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/WebApplicationConfigurationsTests.cs +++ b/test/Microsoft.AspNet.Hosting.Tests/WebApplicationConfigurationsTests.cs @@ -15,11 +15,11 @@ namespace Microsoft.AspNet.Hosting.Tests { var parameters = new Dictionary() { - {"webroot", "wwwroot"}, - {"server", "Microsoft.AspNet.Server.Kestrel"}, - {"app", "MyProjectReference"}, - {"environment", "Development"}, - {"detailederrors", "true"}, + { "webroot", "wwwroot"}, + { "server", "Microsoft.AspNet.Server.Kestrel"}, + { "application", "MyProjectReference"}, + { "environment", "Development"}, + { "detailederrors", "true"}, { "captureStartupErrors", "true" } }; diff --git a/test/Microsoft.AspNet.Hosting.Tests/WebApplicationTests.cs b/test/Microsoft.AspNet.Hosting.Tests/WebApplicationTests.cs index 9278d7ad17..d20f85b16e 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/WebApplicationTests.cs +++ b/test/Microsoft.AspNet.Hosting.Tests/WebApplicationTests.cs @@ -119,7 +119,7 @@ namespace Microsoft.AspNet.Hosting .AddInMemoryCollection(vals); var config = builder.Build(); var application = CreateBuilder(config).Build(); - var app = application.Start(); + application.Start(); Assert.NotNull(application.Services.GetService()); Assert.Equal("http://localhost:abc123", application.ServerFeatures.Get().Addresses.First()); } @@ -136,9 +136,9 @@ namespace Microsoft.AspNet.Hosting .AddInMemoryCollection(vals); var config = builder.Build(); var application = CreateBuilder(config).Build(); - var app = application.Start(); + application.Start(); Assert.NotNull(application.Services.GetService()); - Assert.Equal("http://localhost:5000", application.GetAddresses().First()); + Assert.Equal("http://localhost:5000", application.ServerFeatures.Get().Addresses.First()); } [Fact] @@ -153,7 +153,7 @@ namespace Microsoft.AspNet.Hosting .AddInMemoryCollection(vals); var config = builder.Build(); var application = CreateBuilder(config).Build(); - var app = application.Start(); + application.Start(); var hostingEnvironment = application.Services.GetService(); Assert.NotNull(hostingEnvironment.Configuration); Assert.Equal("Microsoft.AspNet.Hosting.Tests", hostingEnvironment.Configuration["Server"]); @@ -163,9 +163,8 @@ namespace Microsoft.AspNet.Hosting public void WebApplicationCanBeStarted() { var app = CreateBuilder() - .UseServer(this) + .UseServer((IServerFactory)this) .UseStartup("Microsoft.AspNet.Hosting.Tests") - .Build() .Start(); Assert.NotNull(app); @@ -181,7 +180,7 @@ namespace Microsoft.AspNet.Hosting public void WebApplicationDisposesServiceProvider() { var application = CreateBuilder() - .UseServer(this) + .UseServer((IServerFactory)this) .ConfigureServices(s => { s.AddTransient(); @@ -190,7 +189,7 @@ namespace Microsoft.AspNet.Hosting .UseStartup("Microsoft.AspNet.Hosting.Tests") .Build(); - var app = application.Start(); + application.Start(); var singleton = (FakeService)application.Services.GetService(); var transient = (FakeService)application.Services.GetService(); @@ -198,7 +197,7 @@ namespace Microsoft.AspNet.Hosting Assert.False(singleton.Disposed); Assert.False(transient.Disposed); - app.Dispose(); + application.Dispose(); Assert.True(singleton.Disposed); Assert.True(transient.Disposed); @@ -208,13 +207,14 @@ namespace Microsoft.AspNet.Hosting public void WebApplicationNotifiesApplicationStarted() { var application = CreateBuilder() - .UseServer(this) + .UseServer((IServerFactory)this) .Build(); var applicationLifetime = application.Services.GetService(); Assert.False(applicationLifetime.ApplicationStarted.IsCancellationRequested); - using (application.Start()) + using (application) { + application.Start(); Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested); } } @@ -223,13 +223,14 @@ namespace Microsoft.AspNet.Hosting public void WebApplicationInjectsHostingEnvironment() { var application = CreateBuilder() - .UseServer(this) + .UseServer((IServerFactory)this) .UseStartup("Microsoft.AspNet.Hosting.Tests") .UseEnvironment("WithHostingEnvironment") .Build(); - using (application.Start()) + using (application) { + application.Start(); var env = application.Services.GetService(); Assert.Equal("Changed", env.EnvironmentName); } @@ -243,7 +244,7 @@ namespace Microsoft.AspNet.Hosting { services.AddTransient(); }) - .UseServer(this) + .UseServer((IServerFactory)this) .UseStartup("Microsoft.AspNet.Hosting.Tests"); Assert.Throws(() => builder.Build()); @@ -252,14 +253,14 @@ namespace Microsoft.AspNet.Hosting [Fact] public void CanCreateApplicationServicesWithAddedServices() { - var application = CreateBuilder().UseServer(this).ConfigureServices(services => services.AddOptions()).Build(); + var application = CreateBuilder().UseServer((IServerFactory)this).ConfigureServices(services => services.AddOptions()).Build(); Assert.NotNull(application.Services.GetRequiredService>()); } [Fact] public void EnvDefaultsToProductionIfNoConfig() { - var application = CreateBuilder().UseServer(this).Build(); + var application = CreateBuilder().UseServer((IServerFactory)this).Build(); var env = application.Services.GetService(); Assert.Equal(EnvironmentName.Production, env.EnvironmentName); } @@ -278,7 +279,7 @@ namespace Microsoft.AspNet.Hosting .AddInMemoryCollection(vals); var config = builder.Build(); - var application = CreateBuilder(config).UseServer(this).Build(); + var application = CreateBuilder(config).UseServer((IServerFactory)this).Build(); var env = application.Services.GetService(); Assert.Equal("Staging", env.EnvironmentName); } @@ -295,7 +296,7 @@ namespace Microsoft.AspNet.Hosting .AddInMemoryCollection(vals); var config = builder.Build(); - var application = CreateBuilder(config).UseServer(this).Build(); + var application = CreateBuilder(config).UseServer((IServerFactory)this).Build(); var env = application.Services.GetService(); Assert.Equal("Staging", env.EnvironmentName); } @@ -312,7 +313,7 @@ namespace Microsoft.AspNet.Hosting .AddInMemoryCollection(vals); var config = builder.Build(); - var application = CreateBuilder(config).UseServer(this).Build(); + var application = CreateBuilder(config).UseServer((IServerFactory)this).Build(); var env = application.Services.GetService(); Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath); Assert.True(env.WebRootFileProvider.GetFileInfo("TextFile.txt").Exists); @@ -321,9 +322,10 @@ namespace Microsoft.AspNet.Hosting [Fact] public void IsEnvironment_Extension_Is_Case_Insensitive() { - var application = CreateBuilder().UseServer(this).Build(); - using (application.Start()) + var application = CreateBuilder().UseServer((IServerFactory)this).Build(); + using (application) { + application.Start(); var env = application.Services.GetRequiredService(); Assert.True(env.IsEnvironment(EnvironmentName.Production)); Assert.True(env.IsEnvironment("producTion")); @@ -364,7 +366,7 @@ namespace Microsoft.AspNet.Hosting var application = CreateApplication(requestDelegate); // Act - var disposable = application.Start(); + application.Start(); // Assert Assert.NotNull(httpContext); @@ -388,7 +390,7 @@ namespace Microsoft.AspNet.Hosting var application = CreateApplication(requestDelegate); // Act - var disposable = application.Start(); + application.Start(); // Assert Assert.NotNull(httpContext); @@ -399,11 +401,12 @@ namespace Microsoft.AspNet.Hosting public void WebApplication_InvokesConfigureMethodsOnlyOnce() { var application = CreateBuilder() - .UseServer(this) + .UseServer((IServerFactory)this) .UseStartup() .Build(); - using (application.Start()) + using (application) { + application.Start(); var services = application.Services; var services2 = application.Services; Assert.Equal(1, CountStartup.ConfigureCount); @@ -431,7 +434,7 @@ namespace Microsoft.AspNet.Hosting public void WebApplication_ThrowsForBadConfigureServiceSignature() { var builder = CreateBuilder() - .UseServer(this) + .UseServer((IServerFactory)this) .UseStartup(); var ex = Assert.Throws(() => builder.Build()); @@ -447,7 +450,7 @@ namespace Microsoft.AspNet.Hosting private IWebApplication CreateApplication(RequestDelegate requestDelegate) { var builder = CreateBuilder() - .UseServer(this) + .UseServer((IServerFactory)this) .Configure( appBuilder => { @@ -459,10 +462,11 @@ namespace Microsoft.AspNet.Hosting private void RunMapPath(string virtualPath, string expectedSuffix) { - var application = CreateBuilder().UseServer(this).Build(); + var application = CreateBuilder().UseServer((IServerFactory)this).Build(); - using (application.Start()) + using (application) { + application.Start(); var env = application.Services.GetRequiredService(); // MapPath requires webroot to be set, we don't care // about file provider so just set it here @@ -473,7 +477,7 @@ namespace Microsoft.AspNet.Hosting } } - private WebApplicationBuilder CreateBuilder(IConfiguration config = null) + private IWebApplicationBuilder CreateBuilder(IConfiguration config = null) { return new WebApplicationBuilder().UseConfiguration(config ?? new ConfigurationBuilder().Build()).UseStartup("Microsoft.AspNet.Hosting.Tests"); }