diff --git a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostBuilder.cs b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostBuilder.cs index 4d3cd6016bcd84..dedaf5d3be0874 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostBuilder.cs +++ b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/IHostBuilder.cs @@ -59,7 +59,15 @@ public interface IHostBuilder /// The type of builder. /// The factory to register. /// The same instance of the for chaining. +#if NET + IHostBuilder UseServiceProviderFactory(Func> factory) where TContainerBuilder : notnull + { + ArgumentNullException.ThrowIfNull(factory); + throw new NotSupportedException($"The type '{GetType()}' does not support '{nameof(UseServiceProviderFactory)}' with a context-based factory. Override this method to provide an implementation."); + } +#else IHostBuilder UseServiceProviderFactory(Func> factory) where TContainerBuilder : notnull; +#endif /// /// Enables configuring the instantiated dependency container. This can be called multiple times and diff --git a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/HostBuilderContextTests.cs b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/HostBuilderContextTests.cs index 44903b3aca6760..677bf5a0561e2a 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/HostBuilderContextTests.cs +++ b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/HostBuilderContextTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Xunit; @@ -102,4 +103,62 @@ private class TestHostEnvironment : IHostEnvironment public IFileProvider ContentRootFileProvider { get; set; } = null!; } } + +#if NET + /// + /// Tests the default interface method (DIM) for . + /// This DIM ensures old IHostBuilder implementations (e.g., from Microsoft.Extensions.Hosting 2.2.0.0) can be + /// loaded without TypeLoadException on .NET even though they only implement the non-Func overload. + /// + public class IHostBuilderDefaultInterfaceMethodTests + { + [Fact] + public void UseServiceProviderFactory_FuncOverload_WithoutOverride_ThrowsNotSupportedException() + { + IHostBuilder builder = new MinimalHostBuilder(); + Assert.Throws(() => builder.UseServiceProviderFactory( + _ => new MinimalServiceProviderFactory())); + } + + [Fact] + public void UseServiceProviderFactory_FuncOverload_WithNullFactory_ThrowsArgumentNullException() + { + IHostBuilder builder = new MinimalHostBuilder(); + Func>? factory = null; + AssertExtensions.Throws("factory", () => builder.UseServiceProviderFactory(factory)); + } + + [Fact] + public void UseServiceProviderFactory_NonFuncOverload_WithoutDIM_CanBeImplementedAndCalled() + { + IHostBuilder builder = new MinimalHostBuilder(); + // This overload does not have a DIM and the MinimalHostBuilder provides its own implementation. + Assert.Same(builder, builder.UseServiceProviderFactory(new MinimalServiceProviderFactory())); + } + + /// + /// Simulates an old IHostBuilder implementation (e.g., from v2.2) that only implements the + /// non-Func overload of UseServiceProviderFactory. The Func overload falls back to the DIM. + /// + private class MinimalHostBuilder : IHostBuilder + { + public IDictionary Properties { get; } = new Dictionary(); + + public IHostBuilder ConfigureAppConfiguration(Action configureDelegate) => this; + public IHostBuilder ConfigureContainer(Action configureDelegate) => this; + public IHostBuilder ConfigureHostConfiguration(Action configureDelegate) => this; + public IHostBuilder ConfigureServices(Action configureDelegate) => this; + public IHostBuilder UseServiceProviderFactory(IServiceProviderFactory factory) where TContainerBuilder : notnull => this; + // Note: UseServiceProviderFactory(Func<...>) is intentionally not overridden here; + // the DIM on IHostBuilder provides the fallback implementation. + public IHost Build() => null!; + } + + private class MinimalServiceProviderFactory : IServiceProviderFactory + { + public IServiceCollection CreateBuilder(IServiceCollection services) => services; + public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder) => null!; + } + } +#endif } diff --git a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/Microsoft.Extensions.Hosting.Abstractions.Tests.csproj b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/Microsoft.Extensions.Hosting.Abstractions.Tests.csproj index ca92d0164b5fbd..6e3037bad5e40a 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/Microsoft.Extensions.Hosting.Abstractions.Tests.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/tests/Microsoft.Extensions.Hosting.Abstractions.Tests.csproj @@ -7,10 +7,7 @@ - - - - +