Make it easier to add hosting services

Fixes https://github.com/aspnet/Hosting/issues/145
This commit is contained in:
Hao Kung 2015-03-05 12:23:14 -08:00
parent d0980738e3
commit f7c78b8fbc
7 changed files with 165 additions and 16 deletions

View File

@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Hosting
public void Configure(IHostingEnvironment hostingEnv) public void Configure(IHostingEnvironment hostingEnv)
{ {
hostingEnv.EnvironmentName = _config.Get(EnvironmentKey) ?? hostingEnv.EnvironmentName; hostingEnv.EnvironmentName = _config?.Get(EnvironmentKey) ?? hostingEnv.EnvironmentName;
} }
} }
} }

View File

@ -14,44 +14,66 @@ namespace Microsoft.AspNet.Hosting
{ {
public static class HostingServices public static class HostingServices
{ {
private static IServiceCollection Import(IServiceProvider fallbackProvider) private static IServiceCollection Import(IServiceProvider fallbackProvider, Action<IServiceCollection> configureHostServices)
{ {
var services = new ServiceCollection(); var services = new ServiceCollection();
if (configureHostServices != null)
{
configureHostServices(services);
}
var manifest = fallbackProvider.GetRequiredService<IServiceManifest>(); var manifest = fallbackProvider.GetRequiredService<IServiceManifest>();
foreach (var service in manifest.Services) foreach (var service in manifest.Services)
{ {
services.AddTransient(service, sp => fallbackProvider.GetService(service)); services.AddTransient(service, sp => fallbackProvider.GetService(service));
} }
services.AddSingleton<IServiceManifest>(sp => new HostingManifest(services));
return 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(); return Create(fallbackServices, configureHostServices: null, configuration: null);
var services = Import(fallbackServices); }
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.AddHosting(configuration);
services.AddSingleton<IServiceManifest>(sp => new HostingManifest(fallbackServices));
return services; return services;
} }
// Manifest exposes the fallback manifest in addition to ITypeActivator, IHostingEnvironment, and ILoggerFactory // Manifest exposes the fallback manifest in addition to ITypeActivator, IHostingEnvironment, and ILoggerFactory
private class HostingManifest : IServiceManifest private class HostingManifest : IServiceManifest
{ {
public HostingManifest(IServiceProvider fallback) public HostingManifest(IServiceCollection hostServices)
{ {
var manifest = fallback.GetRequiredService<IServiceManifest>();
Services = new Type[] { Services = new Type[] {
typeof(ITypeActivator), typeof(ITypeActivator),
typeof(IHostingEnvironment), typeof(IHostingEnvironment),
typeof(ILoggerFactory), typeof(ILoggerFactory),
typeof(IHttpContextAccessor), typeof(IHttpContextAccessor),
typeof(IApplicationLifetime) typeof(IApplicationLifetime)
}.Concat(manifest.Services).Distinct(); }.Concat(hostServices.Select(s => s.ServiceType)).Distinct();
} }
public IEnumerable<Type> Services { get; private set; } public IEnumerable<Type> Services { get; private set; }

View File

@ -11,7 +11,12 @@ namespace Microsoft.Framework.DependencyInjection
{ {
public static class HostingServicesExtensions 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<IHostingEngine, HostingEngine>());
services.TryAdd(ServiceDescriptor.Transient<IServerLoader, ServerLoader>()); services.TryAdd(ServiceDescriptor.Transient<IServerLoader, ServerLoader>());

View File

@ -47,12 +47,22 @@ namespace Microsoft.AspNet.TestHost
public static TestServer Create(Action<IApplicationBuilder> app) 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) 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(); var config = new Configuration();
return new TestServer(config, appServices, app); return new TestServer(config, appServices, app);
} }

View File

@ -52,6 +52,73 @@ namespace Microsoft.AspNet.Hosting.Tests
Assert.Null(provider.GetService<IFakeScopedService>()); // Make sure we don't leak non manifest services 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] [Fact]
public void CanHideImportedServices() public void CanHideImportedServices()
{ {

View File

@ -12,7 +12,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<SchemaVersion>2.0</SchemaVersion> <SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>23533</DevelopmentServerPort> <DevelopmentServerPort>18007</DevelopmentServerPort>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project> </Project>

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
using Xunit; using Xunit;
namespace Microsoft.AspNet.TestHost namespace Microsoft.AspNet.TestHost
@ -36,10 +37,25 @@ namespace Microsoft.AspNet.TestHost
Assert.Throws<InvalidOperationException>(() => TestServer.Create(services, new Startup().Configuration)); 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] [Fact]
public async Task CanAccessHttpContext() public async Task CanAccessHttpContext()
{ {
var services = new ServiceCollection().BuildServiceProvider();
TestServer server = TestServer.Create(app => TestServer server = TestServer.Create(app =>
{ {
app.Run(context => app.Run(context =>
@ -53,6 +69,35 @@ namespace Microsoft.AspNet.TestHost
Assert.Equal("HasContext:True", result); 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] [Fact]
public async Task CreateInvokesApp() public async Task CreateInvokesApp()
{ {