Skip to content
Open
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
26 changes: 5 additions & 21 deletions src/HealthChecks/HealthChecks/src/DefaultHealthCheckService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
internal class DefaultHealthCheckService : HealthCheckService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IOptions<HealthCheckServiceOptions> _options;
private readonly HealthCheckServiceOptions _options;
private readonly ILogger<DefaultHealthCheckService> _logger;

public DefaultHealthCheckService(
Expand All @@ -27,19 +27,18 @@ public DefaultHealthCheckService(
ILogger<DefaultHealthCheckService> logger)
{
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
_options = options ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));

// We're specifically going out of our way to do this at startup time. We want to make sure you
// get any kind of health-check related error as early as possible. Waiting until someone
// actually tries to **run** health checks would be real baaaaad.
ValidateRegistrations(_options.Value.Registrations);
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

public override async Task<HealthReport> CheckHealthAsync(
Func<HealthCheckRegistration, bool>? predicate,
CancellationToken cancellationToken = default)
{
var registrations = _options.Value.Registrations;
var registrations = _options.Registrations;
if (predicate != null)
{
registrations = registrations.Where(predicate).ToArray();
Expand Down Expand Up @@ -155,21 +154,6 @@ private async Task<HealthReportEntry> RunCheckAsync(IServiceScope scope, HealthC
}
}

private static void ValidateRegistrations(IEnumerable<HealthCheckRegistration> registrations)
{
// Scan the list for duplicate names to provide a better error if there are duplicates.
var duplicateNames = registrations
.GroupBy(c => c.Name, StringComparer.OrdinalIgnoreCase)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();

if (duplicateNames.Count > 0)
{
throw new ArgumentException($"Duplicate health checks were registered with the name(s): {string.Join(", ", duplicateNames)}", nameof(registrations));
}
}

internal static class EventIds
{
public static readonly EventId HealthCheckProcessingBegin = new EventId(100, "HealthCheckProcessingBegin");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.DependencyInjection
{
Expand All @@ -25,6 +27,7 @@ public static class HealthCheckServiceCollectionExtensions
/// <returns>An instance of <see cref="IHealthChecksBuilder"/> from which health checks can be registered.</returns>
public static IHealthChecksBuilder AddHealthChecks(this IServiceCollection services)
{
services.TryAddEnumerable(ServiceDescriptor.Transient<IValidateOptions<HealthCheckServiceOptions>, HealthCheckServiceOptionsValidation>());
services.TryAddSingleton<HealthCheckService, DefaultHealthCheckService>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, HealthCheckPublisherHostedService>());
return new HealthChecksBuilder(services);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Linq;
using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.Diagnostics.HealthChecks.DependencyInjection
{
internal class HealthCheckServiceOptionsValidation : IValidateOptions<HealthCheckServiceOptions>
{
public ValidateOptionsResult Validate(string name, HealthCheckServiceOptions options)
{
// Scan the list for duplicate names to provide a better error if there are duplicates.
var duplicateNames = options.Registrations
.GroupBy(c => c.Name, StringComparer.OrdinalIgnoreCase)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();

if (duplicateNames.Count > 0)
{
return ValidateOptionsResult.Fail($"Duplicate health checks were registered with the name(s): {string.Join(", ", duplicateNames)}");
}

return ValidateOptionsResult.Success;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,8 @@ public void Constructor_ThrowsUsefulExceptionForDuplicateNames()

var services = serviceCollection.BuildServiceProvider();

var scopeFactory = services.GetRequiredService<IServiceScopeFactory>();
var options = services.GetRequiredService<IOptions<HealthCheckServiceOptions>>();
var logger = services.GetRequiredService<ILogger<DefaultHealthCheckService>>();

// Act
var exception = Assert.Throws<ArgumentException>(() => new DefaultHealthCheckService(scopeFactory, options, logger));
var exception = Assert.Throws<OptionsValidationException>(() => services.GetRequiredService<HealthCheckService>());

// Assert
Assert.StartsWith($"Duplicate health checks were registered with the name(s): Foo, Baz", exception.Message);
Expand Down