Skip to content

Commit b1f3416

Browse files
committed
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 :( ).
1 parent e8d2d56 commit b1f3416

File tree

2 files changed

+112
-2
lines changed

2 files changed

+112
-2
lines changed

src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHostBuilder.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Linq;
7-
using System.Net.Http;
87
using Microsoft.AspNetCore.Components.Routing;
98
using Microsoft.AspNetCore.Components.WebAssembly.Services;
109
using Microsoft.Extensions.Configuration;
@@ -20,6 +19,8 @@ namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
2019
/// </summary>
2120
public sealed class WebAssemblyHostBuilder
2221
{
22+
private Func<IServiceProvider> _createServiceProvider;
23+
2324
/// <summary>
2425
/// Creates an instance of <see cref="WebAssemblyHostBuilder"/> using the most common
2526
/// conventions and settings.
@@ -53,6 +54,11 @@ private WebAssemblyHostBuilder()
5354
Services = new ServiceCollection();
5455

5556
InitializeDefaultServices();
57+
58+
_createServiceProvider = () =>
59+
{
60+
return Services.BuildServiceProvider();
61+
};
5662
}
5763

5864
/// <summary>
@@ -71,6 +77,40 @@ private WebAssemblyHostBuilder()
7177
/// </summary>
7278
public IServiceCollection Services { get; }
7379

80+
/// <summary>
81+
/// Registers a <see cref="IServiceProviderFactory{TBuilder}" /> instance to be used to create the <see cref="IServiceProvider" />.
82+
/// </summary>
83+
/// <param name="factory">The <see cref="IServiceProviderFactory{TBuilder}" />.</param>
84+
/// <param name="configure">
85+
/// A delegate used to configure the <typeparamref T="TBuilder" />. This can be used to configure services using
86+
/// APIS specific to the <see cref="IServiceProviderFactory{TBuilder}" /> implementation.
87+
/// </param>
88+
/// <typeparam name="TBuilder">The type of builder provided by the <see cref="IServiceProviderFactory{TBuilder}" />.</typeparam>
89+
/// <remarks>
90+
/// <para>
91+
/// <see cref="ConfigureContainer{TBuilder}(IServiceProviderFactory{TBuilder}, Action{TBuilder})"/> is called by <see cref="Build"/>
92+
/// and so the delegate provided by <paramref name="configure"/> will run after all other services have been registered.
93+
/// </para>
94+
/// <para>
95+
/// Multiple calls to <see cref="ConfigureContainer{TBuilder}(IServiceProviderFactory{TBuilder}, Action{TBuilder})"/> will replace
96+
/// the previously stored <paramref name="factory"/> and <paramref name="configure"/> delegate.
97+
/// </para>
98+
/// </remarks>
99+
public void ConfigureContainer<TBuilder>(IServiceProviderFactory<TBuilder> factory, Action<TBuilder> configure = null)
100+
{
101+
if (factory == null)
102+
{
103+
throw new ArgumentNullException(nameof(factory));
104+
}
105+
106+
_createServiceProvider = () =>
107+
{
108+
var container = factory.CreateBuilder(Services);
109+
configure?.Invoke(container);
110+
return factory.CreateServiceProvider(container);
111+
};
112+
}
113+
74114
/// <summary>
75115
/// Builds a <see cref="WebAssemblyHost"/> instance based on the configuration of this builder.
76116
/// </summary>
@@ -84,7 +124,7 @@ public WebAssemblyHost Build()
84124
// A Blazor application always runs in a scope. Since we want to make it possible for the user
85125
// to configure services inside *that scope* inside their startup code, we create *both* the
86126
// service provider and the scope here.
87-
var services = Services.BuildServiceProvider();
127+
var services = _createServiceProvider();
88128
var scope = services.GetRequiredService<IServiceScopeFactory>().CreateScope();
89129

90130
return new WebAssemblyHost(services, scope, configuration, RootComponents.ToArray());

src/Components/WebAssembly/WebAssembly/test/Hosting/WebAssemblyHostBuilderTest.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,76 @@ public void Build_AllowsConfiguringServices()
5151
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
5252
}
5353

54+
[Fact]
55+
public void Build_AllowsConfiguringContainer()
56+
{
57+
// Arrange
58+
var builder = WebAssemblyHostBuilder.CreateDefault();
59+
60+
builder.Services.AddScoped<StringBuilder>();
61+
var factory = new MyFakeServiceProviderFactory();
62+
builder.ConfigureContainer(factory);
63+
64+
// Act
65+
var host = builder.Build();
66+
67+
// Assert
68+
Assert.True(factory.CreateServiceProviderCalled);
69+
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
70+
}
71+
72+
[Fact]
73+
public void Build_AllowsConfiguringContainer_WithDelegate()
74+
{
75+
// Arrange
76+
var builder = WebAssemblyHostBuilder.CreateDefault();
77+
78+
builder.Services.AddScoped<StringBuilder>();
79+
80+
var factory = new MyFakeServiceProviderFactory();
81+
builder.ConfigureContainer(factory, builder =>
82+
{
83+
builder.ServiceCollection.AddScoped<List<string>>();
84+
});
85+
86+
// Act
87+
var host = builder.Build();
88+
89+
// Assert
90+
Assert.True(factory.CreateServiceProviderCalled);
91+
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
92+
Assert.NotNull(host.Services.GetRequiredService<List<string>>());
93+
}
94+
95+
private class MyFakeDIBuilderThing
96+
{
97+
public MyFakeDIBuilderThing(IServiceCollection serviceCollection)
98+
{
99+
ServiceCollection = serviceCollection;
100+
}
101+
102+
public IServiceCollection ServiceCollection { get; }
103+
}
104+
105+
private class MyFakeServiceProviderFactory : IServiceProviderFactory<MyFakeDIBuilderThing>
106+
{
107+
public bool CreateServiceProviderCalled { get; set; }
108+
109+
public MyFakeDIBuilderThing CreateBuilder(IServiceCollection services)
110+
{
111+
return new MyFakeDIBuilderThing(services);
112+
}
113+
114+
public IServiceProvider CreateServiceProvider(MyFakeDIBuilderThing containerBuilder)
115+
{
116+
// This is the best way to test the factory was actually used. The Host doesn't
117+
// expose the *root* service provider, only a scoped instance. So we can return
118+
// a different type here, but we have no way to inspect it.
119+
CreateServiceProviderCalled = true;
120+
return containerBuilder.ServiceCollection.BuildServiceProvider();
121+
}
122+
}
123+
54124
[Fact]
55125
public void Build_AddsConfigurationToServices()
56126
{

0 commit comments

Comments
 (0)