dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.57k stars 10.05k forks source link

IOptions<HealthCheckServiceOptions> seems to be registered in CreateSlimBuilder even though HealthChecks are not #56475

Closed Simonl9l closed 3 months ago

Simonl9l commented 5 months ago

Is there an existing issue for this?

Describe the bug

WebApplication.CreateSlimBuilder is supposed to be lean in support of AoT etc.

In passing I discovered that if have MyService registered into the DI Container, and it has a default parameterless constructor and a second constructor that takes a IOptions<HealthCheckServiceOptions> this second constructor gets involved even though no HeathChecks have been configured via AddHealthChecks nor MapHealthChecks.

This seems to be unexpected?

Expected Behavior

Per above the default parameterless constructor shed be invoked.

Where is there a default behavior to register IOptions<HealthCheckServiceOptions> if there is no Heath Check usage?

I have a service that I want to be Health Check aware, but more required so it will use the HealthCheckServiceOptions .Registrations.Add to add the HealthCheck is the WebApplication supports it.

Steps To Reproduce

var builder = WebApplication.CreateSlimBuilder();
builder.Services.AddSingleton<MyService>()
var web = builder.Build();
var options = web.Services.GetRequiredService<IOptions<HealthCheckServiceOptions>>();

here is My Service:

public class MyService
{
    private static HealthCheckServiceOptions? _options;

    public MyService()
    {
        Console.WriteLine("RouterHealthCheckRegistrationMgr parameterless");
        // Used by Router DI as there are no HealthChecks Registered
    }

    public MyService(IOptions<HealthCheckServiceOptions> options)
    {
        _options = options.Value;
    }
}

notably if I modify the second constructor - I get the desired behavior:

 public MyService(HealthCheckService _, IOptions<HealthCheckServiceOptions> options)
    {
        _options = options.Value;
    }

Exceptions (if any)

No response

.NET Version

8.0.203

Anything else?

dotnet --info .NET SDK: Version: 8.0.203 Commit: 5e1ceea679 Workload version: 8.0.200-manifests.00e64df5

Runtime Environment: OS Name: Mac OS X OS Version: 13.6 OS Platform: Darwin RID: osx-arm64 Base Path: /usr/local/share/dotnet/sdk/8.0.203/

.NET workloads installed: [wasi-experimental] Installation Source: SDK 8.0.200 Manifest Version: 8.0.6/8.0.100 Manifest Path: /usr/local/share/dotnet/sdk-manifests/8.0.100/microsoft.net.workload.mono.toolchain.current/8.0.6/WorkloadManifest.json Install Type: FileBased

Host: Version: 8.0.3 Architecture: arm64 Commit: 9f4b1f5d66

.NET SDKs installed: 6.0.417 [/usr/local/share/dotnet/sdk] 7.0.306 [/usr/local/share/dotnet/sdk] 8.0.100 [/usr/local/share/dotnet/sdk] 8.0.203 [/usr/local/share/dotnet/sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 6.0.25 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.9 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.25 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.9 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Other architectures found: None

Environment variables: Not set

global.json file: Not found

Learn more: https://aka.ms/dotnet/info

Download .NET: https://aka.ms/dotnet/download

eerhardt commented 3 months ago

Thanks for the issue.

The problem isn't HealthCheckServiceOptions, this reproduces with any IOptions because the open generic IOptions<> is registered as a service.

You will see the same behavior with:

using Microsoft.Extensions.Options;

var builder = WebApplication.CreateSlimBuilder();
builder.Services.AddSingleton<MyService>();
var web = builder.Build();
var options = web.Services.GetRequiredService<MyService>();

public class RandomOptions
{
    public string? Name { get; set; }
}

public class MyService
{
    private static RandomOptions? _options;

    public MyService()
    {
        Console.WriteLine("MyService parameterless");
    }

    public MyService(IOptions<RandomOptions> options)
    {
        _options = options.Value;
        Console.WriteLine("MyService RandomOptions");
    }
}

Console.WriteLine("MyService RandomOptions"); will be called because any IOptions service is registered. CreateSlimBuilder obviously doesn't know anything about RandomOptions in this example above.

Closing as this behavior is by design.