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

View File

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

View File

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

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
}
[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()
{

View File

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

View File

@ -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()
{