Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public sealed class WebAssemblyHostBuilder
private Func<IServiceProvider> _createServiceProvider;
private RootTypeCache? _rootComponentCache;
private string? _persistedState;
private ServiceProviderOptions? _serviceProviderOptions;

/// <summary>
/// Creates an instance of <see cref="WebAssemblyHostBuilder"/> using the most common
Expand Down Expand Up @@ -91,7 +92,16 @@ internal WebAssemblyHostBuilder(IInternalJSImportMethods jsMethods)

_createServiceProvider = () =>
{
return Services.BuildServiceProvider(validateScopes: WebAssemblyHostEnvironmentExtensions.IsDevelopment(hostEnvironment));
var isDevelopment = WebAssemblyHostEnvironmentExtensions.IsDevelopment(hostEnvironment);

// Use custom options if provided, otherwise use defaults
var options = _serviceProviderOptions ?? new ServiceProviderOptions
{
ValidateScopes = isDevelopment,
ValidateOnBuild = isDevelopment
};

return Services.BuildServiceProvider(options);
};
}

Expand Down Expand Up @@ -276,6 +286,17 @@ public void ConfigureContainer<TBuilder>(IServiceProviderFactory<TBuilder> facto
};
}

// In WebAssemblyHostBuilder class:
/// <summary>
/// Configures the service provider options for this host builder.
/// </summary>
/// <param name="options">The service provider options to use.</param>
public WebAssemblyHostBuilder UseServiceProviderOptions(ServiceProviderOptions options)
{
_serviceProviderOptions = options ?? throw new ArgumentNullException(nameof(options));
return this;
}

/// <summary>
/// Builds a <see cref="WebAssemblyHost"/> instance based on the configuration of this builder.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting;

/// <summary>
/// Extension methods for configuring a <see cref="WebAssemblyHostBuilder"/>.
/// </summary>
public static class WebAssemblyHostBuilderExtensions
{
/// <summary>
/// Configures the default service provider for the WebAssembly host.
/// </summary>
/// <param name="builder">The <see cref="WebAssemblyHostBuilder"/> to configure.</param>
/// <param name="configure">A callback used to configure the <see cref="ServiceProviderOptions"/>.</param>
/// <returns>The <see cref="WebAssemblyHostBuilder"/>.</returns>
public static WebAssemblyHostBuilder UseDefaultServiceProvider(
this WebAssemblyHostBuilder builder,
Action<ServiceProviderOptions> configure)
{
return builder.UseDefaultServiceProvider((env, options) => configure(options));
}

/// <summary>
/// Configures the default service provider for the WebAssembly host.
/// </summary>
/// <param name="builder">The <see cref="WebAssemblyHostBuilder"/> to configure.</param>
/// <param name="configure">A callback used to configure the <see cref="ServiceProviderOptions"/> with access to the host environment.</param>
/// <returns>The <see cref="WebAssemblyHostBuilder"/>.</returns>
public static WebAssemblyHostBuilder UseDefaultServiceProvider(
this WebAssemblyHostBuilder builder,
Action<IWebAssemblyHostEnvironment, ServiceProviderOptions> configure)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(configure);

var options = new ServiceProviderOptions();
configure(builder.HostEnvironment, options);

return builder.UseServiceProviderOptions(options);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#nullable enable
Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder.UseServiceProviderOptions(Microsoft.Extensions.DependencyInjection.ServiceProviderOptions! options) -> Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder!
Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilderExtensions
Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.Delta
Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.Delta.Delta() -> void
Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.Delta.ILDelta.get -> byte[]!
Expand All @@ -17,4 +19,6 @@ Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.LogEn
Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.LogEntry.Message.init -> void
Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.LogEntry.Severity.get -> int
Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.LogEntry.Severity.init -> void
static Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilderExtensions.UseDefaultServiceProvider(this Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder! builder, System.Action<Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment!, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions!>! configure) -> Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder!
static Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilderExtensions.UseDefaultServiceProvider(this Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder! builder, System.Action<Microsoft.Extensions.DependencyInjection.ServiceProviderOptions!>! configure) -> Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostBuilder!
static Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.ApplyHotReloadDeltas(Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.Delta[]! deltas, int loggingLevel) -> Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.LogEntry[]!
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,9 @@ public void Build_InDevelopment_ConfiguresWithServiceProviderWithScopeValidation
builder.Services.AddScoped<StringBuilder>();
builder.Services.AddSingleton<TestServiceThatTakesStringBuilder>();

// Act
var host = builder.Build();

// Assert
Assert.NotNull(host.Services.GetRequiredService<StringBuilder>());
Assert.Throws<InvalidOperationException>(() => host.Services.GetRequiredService<TestServiceThatTakesStringBuilder>());
// Act & Assert
var exception = Assert.Throws<AggregateException>(() => builder.Build());
Assert.Contains("Cannot consume scoped service", exception.Message);
}

[Fact]
Expand Down Expand Up @@ -248,4 +245,69 @@ public void Builder_SupportsConfiguringLogging()
Assert.Equal<ILoggerProvider>(provider.Object, loggerProvider);

}

[Fact]
public void UseDefaultServiceProvider_DetectsCircularDependencies()
{
// Arrange
var builder = new WebAssemblyHostBuilder(new TestInternalJSImportMethods());

// Add a circular dependency
builder.Services.AddScoped<CircularServiceA>();
builder.Services.AddScoped<CircularServiceB>();

// Act
builder.UseDefaultServiceProvider(options =>
{
options.ValidateOnBuild = true;
});

// Assert
var exception = Assert.Throws<AggregateException>(() => builder.Build());
Assert.Contains("circular dependency", exception.Message.ToLowerInvariant());
}

[Fact]
public void UseDefaultServiceProvider_EnvironmentOverload_WorksCorrectly()
{
// Arrange
var builder = new WebAssemblyHostBuilder(new TestInternalJSImportMethods(environment: "Development"));

// Act
builder.UseDefaultServiceProvider((env, options) =>
{
options.ValidateOnBuild = env.IsDevelopment();
});

var host = builder.Build();

// Assert
Assert.NotNull(host);
}

[Fact]
public void DefaultServiceProviderOptions_InDevelopment_ValidatesOnBuild()
{
// Arrange
var builder = new WebAssemblyHostBuilder(new TestInternalJSImportMethods(environment: "Development"));

// Add a circular dependency - should throw due to default ValidateOnBuild=true in development
builder.Services.AddScoped<CircularServiceA>();
builder.Services.AddScoped<CircularServiceB>();

// Act & Assert
var exception = Assert.Throws<AggregateException>(() => builder.Build());
Assert.Contains("circular dependency", exception.Message.ToLowerInvariant());
}

// Helper classes for testing circular dependencies
private class CircularServiceA
{
public CircularServiceA(CircularServiceB serviceB) { }
}

private class CircularServiceB
{
public CircularServiceB(CircularServiceA serviceA) { }
}
}
Loading