Hosting rework #2

- Merge HostingContext and HostingEngine
- Cleanup usage via builder pattern
This commit is contained in:
Hao Kung 2015-03-30 18:05:51 -07:00
parent 78ba6f8a65
commit 77e2dc263f
26 changed files with 887 additions and 398 deletions

View File

@ -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);
}
}

View File

@ -7,10 +7,13 @@ namespace Microsoft.AspNet.Hosting
{ {
public interface IHostingEnvironment public interface IHostingEnvironment
{ {
// This must be settable!
string EnvironmentName { get; set; } 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; }
} }
} }

View File

@ -14,10 +14,6 @@ namespace Microsoft.AspNet.Hosting
private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource(); private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource();
private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource(); private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource();
public ApplicationLifetime()
{
}
/// <summary> /// <summary>
/// Triggered when the application host is performing a graceful shutdown. /// Triggered when the application host is performing a graceful shutdown.
/// Request may still be in flight. Shutdown will block until this event completes. /// Request may still be in flight. Shutdown will block until this event completes.

View File

@ -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; }
}
}

View File

@ -5,251 +5,219 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting.Builder; using Microsoft.AspNet.Hosting.Builder;
using Microsoft.AspNet.Hosting.Internal;
using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Server;
using Microsoft.AspNet.Hosting.Startup; using Microsoft.AspNet.Hosting.Startup;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Infrastructure;
namespace Microsoft.AspNet.Hosting 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; // Start/ApplicationServices block use methods
private readonly ApplicationLifetime _appLifetime; private bool _useDisabled;
private readonly IApplicationEnvironment _applicationEnvironment;
private readonly HostingEnvironment _hostingEnvironment;
private IServerLoader _serverLoader; private IServerLoader _serverLoader;
private IApplicationBuilderFactory _builderFactory; 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; _config = config ?? new Configuration();
_appLifetime = new ApplicationLifetime(); _applicationServiceCollection = appServices;
_applicationEnvironment = _fallbackServices.GetRequiredService<IApplicationEnvironment>(); _startupLoader = startupLoader;
_hostingEnvironment = new HostingEnvironment(_applicationEnvironment); _startupAssemblyName = appName;
_fallbackServices = new WrappingServiceProvider(_fallbackServices, _hostingEnvironment, _appLifetime); _applicationLifetime = new ApplicationLifetime();
_hostingEnvironment = hostingEnv;
} }
public IDisposable Start(HostingContext context) public virtual IDisposable Start()
{ {
EnsureContextDefaults(context); EnsureApplicationServices();
EnsureApplicationServices(context); EnsureBuilder();
EnsureBuilder(context); EnsureServer();
EnsureServerFactory(context);
InitalizeServerFactory(context);
EnsureApplicationDelegate(context);
var contextFactory = context.ApplicationServices.GetRequiredService<IHttpContextFactory>(); var applicationDelegate = BuildApplicationDelegate();
var contextAccessor = context.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
var server = context.ServerFactory.Start(context.Server, var _contextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
var _contextAccessor = _applicationServices.GetRequiredService<IHttpContextAccessor>();
var server = _serverFactory.Start(_serverInstance,
features => features =>
{ {
var httpContext = contextFactory.CreateHttpContext(features); var httpContext = _contextFactory.CreateHttpContext(features);
contextAccessor.HttpContext = httpContext; _contextAccessor.HttpContext = httpContext;
return context.ApplicationDelegate(httpContext); return applicationDelegate(httpContext);
}); });
return new Disposable(() => return new Disposable(() =>
{ {
_appLifetime.NotifyStopping(); _applicationLifetime.NotifyStopping();
server.Dispose(); server.Dispose();
_appLifetime.NotifyStopped(); _applicationLifetime.NotifyStopped();
}); });
} }
private void EnsureContextDefaults(HostingContext context) private void EnsureApplicationServices()
{ {
if (context.ApplicationName == null) _useDisabled = true;
{ EnsureStartup();
context.ApplicationName = _applicationEnvironment.ApplicationName;
}
if (context.EnvironmentName == null) _applicationServiceCollection.AddInstance<IApplicationLifetime>(_applicationLifetime);
{
context.EnvironmentName = context.Configuration?.Get(EnvironmentKey) ?? HostingEnvironment.DefaultEnvironmentName;
}
_hostingEnvironment.EnvironmentName = context.EnvironmentName; _applicationServices = _startup.ConfigureServicesDelegate(_applicationServiceCollection);
if (context.WebRootPath != null)
{
_hostingEnvironment.WebRootPath = context.WebRootPath;
}
} }
private void EnsureApplicationServices(HostingContext context) private void EnsureStartup()
{ {
if (context.ApplicationServices != null) if (_startup != null)
{
return;
}
EnsureStartupMethods(context);
context.ApplicationServices = context.StartupMethods.ConfigureServicesDelegate(CreateHostingServices(context));
}
private void EnsureStartupMethods(HostingContext context)
{
if (context.StartupMethods != null)
{ {
return; return;
} }
var diagnosticMessages = new List<string>(); var diagnosticMessages = new List<string>();
context.StartupMethods = ApplicationStartup.LoadStartupMethods( _startup = _startupLoader.Load(
_fallbackServices, _startupAssemblyName,
context.ApplicationName, _hostingEnvironment.EnvironmentName,
context.EnvironmentName,
diagnosticMessages); diagnosticMessages);
if (context.StartupMethods == null) if (_startup == null)
{ {
throw new ArgumentException( throw new ArgumentException(
diagnosticMessages.Aggregate("Failed to find an entry point for the web application.", (a, b) => a + "\r\n" + b), diagnosticMessages.Aggregate("Failed to find a startup entry point for the web application.", (a, b) => a + "\r\n" + b),
nameof(context)); _startupAssemblyName);
} }
} }
private void EnsureBuilder(HostingContext context) private void EnsureBuilder()
{ {
if (context.Builder != null)
{
return;
}
if (_builderFactory == null) if (_builderFactory == null)
{ {
_builderFactory = context.ApplicationServices.GetRequiredService<IApplicationBuilderFactory>(); _builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
} }
context.Builder = _builderFactory.CreateBuilder(); _builder = _builderFactory.CreateBuilder();
context.Builder.ApplicationServices = context.ApplicationServices; _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<IServerLoader>().LoadServerFactory(_serverFactoryLocation);
} }
if (_serverLoader == null) _serverInstance = _serverFactory.Initialize(_config);
{ _builder.Server = _serverInstance;
_serverLoader = context.ApplicationServices.GetRequiredService<IServerLoader>();
}
context.ServerFactory = _serverLoader.LoadServerFactory(context.ServerFactoryLocation);
} }
private void InitalizeServerFactory(HostingContext context) private RequestDelegate BuildApplicationDelegate()
{ {
if (context.Server == null) var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
{ var configure = _startup.ConfigureDelegate;
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<IServerLoader, ServerLoader>());
services.TryAdd(ServiceDescriptor.Transient<IApplicationBuilderFactory, ApplicationBuilderFactory>());
services.TryAdd(ServiceDescriptor.Transient<IHttpContextFactory, HttpContextFactory>());
// TODO: Do we expect this to be provide by the runtime eventually?
services.AddLogging();
services.TryAdd(ServiceDescriptor.Singleton<IHttpContextAccessor, HttpContextAccessor>());
// Apply user services
services.Add(context.Services);
// Jamming in app lifetime and hosting env since these must not be replaceable
services.AddInstance<IApplicationLifetime>(_appLifetime);
services.AddInstance<IHostingEnvironment>(_hostingEnvironment);
// Conjure up a RequestServices
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
return services;
}
private void EnsureApplicationDelegate(HostingContext context)
{
if (context.ApplicationDelegate != null)
{
return;
}
// REVIEW: should we call EnsureApplicationServices?
var startupFilters = context.ApplicationServices.GetService<IEnumerable<IStartupFilter>>();
var configure = context.StartupMethods.ConfigureDelegate;
foreach (var filter in startupFilters) 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(); get
var manifest = fallbackProvider.GetRequiredService<IServiceManifest>();
foreach (var service in manifest.Services)
{ {
services.AddTransient(service, sp => fallbackProvider.GetService(service)); EnsureApplicationServices();
return _applicationServices;
} }
return services;
} }
private class WrappingServiceProvider : IServiceProvider private void CheckUseAllowed()
{ {
private readonly IServiceProvider _sp; if (_useDisabled)
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IApplicationLifetime _applicationLifetime;
public WrappingServiceProvider(IServiceProvider sp,
IHostingEnvironment hostingEnvironment,
IApplicationLifetime applicationLifetime)
{ {
_sp = sp; throw new InvalidOperationException("HostingEngine has already been started.");
_hostingEnvironment = hostingEnvironment;
_applicationLifetime = applicationLifetime;
} }
}
public object GetService(Type serviceType) // Consider cutting
{ public IHostingEngine UseEnvironment(string environment)
if (serviceType == typeof(IHostingEnvironment)) {
{ CheckUseAllowed();
return _hostingEnvironment; _hostingEnvironment.EnvironmentName = environment;
} return this;
}
if (serviceType == typeof(IApplicationLifetime)) public IHostingEngine UseServer(string assemblyName)
{ {
return _applicationLifetime; 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<IApplicationBuilder> configureApp)
{
return UseStartup(configureApp, configureServices: null);
}
public IHostingEngine UseStartup(Action<IApplicationBuilder> configureApp, ConfigureServicesDelegate configureServices)
{
CheckUseAllowed();
_startup = new StartupMethods(configureApp, configureServices);
return this;
}
public IHostingEngine UseStartup(Action<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices)
{
CheckUseAllowed();
_startup = new StartupMethods(configureApp,
services => {
if (configureServices != null)
{
configureServices(services);
}
return services.BuildServiceProvider();
});
return this;
} }
private class Disposable : IDisposable private class Disposable : IDisposable

View File

@ -1,10 +1,8 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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.FileProviders;
using Microsoft.AspNet.Hosting.Internal; using Microsoft.AspNet.Hosting.Internal;
using Microsoft.Framework.Runtime;
namespace Microsoft.AspNet.Hosting namespace Microsoft.AspNet.Hosting
{ {
@ -12,17 +10,10 @@ namespace Microsoft.AspNet.Hosting
{ {
internal const string DefaultEnvironmentName = "Development"; internal const string DefaultEnvironmentName = "Development";
public HostingEnvironment(IApplicationEnvironment appEnvironment) public string EnvironmentName { get; set; } = DefaultEnvironmentName;
{
EnvironmentName = DefaultEnvironmentName;
WebRootPath = HostingUtilities.GetWebRoot(appEnvironment.ApplicationBasePath);
WebRootFileProvider = new PhysicalFileProvider(WebRootPath);
}
public string EnvironmentName { get; set; }
public string WebRootPath { get; set; } public string WebRootPath { get; set; }
public IFileProvider WebRootFileProvider { get; private set; } public IFileProvider WebRootFileProvider { get; set; }
} }
} }

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<IApplicationBuilder> configureApp);
IHostingEngine UseStartup(Action<IApplicationBuilder> configureApp, ConfigureServicesDelegate configureServices);
IHostingEngine UseStartup(Action<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices);
}
}

