Skip to content

Commit a193273

Browse files
committed
Create a service scope for each ASP.NET health check, correct AddHealthContributor documentation
1 parent eb22697 commit a193273

File tree

4 files changed

+64
-3
lines changed

4 files changed

+64
-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/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: 56 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,58 @@ 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.CreateDefault(false);
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+
await using (AsyncServiceScope scope = host.Services.CreateAsyncScope())
535+
{
536+
await using var dbContext = scope.ServiceProvider.GetRequiredService<TestDbContext>();
537+
await dbContext.Database.EnsureDeletedAsync(TestContext.Current.CancellationToken);
538+
await dbContext.Database.EnsureCreatedAsync(TestContext.Current.CancellationToken);
539+
}
540+
541+
host.MapHealthChecks("/health");
542+
await host.StartAsync(TestContext.Current.CancellationToken);
543+
using var httpClient = new HttpClient();
544+
545+
HttpResponseMessage actuatorResponse =
546+
await httpClient.GetAsync(new Uri("http://localhost:5000/actuator/health"), TestContext.Current.CancellationToken);
547+
548+
actuatorResponse.StatusCode.Should().Be(HttpStatusCode.OK);
549+
550+
string actuatorResponseBody = await actuatorResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
551+
552+
actuatorResponseBody.Should().BeJson("""
553+
{
554+
"status": "UP",
555+
"components": {
556+
"TestDbContext": {
557+
"status": "UP"
558+
}
559+
}
560+
}
561+
""");
562+
563+
HttpResponseMessage aspNetResponse = await httpClient.GetAsync(new Uri("http://localhost:5000/health"), TestContext.Current.CancellationToken);
564+
565+
aspNetResponse.StatusCode.Should().Be(HttpStatusCode.OK);
566+
567+
string aspnetResponseBody = await aspNetResponse.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);
568+
569+
aspnetResponseBody.Should().Be("Healthy");
570+
}
571+
519572
private sealed class AspNetHealthyCheck : IHealthCheck
520573
{
521574
public async Task<MicrosoftHealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
@@ -562,4 +615,7 @@ public Task<MicrosoftHealthCheckResult> CheckHealthAsync(HealthCheckContext cont
562615
throw new InvalidOperationException("test-exception");
563616
}
564617
}
618+
619+
private sealed class TestDbContext(DbContextOptions options)
620+
: DbContext(options);
565621
}

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)