Add service provider factory support
Fixes: #18814 This adds back support on the Blazor WASM Host for using ISerivceProviderFactory<>. We previously had this support when the Blazor WASM host was a clone of generic host, but I accidentally lost it when simplifying the host (sorry :( ).
This commit is contained in:
parent
e8d2d562aa
commit
9c16db3e89
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using Microsoft.AspNetCore.Components.Routing;
|
using Microsoft.AspNetCore.Components.Routing;
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
using Microsoft.AspNetCore.Components.WebAssembly.Services;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
@ -20,6 +19,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class WebAssemblyHostBuilder
|
public sealed class WebAssemblyHostBuilder
|
||||||
{
|
{
|
||||||
|
private Func<IServiceProvider> _createServiceProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates an instance of <see cref="WebAssemblyHostBuilder"/> using the most common
|
/// Creates an instance of <see cref="WebAssemblyHostBuilder"/> using the most common
|
||||||
/// conventions and settings.
|
/// conventions and settings.
|
||||||
|
|
@ -53,6 +54,11 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
Services = new ServiceCollection();
|
Services = new ServiceCollection();
|
||||||
|
|
||||||
InitializeDefaultServices();
|
InitializeDefaultServices();
|
||||||
|
|
||||||
|
_createServiceProvider = () =>
|
||||||
|
{
|
||||||
|
return Services.BuildServiceProvider();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -71,6 +77,40 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IServiceCollection Services { get; }
|
public IServiceCollection Services { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a <see cref="IServiceProviderFactory{TBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="factory">The <see cref="IServiceProviderFactory{TBuilder}" />.</param>
|
||||||
|
/// <param name="configure">
|
||||||
|
/// A delegate used to configure the <typeparamref T="TBuilder" />. This can be used to configure services using
|
||||||
|
/// APIS specific to the <see cref="IServiceProviderFactory{TBuilder}" /> implementation.
|
||||||
|
/// </param>
|
||||||
|
/// <typeparam name="TBuilder">The type of builder provided by the <see cref="IServiceProviderFactory{TBuilder}" />.</typeparam>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <see cref="ConfigureContainer{TBuilder}(IServiceProviderFactory{TBuilder}, Action{TBuilder})"/> is called by <see cref="Build"/>
|
||||||
|
/// and so the delegate provided by <paramref name="configure"/> will run after all other services have been registered.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Multiple calls to <see cref="ConfigureContainer{TBuilder}(IServiceProviderFactory{TBuilder}, Action{TBuilder})"/> will replace
|
||||||
|
/// the previously stored <paramref name="factory"/> and <paramref name="configure"/> delegate.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public void ConfigureContainer<TBuilder>(IServiceProviderFactory<TBuilder> factory, Action<TBuilder> configure = null)
|
||||||
|
{
|
||||||
|
if (factory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(factory));
|
||||||
|
}
|
||||||
|
|
||||||
|
_createServiceProvider = () =>
|
||||||
|
{
|
||||||
|
var container = factory.CreateBuilder(Services);
|
||||||
|
configure?.Invoke(container);
|
||||||
|
return factory.CreateServiceProvider(container);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Builds a <see cref="WebAssemblyHost"/> instance based on the configuration of this builder.
|
/// Builds a <see cref="WebAssemblyHost"/> instance based on the configuration of this builder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -84,7 +124,7 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
// A Blazor application always runs in a scope. Since we want to make it possible for the user
|
// A Blazor application always runs in a scope. Since we want to make it possible for the user
|
||||||
// to configure services inside *that scope* inside their startup code, we create *both* the
|
// to configure services inside *that scope* inside their startup code, we create *both* the
|
||||||
// service provider and the scope here.
|
// service provider and the scope here.
|
||||||
var services = Services.BuildServiceProvider();
|
var services = _createServiceProvider();
|
||||||
var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope();
|
||||||
|
|
||||||
return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray());
|
return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray());
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,76 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
|
||||||
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
|
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_AllowsConfiguringContainer()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = WebAssemblyHostBuilder.CreateDefault();
|
||||||
|
|
||||||
|
builder.Services.AddScoped<StringBuilder>();
|
||||||
|
var factory = new MyFakeServiceProviderFactory();
|
||||||
|
builder.ConfigureContainer(factory);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var host = builder.Build();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(factory.CreateServiceProviderCalled);
|
||||||
|
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Build_AllowsConfiguringContainer_WithDelegate()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var builder = WebAssemblyHostBuilder.CreateDefault();
|
||||||
|
|
||||||
|
builder.Services.AddScoped<StringBuilder>();
|
||||||
|
|
||||||
|
var factory = new MyFakeServiceProviderFactory();
|
||||||
|
builder.ConfigureContainer(factory, builder =>
|
||||||
|
{
|
||||||
|
builder.ServiceCollection.AddScoped<List<string>>();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var host = builder.Build();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(factory.CreateServiceProviderCalled);
|
||||||
|
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
|
||||||
|
Assert.NotNull(host.Services.GetRequiredService<List<string>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyFakeDIBuilderThing
|
||||||
|
{
|
||||||
|
public MyFakeDIBuilderThing(IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
ServiceCollection = serviceCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IServiceCollection ServiceCollection { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MyFakeServiceProviderFactory : IServiceProviderFactory<MyFakeDIBuilderThing>
|
||||||
|
{
|
||||||
|
public bool CreateServiceProviderCalled { get; set; }
|
||||||
|
|
||||||
|
public MyFakeDIBuilderThing CreateBuilder(IServiceCollection services)
|
||||||
|
{
|
||||||
|
return new MyFakeDIBuilderThing(services);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IServiceProvider CreateServiceProvider(MyFakeDIBuilderThing containerBuilder)
|
||||||
|
{
|
||||||
|
// This is the best way to test the factory was actually used. The Host doesn't
|
||||||
|
// expose the *root* service provider, only a scoped instance. So we can return
|
||||||
|
// a different type here, but we have no way to inspect it.
|
||||||
|
CreateServiceProviderCalled = true;
|
||||||
|
return containerBuilder.ServiceCollection.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Build_AddsConfigurationToServices()
|
public void Build_AddsConfigurationToServices()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue