diff --git a/src/HealthChecks.System/DependencyInjection/SystemHealthCheckBuilderExtensions.cs b/src/HealthChecks.System/DependencyInjection/SystemHealthCheckBuilderExtensions.cs
index 0ee8538000..dd2f3bda52 100644
--- a/src/HealthChecks.System/DependencyInjection/SystemHealthCheckBuilderExtensions.cs
+++ b/src/HealthChecks.System/DependencyInjection/SystemHealthCheckBuilderExtensions.cs
@@ -16,6 +16,7 @@ public static class SystemHealthCheckBuilderExtensions
private const string PROCESS_ALLOCATED_MEMORY = "process_allocated_memory";
private const string WINDOWS_SERVICE_NAME = "windowsservice";
private const string FOLDER_NAME = "folder";
+ private const string FILE_NAME = "file";
///
/// Add a health check for disk storage.
@@ -248,7 +249,7 @@ public static IHealthChecksBuilder AddWindowsServiceHealthCheck(
/// Add a healthcheck that allows to check for the existence of one or more folders.
///
/// The .
- /// The action method to configure the health check parameters.
+ /// Delegate for configuring the health check. Optional.
/// The health check name. Optional. If null the type name 'folder' will be used for the name.
///
/// The that should be reported when the health check fails. Optional. If null then
@@ -275,4 +276,62 @@ public static IHealthChecksBuilder AddFolder(
tags,
timeout));
}
+
+ ///
+ /// Add a healthcheck that allows to check for the existence of one or more files.
+ ///
+ /// The .
+ /// Delegate for configuring the health check. Optional.
+ /// The health check name. Optional. If null the type name 'file' will be used for the name.
+ ///
+ /// The that should be reported when the health check fails. Optional. If null then
+ /// the default status of will be reported.
+ ///
+ /// A list of tags that can be used to filter sets of health checks. Optional.
+ /// An optional representing the timeout of the check.
+ /// The specified .
+ public static IHealthChecksBuilder AddFile(
+ this IHealthChecksBuilder builder,
+ Action? setup,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default)
+ {
+ return AddFile(builder, (_, options) => setup?.Invoke(options), name, failureStatus, tags, timeout);
+ }
+
+ ///
+ /// Add a healthcheck that allows to check for the existence of one or more files.
+ ///
+ /// The .
+ /// The action method to configure the health check parameters.
+ /// The health check name. Optional. If null the type name 'file' will be used for the name.
+ ///
+ /// The that should be reported when the health check fails. Optional. If null then
+ /// the default status of will be reported.
+ ///
+ /// A list of tags that can be used to filter sets of health checks. Optional.
+ /// An optional representing the timeout of the check.
+ /// The specified .
+ public static IHealthChecksBuilder AddFile(
+ this IHealthChecksBuilder builder,
+ Action? setup,
+ string? name = default,
+ HealthStatus? failureStatus = default,
+ IEnumerable? tags = default,
+ TimeSpan? timeout = default)
+ {
+ return builder.Add(new HealthCheckRegistration(
+ name ?? FILE_NAME,
+ sp =>
+ {
+ var options = new FileHealthCheckOptions();
+ setup?.Invoke(sp, options);
+ return new FileHealthCheck(options);
+ },
+ failureStatus,
+ tags,
+ timeout));
+ }
}
diff --git a/src/HealthChecks.System/FileHealthCheck.cs b/src/HealthChecks.System/FileHealthCheck.cs
new file mode 100644
index 0000000000..ff37a00822
--- /dev/null
+++ b/src/HealthChecks.System/FileHealthCheck.cs
@@ -0,0 +1,41 @@
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+
+namespace HealthChecks.System;
+
+public class FileHealthCheck : IHealthCheck
+{
+ private readonly FileHealthCheckOptions _fileOptions;
+
+ public FileHealthCheck(FileHealthCheckOptions fileOptions)
+ {
+ _fileOptions = fileOptions;
+ }
+
+ public Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ List errorList = new();
+ foreach (string file in _fileOptions.Files)
+ {
+ if (!string.IsNullOrEmpty(file))
+ {
+ if (!File.Exists(file))
+ {
+ errorList.Add($"File {file} does not exist.");
+ if (!_fileOptions.CheckAllFiles)
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ return Task.FromResult(errorList.GetHealthState(context));
+ }
+ catch (Exception ex)
+ {
+ return Task.FromResult(new HealthCheckResult(context.Registration.FailureStatus, exception: ex));
+ }
+ }
+}
diff --git a/src/HealthChecks.System/FileHealthCheckOptions.cs b/src/HealthChecks.System/FileHealthCheckOptions.cs
new file mode 100644
index 0000000000..d1c70ff7a6
--- /dev/null
+++ b/src/HealthChecks.System/FileHealthCheckOptions.cs
@@ -0,0 +1,19 @@
+namespace HealthChecks.System;
+
+public class FileHealthCheckOptions
+{
+ public List Files { get; } = new();
+ public bool CheckAllFiles { get; set; }
+
+ public FileHealthCheckOptions AddFile(string file)
+ {
+ Files.Add(file);
+ return this;
+ }
+
+ public FileHealthCheckOptions WithCheckAllFiles()
+ {
+ CheckAllFiles = true;
+ return this;
+ }
+}
diff --git a/test/HealthChecks.System.Tests/Functional/FileHealthCheckTests.cs b/test/HealthChecks.System.Tests/Functional/FileHealthCheckTests.cs
new file mode 100644
index 0000000000..423b9eb8e4
--- /dev/null
+++ b/test/HealthChecks.System.Tests/Functional/FileHealthCheckTests.cs
@@ -0,0 +1,80 @@
+using System.Net;
+
+namespace HealthChecks.System.Tests.Functional;
+
+public class file_healthcheck_should
+{
+ [Fact]
+ public async Task be_healthy_if_file_is_available()
+ {
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddFile(setup => setup.AddFile(Path.Combine(Directory.GetCurrentDirectory(), "HealthChecks.System.Tests.dll")), tags: new string[] { "file" });
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("file")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync().ConfigureAwait(false);
+
+ response.StatusCode.ShouldBe(HttpStatusCode.OK);
+ }
+
+ [Fact]
+ public async Task be_healthy_if_no_file_provided()
+ {
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddFile(setup =>
+ {
+ }, tags: new string[] { "file" });
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("file")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync().ConfigureAwait(false);
+
+ response.StatusCode.ShouldBe(HttpStatusCode.OK);
+ }
+
+ [Fact]
+ public async Task be_unhealthy_if_file_is_not_available()
+ {
+ var webHostBuilder = new WebHostBuilder()
+ .ConfigureServices(services =>
+ {
+ services.AddHealthChecks()
+ .AddFile(setup => setup.AddFile($"{Directory.GetCurrentDirectory()}/non-existing-file"), tags: new string[] { "file" });
+ })
+ .Configure(app =>
+ {
+ app.UseHealthChecks("/health", new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("file")
+ });
+ });
+
+ using var server = new TestServer(webHostBuilder);
+
+ using var response = await server.CreateRequest("/health").GetAsync().ConfigureAwait(false);
+
+ response.StatusCode.ShouldBe(HttpStatusCode.ServiceUnavailable);
+ }
+}
diff --git a/test/HealthChecks.System.Tests/HealthChecks.System.approved.txt b/test/HealthChecks.System.Tests/HealthChecks.System.approved.txt
index cb59cc415c..52b1fee702 100644
--- a/test/HealthChecks.System.Tests/HealthChecks.System.approved.txt
+++ b/test/HealthChecks.System.Tests/HealthChecks.System.approved.txt
@@ -14,6 +14,19 @@ namespace HealthChecks.System
public HealthChecks.System.DiskStorageOptions WithCheckAllDrives() { }
public delegate string ErrorDescription(string driveName, long minimumFreeMegabytes, long? actualFreeMegabytes);
}
+ public class FileHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
+ {
+ public FileHealthCheck(HealthChecks.System.FileHealthCheckOptions fileOptions) { }
+ public System.Threading.Tasks.Task CheckHealthAsync(Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckContext context, System.Threading.CancellationToken cancellationToken = default) { }
+ }
+ public class FileHealthCheckOptions
+ {
+ public FileHealthCheckOptions() { }
+ public bool CheckAllFiles { get; set; }
+ public System.Collections.Generic.List Files { get; }
+ public HealthChecks.System.FileHealthCheckOptions AddFile(string file) { }
+ public HealthChecks.System.FileHealthCheckOptions WithCheckAllFiles() { }
+ }
public class FolderHealthCheck : Microsoft.Extensions.Diagnostics.HealthChecks.IHealthCheck
{
public FolderHealthCheck(HealthChecks.System.FolderHealthCheckOptions folderOptions) { }
@@ -54,6 +67,8 @@ namespace Microsoft.Extensions.DependencyInjection
public static class SystemHealthCheckBuilderExtensions
{
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddDiskStorageHealthCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Action? setup, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { }
+ public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddFile(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Action? setup, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { }
+ public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddFile(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Action? setup, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { }
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddFolder(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, System.Action? setup, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { }
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddPrivateMemoryHealthCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, long maximumMemoryBytes, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { }
public static Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder AddProcessAllocatedMemoryHealthCheck(this Microsoft.Extensions.DependencyInjection.IHealthChecksBuilder builder, int maximumMegabytesAllocated, string? name = null, Microsoft.Extensions.Diagnostics.HealthChecks.HealthStatus? failureStatus = default, System.Collections.Generic.IEnumerable? tags = null, System.TimeSpan? timeout = default) { }