Skip to content

Commit 16973f2

Browse files
bart-vmwareCopilot
andauthored
Fix scoped ASP.NET health checks, correct AddHealthContributor docs (#1636)
* Create a service scope for each ASP.NET health check, correct AddHealthContributor documentation * Update src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs Co-authored-by: Copilot <[email protected]> --------- Co-authored-by: Copilot <[email protected]>
1 parent eb22697 commit 16973f2

File tree

6 files changed

+59
-3
lines changed

6 files changed

+59
-3
lines changed

src/Common/src/Common/HealthChecks/HealthAggregator.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections.Concurrent;
6+
using Microsoft.Extensions.DependencyInjection;
67
using Microsoft.Extensions.Diagnostics.HealthChecks;
78
using Steeltoe.Common.Extensions;
89
using MicrosoftHealthCheckResult = Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult;
@@ -108,7 +109,10 @@ private static async Task<SteeltoeHealthCheckResult> RunMicrosoftHealthCheckAsyn
108109

109110
try
110111
{
111-
IHealthCheck check = registration.Factory(serviceProvider);
112+
// Match the behavior of ASP.NET's HealthCheckService, which creates a scope for each check.
113+
await using AsyncServiceScope serviceScope = serviceProvider.CreateAsyncScope();
114+
115+
IHealthCheck check = registration.Factory(serviceScope.ServiceProvider);
112116
MicrosoftHealthCheckResult result = await check.CheckHealthAsync(context, cancellationToken);
113117

114118
healthCheckResult.Status = ToHealthStatus(result.Status);

src/Common/test/TestResources/TestHostBuilderFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ private static void ConfigureBuilder(HostBuilder builder, bool configureWebHost,
4848
{
4949
builder.ConfigureWebHostDefaults(webHostBuilder =>
5050
{
51+
webHostBuilder.UseDefaultServiceProvider(ConfigureServiceProvider);
5152
webHostBuilder.Configure(EmptyAction);
5253

5354
if (useTestServer)

src/Common/test/TestResources/TestWebApplicationBuilderFactory.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.AspNetCore.TestHost;
99
using Microsoft.Extensions.DependencyInjection;
1010
using Microsoft.Extensions.DependencyInjection.Extensions;
11+
using Microsoft.Extensions.Hosting;
1112

1213
namespace Steeltoe.Common.TestResources;
1314

@@ -89,6 +90,7 @@ public static WebApplicationBuilder CreateDefault(bool useTestServer)
8990

9091
private static void ConfigureBuilder(WebApplicationBuilder builder, bool useTestServer, bool deactivateDiagnostics)
9192
{
93+
builder.Host.UseDefaultServiceProvider(ConfigureServiceProvider);
9294
builder.WebHost.UseDefaultServiceProvider(ConfigureServiceProvider);
9395

9496
if (useTestServer)

src/Management/src/Endpoint/Actuators/Health/EndpointServiceCollectionExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ private static void RegisterDefaultHealthContributors(IServiceCollection service
7676
}
7777

7878
/// <summary>
79-
/// Adds the specified <see cref="IHealthContributor" /> to the D/I container as a scoped service.
79+
/// Adds the specified <see cref="IHealthContributor" /> to the D/I container as a singleton service.
8080
/// </summary>
8181
/// <typeparam name="T">
8282
/// The type of health contributor to add.
@@ -98,7 +98,7 @@ public static IServiceCollection AddHealthContributor<T>(this IServiceCollection
9898
}
9999

100100
/// <summary>
101-
/// Adds the specified <see cref="IHealthContributor" /> to the D/I container as a scoped service.
101+
/// Adds the specified <see cref="IHealthContributor" /> to the D/I container as a singleton service.
102102
/// </summary>
103103
/// <param name="services">
104104
/// The <see cref="IServiceCollection" /> to add services to.

src/Management/test/Endpoint.Test/Actuators/Health/HealthAggregationTest.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using FluentAssertions.Extensions;
88
using Microsoft.AspNetCore.Builder;
99
using Microsoft.AspNetCore.TestHost;
10+
using Microsoft.EntityFrameworkCore;
1011
using Microsoft.Extensions.Configuration;
1112
using Microsoft.Extensions.DependencyInjection;
1213
using Microsoft.Extensions.Diagnostics.HealthChecks;
@@ -516,6 +517,50 @@ public async Task Converts_AspNet_health_check_results()
516517
""");
517518
}
518519

520+
[Fact]
521+
public async Task Can_use_scoped_AspNet_health_check()
522+
{
523+
WebApplicationBuilder builder = TestWebApplicationBuilderFactory.Create();
524+
builder.Configuration.AddInMemoryCollection(AppSettings);
525+
builder.Services.AddDbContext<TestDbContext>(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString()));
526+
builder.Services.AddHealthChecks().AddDbContextCheck<TestDbContext>();
527+
builder.Services.AddHealthActuator();
528+
await using WebApplication host = builder.Build();
529+
530+
// ReSharper disable once AccessToDisposedClosure
531+
Action action = () => host.Services.GetRequiredService<TestDbContext>();
532+
action.Should().ThrowExactly<InvalidOperationException>();
533+
534+
host.MapHealthChecks("/health");
535+
await host.StartAsync(TestContext.Current.CancellationToken);
536+
using HttpClient httpClient = host.GetTestClient();
537+
538+
HttpResponseMessage actuatorResponse = await httpClient.GetAsync(new Uri("http://localhost/actuator/health"), TestContext.Current.CancellationToken);
539+
540+
actuatorResponse.StatusCode.Should().Be(HttpStatusCode.OK);
541+
542+
string actuatorResponseBody = await actuatorResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
543+
544+
actuatorResponseBody.Should().BeJson("""
545+
{
546+
"status": "UP",
547+
"components": {
548+
"TestDbContext": {
549+
"status": "UP"
550+
}
551+
}
552+
}
553+
""");
554+
555+
HttpResponseMessage aspNetResponse = await httpClient.GetAsync(new Uri("http://localhost/health"), TestContext.Current.CancellationToken);
556+
557+
aspNetResponse.StatusCode.Should().Be(HttpStatusCode.OK);
558+
559+
string aspNetResponseBody = await aspNetResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
560+
561+
aspNetResponseBody.Should().Be("Healthy");
562+
}
563+
519564
private sealed class AspNetHealthyCheck : IHealthCheck
520565
{
521566
public async Task<MicrosoftHealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
@@ -562,4 +607,7 @@ public Task<MicrosoftHealthCheckResult> CheckHealthAsync(HealthCheckContext cont
562607
throw new InvalidOperationException("test-exception");
563608
}
564609
}
610+
611+
private sealed class TestDbContext(DbContextOptions options)
612+
: DbContext(options);
565613
}

src/Management/test/Endpoint.Test/Steeltoe.Management.Endpoint.Test.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(MatchTargetFrameworkVersion)" />
1717
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(EntityFrameworkCoreTestVersion)" />
1818
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="$(EntityFrameworkCoreTestVersion)" />
19+
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="$(EntityFrameworkCoreTestVersion)" />
1920
</ItemGroup>
2021

2122
<ItemGroup>

0 commit comments

Comments
 (0)