Support IServiceProviderFactory in WebAssemblyHostBuilder (imported from Blazor PR 1623) (#4785)

This commit is contained in:
Steve Sanderson 2018-12-14 17:37:14 +00:00 committed by GitHub
parent 54c1122582
commit d30c407dd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 187 additions and 1 deletions

View File

@ -17,6 +17,18 @@ namespace Microsoft.AspNetCore.Components.Hosting
/// </summary>
IDictionary<object, object> Properties { get; }
/// <summary>
/// Overrides the factory used to create the service provider.
/// </summary>
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
/// <summary>
/// Overrides the factory used to create the service provider.
/// </summary>
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory);
/// <summary>
/// Adds services to the container. This can be called multiple times and the results will be additive.
/// </summary>

View File

@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. 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.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Components.Hosting
{
// Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/IServiceFactoryAdapter.cs
internal interface IWebAssemblyServiceFactoryAdapter
{
object CreateBuilder(IServiceCollection services);
IServiceProvider CreateServiceProvider(object containerBuilder);
}
}

View File

@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Components.Hosting
private List<Action<WebAssemblyHostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<WebAssemblyHostBuilderContext, IServiceCollection>>();
private bool _hostBuilt;
private WebAssemblyHostBuilderContext _BrowserHostBuilderContext;
private IWebAssemblyServiceFactoryAdapter _serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
private IServiceProvider _appServices;
/// <summary>
@ -39,6 +40,26 @@ namespace Microsoft.AspNetCore.Components.Hosting
return this;
}
/// <summary>
/// Overrides the factory used to create the service provider.
/// </summary>
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
public IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
{
_serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
return this;
}
/// <summary>
/// Overrides the factory used to create the service provider.
/// </summary>
/// <returns>The same instance of the <see cref="IWebAssemblyHostBuilder"/> for chaining.</returns>
public IWebAssemblyHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
{
_serviceProviderFactory = new WebAssemblyServiceFactoryAdapter<TContainerBuilder>(() => _BrowserHostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));
return this;
}
/// <summary>
/// Run the given actions to initialize the host. This can only be called once.
/// </summary>
@ -85,7 +106,8 @@ namespace Microsoft.AspNetCore.Components.Hosting
configureServicesAction(_BrowserHostBuilderContext, services);
}
_appServices = services.BuildServiceProvider();
var builder = _serviceProviderFactory.CreateBuilder(services);
_appServices = _serviceProviderFactory.CreateServiceProvider(builder);
}
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) .NET Foundation. 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.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Components.Hosting
{
// Equivalent to https://github.com/aspnet/Extensions/blob/master/src/Hosting/Hosting/src/Internal/ServiceFactoryAdapter.cs
internal class WebAssemblyServiceFactoryAdapter<TContainerBuilder> : IWebAssemblyServiceFactoryAdapter
{
private IServiceProviderFactory<TContainerBuilder> _serviceProviderFactory;
private readonly Func<WebAssemblyHostBuilderContext> _contextResolver;
private Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> _factoryResolver;
public WebAssemblyServiceFactoryAdapter(IServiceProviderFactory<TContainerBuilder> serviceProviderFactory)
{
_serviceProviderFactory = serviceProviderFactory ?? throw new ArgumentNullException(nameof(serviceProviderFactory));
}
public WebAssemblyServiceFactoryAdapter(Func<WebAssemblyHostBuilderContext> contextResolver, Func<WebAssemblyHostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factoryResolver)
{
_contextResolver = contextResolver ?? throw new ArgumentNullException(nameof(contextResolver));
_factoryResolver = factoryResolver ?? throw new ArgumentNullException(nameof(factoryResolver));
}
public object CreateBuilder(IServiceCollection services)
{
if (_serviceProviderFactory == null)
{
_serviceProviderFactory = _factoryResolver(_contextResolver());
if (_serviceProviderFactory == null)
{
throw new InvalidOperationException("The resolver returned a null IServiceProviderFactory");
}
}
return _serviceProviderFactory.CreateBuilder(services);
}
public IServiceProvider CreateServiceProvider(object containerBuilder)
{
if (_serviceProviderFactory == null)
{
throw new InvalidOperationException("CreateBuilder must be called before CreateServiceProvider");
}
return _serviceProviderFactory.CreateServiceProvider((TContainerBuilder)containerBuilder);
}
}
}

View File

@ -2,8 +2,10 @@
// 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.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using Xunit;
namespace Microsoft.AspNetCore.Components.Hosting
@ -76,5 +78,86 @@ namespace Microsoft.AspNetCore.Components.Hosting
services.AddSingleton<string>("foo");
}
}
[Fact]
public void HostBuilder_CanCustomizeServiceFactory()
{
// Arrange
var builder = new WebAssemblyHostBuilder();
builder.UseServiceProviderFactory(new TestServiceProviderFactory());
// Act
var host = builder.Build();
// Assert
Assert.IsType<TestServiceProvider>(host.Services);
}
[Fact]
public void HostBuilder_CanCustomizeServiceFactoryWithContext()
{
// Arrange
var builder = new WebAssemblyHostBuilder();
builder.UseServiceProviderFactory(context =>
{
Assert.NotNull(context.Properties);
Assert.Same(builder.Properties, context.Properties);
return new TestServiceProviderFactory();
});
// Act
var host = builder.Build();
// Assert
Assert.IsType<TestServiceProvider>(host.Services);
}
private class TestServiceProvider : IServiceProvider
{
private readonly IServiceProvider _underlyingProvider;
public TestServiceProvider(IServiceProvider underlyingProvider)
{
_underlyingProvider = underlyingProvider;
}
public object GetService(Type serviceType)
{
if (serviceType == typeof(IWebAssemblyHost))
{
// Since the test will make assertions about the resulting IWebAssemblyHost,
// show that custom DI containers have the power to substitute themselves
// as the IServiceProvider
return new WebAssemblyHost(
this, _underlyingProvider.GetRequiredService<IJSRuntime>());
}
else
{
return _underlyingProvider.GetService(serviceType);
}
}
}
private class TestServiceProviderFactory : IServiceProviderFactory<IServiceCollection>
{
public IServiceCollection CreateBuilder(IServiceCollection services)
{
return new TestServiceCollection(services);
}
public IServiceProvider CreateServiceProvider(IServiceCollection serviceCollection)
{
Assert.IsType<TestServiceCollection>(serviceCollection);
return new TestServiceProvider(serviceCollection.BuildServiceProvider());
}
class TestServiceCollection : List<ServiceDescriptor>, IServiceCollection
{
public TestServiceCollection(IEnumerable<ServiceDescriptor> collection)
: base(collection)
{
}
}
}
}
}