Skip to content
Draft
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 @@ -59,7 +59,15 @@ public interface IHostBuilder
/// <typeparam name="TContainerBuilder">The type of builder.</typeparam>
/// <param name="factory">The factory to register.</param>
/// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
#if NET
IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> 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<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory) where TContainerBuilder : notnull;
#endif

/// <summary>
/// Enables configuring the instantiated dependency container. This can be called multiple times and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Xunit;

Expand Down Expand Up @@ -102,4 +103,62 @@ private class TestHostEnvironment : IHostEnvironment
public IFileProvider ContentRootFileProvider { get; set; } = null!;
}
}

#if NET
/// <summary>
/// Tests the default interface method (DIM) for <see cref="IHostBuilder.UseServiceProviderFactory{TContainerBuilder}(Func{HostBuilderContext, IServiceProviderFactory{TContainerBuilder}})"/>.
/// 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.
/// </summary>
public class IHostBuilderDefaultInterfaceMethodTests
{
[Fact]
public void UseServiceProviderFactory_FuncOverload_WithoutOverride_ThrowsNotSupportedException()
{
IHostBuilder builder = new MinimalHostBuilder();
Assert.Throws<NotSupportedException>(() => builder.UseServiceProviderFactory<IServiceCollection>(
_ => new MinimalServiceProviderFactory()));
}

[Fact]
public void UseServiceProviderFactory_FuncOverload_WithNullFactory_ThrowsArgumentNullException()
{
IHostBuilder builder = new MinimalHostBuilder();
Func<HostBuilderContext, IServiceProviderFactory<IServiceCollection>>? factory = null;
AssertExtensions.Throws<ArgumentNullException>("factory", () => builder.UseServiceProviderFactory<IServiceCollection>(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()));
}

/// <summary>
/// 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.
/// </summary>
private class MinimalHostBuilder : IHostBuilder
{
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();

public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate) => this;
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate) => this;
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate) => this;
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate) => this;
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> 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<IServiceCollection>
{
public IServiceCollection CreateBuilder(IServiceCollection services) => services;
public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder) => null!;
}
}
#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@

<ItemGroup>
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Configuration\src\Microsoft.Extensions.Configuration.csproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Hosting.Abstractions\src\Microsoft.Extensions.Hosting.Abstractions.csproj" />
<ProjectReference Include="$(LibrariesProjectRoot)Microsoft.Extensions.Hosting.Abstractions\src\Microsoft.Extensions.Hosting.Abstractions.csproj" SkipUseReferenceAssembly="true" />
</ItemGroup>

</Project>
Loading