View File

@ -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);
}
}

View File

@ -5,6 +5,7 @@ using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Hosting namespace Microsoft.AspNet.Hosting
{ {
// REVIEW: move to interfaces
public interface IHttpContextAccessor public interface IHttpContextAccessor
{ {
HttpContext HttpContext { get; set; } HttpContext HttpContext { get; set; }

View File

@ -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<IServiceCollection> _configureServices;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILoggerFactory _loggerFactory;
public RootHostingServiceCollectionInitializer(IServiceProvider fallbackServices, Action<IServiceCollection> 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<IServiceManifest>();
foreach (var service in manifest.Services)
{
services.AddTransient(service, sp => _fallbackServices.GetService(service));
}
services.AddInstance(_hostingEnvironment);
services.AddInstance(this);
services.AddInstance(_loggerFactory);
services.AddTransient<IHostingFactory, HostingFactory>();
services.AddTransient<IStartupLoader, StartupLoader>();
services.AddTransient<IServerLoader, ServerLoader>();
services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
services.AddTransient<IHttpContextFactory, HttpContextFactory>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddLogging();
// Conjure up a RequestServices
services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
if (_configureServices != null)
{
_configureServices(services);
}
return services;
}
}
}

View File

@ -33,18 +33,13 @@ namespace Microsoft.AspNet.Hosting
config.AddEnvironmentVariables(); config.AddEnvironmentVariables();
config.AddCommandLine(args); config.AddCommandLine(args);
var context = new HostingContext() var engine = WebHost.CreateEngine(_serviceProvider, config)
{ .UseServer(config.Get("server"))
Configuration = config, .UseStartup(config.Get("app"));
ServerFactoryLocation = config.Get("server"),
ApplicationName = config.Get("app")
};
var engine = new HostingEngine(_serviceProvider); var serverShutdown = engine.Start();
var loggerFactory = engine.ApplicationServices.GetRequiredService<ILoggerFactory>();
var serverShutdown = engine.Start(context); var appShutdownService = engine.ApplicationServices.GetRequiredService<IApplicationShutdown>();
var loggerFactory = context.ApplicationServices.GetRequiredService<ILoggerFactory>();
var appShutdownService = context.ApplicationServices.GetRequiredService<IApplicationShutdown>();
var shutdownHandle = new ManualResetEvent(false); var shutdownHandle = new ManualResetEvent(false);
appShutdownService.ShutdownRequested.Register(() => appShutdownService.ShutdownRequested.Register(() =>

View File

@ -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<string> diagnosticMessages);
}
}

View File

@ -10,25 +10,46 @@ using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Hosting.Startup 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( public StartupLoader(IServiceProvider services)
IServiceProvider services, {
string applicationName, _services = services;
}
public StartupMethods Load(
Type startupType,
string environmentName, string environmentName,
IList<string> diagnosticMessages) IList<string> 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<string> 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) 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; var startupNameWithEnv = "Startup" + environmentName;
@ -37,9 +58,9 @@ namespace Microsoft.AspNet.Hosting.Startup
// Check the most likely places first // Check the most likely places first
var type = var type =
assembly.GetType(startupNameWithEnv) ?? assembly.GetType(startupNameWithEnv) ??
assembly.GetType(applicationName + "." + startupNameWithEnv) ?? assembly.GetType(startupAssemblyName + "." + startupNameWithEnv) ??
assembly.GetType(startupNameWithoutEnv) ?? assembly.GetType(startupNameWithoutEnv) ??
assembly.GetType(applicationName + "." + startupNameWithoutEnv); assembly.GetType(startupAssemblyName + "." + startupNameWithoutEnv);
if (type == null) 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}'.", throw new InvalidOperationException(String.Format("A type named '{0}' or '{1}' could not be found in assembly '{2}'.",
startupNameWithEnv, startupNameWithEnv,
startupNameWithoutEnv, startupNameWithoutEnv,
applicationName)); startupAssemblyName));
} }
var configureMethod = FindConfigureDelegate(type, environmentName); return Load(type, environmentName, diagnosticMessages);
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));
} }
private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
public static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
{ {
var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
return new ConfigureBuilder(configureMethod); 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) var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);

