Make it easier to add hosting services
Fixes https://github.com/aspnet/Hosting/issues/145
This commit is contained in:
parent
d0980738e3
commit
f7c78b8fbc
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Hosting
|
|||
|
||||
public void Configure(IHostingEnvironment hostingEnv)
|
||||
{
|
||||
hostingEnv.EnvironmentName = _config.Get(EnvironmentKey) ?? hostingEnv.EnvironmentName;
|
||||
hostingEnv.EnvironmentName = _config?.Get(EnvironmentKey) ?? hostingEnv.EnvironmentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,44 +14,66 @@ namespace Microsoft.AspNet.Hosting
|
|||
{
|
||||
public static class HostingServices
|
||||
{
|
||||
private static IServiceCollection Import(IServiceProvider fallbackProvider)
|
||||
private static IServiceCollection Import(IServiceProvider fallbackProvider, Action<IServiceCollection> configureHostServices)
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
if (configureHostServices != null)
|
||||
{
|
||||
configureHostServices(services);
|
||||
}
|
||||
var manifest = fallbackProvider.GetRequiredService<IServiceManifest>();
|
||||
foreach (var service in manifest.Services)
|
||||
{
|
||||
services.AddTransient(service, sp => fallbackProvider.GetService(service));
|
||||
}
|
||||
services.AddSingleton<IServiceManifest>(sp => new HostingManifest(services));
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection Create(IConfiguration configuration = null)
|
||||
public static IServiceCollection Create()
|
||||
{
|
||||
return Create(CallContextServiceLocator.Locator.ServiceProvider, configuration);
|
||||
return Create(CallContextServiceLocator.Locator.ServiceProvider, configureHostServices: null, configuration: null);
|
||||
}
|
||||
|
||||
public static IServiceCollection Create(IServiceProvider fallbackServices, IConfiguration configuration = null)
|
||||
public static IServiceCollection Create(IServiceProvider fallbackServices)
|
||||
{
|
||||
configuration = configuration ?? new Configuration();
|
||||
var services = Import(fallbackServices);
|
||||
return Create(fallbackServices, configureHostServices: null, configuration: null);
|
||||
}
|
||||
|
||||
public static IServiceCollection Create(IServiceProvider fallbackServices, Action<IServiceCollection> configureHostServices)
|
||||
{
|
||||
return Create(fallbackServices, configureHostServices, configuration: null);
|
||||
}
|
||||
|
||||
public static IServiceCollection Create(Action<IServiceCollection> configureHostServices, IConfiguration configuration)
|
||||
{
|
||||
return Create(CallContextServiceLocator.Locator.ServiceProvider, configureHostServices, configuration);
|
||||
}
|
||||
|
||||
public static IServiceCollection Create(IServiceProvider fallbackServices, IConfiguration configuration)
|
||||
{
|
||||
return Create(CallContextServiceLocator.Locator.ServiceProvider, configureHostServices: null, configuration: configuration);
|
||||
}
|
||||
|
||||
public static IServiceCollection Create(IServiceProvider fallbackServices, Action<IServiceCollection> configureHostServices, IConfiguration configuration)
|
||||
{
|
||||
var services = Import(fallbackServices, configureHostServices);
|
||||
services.AddHosting(configuration);
|
||||
services.AddSingleton<IServiceManifest>(sp => new HostingManifest(fallbackServices));
|
||||
return services;
|
||||
}
|
||||
|
||||
// Manifest exposes the fallback manifest in addition to ITypeActivator, IHostingEnvironment, and ILoggerFactory
|
||||
private class HostingManifest : IServiceManifest
|
||||
{
|
||||
public HostingManifest(IServiceProvider fallback)
|
||||
public HostingManifest(IServiceCollection hostServices)
|
||||
{
|
||||
var manifest = fallback.GetRequiredService<IServiceManifest>();
|
||||
Services = new Type[] {
|
||||
typeof(ITypeActivator),
|
||||
typeof(IHostingEnvironment),
|
||||
typeof(ILoggerFactory),
|
||||
typeof(IHttpContextAccessor),
|
||||
typeof(IApplicationLifetime)
|
||||
}.Concat(manifest.Services).Distinct();
|
||||
}.Concat(hostServices.Select(s => s.ServiceType)).Distinct();
|
||||
}
|
||||
|
||||
public IEnumerable<Type> Services { get; private set; }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,12 @@ namespace Microsoft.Framework.DependencyInjection
|
|||
{
|
||||
public static class HostingServicesExtensions
|
||||
{
|
||||
public static IServiceCollection AddHosting(this IServiceCollection services, IConfiguration configuration = null)
|
||||
public static IServiceCollection AddHosting(this IServiceCollection services)
|
||||
{
|
||||
return services.AddHosting(configuration: null);
|
||||
}
|
||||
|
||||
public static IServiceCollection AddHosting(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.TryAdd(ServiceDescriptor.Transient<IHostingEngine, HostingEngine>());
|
||||
services.TryAdd(ServiceDescriptor.Transient<IServerLoader, ServerLoader>());
|
||||
|
|
|
|||
|
|
@ -47,12 +47,22 @@ namespace Microsoft.AspNet.TestHost
|
|||
|
||||
public static TestServer Create(Action<IApplicationBuilder> app)
|
||||
{
|
||||
return Create(CallContextServiceLocator.Locator.ServiceProvider, app);
|
||||
return Create(CallContextServiceLocator.Locator.ServiceProvider, app, configureHostServices: null);
|
||||
}
|
||||
|
||||
public static TestServer Create(Action<IApplicationBuilder> app, Action<IServiceCollection> configureHostServices)
|
||||
{
|
||||
return Create(CallContextServiceLocator.Locator.ServiceProvider, app, configureHostServices);
|
||||
}
|
||||
|
||||
public static TestServer Create(IServiceProvider serviceProvider, Action<IApplicationBuilder> app)
|
||||
{
|
||||
var appServices = HostingServices.Create(serviceProvider).BuildServiceProvider();
|
||||
return Create(serviceProvider, app, configureHostServices: null);
|
||||
}
|
||||
|
||||
public static TestServer Create(IServiceProvider serviceProvider, Action<IApplicationBuilder> app, Action<IServiceCollection> configureHostServices)
|
||||
{
|
||||
var appServices = HostingServices.Create(serviceProvider, configureHostServices).BuildServiceProvider();
|
||||
var config = new Configuration();
|
||||
return new TestServer(config, appServices, app);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,73 @@ namespace Microsoft.AspNet.Hosting.Tests
|
|||
Assert.Null(provider.GetService<IFakeScopedService>()); // Make sure we don't leak non manifest services
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateCanAddAdditionalServices()
|
||||
{
|
||||
// Arrange
|
||||
var fallbackServices = new ServiceCollection();
|
||||
fallbackServices.AddTransient<IFakeService, FakeService>();
|
||||
fallbackServices.AddTransient<IFakeScopedService, FakeService>(); // Don't register in manifest
|
||||
|
||||
fallbackServices.AddInstance<IServiceManifest>(new ServiceManifest(
|
||||
new Type[] {
|
||||
typeof(IFakeService),
|
||||
}));
|
||||
|
||||
var instance = new FakeService();
|
||||
var factoryInstance = new FakeFactoryService(instance);
|
||||
|
||||
var services = HostingServices.Create(fallbackServices.BuildServiceProvider(),
|
||||
additionalHostServices =>
|
||||
{
|
||||
additionalHostServices.AddSingleton<IFakeSingletonService, FakeService>();
|
||||
additionalHostServices.AddInstance<IFakeServiceInstance>(instance);
|
||||
additionalHostServices.AddSingleton<IFactoryService>(serviceProvider => factoryInstance);
|
||||
});
|
||||
|
||||
// Act
|
||||
var provider = services.BuildServiceProvider();
|
||||
var singleton = provider.GetRequiredService<IFakeSingletonService>();
|
||||
var transient = provider.GetRequiredService<IFakeService>();
|
||||
var factory = provider.GetRequiredService<IFactoryService>();
|
||||
var manifest = provider.GetRequiredService<IServiceManifest>();
|
||||
|
||||
// Assert
|
||||
Assert.Same(singleton, provider.GetRequiredService<IFakeSingletonService>());
|
||||
Assert.NotSame(transient, provider.GetRequiredService<IFakeService>());
|
||||
Assert.Same(instance, provider.GetRequiredService<IFakeServiceInstance>());
|
||||
Assert.Same(factoryInstance, factory);
|
||||
Assert.Same(factory.FakeService, instance);
|
||||
Assert.Null(provider.GetService<INonexistentService>());
|
||||
Assert.Null(provider.GetService<IFakeScopedService>()); // Make sure we don't leak non manifest services
|
||||
Assert.Contains(typeof(IFakeSingletonService), manifest.Services);
|
||||
Assert.Contains(typeof(IFakeServiceInstance), manifest.Services);
|
||||
Assert.Contains(typeof(IFactoryService), manifest.Services);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateAdditionalServicesDoNotOverrideFallback()
|
||||
{
|
||||
// Arrange
|
||||
var fallbackServices = new ServiceCollection();
|
||||
fallbackServices.AddTransient<IFakeService, FakeService>();
|
||||
|
||||
fallbackServices.AddInstance<IServiceManifest>(new ServiceManifest(
|
||||
new Type[] {
|
||||
typeof(IFakeService),
|
||||
}));
|
||||
|
||||
var services = HostingServices.Create(fallbackServices.BuildServiceProvider(),
|
||||
additionalHostServices => additionalHostServices.AddSingleton<IFakeService, FakeService>());
|
||||
|
||||
// Act
|
||||
var provider = services.BuildServiceProvider();
|
||||
var stillTransient = provider.GetRequiredService<IFakeService>();
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(stillTransient, provider.GetRequiredService<IFakeService>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanHideImportedServices()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<DevelopmentServerPort>23533</DevelopmentServerPort>
|
||||
<DevelopmentServerPort>18007</DevelopmentServerPort>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -10,6 +10,7 @@ using Microsoft.AspNet.Builder;
|
|||
using Microsoft.AspNet.Hosting;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.TestHost
|
||||
|
|
@ -36,10 +37,25 @@ namespace Microsoft.AspNet.TestHost
|
|||
Assert.Throws<InvalidOperationException>(() => TestServer.Create(services, new Startup().Configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanAccessLogger()
|
||||
{
|
||||
TestServer server = TestServer.Create(app =>
|
||||
{
|
||||
app.Run(context =>
|
||||
{
|
||||
var logger = app.ApplicationServices.GetRequiredService<ILogger<HttpContext>>();
|
||||
return context.Response.WriteAsync("FoundLogger:" + (logger != null));
|
||||
});
|
||||
});
|
||||
|
||||
string result = await server.CreateClient().GetStringAsync("/path");
|
||||
Assert.Equal("FoundLogger:True", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanAccessHttpContext()
|
||||
{
|
||||
var services = new ServiceCollection().BuildServiceProvider();
|
||||
TestServer server = TestServer.Create(app =>
|
||||
{
|
||||
app.Run(context =>
|
||||
|
|
@ -53,6 +69,35 @@ namespace Microsoft.AspNet.TestHost
|
|||
Assert.Equal("HasContext:True", result);
|
||||
}
|
||||
|
||||
public class ContextHolder
|
||||
{
|
||||
public ContextHolder(IHttpContextAccessor accessor)
|
||||
{
|
||||
Accessor = accessor;
|
||||
}
|
||||
|
||||
public IHttpContextAccessor Accessor { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanAddNewHostServices()
|
||||
{
|
||||
TestServer server = TestServer.Create(app =>
|
||||
{
|
||||
var a = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
|
||||
|
||||
app.Run(context =>
|
||||
{
|
||||
var b = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
|
||||
var accessor = app.ApplicationServices.GetRequiredService<ContextHolder>();
|
||||
return context.Response.WriteAsync("HasContext:" + (accessor.Accessor.HttpContext != null));
|
||||
});
|
||||
}, newHostServices => newHostServices.AddSingleton<ContextHolder>());
|
||||
|
||||
string result = await server.CreateClient().GetStringAsync("/path");
|
||||
Assert.Equal("HasContext:True", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateInvokesApp()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue