diff --git a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs index cef05b9bdbfc..da4617081193 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs +++ b/src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.Http; using Microsoft.AspNetCore.Components.Routing; using Microsoft.AspNetCore.Components.WebAssembly.Services; using Microsoft.Extensions.Configuration; @@ -20,6 +19,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting /// public sealed class WebAssemblyHostBuilder { + private Func _createServiceProvider; + /// /// Creates an instance of using the most common /// conventions and settings. @@ -53,6 +54,11 @@ private WebAssemblyHostBuilder() Services = new ServiceCollection(); InitializeDefaultServices(); + + _createServiceProvider = () => + { + return Services.BuildServiceProvider(); + }; } /// @@ -71,6 +77,40 @@ private WebAssemblyHostBuilder() /// public IServiceCollection Services { get; } + /// + /// Registers a instance to be used to create the . + /// + /// The . + /// + /// A delegate used to configure the . This can be used to configure services using + /// APIS specific to the implementation. + /// + /// The type of builder provided by the . + /// + /// + /// is called by + /// and so the delegate provided by will run after all other services have been registered. + /// + /// + /// Multiple calls to will replace + /// the previously stored and delegate. + /// + /// + public void ConfigureContainer(IServiceProviderFactory factory, Action configure = null) + { + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + _createServiceProvider = () => + { + var container = factory.CreateBuilder(Services); + configure?.Invoke(container); + return factory.CreateServiceProvider(container); + }; + } + /// /// Builds a instance based on the configuration of this builder. /// @@ -84,7 +124,7 @@ public WebAssemblyHost Build() // 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 // service provider and the scope here. - var services = Services.BuildServiceProvider(); + var services = _createServiceProvider(); var scope = services.GetRequiredService().CreateScope(); return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray()); diff --git a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs index 13465b84933b..5acc214437fd 100644 --- a/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs @@ -51,6 +51,76 @@ public void Build_AllowsConfiguringServices() Assert.NotNull(host.Services.GetRequiredService()); } + [Fact] + public void Build_AllowsConfiguringContainer() + { + // Arrange + var builder = WebAssemblyHostBuilder.CreateDefault(); + + builder.Services.AddScoped(); + var factory = new MyFakeServiceProviderFactory(); + builder.ConfigureContainer(factory); + + // Act + var host = builder.Build(); + + // Assert + Assert.True(factory.CreateServiceProviderCalled); + Assert.NotNull(host.Services.GetRequiredService()); + } + + [Fact] + public void Build_AllowsConfiguringContainer_WithDelegate() + { + // Arrange + var builder = WebAssemblyHostBuilder.CreateDefault(); + + builder.Services.AddScoped(); + + var factory = new MyFakeServiceProviderFactory(); + builder.ConfigureContainer(factory, builder => + { + builder.ServiceCollection.AddScoped>(); + }); + + // Act + var host = builder.Build(); + + // Assert + Assert.True(factory.CreateServiceProviderCalled); + Assert.NotNull(host.Services.GetRequiredService()); + Assert.NotNull(host.Services.GetRequiredService>()); + } + + private class MyFakeDIBuilderThing + { + public MyFakeDIBuilderThing(IServiceCollection serviceCollection) + { + ServiceCollection = serviceCollection; + } + + public IServiceCollection ServiceCollection { get; } + } + + private class MyFakeServiceProviderFactory : IServiceProviderFactory + { + 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] public void Build_AddsConfigurationToServices() {