View File

@ -3,16 +3,22 @@
using System; using System;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Hosting.Startup namespace Microsoft.AspNet.Hosting.Startup
{ {
public class StartupMethods public class StartupMethods
{ {
internal static ConfigureServicesDelegate DefaultBuildServiceProvider = s => s.BuildServiceProvider();
public StartupMethods(Action<IApplicationBuilder> configure)
: this(configure, configureServices: null) { }
// TODO: switch to ConfigureDelegate eventually // TODO: switch to ConfigureDelegate eventually
public StartupMethods(Action<IApplicationBuilder> configure, ConfigureServicesDelegate configureServices) public StartupMethods(Action<IApplicationBuilder> configure, ConfigureServicesDelegate configureServices)
{ {
ConfigureDelegate = configure; ConfigureDelegate = configure;
ConfigureServicesDelegate = configureServices ?? ApplicationStartup.DefaultBuildServiceProvider; ConfigureServicesDelegate = configureServices ?? DefaultBuildServiceProvider;
} }
public ConfigureServicesDelegate ConfigureServicesDelegate { get; } public ConfigureServicesDelegate ConfigureServicesDelegate { get; }

View File

@ -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<IServiceCollection> configureServices)
{
return CreateEngine(new Configuration(), configureServices);
}
public static IHostingEngine CreateEngine(IConfiguration config)
{
return CreateEngine(config, configureServices: null);
}
public static IHostingEngine CreateEngine(IConfiguration config, Action<IServiceCollection> 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<IServiceCollection> configureServices)
{
return CreateFactory(fallbackServices, configureServices).Create(config);
}
public static IHostingFactory CreateFactory()
{
return CreateFactory(fallbackServices: null, configureServices: null);
}
public static IHostingFactory CreateFactory(Action<IServiceCollection> configureServices)
{
return CreateFactory(fallbackServices: null, configureServices: configureServices);
}
public static IHostingFactory CreateFactory(IServiceProvider fallbackServices, Action<IServiceCollection> configureServices)
{
fallbackServices = fallbackServices ?? CallContextServiceLocator.Locator.ServiceProvider;
return new RootHostingServiceCollectionInitializer(fallbackServices, configureServices)
.Build()
.BuildServiceProvider()
.GetRequiredService<IHostingFactory>();
}
}
}

View File

@ -12,7 +12,6 @@ using Microsoft.AspNet.Hosting.Startup;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Runtime.Infrastructure;
namespace Microsoft.AspNet.TestHost namespace Microsoft.AspNet.TestHost
{ {
@ -25,63 +24,95 @@ namespace Microsoft.AspNet.TestHost
private IDisposable _appInstance; private IDisposable _appInstance;
private bool _disposed = false; private bool _disposed = false;
// REVIEW: we can configure services via AppStartup or via hostContext.Services public TestServer(IHostingEngine engine)
public TestServer(IConfiguration config, IServiceProvider serviceProvider, Action<IApplicationBuilder> configureApp, ConfigureServicesDelegate configureServices)
{ {
var hostContext = new HostingContext() _appInstance = engine.UseServer(this).Start();
{
ApplicationName = "Test App",
Configuration = config,
ServerFactory = this,
StartupMethods = new StartupMethods(configureApp, configureServices)
};
_appInstance = new HostingEngine(serviceProvider).Start(hostContext);
} }
public Uri BaseAddress { get; set; } = new Uri("http://localhost/"); 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<IApplicationBuilder> configureApp) public static TestServer Create(Action<IApplicationBuilder> configureApp)
{ {
return Create(CallContextServiceLocator.Locator.ServiceProvider, configureApp, configureServices: null); return Create(fallbackServices: null, config: null, configureApp: configureApp, configureServices: null);
} }
public static TestServer Create(Action<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices) public static TestServer Create(Action<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices)
{ {
return Create(CallContextServiceLocator.Locator.ServiceProvider, configureApp, return Create(fallbackServices: null, config: null, configureApp: configureApp, configureServices: configureServices);
sc => }
public static TestServer Create(IServiceProvider fallbackServices, Action<IApplicationBuilder> configureApp, ConfigureServicesDelegate configureServices)
{
return CreateBuilder(fallbackServices, config: null, configureApp: configureApp, configureServices: configureServices).Build();
}
public static TestServer Create(IServiceProvider fallbackServices, IConfiguration config, Action<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices)
{
return CreateBuilder(fallbackServices, config, configureApp, configureServices).Build();
}
public static TestServer Create<TStartup>() where TStartup : class
{
return Create<TStartup>(fallbackServices: null, config: null, configureApp: null, configureServices: null);
}
public static TestServer Create<TStartup>(Action<IApplicationBuilder> configureApp) where TStartup : class
{
return Create<TStartup>(fallbackServices: null, config: null, configureApp: configureApp, configureServices: null);
}
public static TestServer Create<TStartup>(Action<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices) where TStartup : class
{
return Create<TStartup>(fallbackServices: null, config: null, configureApp: configureApp, configureServices: configureServices);
}
public static TestServer Create<TStartup>(IServiceProvider fallbackServices, IConfiguration config, Action<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices) where TStartup : class
{
var builder = CreateBuilder(fallbackServices, config, configureApp, configureServices);
builder.StartupType = typeof(TStartup);
return builder.Build();
}
public static TestServerBuilder CreateBuilder<TStartup>() where TStartup : class
{
var builder = CreateBuilder(fallbackServices: null, config: null, configureApp: null, configureServices: null);
builder.StartupType = typeof(TStartup);
return builder;
}
public static TestServerBuilder CreateBuilder<TStartup>(IServiceProvider fallbackServices, IConfiguration config, Action<IApplicationBuilder> configureApp, Action<IServiceCollection> 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<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices)
{
return CreateBuilder(fallbackServices, config, configureApp,
services =>
{ {
if (configureServices != null) if (configureServices != null)
{ {
configureServices(sc); configureServices(services);
} }
return sc.BuildServiceProvider(); return services.BuildServiceProvider();
}); });
} }
public static TestServer Create(IServiceProvider serviceProvider, Action<IApplicationBuilder> configureApp) public static TestServerBuilder CreateBuilder(IServiceProvider fallbackServices, IConfiguration config, Action<IApplicationBuilder> configureApp, ConfigureServicesDelegate configureServices)
{ {
return Create(serviceProvider, configureApp, configureServices: null); return new TestServerBuilder
} {
FallbackServices = fallbackServices,
public static TestServer Create(IServiceProvider serviceProvider, Action<IApplicationBuilder> configureApp, Action<IServiceCollection> configureServices) Startup = new StartupMethods(configureApp, configureServices),
{ Config = config
return Create(serviceProvider, configureApp, };
sc =>
{
if (configureServices != null)
{
configureServices(sc);
}
return sc.BuildServiceProvider();
});
}
public static TestServer Create(IServiceProvider serviceProvider, Action<IApplicationBuilder> 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);
} }
public HttpMessageHandler CreateHandler() public HttpMessageHandler CreateHandler()

View File

@ -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<IApplicationEnvironment>());
appEnv.ApplicationBasePath = ApplicationBasePath;
appEnv.ApplicationName = ApplicationName;
AdditionalServices.AddInstance<IApplicationEnvironment>(appEnv);
}
var engine = WebHost.CreateEngine(fallbackServices,
config,
services => services.Add(AdditionalServices));
if (StartupType != null)
{
Startup = new StartupLoader(fallbackServices).Load(StartupType, Environment, new List<string>());
}
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();
}
}
}
}
}

