Hosting rework #2
- Merge HostingContext and HostingEngine - Cleanup usage via builder pattern
This commit is contained in:
parent
78ba6f8a65
commit
77e2dc263f
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(() =>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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; }
|
||||||
|
|
|
||||||
|
|
@ -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>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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 =>
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue