diff --git a/src/Microsoft.AspNet.Hosting.Interfaces/IConfigureHostingEnvironment.cs b/src/Microsoft.AspNet.Hosting.Interfaces/IConfigureHostingEnvironment.cs deleted file mode 100644 index 6ea92fde03c9..000000000000 --- 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 393c963e20ad..da774985c4b2 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 3d9fa53b8b7b..a8bb03860bd3 100644 --- a/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs +++ b/src/Microsoft.AspNet.Hosting/ApplicationLifetime.cs @@ -14,10 +14,6 @@ public class ApplicationLifetime : IApplicationLifetime 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 3ff679a6b30c..000000000000 --- 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 01fae429d53e..69ca5e688359 100644 --- a/src/Microsoft.AspNet.Hosting/HostingEngine.cs +++ b/src/Microsoft.AspNet.Hosting/HostingEngine.cs @@ -5,251 +5,219 @@ 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); - - var contextFactory = context.ApplicationServices.GetRequiredService(); - var contextAccessor = context.ApplicationServices.GetRequiredService(); - var server = context.ServerFactory.Start(context.Server, + EnsureApplicationServices(); + EnsureBuilder(); + EnsureServer(); + + 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()"); + } - if (_serverLoader == null) - { - _serverLoader = context.ApplicationServices.GetRequiredService(); + _serverFactory = _applicationServices.GetRequiredService().LoadServerFactory(_serverFactoryLocation); } - 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) + var startupFilters = _applicationServices.GetService>(); + var configure = _startup.ConfigureDelegate; + foreach (var filter in startupFilters) { - context.Builder.Server = context.Server; + configure = filter.Configure(_builder, configure); } - } - - 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); + configure(_builder); - // Conjure up a RequestServices - services.AddTransient(); - - return services; + return _builder.Build(); } - private void EnsureApplicationDelegate(HostingContext context) + public IServiceProvider ApplicationServices { - if (context.ApplicationDelegate != null) + get { - return; + EnsureApplicationServices(); + return _applicationServices; } + } - // REVIEW: should we call EnsureApplicationServices? - var startupFilters = context.ApplicationServices.GetService>(); - var configure = context.StartupMethods.ConfigureDelegate; - foreach (var filter in startupFilters) + private void CheckUseAllowed() + { + if (_useDisabled) { - configure = filter.Configure(context.Builder, configure); + throw new InvalidOperationException("HostingEngine has already been started."); } - - configure(context.Builder); - - context.ApplicationDelegate = context.Builder.Build(); } - private static IServiceCollection Import(IServiceProvider fallbackProvider) + // Consider cutting + public IHostingEngine UseEnvironment(string environment) { - var services = new ServiceCollection(); - var manifest = fallbackProvider.GetRequiredService(); - foreach (var service in manifest.Services) - { - services.AddTransient(service, sp => fallbackProvider.GetService(service)); - } + CheckUseAllowed(); + _hostingEnvironment.EnvironmentName = environment; + return this; + } - return services; + public IHostingEngine UseServer(string assemblyName) + { + CheckUseAllowed(); + _serverFactoryLocation = assemblyName; + return this; } - private class WrappingServiceProvider : IServiceProvider + public IHostingEngine UseServer(IServerFactory factory) { - private readonly IServiceProvider _sp; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly IApplicationLifetime _applicationLifetime; + CheckUseAllowed(); + _serverFactory = factory; + return this; + } - public WrappingServiceProvider(IServiceProvider sp, - IHostingEnvironment hostingEnvironment, - IApplicationLifetime applicationLifetime) - { - _sp = sp; - _hostingEnvironment = hostingEnvironment; - _applicationLifetime = applicationLifetime; - } + public IHostingEngine UseStartup(string startupAssemblyName) + { + CheckUseAllowed(); + _startupAssemblyName = startupAssemblyName; + return this; + } - public object GetService(Type serviceType) - { - if (serviceType == typeof(IHostingEnvironment)) - { - return _hostingEnvironment; - } + public IHostingEngine UseStartup(Action configureApp) + { + return UseStartup(configureApp, configureServices: null); + } - if (serviceType == typeof(IApplicationLifetime)) - { - return _applicationLifetime; - } + public IHostingEngine UseStartup(Action configureApp, ConfigureServicesDelegate configureServices) + { + CheckUseAllowed(); + _startup = new StartupMethods(configureApp, configureServices); + return this; + } - return _sp.GetService(serviceType); - } + 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 b7a0249319fa..dac9b27d89fc 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 @@ public class HostingEnvironment : IHostingEnvironment { 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 000000000000..cd188890348a --- /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 000000000000..1b77dc7e59a4 --- /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 000000000000..dbd6577b0578 --- /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 000000000000..f44b02c1dbdf --- /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 8b1719880b32..f406c8e3b3ff 100644 --- a/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs +++ b/src/Microsoft.AspNet.Hosting/IHttpContextAccessor.cs @@ -5,6 +5,7 @@ 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 000000000000..3e9a9babec1b --- /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 47211a65ed6d..555fc241a8e9 100644 --- a/src/Microsoft.AspNet.Hosting/Program.cs +++ b/src/Microsoft.AspNet.Hosting/Program.cs @@ -33,18 +33,13 @@ public void Main(string[] args) 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 000000000000..cab29b61ceab --- /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 811afb55903d..6ebc09f04e4b 100644 --- a/src/Microsoft.AspNet.Hosting/Startup/ApplicationStartup.cs +++ b/src/Microsoft.AspNet.Hosting/Startup/StartupLoader.cs @@ -10,25 +10,46 @@ 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 @@ public static StartupMethods LoadStartupMethods( // 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 @@ public static StartupMethods LoadStartupMethods( 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 4b3a5c9c5c62..4644b5a55ed5 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 000000000000..c1d331e0c4d9 --- /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 0cc7b83a6504..0e3a9927b5db 100644 --- a/src/Microsoft.AspNet.TestHost/TestServer.cs +++ b/src/Microsoft.AspNet.TestHost/TestServer.cs @@ -12,7 +12,6 @@ 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 @@ public class TestServer : IServerFactory, IDisposable 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 => - { - if (configureServices != null) - { - configureServices(sc); - } - return sc.BuildServiceProvider(); - }); + 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(IServiceProvider serviceProvider, Action configureApp) + public static TestServer Create() where TStartup : class { - return Create(serviceProvider, configureApp, configureServices: null); + return Create(fallbackServices: null, config: null, configureApp: null, configureServices: null); } - public static TestServer Create(IServiceProvider serviceProvider, Action configureApp, Action configureServices) + public static TestServer Create(Action configureApp) where TStartup : class { - return Create(serviceProvider, configureApp, - sc => + 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, ConfigureServicesDelegate configureServices) + public static TestServerBuilder CreateBuilder(IServiceProvider fallbackServices, IConfiguration config, 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 @@ public string Name } } } -} +} \ 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 000000000000..74425f89f50e --- /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 7bebf1c0e772..6e2547f0edd9 100644 --- a/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs +++ b/test/Microsoft.AspNet.Hosting.Tests/HostingEngineTests.cs @@ -8,8 +8,10 @@ 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 @@ -19,57 +21,59 @@ public class HostingEngineTests : IServerFactory private readonly IList _startInstances = new List(); [Fact] - public void HostingEngineCanBeStarted() + public void HostingEngineThrowsWithNoServer() { - var context = new HostingContext - { - ServerFactory = this, - ApplicationName = "Microsoft.AspNet.Hosting.Tests" - }; + Assert.Throws(() => WebHost.CreateEngine().Start()); + } - var engineStart = new HostingEngine().Start(context); + [Fact] + public void HostingEngineCanBeStarted() + { + var engine = WebHost.CreateEngine() + .UseServer(this) + .UseStartup("Microsoft.AspNet.Hosting.Tests") + .Start(); - 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(); - - using (engine.Start(context)) - { - Assert.Equal("Microsoft.AspNet.Hosting.Tests", context.ApplicationName); - } + Assert.NotNull(factory as TestEngineFactory); } [Fact] - public void EnvDefaultsToDevelopmentIfNoConfig() + public void CanReplaceStartupLoader() { - var context = new HostingContext - { - ServerFactory = this - }; + var engine = WebHost.CreateEngine(services => services.AddTransient()) + .UseServer(this) + .UseStartup("Microsoft.AspNet.Hosting.Tests"); - var engine = new HostingEngine(); + Assert.Throws(() => engine.Start()); + } - using (engine.Start(context)) - { - Assert.Equal("Development", context.EnvironmentName); - var env = context.ApplicationServices.GetRequiredService(); - Assert.Equal("Development", env.EnvironmentName); - } + [Fact] + public void CanCreateApplicationServicesWithAddedServices() + { + var engineStart = WebHost.CreateEngine(services => services.AddOptions()); + Assert.NotNull(engineStart.ApplicationServices.GetRequiredService>()); + } + + [Fact] + public void EnvDefaultsToDevelopmentIfNoConfig() + { + var engine = WebHost.CreateEngine(new Configuration()); + var env = engine.ApplicationServices.GetRequiredService(); + Assert.Equal("Development", env.EnvironmentName); } [Fact] @@ -83,32 +87,16 @@ public void EnvDefaultsToDevelopmentConfigValueIfSpecified() 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 @@ public void WebRootCanBeResolvedFromTheProjectJson() [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 @@ public void IsEnvironment_Extension_Is_Case_Insensitive() [InlineData(@"sub/sub2\sub3\", @"sub/sub2/sub3/")] public void MapPath_Facts(string virtualPath, string expectedSuffix) { - var context = new HostingContext - { - ServerFactory = this - }; - - var engine = new HostingEngine(); + var engine = WebHost.CreateEngine().UseServer(this); - 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 @@ public void Dispose() 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 8f9318a85a42..d4b7ee1bcd6f 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 a7ecf91aaad6..cce2931e22b3 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 @@ public void StartupClassMayHaveHostingServicesInjected() 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 @@ public void StartupClassAddsConfigureServicesToApplicationServices(string enviro 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 @@ public void StartupWithNoConfigureThrows() 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 @@ public void StartupClassCanHandleConfigureServicesThatReturnsNull() 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 @@ public void StartupClassWithConfigureServicesShouldMakeServiceAvailableInConfigu 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 @@ public void StartupClassWithConfigureServicesShouldMakeServiceAvailableInConfigu 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 aa3d3b7bb0fd..6dc70865273c 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 b70af08ea5ee..55caa51ccfa5 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 0f637c7d2435..6a005fd0dcc1 100644 --- a/test/Microsoft.AspNet.TestHost.Tests/RequestBuilderTests.cs +++ b/test/Microsoft.AspNet.TestHost.Tests/RequestBuilderTests.cs @@ -10,7 +10,7 @@ public class RequestBuilderTests [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 @@ public void AddRequestHeader() [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 f3a3fc0bef10..72f727799043 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 @@ public void ThrowsIfNoApplicationEnvironmentIsRegisteredWithTheProvider() 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 @@ public async Task RequestServicesAutoCreated() 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 @@ public async Task CanAddNewHostServices() { 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 @@ public void CancelAborts() 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() + { + } + + 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) { - builder.Run(ctx => ctx.Response.WriteAsync("Another Startup")); + 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)); + }); } } }