View File

@ -8,8 +8,10 @@ using System.Threading.Tasks;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.FeatureModel; using Microsoft.AspNet.FeatureModel;
using Microsoft.AspNet.Hosting.Server; using Microsoft.AspNet.Hosting.Server;
using Microsoft.AspNet.Hosting.Startup;
using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Hosting namespace Microsoft.AspNet.Hosting
@ -18,58 +20,60 @@ namespace Microsoft.AspNet.Hosting
{ {
private readonly IList<StartInstance> _startInstances = new List<StartInstance>(); private readonly IList<StartInstance> _startInstances = new List<StartInstance>();
[Fact]
public void HostingEngineThrowsWithNoServer()
{
Assert.Throws<InvalidOperationException>(() => WebHost.CreateEngine().Start());
}
[Fact] [Fact]
public void HostingEngineCanBeStarted() public void HostingEngineCanBeStarted()
{ {
var context = new HostingContext var engine = WebHost.CreateEngine()
{ .UseServer(this)
ServerFactory = this, .UseStartup("Microsoft.AspNet.Hosting.Tests")
ApplicationName = "Microsoft.AspNet.Hosting.Tests" .Start();
};
var engineStart = new HostingEngine().Start(context); Assert.NotNull(engine);
Assert.NotNull(engineStart);
Assert.Equal(1, _startInstances.Count); Assert.Equal(1, _startInstances.Count);
Assert.Equal(0, _startInstances[0].DisposeCalls); Assert.Equal(0, _startInstances[0].DisposeCalls);
engineStart.Dispose(); engine.Dispose();
Assert.Equal(1, _startInstances[0].DisposeCalls); Assert.Equal(1, _startInstances[0].DisposeCalls);
} }
[Fact] [Fact]
public void ApplicationNameDefaultsToApplicationEnvironmentName() public void CanReplaceHostingFactory()
{ {
var context = new HostingContext var factory = WebHost.CreateFactory(services => services.AddTransient<IHostingFactory, TestEngineFactory>());
{
ServerFactory = this
};
var engine = new HostingEngine(); Assert.NotNull(factory as TestEngineFactory);
}
using (engine.Start(context)) [Fact]
{ public void CanReplaceStartupLoader()
Assert.Equal("Microsoft.AspNet.Hosting.Tests", context.ApplicationName); {
} var engine = WebHost.CreateEngine(services => services.AddTransient<IStartupLoader, TestLoader>())
.UseServer(this)
.UseStartup("Microsoft.AspNet.Hosting.Tests");
Assert.Throws<NotImplementedException>(() => engine.Start());
}
[Fact]
public void CanCreateApplicationServicesWithAddedServices()
{
var engineStart = WebHost.CreateEngine(services => services.AddOptions());
Assert.NotNull(engineStart.ApplicationServices.GetRequiredService<IOptions<object>>());
} }
[Fact] [Fact]
public void EnvDefaultsToDevelopmentIfNoConfig() public void EnvDefaultsToDevelopmentIfNoConfig()
{ {
var context = new HostingContext var engine = WebHost.CreateEngine(new Configuration());
{ var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
ServerFactory = this Assert.Equal("Development", env.EnvironmentName);
};
var engine = new HostingEngine();
using (engine.Start(context))
{
Assert.Equal("Development", context.EnvironmentName);
var env = context.ApplicationServices.GetRequiredService<IHostingEnvironment>();
Assert.Equal("Development", env.EnvironmentName);
}
} }
[Fact] [Fact]
@ -83,32 +87,16 @@ namespace Microsoft.AspNet.Hosting
var config = new Configuration() var config = new Configuration()
.Add(new MemoryConfigurationSource(vals)); .Add(new MemoryConfigurationSource(vals));
var context = new HostingContext var engine = WebHost.CreateEngine(config);
{ var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
ServerFactory = this, Assert.Equal("Staging", env.EnvironmentName);
Configuration = config
};
var engine = new HostingEngine();
using (engine.Start(context))
{
Assert.Equal("Staging", context.EnvironmentName);
var env = context.ApplicationServices.GetRequiredService<IHostingEnvironment>();
Assert.Equal("Staging", env.EnvironmentName);
}
} }
[Fact] [Fact]
public void WebRootCanBeResolvedFromTheProjectJson() public void WebRootCanBeResolvedFromTheProjectJson()
{ {
var context = new HostingContext var engine = WebHost.CreateEngine().UseServer(this);
{ var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
ServerFactory = this
};
var engineStart = new HostingEngine().Start(context);
var env = context.ApplicationServices.GetRequiredService<IHostingEnvironment>();
Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath); Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath);
Assert.True(env.WebRootFileProvider.GetFileInfo("TextFile.txt").Exists); Assert.True(env.WebRootFileProvider.GetFileInfo("TextFile.txt").Exists);
} }
@ -116,16 +104,11 @@ namespace Microsoft.AspNet.Hosting
[Fact] [Fact]
public void IsEnvironment_Extension_Is_Case_Insensitive() public void IsEnvironment_Extension_Is_Case_Insensitive()
{ {
var context = new HostingContext var engine = WebHost.CreateEngine().UseServer(this);
{
ServerFactory = this
};
var engine = new HostingEngine(); using (engine.Start())
using (engine.Start(context))
{ {
var env = context.ApplicationServices.GetRequiredService<IHostingEnvironment>(); var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
Assert.True(env.IsEnvironment("Development")); Assert.True(env.IsEnvironment("Development"));
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/")] [InlineData(@"sub/sub2\sub3\", @"sub/sub2/sub3/")]
public void MapPath_Facts(string virtualPath, string expectedSuffix) public void MapPath_Facts(string virtualPath, string expectedSuffix)
{ {
var context = new HostingContext var engine = WebHost.CreateEngine().UseServer(this);
{
ServerFactory = this
};
var engine = new HostingEngine(); using (engine.Start())
using (engine.Start(context))
{ {
var env = context.ApplicationServices.GetRequiredService<IHostingEnvironment>(); var env = engine.ApplicationServices.GetRequiredService<IHostingEnvironment>();
var mappedPath = env.MapPath(virtualPath); var mappedPath = env.MapPath(virtualPath);
expectedSuffix = expectedSuffix.Replace('/', Path.DirectorySeparatorChar); expectedSuffix = expectedSuffix.Replace('/', Path.DirectorySeparatorChar);
Assert.Equal(Path.Combine(env.WebRootPath, expectedSuffix), mappedPath); Assert.Equal(Path.Combine(env.WebRootPath, expectedSuffix), mappedPath);
} }
} }
public void Initialize(IApplicationBuilder builder)
{
}
public IServerInformation Initialize(IConfiguration configuration) public IServerInformation Initialize(IConfiguration configuration)
{ {
return null; return null;
@ -190,5 +163,21 @@ namespace Microsoft.AspNet.Hosting
DisposeCalls += 1; DisposeCalls += 1;
} }
} }
private class TestLoader : IStartupLoader
{
public StartupMethods Load(string startupAssemblyName, string environmentName, IList<string> diagnosticMessages)
{
throw new NotImplementedException();
}
}
private class TestEngineFactory : IHostingFactory
{
public IHostingEngine Create(IConfiguration config)
{
throw new NotImplementedException();
}
}
} }
} }

