From 77e2dc263f11655312d4c73bb8e22d7b6254d485 Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Mon, 30 Mar 2015 18:05:51 -0700 Subject: [PATCH] Hosting rework #2 - Merge HostingContext and HostingEngine - Cleanup usage via builder pattern --- .../IConfigureHostingEnvironment.cs | 12 - .../IHostingEnvironment.cs | 7 +- .../ApplicationLifetime.cs | 4 - .../HostingContext.cs | 34 -- src/Microsoft.AspNet.Hosting/HostingEngine.cs | 300 ++++++++---------- .../HostingEnvironment.cs | 13 +- .../HostingEnvironmentExtensions.cs | 18 ++ .../HostingFactory.cs | 35 ++ .../IHostingEngine.cs | 32 ++ .../IHostingFactory.cs | 12 + .../IHttpContextAccessor.cs | 1 + ...RootHostingServiceCollectionInitializer.cs | 66 ++++ src/Microsoft.AspNet.Hosting/Program.cs | 17 +- .../Startup/IStartupLoader.cs | 16 + ...ApplicationStartup.cs => StartupLoader.cs} | 61 ++-- .../Startup/StartupMethods.cs | 8 +- src/Microsoft.AspNet.Hosting/WebHost.cs | 63 ++++ src/Microsoft.AspNet.TestHost/TestServer.cs | 111 ++++--- .../TestServerBuilder.cs | 126 ++++++++ .../HostingEngineTests.cs | 137 ++++---- .../Microsoft.AspNet.Hosting.Tests.xproj | 3 + .../StartupManagerTests.cs | 77 ++++- .../project.json | 1 + .../Microsoft.AspNet.TestHost.Tests.xproj | 7 +- .../RequestBuilderTests.cs | 4 +- .../TestServerTests.cs | 120 ++++++- 26 files changed, 887 insertions(+), 398 deletions(-) delete mode 100644 src/Microsoft.AspNet.Hosting.Interfaces/IConfigureHostingEnvironment.cs delete mode 100644 src/Microsoft.AspNet.Hosting/HostingContext.cs create mode 100644 src/Microsoft.AspNet.Hosting/HostingEnvironmentExtensions.cs create mode 100644 src/Microsoft.AspNet.Hosting/HostingFactory.cs create mode 100644 src/Microsoft.AspNet.Hosting/IHostingEngine.cs create mode 100644 src/Microsoft.AspNet.Hosting/IHostingFactory.cs create mode 100644 src/Microsoft.AspNet.Hosting/Internal/RootHostingServiceCollectionInitializer.cs create mode 100644 src/Microsoft.AspNet.Hosting/Startup/IStartupLoader.cs rename src/Microsoft.AspNet.Hosting/Startup/{ApplicationStartup.cs => StartupLoader.cs} (76%) create mode 100644 src/Microsoft.AspNet.Hosting/WebHost.cs create mode 100644 src/Microsoft.AspNet.TestHost/TestServerBuilder.cs diff --git a/src/Microsoft.AspNet.Hosting.Interfaces/IConfigureHostingEnvironment.cs b/src/Microsoft.AspNet.Hosting.Interfaces/IConfigureHostingEnvironment.cs deleted file mode 100644 index 6ea92fde03..0000000000 --- a/src/Microsoft.AspNet.Hosting.Interfaces/IConfigureHostingEnvironment.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.Framework.Runtime; - -namespace Microsoft.AspNet.Hosting -{ - public interface IConfigureHostingEnvironment - { - void Configure(IHostingEnvironment hostingEnv); - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting.Interfaces/IHostingEnvironment.cs b/src/Microsoft.AspNet.Hosting.Interfaces/IHostingEnvironment.cs index 393c963e20..da774985c4 100644 --- a/src/Microsoft.AspNet.Hosting.Interfaces/IHostingEnvironment.cs +++ b/src/Microsoft.AspNet.Hosting.Interfaces/IHostingEnvironment.cs @@ -7,10 +7,13 @@ namespace Microsoft.AspNet.Hosting { public interface IHostingEnvironment { + // This must be settable! string EnvironmentName { get; set; } - string WebRootPath { get; } + // This must be settable! + string WebRootPath { get; set; } - IFileProvider WebRootFileProvider { get; } + // This must be settable! + IFileProvider WebRootFileProvider { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs b/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs index 3d9fa53b8b..a8bb03860b 100644 --- a/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs +++ b/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs @@ -14,10 +14,6 @@ namespace Microsoft.AspNet.Hosting private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource(); private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource(); - public ApplicationLifetime() - { - } - /// /// Triggered when the application host is performing a graceful shutdown. /// Request may still be in flight. Shutdown will block until this event completes. diff --git a/src/Microsoft.AspNet.Hosting/HostingContext.cs b/src/Microsoft.AspNet.Hosting/HostingContext.cs deleted file mode 100644 index 3ff679a6b3..0000000000 --- a/src/Microsoft.AspNet.Hosting/HostingContext.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Hosting.Startup; -using Microsoft.Framework.ConfigurationModel; -using Microsoft.Framework.DependencyInjection; - -namespace Microsoft.AspNet.Hosting -{ - public class HostingContext - { - public IConfiguration Configuration { get; set; } - - public IApplicationBuilder Builder { get; set; } - - public string ApplicationName { get; set; } - public string WebRootPath { get; set; } - public string EnvironmentName { get; set; } - public StartupMethods StartupMethods { get; set; } - public RequestDelegate ApplicationDelegate { get; set; } - - public IServiceCollection Services { get; } = new ServiceCollection(); - - // Result of ConfigureServices - public IServiceProvider ApplicationServices { get; set; } - - public string ServerFactoryLocation { get; set; } - public IServerFactory ServerFactory { get; set; } - public IServerInformation Server { get; set; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/HostingEngine.cs b/src/Microsoft.AspNet.Hosting/HostingEngine.cs index 01fae429d5..69ca5e6883 100644 --- a/src/Microsoft.AspNet.Hosting/HostingEngine.cs +++ b/src/Microsoft.AspNet.Hosting/HostingEngine.cs @@ -5,251 +5,219 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting.Builder; -using Microsoft.AspNet.Hosting.Internal; using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Startup; +using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.Runtime; -using Microsoft.Framework.Runtime.Infrastructure; namespace Microsoft.AspNet.Hosting { - public class HostingEngine + public class HostingEngine : IHostingEngine { - private const string EnvironmentKey = "ASPNET_ENV"; + private readonly IServiceCollection _applicationServiceCollection; + private readonly IStartupLoader _startupLoader; + private readonly ApplicationLifetime _applicationLifetime; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly IConfiguration _config; - private readonly IServiceProvider _fallbackServices; - private readonly ApplicationLifetime _appLifetime; - private readonly IApplicationEnvironment _applicationEnvironment; - private readonly HostingEnvironment _hostingEnvironment; + // Start/ApplicationServices block use methods + private bool _useDisabled; private IServerLoader _serverLoader; private IApplicationBuilderFactory _builderFactory; + private IApplicationBuilder _builder; + private IServiceProvider _applicationServices; - public HostingEngine() : this(fallbackServices: null) { } + // Only one of these should be set + private string _startupAssemblyName; + private StartupMethods _startup; - public HostingEngine(IServiceProvider fallbackServices) + // Only one of these should be set + private string _serverFactoryLocation; + private IServerFactory _serverFactory; + private IServerInformation _serverInstance; + + public HostingEngine(IServiceCollection appServices, IStartupLoader startupLoader, IConfiguration config, IHostingEnvironment hostingEnv, string appName) { - _fallbackServices = fallbackServices ?? CallContextServiceLocator.Locator.ServiceProvider; - _appLifetime = new ApplicationLifetime(); - _applicationEnvironment = _fallbackServices.GetRequiredService(); - _hostingEnvironment = new HostingEnvironment(_applicationEnvironment); - _fallbackServices = new WrappingServiceProvider(_fallbackServices, _hostingEnvironment, _appLifetime); + _config = config ?? new Configuration(); + _applicationServiceCollection = appServices; + _startupLoader = startupLoader; + _startupAssemblyName = appName; + _applicationLifetime = new ApplicationLifetime(); + _hostingEnvironment = hostingEnv; } - public IDisposable Start(HostingContext context) + public virtual IDisposable Start() { - EnsureContextDefaults(context); - EnsureApplicationServices(context); - EnsureBuilder(context); - EnsureServerFactory(context); - InitalizeServerFactory(context); - EnsureApplicationDelegate(context); + EnsureApplicationServices(); + EnsureBuilder(); + EnsureServer(); - var contextFactory = context.ApplicationServices.GetRequiredService(); - var contextAccessor = context.ApplicationServices.GetRequiredService(); - var server = context.ServerFactory.Start(context.Server, + var applicationDelegate = BuildApplicationDelegate(); + + var _contextFactory = _applicationServices.GetRequiredService(); + var _contextAccessor = _applicationServices.GetRequiredService(); + var server = _serverFactory.Start(_serverInstance, features => { - var httpContext = contextFactory.CreateHttpContext(features); - contextAccessor.HttpContext = httpContext; - return context.ApplicationDelegate(httpContext); + var httpContext = _contextFactory.CreateHttpContext(features); + _contextAccessor.HttpContext = httpContext; + return applicationDelegate(httpContext); }); return new Disposable(() => { - _appLifetime.NotifyStopping(); + _applicationLifetime.NotifyStopping(); server.Dispose(); - _appLifetime.NotifyStopped(); + _applicationLifetime.NotifyStopped(); }); } - private void EnsureContextDefaults(HostingContext context) + private void EnsureApplicationServices() { - if (context.ApplicationName == null) - { - context.ApplicationName = _applicationEnvironment.ApplicationName; - } + _useDisabled = true; + EnsureStartup(); - if (context.EnvironmentName == null) - { - context.EnvironmentName = context.Configuration?.Get(EnvironmentKey) ?? HostingEnvironment.DefaultEnvironmentName; - } + _applicationServiceCollection.AddInstance(_applicationLifetime); - _hostingEnvironment.EnvironmentName = context.EnvironmentName; - - if (context.WebRootPath != null) - { - _hostingEnvironment.WebRootPath = context.WebRootPath; - } + _applicationServices = _startup.ConfigureServicesDelegate(_applicationServiceCollection); } - private void EnsureApplicationServices(HostingContext context) + private void EnsureStartup() { - if (context.ApplicationServices != null) - { - return; - } - - EnsureStartupMethods(context); - - context.ApplicationServices = context.StartupMethods.ConfigureServicesDelegate(CreateHostingServices(context)); - } - - private void EnsureStartupMethods(HostingContext context) - { - if (context.StartupMethods != null) + if (_startup != null) { return; } var diagnosticMessages = new List(); - context.StartupMethods = ApplicationStartup.LoadStartupMethods( - _fallbackServices, - context.ApplicationName, - context.EnvironmentName, + _startup = _startupLoader.Load( + _startupAssemblyName, + _hostingEnvironment.EnvironmentName, diagnosticMessages); - if (context.StartupMethods == null) + if (_startup == null) { throw new ArgumentException( - diagnosticMessages.Aggregate("Failed to find an entry point for the web application.", (a, b) => a + "\r\n" + b), - nameof(context)); + diagnosticMessages.Aggregate("Failed to find a startup entry point for the web application.", (a, b) => a + "\r\n" + b), + _startupAssemblyName); } } - private void EnsureBuilder(HostingContext context) + private void EnsureBuilder() { - if (context.Builder != null) - { - return; - } - if (_builderFactory == null) { - _builderFactory = context.ApplicationServices.GetRequiredService(); + _builderFactory = _applicationServices.GetRequiredService(); } - context.Builder = _builderFactory.CreateBuilder(); - context.Builder.ApplicationServices = context.ApplicationServices; + _builder = _builderFactory.CreateBuilder(); + _builder.ApplicationServices = _applicationServices; } - private void EnsureServerFactory(HostingContext context) + private void EnsureServer() { - if (context.ServerFactory != null) + if (_serverFactory == null) { - return; + // Blow up if we don't have a server set at this point + if (_serverFactoryLocation == null) + { + throw new InvalidOperationException("UseStartup() is required for Start()"); + } + + _serverFactory = _applicationServices.GetRequiredService().LoadServerFactory(_serverFactoryLocation); } - if (_serverLoader == null) - { - _serverLoader = context.ApplicationServices.GetRequiredService(); - } - - context.ServerFactory = _serverLoader.LoadServerFactory(context.ServerFactoryLocation); + _serverInstance = _serverFactory.Initialize(_config); + _builder.Server = _serverInstance; } - private void InitalizeServerFactory(HostingContext context) + private RequestDelegate BuildApplicationDelegate() { - if (context.Server == null) - { - context.Server = context.ServerFactory.Initialize(context.Configuration); - } - - if (context.Builder.Server == null) - { - context.Builder.Server = context.Server; - } - } - - private IServiceCollection CreateHostingServices(HostingContext context) - { - var services = Import(_fallbackServices); - - services.TryAdd(ServiceDescriptor.Transient()); - - services.TryAdd(ServiceDescriptor.Transient()); - services.TryAdd(ServiceDescriptor.Transient()); - - // TODO: Do we expect this to be provide by the runtime eventually? - services.AddLogging(); - services.TryAdd(ServiceDescriptor.Singleton()); - - // Apply user services - services.Add(context.Services); - - // Jamming in app lifetime and hosting env since these must not be replaceable - services.AddInstance(_appLifetime); - services.AddInstance(_hostingEnvironment); - - // Conjure up a RequestServices - services.AddTransient(); - - return services; - } - - private void EnsureApplicationDelegate(HostingContext context) - { - if (context.ApplicationDelegate != null) - { - return; - } - - // REVIEW: should we call EnsureApplicationServices? - var startupFilters = context.ApplicationServices.GetService>(); - var configure = context.StartupMethods.ConfigureDelegate; + var startupFilters = _applicationServices.GetService>(); + var configure = _startup.ConfigureDelegate; foreach (var filter in startupFilters) { - configure = filter.Configure(context.Builder, configure); + configure = filter.Configure(_builder, configure); } - configure(context.Builder); + configure(_builder); - context.ApplicationDelegate = context.Builder.Build(); + return _builder.Build(); } - private static IServiceCollection Import(IServiceProvider fallbackProvider) + public IServiceProvider ApplicationServices { - var services = new ServiceCollection(); - var manifest = fallbackProvider.GetRequiredService(); - foreach (var service in manifest.Services) + get { - services.AddTransient(service, sp => fallbackProvider.GetService(service)); + EnsureApplicationServices(); + return _applicationServices; } - - return services; } - private class WrappingServiceProvider : IServiceProvider + private void CheckUseAllowed() { - private readonly IServiceProvider _sp; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly IApplicationLifetime _applicationLifetime; - - public WrappingServiceProvider(IServiceProvider sp, - IHostingEnvironment hostingEnvironment, - IApplicationLifetime applicationLifetime) + if (_useDisabled) { - _sp = sp; - _hostingEnvironment = hostingEnvironment; - _applicationLifetime = applicationLifetime; + throw new InvalidOperationException("HostingEngine has already been started."); } + } - public object GetService(Type serviceType) - { - if (serviceType == typeof(IHostingEnvironment)) - { - return _hostingEnvironment; - } + // Consider cutting + public IHostingEngine UseEnvironment(string environment) + { + CheckUseAllowed(); + _hostingEnvironment.EnvironmentName = environment; + return this; + } - if (serviceType == typeof(IApplicationLifetime)) - { - return _applicationLifetime; - } + public IHostingEngine UseServer(string assemblyName) + { + CheckUseAllowed(); + _serverFactoryLocation = assemblyName; + return this; + } - return _sp.GetService(serviceType); - } + public IHostingEngine UseServer(IServerFactory factory) + { + CheckUseAllowed(); + _serverFactory = factory; + return this; + } + + public IHostingEngine UseStartup(string startupAssemblyName) + { + CheckUseAllowed(); + _startupAssemblyName = startupAssemblyName; + return this; + } + + public IHostingEngine UseStartup(Action configureApp) + { + return UseStartup(configureApp, configureServices: null); + } + + public IHostingEngine UseStartup(Action configureApp, ConfigureServicesDelegate configureServices) + { + CheckUseAllowed(); + _startup = new StartupMethods(configureApp, configureServices); + return this; + } + + public IHostingEngine UseStartup(Action configureApp, Action configureServices) + { + CheckUseAllowed(); + _startup = new StartupMethods(configureApp, + services => { + if (configureServices != null) + { + configureServices(services); + } + return services.BuildServiceProvider(); + }); + return this; } private class Disposable : IDisposable diff --git a/src/Microsoft.AspNet.Hosting/HostingEnvironment.cs b/src/Microsoft.AspNet.Hosting/HostingEnvironment.cs index b7a0249319..dac9b27d89 100644 --- a/src/Microsoft.AspNet.Hosting/HostingEnvironment.cs +++ b/src/Microsoft.AspNet.Hosting/HostingEnvironment.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Hosting.Internal; -using Microsoft.Framework.Runtime; namespace Microsoft.AspNet.Hosting { @@ -12,17 +10,10 @@ namespace Microsoft.AspNet.Hosting { internal const string DefaultEnvironmentName = "Development"; - public HostingEnvironment(IApplicationEnvironment appEnvironment) - { - EnvironmentName = DefaultEnvironmentName; - WebRootPath = HostingUtilities.GetWebRoot(appEnvironment.ApplicationBasePath); - WebRootFileProvider = new PhysicalFileProvider(WebRootPath); - } - - public string EnvironmentName { get; set; } + public string EnvironmentName { get; set; } = DefaultEnvironmentName; public string WebRootPath { get; set; } - public IFileProvider WebRootFileProvider { get; private set; } + public IFileProvider WebRootFileProvider { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/HostingEnvironmentExtensions.cs b/src/Microsoft.AspNet.Hosting/HostingEnvironmentExtensions.cs new file mode 100644 index 0000000000..cd18889034 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/HostingEnvironmentExtensions.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.FileProviders; +using Microsoft.AspNet.Hosting.Internal; + +namespace Microsoft.AspNet.Hosting +{ + public static class HostingEnvironmentExtensions + { + public static void Initialize(this IHostingEnvironment hostingEnvironment, string applicationBasePath, string environmentName) + { + hostingEnvironment.WebRootPath = HostingUtilities.GetWebRoot(applicationBasePath); + hostingEnvironment.WebRootFileProvider = new PhysicalFileProvider(hostingEnvironment.WebRootPath); + hostingEnvironment.EnvironmentName = environmentName ?? hostingEnvironment.EnvironmentName; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/HostingFactory.cs b/src/Microsoft.AspNet.Hosting/HostingFactory.cs new file mode 100644 index 0000000000..1b77dc7e59 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/HostingFactory.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNet.Hosting.Internal; +using Microsoft.AspNet.Hosting.Startup; +using Microsoft.Framework.ConfigurationModel; +using Microsoft.Framework.Runtime; + +namespace Microsoft.AspNet.Hosting +{ + public class HostingFactory : IHostingFactory + { + public const string EnvironmentKey = "ASPNET_ENV"; + + private readonly RootHostingServiceCollectionInitializer _serviceInitializer; + private readonly IStartupLoader _startupLoader; + private readonly IApplicationEnvironment _applicationEnvironment; + private readonly IHostingEnvironment _hostingEnvironment; + + public HostingFactory(RootHostingServiceCollectionInitializer initializer, IStartupLoader startupLoader, IApplicationEnvironment appEnv, IHostingEnvironment hostingEnv) + { + _serviceInitializer = initializer; + _startupLoader = startupLoader; + _applicationEnvironment = appEnv; + _hostingEnvironment = hostingEnv; + } + + public IHostingEngine Create(IConfiguration config) + { + _hostingEnvironment.Initialize(_applicationEnvironment.ApplicationBasePath, config?[EnvironmentKey]); + + return new HostingEngine(_serviceInitializer.Build(), _startupLoader, config, _hostingEnvironment, _applicationEnvironment.ApplicationName); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/IHostingEngine.cs b/src/Microsoft.AspNet.Hosting/IHostingEngine.cs new file mode 100644 index 0000000000..dbd6577b05 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/IHostingEngine.cs @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.AspNet.Hosting.Startup; +using Microsoft.Framework.DependencyInjection; + +namespace Microsoft.AspNet.Hosting +{ + public interface IHostingEngine + { + IDisposable Start(); + + // Accessing this will block Use methods + IServiceProvider ApplicationServices { get; } + + // Use methods blow up after any of the above methods are called + IHostingEngine UseEnvironment(string environment); + + // Mutually exclusive + IHostingEngine UseServer(string assemblyName); + IHostingEngine UseServer(IServerFactory factory); + + // Mutually exclusive + IHostingEngine UseStartup(string startupAssemblyName); + IHostingEngine UseStartup(Action configureApp); + IHostingEngine UseStartup(Action configureApp, ConfigureServicesDelegate configureServices); + IHostingEngine UseStartup(Action configureApp, Action configureServices); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/IHostingFactory.cs b/src/Microsoft.AspNet.Hosting/IHostingFactory.cs new file mode 100644 index 0000000000..f44b02c1db --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/IHostingFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Framework.ConfigurationModel; + +namespace Microsoft.AspNet.Hosting +{ + public interface IHostingFactory + { + IHostingEngine Create(IConfiguration config); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs b/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs index 8b1719880b..f406c8e3b3 100644 --- a/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs +++ b/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs @@ -5,6 +5,7 @@ using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Hosting { + // REVIEW: move to interfaces public interface IHttpContextAccessor { HttpContext HttpContext { get; set; } diff --git a/src/Microsoft.AspNet.Hosting/Internal/RootHostingServiceCollectionInitializer.cs b/src/Microsoft.AspNet.Hosting/Internal/RootHostingServiceCollectionInitializer.cs new file mode 100644 index 0000000000..3e9a9babec --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/Internal/RootHostingServiceCollectionInitializer.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Builder; +using Microsoft.AspNet.Hosting.Internal; +using Microsoft.AspNet.Hosting.Server; +using Microsoft.AspNet.Hosting.Startup; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; +using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime.Infrastructure; + +namespace Microsoft.AspNet.Hosting.Internal +{ + public class RootHostingServiceCollectionInitializer + { + private readonly IServiceProvider _fallbackServices; + private readonly Action _configureServices; + private readonly IHostingEnvironment _hostingEnvironment; + private readonly ILoggerFactory _loggerFactory; + + public RootHostingServiceCollectionInitializer(IServiceProvider fallbackServices, Action configureServices) + { + _fallbackServices = fallbackServices; + _configureServices = configureServices; + _hostingEnvironment = new HostingEnvironment(); + _loggerFactory = new LoggerFactory(); + } + + public IServiceCollection Build() + { + var services = new ServiceCollection(); + + // Import from manifest + var manifest = _fallbackServices.GetRequiredService(); + foreach (var service in manifest.Services) + { + services.AddTransient(service, sp => _fallbackServices.GetService(service)); + } + + services.AddInstance(_hostingEnvironment); + services.AddInstance(this); + services.AddInstance(_loggerFactory); + + services.AddTransient(); + services.AddTransient(); + + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + services.AddLogging(); + + // Conjure up a RequestServices + services.AddTransient(); + + if (_configureServices != null) + { + _configureServices(services); + } + + return services; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Hosting/Program.cs b/src/Microsoft.AspNet.Hosting/Program.cs index 47211a65ed..555fc241a8 100644 --- a/src/Microsoft.AspNet.Hosting/Program.cs +++ b/src/Microsoft.AspNet.Hosting/Program.cs @@ -33,18 +33,13 @@ namespace Microsoft.AspNet.Hosting config.AddEnvironmentVariables(); config.AddCommandLine(args); - var context = new HostingContext() - { - Configuration = config, - ServerFactoryLocation = config.Get("server"), - ApplicationName = config.Get("app") - }; - - var engine = new HostingEngine(_serviceProvider); + var engine = WebHost.CreateEngine(_serviceProvider, config) + .UseServer(config.Get("server")) + .UseStartup(config.Get("app")); - var serverShutdown = engine.Start(context); - var loggerFactory = context.ApplicationServices.GetRequiredService(); - var appShutdownService = context.ApplicationServices.GetRequiredService(); + var serverShutdown = engine.Start(); + var loggerFactory = engine.ApplicationServices.GetRequiredService(); + var appShutdownService = engine.ApplicationServices.GetRequiredService(); var shutdownHandle = new ManualResetEvent(false); appShutdownService.ShutdownRequested.Register(() => diff --git a/src/Microsoft.AspNet.Hosting/Startup/IStartupLoader.cs b/src/Microsoft.AspNet.Hosting/Startup/IStartupLoader.cs new file mode 100644 index 0000000000..cab29b61ce --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/Startup/IStartupLoader.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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; + +namespace Microsoft.AspNet.Hosting.Startup +{ + public interface IStartupLoader + { + StartupMethods Load( + string startupAssemblyName, + string environmentName, + IList diagnosticMessages); + } +} diff --git a/src/Microsoft.AspNet.Hosting/Startup/ApplicationStartup.cs b/src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs similarity index 76% rename from src/Microsoft.AspNet.Hosting/Startup/ApplicationStartup.cs rename to src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs index 811afb5590..6ebc09f04e 100644 --- a/src/Microsoft.AspNet.Hosting/Startup/ApplicationStartup.cs +++ b/src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs @@ -10,25 +10,46 @@ using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Hosting.Startup { - public static class ApplicationStartup + public class StartupLoader : IStartupLoader { - internal static ConfigureServicesDelegate DefaultBuildServiceProvider = s => s.BuildServiceProvider(); + private readonly IServiceProvider _services; - public static StartupMethods LoadStartupMethods( - IServiceProvider services, - string applicationName, + public StartupLoader(IServiceProvider services) + { + _services = services; + } + + public StartupMethods Load( + Type startupType, string environmentName, IList diagnosticMessages) { - if (string.IsNullOrEmpty(applicationName)) + var configureMethod = FindConfigureDelegate(startupType, environmentName); + var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); + + object instance = null; + if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) { - throw new ArgumentException("Value cannot be null or empty.", "applicationName"); + instance = ActivatorUtilities.GetServiceOrCreateInstance(_services, startupType); } - var assembly = Assembly.Load(new AssemblyName(applicationName)); + return new StartupMethods(configureMethod.Build(instance), servicesMethod?.Build(instance)); + } + + public StartupMethods Load( + string startupAssemblyName, + string environmentName, + IList diagnosticMessages) + { + if (string.IsNullOrEmpty(startupAssemblyName)) + { + throw new ArgumentException("Value cannot be null or empty.", nameof(startupAssemblyName)); + } + + var assembly = Assembly.Load(new AssemblyName(startupAssemblyName)); if (assembly == null) { - throw new InvalidOperationException(String.Format("The assembly '{0}' failed to load.", applicationName)); + throw new InvalidOperationException(String.Format("The assembly '{0}' failed to load.", startupAssemblyName)); } var startupNameWithEnv = "Startup" + environmentName; @@ -37,9 +58,9 @@ namespace Microsoft.AspNet.Hosting.Startup // Check the most likely places first var type = assembly.GetType(startupNameWithEnv) ?? - assembly.GetType(applicationName + "." + startupNameWithEnv) ?? + assembly.GetType(startupAssemblyName + "." + startupNameWithEnv) ?? assembly.GetType(startupNameWithoutEnv) ?? - assembly.GetType(applicationName + "." + startupNameWithoutEnv); + assembly.GetType(startupAssemblyName + "." + startupNameWithoutEnv); if (type == null) { @@ -61,29 +82,19 @@ namespace Microsoft.AspNet.Hosting.Startup throw new InvalidOperationException(String.Format("A type named '{0}' or '{1}' could not be found in assembly '{2}'.", startupNameWithEnv, startupNameWithoutEnv, - applicationName)); + startupAssemblyName)); } - var configureMethod = FindConfigureDelegate(type, environmentName); - var servicesMethod = FindConfigureServicesDelegate(type, environmentName); - - object instance = null; - if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) - { - instance = ActivatorUtilities.GetServiceOrCreateInstance(services, type); - } - - return new StartupMethods(configureMethod.Build(instance), servicesMethod?.Build(instance)); + return Load(type, environmentName, diagnosticMessages); } - - public static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) + private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); return new ConfigureBuilder(configureMethod); } - public static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) + private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) { var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false) ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); diff --git a/src/Microsoft.AspNet.Hosting/Startup/StartupMethods.cs b/src/Microsoft.AspNet.Hosting/Startup/StartupMethods.cs index 4b3a5c9c5c..4644b5a55e 100644 --- a/src/Microsoft.AspNet.Hosting/Startup/StartupMethods.cs +++ b/src/Microsoft.AspNet.Hosting/Startup/StartupMethods.cs @@ -3,16 +3,22 @@ using System; using Microsoft.AspNet.Builder; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Hosting.Startup { public class StartupMethods { + internal static ConfigureServicesDelegate DefaultBuildServiceProvider = s => s.BuildServiceProvider(); + + public StartupMethods(Action configure) + : this(configure, configureServices: null) { } + // TODO: switch to ConfigureDelegate eventually public StartupMethods(Action configure, ConfigureServicesDelegate configureServices) { ConfigureDelegate = configure; - ConfigureServicesDelegate = configureServices ?? ApplicationStartup.DefaultBuildServiceProvider; + ConfigureServicesDelegate = configureServices ?? DefaultBuildServiceProvider; } public ConfigureServicesDelegate ConfigureServicesDelegate { get; } diff --git a/src/Microsoft.AspNet.Hosting/WebHost.cs b/src/Microsoft.AspNet.Hosting/WebHost.cs new file mode 100644 index 0000000000..c1d331e0c4 --- /dev/null +++ b/src/Microsoft.AspNet.Hosting/WebHost.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Open Technologies, Inc. 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.Internal; +using Microsoft.Framework.ConfigurationModel; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Runtime.Infrastructure; + +namespace Microsoft.AspNet.Hosting +{ + public static class WebHost + { + public static IHostingEngine CreateEngine() + { + return CreateEngine(new Configuration()); + } + + public static IHostingEngine CreateEngine(Action configureServices) + { + return CreateEngine(new Configuration(), configureServices); + } + + public static IHostingEngine CreateEngine(IConfiguration config) + { + return CreateEngine(config, configureServices: null); + } + + public static IHostingEngine CreateEngine(IConfiguration config, Action configureServices) + { + return CreateEngine(fallbackServices: null, config: config, configureServices: configureServices); + } + + public static IHostingEngine CreateEngine(IServiceProvider fallbackServices, IConfiguration config) + { + return CreateEngine(fallbackServices, config, configureServices: null); + } + + public static IHostingEngine CreateEngine(IServiceProvider fallbackServices, IConfiguration config, Action configureServices) + { + return CreateFactory(fallbackServices, configureServices).Create(config); + } + + public static IHostingFactory CreateFactory() + { + return CreateFactory(fallbackServices: null, configureServices: null); + } + + public static IHostingFactory CreateFactory(Action configureServices) + { + return CreateFactory(fallbackServices: null, configureServices: configureServices); + } + + public static IHostingFactory CreateFactory(IServiceProvider fallbackServices, Action configureServices) + { + fallbackServices = fallbackServices ?? CallContextServiceLocator.Locator.ServiceProvider; + return new RootHostingServiceCollectionInitializer(fallbackServices, configureServices) + .Build() + .BuildServiceProvider() + .GetRequiredService(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TestHost/TestServer.cs b/src/Microsoft.AspNet.TestHost/TestServer.cs index 0cc7b83a65..0e3a9927b5 100644 --- a/src/Microsoft.AspNet.TestHost/TestServer.cs +++ b/src/Microsoft.AspNet.TestHost/TestServer.cs @@ -12,7 +12,6 @@ using Microsoft.AspNet.Hosting.Startup; using Microsoft.AspNet.Http; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.Runtime.Infrastructure; namespace Microsoft.AspNet.TestHost { @@ -25,63 +24,95 @@ namespace Microsoft.AspNet.TestHost private IDisposable _appInstance; private bool _disposed = false; - // REVIEW: we can configure services via AppStartup or via hostContext.Services - public TestServer(IConfiguration config, IServiceProvider serviceProvider, Action configureApp, ConfigureServicesDelegate configureServices) + public TestServer(IHostingEngine engine) { - var hostContext = new HostingContext() - { - ApplicationName = "Test App", - Configuration = config, - ServerFactory = this, - StartupMethods = new StartupMethods(configureApp, configureServices) - }; - - _appInstance = new HostingEngine(serviceProvider).Start(hostContext); + _appInstance = engine.UseServer(this).Start(); } public Uri BaseAddress { get; set; } = new Uri("http://localhost/"); + public static TestServer Create() + { + return Create(fallbackServices: null, config: null, configureApp: null, configureServices: null); + } + public static TestServer Create(Action configureApp) { - return Create(CallContextServiceLocator.Locator.ServiceProvider, configureApp, configureServices: null); + return Create(fallbackServices: null, config: null, configureApp: configureApp, configureServices: null); } public static TestServer Create(Action configureApp, Action configureServices) { - return Create(CallContextServiceLocator.Locator.ServiceProvider, configureApp, - sc => + return Create(fallbackServices: null, config: null, configureApp: configureApp, configureServices: configureServices); + } + + public static TestServer Create(IServiceProvider fallbackServices, Action configureApp, ConfigureServicesDelegate configureServices) + { + return CreateBuilder(fallbackServices, config: null, configureApp: configureApp, configureServices: configureServices).Build(); + } + + public static TestServer Create(IServiceProvider fallbackServices, IConfiguration config, Action configureApp, Action configureServices) + { + return CreateBuilder(fallbackServices, config, configureApp, configureServices).Build(); + } + + public static TestServer Create() where TStartup : class + { + return Create(fallbackServices: null, config: null, configureApp: null, configureServices: null); + } + + public static TestServer Create(Action configureApp) where TStartup : class + { + return Create(fallbackServices: null, config: null, configureApp: configureApp, configureServices: null); + } + + public static TestServer Create(Action configureApp, Action configureServices) where TStartup : class + { + return Create(fallbackServices: null, config: null, configureApp: configureApp, configureServices: configureServices); + } + + public static TestServer Create(IServiceProvider fallbackServices, IConfiguration config, Action configureApp, Action configureServices) where TStartup : class + { + var builder = CreateBuilder(fallbackServices, config, configureApp, configureServices); + builder.StartupType = typeof(TStartup); + return builder.Build(); + } + + public static TestServerBuilder CreateBuilder() where TStartup : class + { + var builder = CreateBuilder(fallbackServices: null, config: null, configureApp: null, configureServices: null); + builder.StartupType = typeof(TStartup); + return builder; + } + + public static TestServerBuilder CreateBuilder(IServiceProvider fallbackServices, IConfiguration config, Action configureApp, Action configureServices) where TStartup : class + { + var builder = CreateBuilder(fallbackServices, config, configureApp, configureServices); + builder.StartupType = typeof(TStartup); + return builder; + } + + public static TestServerBuilder CreateBuilder(IServiceProvider fallbackServices, IConfiguration config, Action configureApp, Action configureServices) + { + return CreateBuilder(fallbackServices, config, configureApp, + services => { if (configureServices != null) { - configureServices(sc); + configureServices(services); } - return sc.BuildServiceProvider(); + return services.BuildServiceProvider(); }); } - public static TestServer Create(IServiceProvider serviceProvider, Action configureApp) + public static TestServerBuilder CreateBuilder(IServiceProvider fallbackServices, IConfiguration config, Action configureApp, ConfigureServicesDelegate configureServices) { - return Create(serviceProvider, configureApp, configureServices: null); - } - - public static TestServer Create(IServiceProvider serviceProvider, Action configureApp, Action configureServices) - { - return Create(serviceProvider, configureApp, - sc => - { - if (configureServices != null) - { - configureServices(sc); - } - return sc.BuildServiceProvider(); - }); - } - - public static TestServer Create(IServiceProvider serviceProvider, Action configureApp, ConfigureServicesDelegate configureServices) - { - // REVIEW: do we need an overload that takes Config for Create? - var config = new Configuration(); - return new TestServer(config, serviceProvider, configureApp, configureServices); + return new TestServerBuilder + { + FallbackServices = fallbackServices, + Startup = new StartupMethods(configureApp, configureServices), + Config = config + }; } public HttpMessageHandler CreateHandler() @@ -145,4 +176,4 @@ namespace Microsoft.AspNet.TestHost } } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.TestHost/TestServerBuilder.cs b/src/Microsoft.AspNet.TestHost/TestServerBuilder.cs new file mode 100644 index 0000000000..74425f89f5 --- /dev/null +++ b/src/Microsoft.AspNet.TestHost/TestServerBuilder.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Runtime.Versioning; +using Microsoft.AspNet.Hosting; +using Microsoft.AspNet.Hosting.Startup; +using Microsoft.Framework.ConfigurationModel; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime.Infrastructure; + +namespace Microsoft.AspNet.TestHost +{ + public class TestServerBuilder + { + public IServiceProvider FallbackServices { get; set; } + public string Environment { get; set; } + public string ApplicationName { get; set; } + public string ApplicationBasePath { get; set; } + + public Type StartupType { get; set; } + public string StartupAssemblyName { get; set; } + public IConfiguration Config { get; set; } + + public IServiceCollection AdditionalServices { get; } = new ServiceCollection(); + + public StartupMethods Startup { get; set; } + + public TestServer Build() + { + var fallbackServices = FallbackServices ?? CallContextServiceLocator.Locator.ServiceProvider; + var config = Config ?? new Configuration(); + if (Environment != null) + { + config[HostingFactory.EnvironmentKey] = Environment; + } + if (ApplicationName != null || ApplicationBasePath != null) + { + var appEnv = new TestApplicationEnvironment(fallbackServices.GetRequiredService()); + appEnv.ApplicationBasePath = ApplicationBasePath; + appEnv.ApplicationName = ApplicationName; + AdditionalServices.AddInstance(appEnv); + } + + var engine = WebHost.CreateEngine(fallbackServices, + config, + services => services.Add(AdditionalServices)); + if (StartupType != null) + { + Startup = new StartupLoader(fallbackServices).Load(StartupType, Environment, new List()); + } + if (Startup != null) + { + engine.UseStartup(Startup.ConfigureDelegate, Startup.ConfigureServicesDelegate); + } + else if (StartupAssemblyName != null) + { + engine.UseStartup(StartupAssemblyName); + } + + return new TestServer(engine); + } + + private class TestApplicationEnvironment : IApplicationEnvironment + { + private readonly IApplicationEnvironment _appEnv; + private string _appName; + private string _appBasePath; + + public TestApplicationEnvironment(IApplicationEnvironment appEnv) + { + _appEnv = appEnv; + } + + public string ApplicationBasePath + { + get + { + return _appBasePath ?? _appEnv.ApplicationBasePath; + } + set + { + _appBasePath = value; + } + } + + public string ApplicationName + { + get + { + return _appName ?? _appEnv.ApplicationName; + } + set + { + _appName = value; + } + } + + public string Configuration + { + get + { + return _appEnv.Configuration; + } + } + + public FrameworkName RuntimeFramework + { + get + { + return _appEnv.RuntimeFramework; + } + } + + public string Version + { + get + { + throw new NotImplementedException(); + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs b/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs index 7bebf1c0e7..6e2547f0ed 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs +++ b/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs @@ -8,8 +8,10 @@ using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.FeatureModel; using Microsoft.AspNet.Hosting.Server; +using Microsoft.AspNet.Hosting.Startup; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.OptionsModel; using Xunit; namespace Microsoft.AspNet.Hosting @@ -18,58 +20,60 @@ namespace Microsoft.AspNet.Hosting { private readonly IList _startInstances = new List(); + [Fact] + public void HostingEngineThrowsWithNoServer() + { + Assert.Throws(() => WebHost.CreateEngine().Start()); + } + [Fact] public void HostingEngineCanBeStarted() { - var context = new HostingContext - { - ServerFactory = this, - ApplicationName = "Microsoft.AspNet.Hosting.Tests" - }; + var engine = WebHost.CreateEngine() + .UseServer(this) + .UseStartup("Microsoft.AspNet.Hosting.Tests") + .Start(); - var engineStart = new HostingEngine().Start(context); - - Assert.NotNull(engineStart); + Assert.NotNull(engine); Assert.Equal(1, _startInstances.Count); Assert.Equal(0, _startInstances[0].DisposeCalls); - engineStart.Dispose(); + engine.Dispose(); Assert.Equal(1, _startInstances[0].DisposeCalls); } [Fact] - public void ApplicationNameDefaultsToApplicationEnvironmentName() + public void CanReplaceHostingFactory() { - var context = new HostingContext - { - ServerFactory = this - }; + var factory = WebHost.CreateFactory(services => services.AddTransient()); - var engine = new HostingEngine(); + Assert.NotNull(factory as TestEngineFactory); + } - using (engine.Start(context)) - { - Assert.Equal("Microsoft.AspNet.Hosting.Tests", context.ApplicationName); - } + [Fact] + public void CanReplaceStartupLoader() + { + var engine = WebHost.CreateEngine(services => services.AddTransient()) + .UseServer(this) + .UseStartup("Microsoft.AspNet.Hosting.Tests"); + + Assert.Throws(() => engine.Start()); + } + + [Fact] + public void CanCreateApplicationServicesWithAddedServices() + { + var engineStart = WebHost.CreateEngine(services => services.AddOptions()); + Assert.NotNull(engineStart.ApplicationServices.GetRequiredService>()); } [Fact] public void EnvDefaultsToDevelopmentIfNoConfig() { - var context = new HostingContext - { - ServerFactory = this - }; - - var engine = new HostingEngine(); - - using (engine.Start(context)) - { - Assert.Equal("Development", context.EnvironmentName); - var env = context.ApplicationServices.GetRequiredService(); - Assert.Equal("Development", env.EnvironmentName); - } + var engine = WebHost.CreateEngine(new Configuration()); + var env = engine.ApplicationServices.GetRequiredService(); + Assert.Equal("Development", env.EnvironmentName); } [Fact] @@ -83,32 +87,16 @@ namespace Microsoft.AspNet.Hosting var config = new Configuration() .Add(new MemoryConfigurationSource(vals)); - var context = new HostingContext - { - ServerFactory = this, - Configuration = config - }; - - var engine = new HostingEngine(); - - using (engine.Start(context)) - { - Assert.Equal("Staging", context.EnvironmentName); - var env = context.ApplicationServices.GetRequiredService(); - Assert.Equal("Staging", env.EnvironmentName); - } + var engine = WebHost.CreateEngine(config); + var env = engine.ApplicationServices.GetRequiredService(); + Assert.Equal("Staging", env.EnvironmentName); } [Fact] public void WebRootCanBeResolvedFromTheProjectJson() { - var context = new HostingContext - { - ServerFactory = this - }; - - var engineStart = new HostingEngine().Start(context); - var env = context.ApplicationServices.GetRequiredService(); + var engine = WebHost.CreateEngine().UseServer(this); + var env = engine.ApplicationServices.GetRequiredService(); Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath); Assert.True(env.WebRootFileProvider.GetFileInfo("TextFile.txt").Exists); } @@ -116,16 +104,11 @@ namespace Microsoft.AspNet.Hosting [Fact] public void IsEnvironment_Extension_Is_Case_Insensitive() { - var context = new HostingContext - { - ServerFactory = this - }; + var engine = WebHost.CreateEngine().UseServer(this); - var engine = new HostingEngine(); - - using (engine.Start(context)) + using (engine.Start()) { - var env = context.ApplicationServices.GetRequiredService(); + var env = engine.ApplicationServices.GetRequiredService(); Assert.True(env.IsEnvironment("Development")); Assert.True(env.IsEnvironment("developMent")); } @@ -141,27 +124,17 @@ namespace Microsoft.AspNet.Hosting [InlineData(@"sub/sub2\sub3\", @"sub/sub2/sub3/")] public void MapPath_Facts(string virtualPath, string expectedSuffix) { - var context = new HostingContext - { - ServerFactory = this - }; + var engine = WebHost.CreateEngine().UseServer(this); - var engine = new HostingEngine(); - - using (engine.Start(context)) + using (engine.Start()) { - var env = context.ApplicationServices.GetRequiredService(); + var env = engine.ApplicationServices.GetRequiredService(); var mappedPath = env.MapPath(virtualPath); expectedSuffix = expectedSuffix.Replace('/', Path.DirectorySeparatorChar); Assert.Equal(Path.Combine(env.WebRootPath, expectedSuffix), mappedPath); } } - public void Initialize(IApplicationBuilder builder) - { - - } - public IServerInformation Initialize(IConfiguration configuration) { return null; @@ -190,5 +163,21 @@ namespace Microsoft.AspNet.Hosting DisposeCalls += 1; } } + + private class TestLoader : IStartupLoader + { + public StartupMethods Load(string startupAssemblyName, string environmentName, IList diagnosticMessages) + { + throw new NotImplementedException(); + } + } + + private class TestEngineFactory : IHostingFactory + { + public IHostingEngine Create(IConfiguration config) + { + throw new NotImplementedException(); + } + } } } diff --git a/test/Microsoft.AspNet.Hosting.Tests/Microsoft.AspNet.Hosting.Tests.xproj b/test/Microsoft.AspNet.Hosting.Tests/Microsoft.AspNet.Hosting.Tests.xproj index 8f9318a85a..d4b7ee1bcd 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/Microsoft.AspNet.Hosting.Tests.xproj +++ b/test/Microsoft.AspNet.Hosting.Tests/Microsoft.AspNet.Hosting.Tests.xproj @@ -14,5 +14,8 @@ 2.0 18007 + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Hosting.Tests/StartupManagerTests.cs b/test/Microsoft.AspNet.Hosting.Tests/StartupManagerTests.cs index a7ecf91aaa..cce2931e22 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/StartupManagerTests.cs +++ b/test/Microsoft.AspNet.Hosting.Tests/StartupManagerTests.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting.Fakes; using Microsoft.AspNet.Hosting.Startup; @@ -24,7 +26,7 @@ namespace Microsoft.AspNet.Hosting.Tests var services = serviceCollection.BuildServiceProvider(); var diagnosticMessages = new List(); - var startup = ApplicationStartup.LoadStartupMethods(services, "Microsoft.AspNet.Hosting.Tests", "WithServices", diagnosticMessages); + var startup = new StartupLoader(services).Load("Microsoft.AspNet.Hosting.Tests", "WithServices", diagnosticMessages); var app = new ApplicationBuilder(services); app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); @@ -47,7 +49,7 @@ namespace Microsoft.AspNet.Hosting.Tests var services = new ServiceCollection().BuildServiceProvider(); var diagnosticMesssages = new List(); - var startup = ApplicationStartup.LoadStartupMethods(services, "Microsoft.AspNet.Hosting.Tests", environment ?? "", diagnosticMesssages); + var startup = new StartupLoader(services).Load("Microsoft.AspNet.Hosting.Tests", environment ?? "", diagnosticMesssages); var app = new ApplicationBuilder(services); app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection()); @@ -67,7 +69,7 @@ namespace Microsoft.AspNet.Hosting.Tests var services = serviceCollection.BuildServiceProvider(); var diagnosticMessages = new List(); - var ex = Assert.Throws(() => ApplicationStartup.LoadStartupMethods(services, "Microsoft.AspNet.Hosting.Tests", "Boom", diagnosticMessages)); + var ex = Assert.Throws(() => new StartupLoader(services).Load("Microsoft.AspNet.Hosting.Tests", "Boom", diagnosticMessages)); Assert.Equal("A method named 'ConfigureBoom' or 'Configure' in the type 'Microsoft.AspNet.Hosting.Fakes.StartupBoom' could not be found.", ex.Message); } @@ -78,7 +80,7 @@ namespace Microsoft.AspNet.Hosting.Tests var services = serviceCollection.BuildServiceProvider(); var diagnosticMessages = new List(); - var startup = ApplicationStartup.LoadStartupMethods(services, "Microsoft.AspNet.Hosting.Tests", "WithNullConfigureServices", diagnosticMessages); + var startup = new StartupLoader(services).Load("Microsoft.AspNet.Hosting.Tests", "WithNullConfigureServices", diagnosticMessages); var app = new ApplicationBuilder(services); app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection()); @@ -94,7 +96,7 @@ namespace Microsoft.AspNet.Hosting.Tests var services = serviceCollection.BuildServiceProvider(); var diagnosticMessages = new List(); - var startup = ApplicationStartup.LoadStartupMethods(services, "Microsoft.AspNet.Hosting.Tests", "WithConfigureServices", diagnosticMessages); + var startup = new StartupLoader(services).Load("Microsoft.AspNet.Hosting.Tests", "WithConfigureServices", diagnosticMessages); var app = new ApplicationBuilder(services); app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); @@ -104,6 +106,71 @@ namespace Microsoft.AspNet.Hosting.Tests Assert.True(foo.Invoked); } + [Fact] + public void StartupLoaderCanLoadByType() + { + var serviceCollection = new ServiceCollection(); + var services = serviceCollection.BuildServiceProvider(); + + var diagnosticMessages = new List(); + var startup = new StartupLoader(services).Load(typeof(TestStartup), "", diagnosticMessages); + + var app = new ApplicationBuilder(services); + app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); + startup.ConfigureDelegate(app); + + var foo = app.ApplicationServices.GetRequiredService(); + Assert.Equal("Configure", foo.Message); + } + + [Fact] + public void StartupLoaderCanLoadByTypeWithEnvironment() + { + var serviceCollection = new ServiceCollection(); + var services = serviceCollection.BuildServiceProvider(); + + var diagnosticMessages = new List(); + var startup = new StartupLoader(services).Load(typeof(TestStartup), "No", diagnosticMessages); + + var app = new ApplicationBuilder(services); + app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); + + var ex = Assert.Throws(() => startup.ConfigureDelegate(app)); + Assert.IsAssignableFrom(typeof(InvalidOperationException), ex.InnerException); + } + + public class SimpleService + { + public SimpleService() + { + } + + public string Message { get; set; } + } + + public class TestStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + } + + public void ConfigureNoServices(IServiceCollection services) + { + } + + public void Configure(IApplicationBuilder app) + { + var service = app.ApplicationServices.GetRequiredService(); + service.Message = "Configure"; + } + + public void ConfigureNo(IApplicationBuilder app) + { + var service = app.ApplicationServices.GetRequiredService(); + } + } + public void ConfigurationMethodCalled(object instance) { _configurationMethodCalledList.Add(instance); diff --git a/test/Microsoft.AspNet.Hosting.Tests/project.json b/test/Microsoft.AspNet.Hosting.Tests/project.json index aa3d3b7bb0..6dc7086527 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/project.json +++ b/test/Microsoft.AspNet.Hosting.Tests/project.json @@ -3,6 +3,7 @@ "Microsoft.AspNet.Hosting": "1.0.0-*", "Microsoft.AspNet.Owin": "1.0.0-*", "Microsoft.Framework.OptionsModel": "1.0.0-*", + "Microsoft.Framework.Runtime.Interfaces": "1.0.0-*", "xunit.runner.aspnet": "2.0.0-aspnet-*" }, "frameworks": { diff --git a/test/Microsoft.AspNet.TestHost.Tests/Microsoft.AspNet.TestHost.Tests.xproj b/test/Microsoft.AspNet.TestHost.Tests/Microsoft.AspNet.TestHost.Tests.xproj index b70af08ea5..55caa51ccf 100644 --- a/test/Microsoft.AspNet.TestHost.Tests/Microsoft.AspNet.TestHost.Tests.xproj +++ b/test/Microsoft.AspNet.TestHost.Tests/Microsoft.AspNet.TestHost.Tests.xproj @@ -1,4 +1,4 @@ - + 14.0 @@ -13,5 +13,8 @@ 2.0 + + + - + \ No newline at end of file diff --git a/test/Microsoft.AspNet.TestHost.Tests/RequestBuilderTests.cs b/test/Microsoft.AspNet.TestHost.Tests/RequestBuilderTests.cs index 0f637c7d24..6a005fd0dc 100644 --- a/test/Microsoft.AspNet.TestHost.Tests/RequestBuilderTests.cs +++ b/test/Microsoft.AspNet.TestHost.Tests/RequestBuilderTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNet.TestHost [Fact] public void AddRequestHeader() { - TestServer server = TestServer.Create(app => { }); + var server = TestServer.Create(app => { }); server.CreateRequest("/") .AddHeader("Host", "MyHost:90") .And(request => @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.TestHost [Fact] public void AddContentHeaders() { - TestServer server = TestServer.Create(app => { }); + var server = TestServer.Create(app => { }); server.CreateRequest("/") .AddHeader("Content-Type", "Test/Value") .And(request => diff --git a/test/Microsoft.AspNet.TestHost.Tests/TestServerTests.cs b/test/Microsoft.AspNet.TestHost.Tests/TestServerTests.cs index f3a3fc0bef..72f7277990 100644 --- a/test/Microsoft.AspNet.TestHost.Tests/TestServerTests.cs +++ b/test/Microsoft.AspNet.TestHost.Tests/TestServerTests.cs @@ -2,15 +2,21 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using System.IO; using System.Net; using System.Net.Http; +using System.Runtime.Versioning; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Hosting; +using Microsoft.AspNet.Hosting.Startup; using Microsoft.AspNet.Http; +using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Logging; +using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime.Infrastructure; using Xunit; namespace Microsoft.AspNet.TestHost @@ -32,7 +38,7 @@ namespace Microsoft.AspNet.TestHost var services = new ServiceCollection().BuildServiceProvider(); // Act & Assert - Assert.Throws(() => TestServer.Create(services, new Startup().Configuration)); + Assert.Throws(() => TestServer.Create(services, new Configuration(), new Startup().Configure, configureServices: null)); } [Fact] @@ -50,6 +56,54 @@ namespace Microsoft.AspNet.TestHost Assert.Equal("RequestServices:True", result); } + [Fact] + public async Task CanChangeApplicationName() + { + var fallbackServices = CallContextServiceLocator.Locator.ServiceProvider; + var appName = "gobblegobble"; + + var builder = TestServer.CreateBuilder(fallbackServices, new Configuration(), + app => + { + app.Run(context => + { + var appEnv = app.ApplicationServices.GetRequiredService(); + return context.Response.WriteAsync("AppName:" + appEnv.ApplicationName); + }); + }, + configureServices: null); + + builder.ApplicationName = appName; + var server = builder.Build(); + + string result = await server.CreateClient().GetStringAsync("/path"); + Assert.Equal("AppName:" + appName, result); + } + + [Fact] + public async Task CanChangeAppPath() + { + var fallbackServices = CallContextServiceLocator.Locator.ServiceProvider; + var appPath = "."; + + var builder = TestServer.CreateBuilder(fallbackServices, new Configuration(), + app => + { + app.Run(context => + { + var env = app.ApplicationServices.GetRequiredService(); + return context.Response.WriteAsync("AppPath:" + env.ApplicationBasePath); + }); + }, + configureServices: null); + + builder.ApplicationBasePath = appPath; + var server = builder.Build(); + + string result = await server.CreateClient().GetStringAsync("/path"); + Assert.Equal("AppPath:" + appPath, result); + } + [Fact] public async Task CanAccessLogger() { @@ -97,16 +151,13 @@ namespace Microsoft.AspNet.TestHost { TestServer server = TestServer.Create(app => { - var a = app.ApplicationServices.GetRequiredService(); - app.Run(context => { - var b = app.ApplicationServices.GetRequiredService(); var accessor = app.ApplicationServices.GetRequiredService(); return context.Response.WriteAsync("HasContext:" + (accessor.Accessor.HttpContext != null)); }); }, - services => services.AddSingleton().BuildServiceProvider()); + services => services.AddSingleton()); string result = await server.CreateClient().GetStringAsync("/path"); Assert.Equal("HasContext:True", result); @@ -188,19 +239,70 @@ namespace Microsoft.AspNet.TestHost Assert.Throws(() => { string result = server.CreateClient().GetStringAsync("/path").Result; }); } + [Fact] + public async Task CanCreateViaStartupType() + { + TestServer server = TestServer.Create(); + HttpResponseMessage result = await server.CreateClient().GetAsync("/"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal("FoundService:True", await result.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task CanCreateViaStartupTypeAndSpecifyEnv() + { + var builder = TestServer.CreateBuilder(); + builder.Environment = "Foo"; + var server = builder.Build(); + HttpResponseMessage result = await server.CreateClient().GetAsync("/"); + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal("FoundFoo:False", await result.Content.ReadAsStringAsync()); + } + public class Startup { - public void Configuration(IApplicationBuilder builder) + public void Configure(IApplicationBuilder builder) { builder.Run(ctx => ctx.Response.WriteAsync("Startup")); } } - public class AnotherStartup + public class SimpleService { - public void Configuration(IApplicationBuilder builder) + public SimpleService() { - builder.Run(ctx => ctx.Response.WriteAsync("Another Startup")); + } + + public string Message { get; set; } + } + + public class TestStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); + } + + public void ConfigureFooServices(IServiceCollection services) + { + } + + public void Configure(IApplicationBuilder app) + { + app.Run(context => + { + var service = app.ApplicationServices.GetRequiredService(); + return context.Response.WriteAsync("FoundService:" + (service != null)); + }); + } + + public void ConfigureFoo(IApplicationBuilder app) + { + app.Run(context => + { + var service = app.ApplicationServices.GetService(); + return context.Response.WriteAsync("FoundFoo:" + (service != null)); + }); } } }