Make it easier to add hosting services

Fixes https://github.com/aspnet/Hosting/pull/146
This commit is contained in:
Hao Kung 2015-02-19 13:22:58 -08:00
parent 34acb16e20
commit 2ee12735b3
6 changed files with 153 additions and 19 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,39 +14,61 @@ 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) }
.Concat(manifest.Services).Distinct();
.Concat(hostServices.Select(s => s.ServiceType)).Distinct();
}
public IEnumerable<Type> Services { get; private set; }

View File

@ -12,8 +12,13 @@ namespace Microsoft.Framework.DependencyInjection
{
public static class HostingServicesExtensions
{
public static IServiceCollection AddLogging(this IServiceCollection services)
{
return services.AddLogging(config: null);
}
// REVIEW: Logging doesn't depend on DI, where should this live?
public static IServiceCollection AddLogging(this IServiceCollection services, IConfiguration config = null)
public static IServiceCollection AddLogging(this IServiceCollection services, IConfiguration config)
{
var describe = new ServiceDescriber(config);
services.TryAdd(describe.Singleton<ILoggerFactory, LoggerFactory>());
@ -21,7 +26,12 @@ namespace Microsoft.Framework.DependencyInjection
return services;
}
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)
{
var describer = new ServiceDescriber(configuration);

View File

@ -2,10 +2,7 @@
// 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.Linq;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
@ -49,12 +46,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

@ -53,6 +53,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

@ -40,7 +40,6 @@ namespace Microsoft.AspNet.TestHost
[Fact]
public async Task CanAccessHttpContext()
{
var services = new ServiceCollection().BuildServiceProvider();
TestServer server = TestServer.Create(app =>
{
app.Run(context =>
@ -54,6 +53,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.Value != null));
});
}, newHostServices => newHostServices.AddSingleton<ContextHolder>());
string result = await server.CreateClient().GetStringAsync("/path");
Assert.Equal("HasContext:True", result);
}
[Fact]
public async Task CreateInvokesApp()
{