View File

@ -14,5 +14,8 @@
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>18007</DevelopmentServerPort> <DevelopmentServerPort>18007</DevelopmentServerPort>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting.Fakes; using Microsoft.AspNet.Hosting.Fakes;
using Microsoft.AspNet.Hosting.Startup; using Microsoft.AspNet.Hosting.Startup;
@ -24,7 +26,7 @@ namespace Microsoft.AspNet.Hosting.Tests
var services = serviceCollection.BuildServiceProvider(); var services = serviceCollection.BuildServiceProvider();
var diagnosticMessages = new List<string>(); var diagnosticMessages = new List<string>();
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); var app = new ApplicationBuilder(services);
app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
@ -47,7 +49,7 @@ namespace Microsoft.AspNet.Hosting.Tests
var services = new ServiceCollection().BuildServiceProvider(); var services = new ServiceCollection().BuildServiceProvider();
var diagnosticMesssages = new List<string>(); var diagnosticMesssages = new List<string>();
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); var app = new ApplicationBuilder(services);
app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection()); app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection());
@ -67,7 +69,7 @@ namespace Microsoft.AspNet.Hosting.Tests
var services = serviceCollection.BuildServiceProvider(); var services = serviceCollection.BuildServiceProvider();
var diagnosticMessages = new List<string>(); var diagnosticMessages = new List<string>();
var ex = Assert.Throws<InvalidOperationException>(() => ApplicationStartup.LoadStartupMethods(services, "Microsoft.AspNet.Hosting.Tests", "Boom", diagnosticMessages)); var ex = Assert.Throws<InvalidOperationException>(() => 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); 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 services = serviceCollection.BuildServiceProvider();
var diagnosticMessages = new List<string>(); var diagnosticMessages = new List<string>();
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); var app = new ApplicationBuilder(services);
app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection()); app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection());
@ -94,7 +96,7 @@ namespace Microsoft.AspNet.Hosting.Tests
var services = serviceCollection.BuildServiceProvider(); var services = serviceCollection.BuildServiceProvider();
var diagnosticMessages = new List<string>(); var diagnosticMessages = new List<string>();
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); var app = new ApplicationBuilder(services);
app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection); app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
@ -104,6 +106,71 @@ namespace Microsoft.AspNet.Hosting.Tests
Assert.True(foo.Invoked); Assert.True(foo.Invoked);
} }
[Fact]
public void StartupLoaderCanLoadByType()
{
var serviceCollection = new ServiceCollection();
var services = serviceCollection.BuildServiceProvider();
var diagnosticMessages = new List<string>();
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<SimpleService>();
Assert.Equal("Configure", foo.Message);
}
[Fact]
public void StartupLoaderCanLoadByTypeWithEnvironment()
{
var serviceCollection = new ServiceCollection();
var services = serviceCollection.BuildServiceProvider();
var diagnosticMessages = new List<string>();
var startup = new StartupLoader(services).Load(typeof(TestStartup), "No", diagnosticMessages);
var app = new ApplicationBuilder(services);
app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
var ex = Assert.Throws<TargetInvocationException>(() => 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<SimpleService>();
}
public void ConfigureNoServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
var service = app.ApplicationServices.GetRequiredService<SimpleService>();
service.Message = "Configure";
}
public void ConfigureNo(IApplicationBuilder app)
{
var service = app.ApplicationServices.GetRequiredService<SimpleService>();
}
}
public void ConfigurationMethodCalled(object instance) public void ConfigurationMethodCalled(object instance)
{ {
_configurationMethodCalledList.Add(instance); _configurationMethodCalledList.Add(instance);

View File

@ -3,6 +3,7 @@
"Microsoft.AspNet.Hosting": "1.0.0-*", "Microsoft.AspNet.Hosting": "1.0.0-*",
"Microsoft.AspNet.Owin": "1.0.0-*", "Microsoft.AspNet.Owin": "1.0.0-*",
"Microsoft.Framework.OptionsModel": "1.0.0-*", "Microsoft.Framework.OptionsModel": "1.0.0-*",
"Microsoft.Framework.Runtime.Interfaces": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*" "xunit.runner.aspnet": "2.0.0-aspnet-*"
}, },
"frameworks": { "frameworks": {

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion> <VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
@ -13,5 +13,8 @@
<PropertyGroup> <PropertyGroup>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNet.TestHost
[Fact] [Fact]
public void AddRequestHeader() public void AddRequestHeader()
{ {
TestServer server = TestServer.Create(app => { }); var server = TestServer.Create(app => { });
server.CreateRequest("/") server.CreateRequest("/")
.AddHeader("Host", "MyHost:90") .AddHeader("Host", "MyHost:90")
.And(request => .And(request =>
@ -22,7 +22,7 @@ namespace Microsoft.AspNet.TestHost
[Fact] [Fact]
public void AddContentHeaders() public void AddContentHeaders()
{ {
TestServer server = TestServer.Create(app => { }); var server = TestServer.Create(app => { });
server.CreateRequest("/") server.CreateRequest("/")
.AddHeader("Content-Type", "Test/Value") .AddHeader("Content-Type", "Test/Value")
.And(request => .And(request =>

View File

@ -2,15 +2,21 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Runtime.Versioning;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Hosting.Startup;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging; using Microsoft.Framework.Logging;
using Microsoft.Framework.Runtime;
using Microsoft.Framework.Runtime.Infrastructure;
using Xunit; using Xunit;
namespace Microsoft.AspNet.TestHost namespace Microsoft.AspNet.TestHost
@ -32,7 +38,7 @@ namespace Microsoft.AspNet.TestHost
var services = new ServiceCollection().BuildServiceProvider(); var services = new ServiceCollection().BuildServiceProvider();
// Act & Assert // Act & Assert
Assert.Throws<InvalidOperationException>(() => TestServer.Create(services, new Startup().Configuration)); Assert.Throws<InvalidOperationException>(() => TestServer.Create(services, new Configuration(), new Startup().Configure, configureServices: null));
} }
[Fact] [Fact]
@ -50,6 +56,54 @@ namespace Microsoft.AspNet.TestHost
Assert.Equal("RequestServices:True", result); 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<IApplicationEnvironment>();
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<IApplicationEnvironment>();
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] [Fact]
public async Task CanAccessLogger() public async Task CanAccessLogger()
{ {
@ -97,16 +151,13 @@ namespace Microsoft.AspNet.TestHost
{ {
TestServer server = TestServer.Create(app => TestServer server = TestServer.Create(app =>
{ {
var a = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
app.Run(context => app.Run(context =>
{ {
var b = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
var accessor = app.ApplicationServices.GetRequiredService<ContextHolder>(); var accessor = app.ApplicationServices.GetRequiredService<ContextHolder>();
return context.Response.WriteAsync("HasContext:" + (accessor.Accessor.HttpContext != null)); return context.Response.WriteAsync("HasContext:" + (accessor.Accessor.HttpContext != null));
}); });
}, },
services => services.AddSingleton<ContextHolder>().BuildServiceProvider()); services => services.AddSingleton<ContextHolder>());
string result = await server.CreateClient().GetStringAsync("/path"); string result = await server.CreateClient().GetStringAsync("/path");
Assert.Equal("HasContext:True", result); Assert.Equal("HasContext:True", result);
@ -188,19 +239,70 @@ namespace Microsoft.AspNet.TestHost
Assert.Throws<AggregateException>(() => { string result = server.CreateClient().GetStringAsync("/path").Result; }); Assert.Throws<AggregateException>(() => { string result = server.CreateClient().GetStringAsync("/path").Result; });
} }
[Fact]
public async Task CanCreateViaStartupType()
{
TestServer server = TestServer.Create<TestStartup>();
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<TestStartup>();
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 class Startup
{ {
public void Configuration(IApplicationBuilder builder) public void Configure(IApplicationBuilder builder)
{ {
builder.Run(ctx => ctx.Response.WriteAsync("Startup")); 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<SimpleService>();
}
public void ConfigureFooServices(IServiceCollection services)
{
}
public void Configure(IApplicationBuilder app)
{
app.Run(context =>
{
var service = app.ApplicationServices.GetRequiredService<SimpleService>();
return context.Response.WriteAsync("FoundService:" + (service != null));
});
}
public void ConfigureFoo(IApplicationBuilder app)
{
app.Run(context =>
{
var service = app.ApplicationServices.GetService<SimpleService>();
return context.Response.WriteAsync("FoundFoo:" + (service != null));
});
} }
} }
} }