Core interfaces, models, enums, and abstract base classes for Healthie.NET -- a lightweight health monitoring framework for .NET applications.
$ dotnet add package Healthie.NET.Abstractions
Trust your uptime. A lightweight, extensible health monitoring framework for .NET applications.
Healthie.NET lets you define pulse checkers -- small classes that monitor the health of databases, APIs, queues, caches, or anything else in your stack. Each checker runs on a configurable interval, tracks consecutive failures with a three-state health model (Healthy / Suspicious / Unhealthy), and persists its state through pluggable providers. Monitor everything through a real-time Blazor dashboard or REST API.
Why Healthie.NET?
PeriodicTimer-based scheduler.CancellationToken propagation throughoutPulseInterval enum)Healthy, Suspicious, UnhealthySemaphoreSlimEventHandler<PulseCheckerStateChangedEventArgs>)IPulseScheduler) and state storage (IStateProvider)| Package | Description | Install |
|---|---|---|
| Healthie.NET.Abstractions | Core interfaces, models, enums, and the PulseChecker abstract base class. | dotnet add package Healthie.NET.Abstractions |
| Healthie.NET.DependencyInjection | DI registration (AddHealthie), assembly scanning, and the built-in TimerPulseScheduler. | dotnet add package Healthie.NET.DependencyInjection |
| Healthie.NET.Api | ASP.NET Core API controller for managing pulse checkers via REST endpoints. | dotnet add package Healthie.NET.Api |
| Healthie.NET.CosmosDb | Azure CosmosDB IStateProvider implementation for persisting pulse checker state. | dotnet add package Healthie.NET.CosmosDb |
| Healthie.NET.Dashboard | Blazor health monitoring dashboard (Razor Class Library, zero third-party dependencies). | dotnet add package Healthie.NET.Dashboard |
| Healthie.NET.Scheduling.Quartz | Quartz.NET IPulseScheduler implementation for persistent, CRON-based scheduling. | dotnet add package Healthie.NET.Scheduling.Quartz |
dotnet add package Healthie.NET.Abstractions
dotnet add package Healthie.NET.DependencyInjection
using Healthie.Abstractions;
using Healthie.Abstractions.Enums;
using Healthie.Abstractions.Models;
using Healthie.Abstractions.StateProviding;
public class DatabasePulseChecker : PulseChecker
{
private readonly IDbConnectionFactory _connectionFactory;
public DatabasePulseChecker(
IStateProvider stateProvider,
IDbConnectionFactory connectionFactory)
: base(stateProvider, PulseInterval.Every30Seconds, unhealthyThreshold: 3)
{
_connectionFactory = connectionFactory;
}
public override async Task<PulseCheckerResult> CheckAsync(
CancellationToken cancellationToken = default)
{
try
{
using var connection = await _connectionFactory
.CreateConnectionAsync(cancellationToken);
return new PulseCheckerResult(PulseCheckerHealth.Healthy, "Database connection OK.");
}
catch (Exception ex)
{
return new PulseCheckerResult(
PulseCheckerHealth.Unhealthy,
$"Database connection failed: {ex.Message}");
}
}
}
using Healthie.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddHealthie(typeof(Program).Assembly) // Scan assembly for pulse checkers
.AddHealthieDefaultScheduler(); // Built-in PeriodicTimer scheduler
var app = builder.Build();
app.Run();
The PulsesScheduler hosted service starts automatically on application startup and begins executing all discovered pulse checkers at their configured intervals. No additional configuration is required.
Every pulse checker inherits from the PulseChecker abstract base class and overrides CheckAsync. The base class handles state management, threshold evaluation, concurrency control, and event notifications.
public class ExternalApiPulseChecker : PulseChecker
{
private readonly HttpClient _httpClient;
public ExternalApiPulseChecker(
IStateProvider stateProvider,
HttpClient httpClient)
: base(stateProvider, PulseInterval.EveryMinute)
{
_httpClient = httpClient;
}
public override async Task<PulseCheckerResult> CheckAsync(
CancellationToken cancellationToken = default)
{
var response = await _httpClient.GetAsync(
"https://api.example.com/health",
cancellationToken);
return response.IsSuccessStatusCode
? new PulseCheckerResult(PulseCheckerHealth.Healthy, "API is reachable.")
: new PulseCheckerResult(
PulseCheckerHealth.Suspicious,
$"API returned {response.StatusCode}.");
}
}
Override the DisplayName property to show a friendly name in the dashboard and API instead of the full class name:
public class DatabasePulseChecker : PulseChecker
{
public DatabasePulseChecker(IStateProvider stateProvider)
: base(stateProvider, PulseInterval.Every30Seconds, 3) { }
public override string DisplayName => "Database Health Check";
public override async Task<PulseCheckerResult> CheckAsync(
CancellationToken cancellationToken = default)
{
// ... check logic
}
}
If not overridden, the default DisplayName returns the fully-qualified class name (GetType().FullName).
Each pulse checker maintains a rolling history of recent executions. History recording can be toggled on/off per checker from the dashboard UI or programmatically:
await checker.SetHistoryEnabledAsync(false); // Disable history recording
await checker.ClearHistoryAsync(); // Clear existing history
var history = await checker.GetHistoryAsync(); // Get history entries
The maximum number of history entries is configured globally via HealthieOptions.MaxHistoryLength (default: 10, range: 1-10).
| Constructor | Description |
|---|---|
PulseChecker(IStateProvider) | Default interval (EveryMinute), threshold 0. |
PulseChecker(IStateProvider, PulseInterval) | Custom interval, threshold 0. |
PulseChecker(IStateProvider, PulseInterval, uint) | Custom interval and unhealthy threshold. |
| Status | Value | Description |
|---|---|---|
Healthy | 0 | The check passed. Consecutive failure count resets to 0. |
Suspicious | 1 | The check failed, but consecutive failures have not crossed the unhealthy threshold. |
Unhealthy | 2 | The check failed and consecutive failures exceed the threshold. |
| Interval | Description |
|---|---|
EverySecond | Every 1 second |
Every2Seconds | Every 2 seconds |
Every3Seconds | Every 3 seconds |
Every5Seconds | Every 5 seconds |
Every10Seconds | Every 10 seconds |
Every15Seconds | Every 15 seconds |
Every20Seconds | Every 20 seconds |
Every30Seconds | Every 30 seconds |
EveryMinute | Every 1 minute |
Every2Minutes | Every 2 minutes |
Every3Minutes | Every 3 minutes |
Every4Minutes | Every 4 minutes |
Every5Minutes | Every 5 minutes |
Subscribe to state transitions on any pulse checker:
checker.StateChanged += (sender, args) =>
{
Console.WriteLine(
$"Health changed from {args.OldState.LastResult?.Health} " +
$"to {args.NewState.LastResult?.Health}");
};
The simplest configuration uses the built-in TimerPulseScheduler, which runs health checks on PeriodicTimer intervals with no external dependencies:
using Healthie.DependencyInjection;
builder.Services
.AddHealthie(typeof(Program).Assembly)
.AddHealthieDefaultScheduler();
For persistent, CRON-based scheduling with clustering support, use the Quartz provider:
dotnet add package Healthie.NET.Scheduling.Quartz
using Healthie.Scheduling.Quartz;
builder.Services
.AddHealthie(typeof(Program).Assembly)
.AddHealthieQuartz();
You can further configure Quartz with a callback:
builder.Services.AddHealthieQuartz(quartz =>
{
quartz.UsePersistentStore(store =>
{
store.UseSqlServer("your-connection-string");
});
});
By default, pulse checker state is held in-memory. For durable state persistence across restarts or in distributed environments, use the CosmosDB provider:
dotnet add package Healthie.NET.CosmosDb
using Healthie.StateProviding.CosmosDb;
using Microsoft.Azure.Cosmos;
var cosmosClient = new CosmosClient("your-connection-string");
var container = cosmosClient.GetContainer("your-database", "healthie-state");
builder.Services
.AddHealthie(typeof(Program).Assembly)
.AddHealthieDefaultScheduler()
.AddHealthieCosmosDb(container);
Note: The CosmosDB container must use
/idas the partition key path.
dotnet add package Healthie.NET.Api
using Healthie.Api;
builder.Services.AddHealthieController();
var app = builder.Build();
app.MapControllers();
app.Run();
With authorization:
builder.Services.AddHealthieController(
requireAuthorization: true,
authorizationPolicy: "AdminPolicy");
using Healthie.Api;
using Healthie.DependencyInjection;
using Healthie.Scheduling.Quartz;
using Healthie.StateProviding.CosmosDb;
using Healthie.Dashboard;
using Microsoft.Azure.Cosmos;
var builder = WebApplication.CreateBuilder(args);
var cosmosClient = new CosmosClient("your-connection-string");
var container = cosmosClient.GetContainer("your-database", "healthie-state");
builder.Services
.AddHealthie(typeof(Program).Assembly)
.AddHealthieQuartz()
.AddHealthieCosmosDb(container);
builder.Services.AddHealthieController(
requireAuthorization: true,
authorizationPolicy: "AdminPolicy");
builder.Services.AddHealthieUI(options =>
{
options.DashboardTitle = "My App Health";
});
var app = builder.Build();
app.MapControllers();
app.MapHealthieUI().RequireAuthorization("AdminPolicy");
app.Run();
All endpoints are served under the /healthie route prefix.
| HTTP Method | Route | Description | Success | Error |
|---|---|---|---|---|
GET | / | Get all pulse checker states | 200 with Dictionary<string, PulseCheckerState> | 500 |
GET | /intervals | Get available polling intervals with descriptions | 200 with HashSet<PulseIntervalDescription> | -- |
PUT | /{checkerName}/interval?interval={value} | Set the polling interval for a checker | 204 | 400 / 404 |
PUT | /{checkerName}/threshold?threshold={value} | Set the unhealthy threshold for a checker | 204 | 400 / 404 |
POST | /{checkerName}/start | Start (activate) a checker | 204 | 400 / 404 |
POST | /{checkerName}/stop | Stop (deactivate) a checker | 204 | 400 / 404 |
POST | /{checkerName}/trigger | Trigger an immediate check execution | 204 | 400 / 404 |
PATCH | /{checkerName}/reset | Reset a checker state to healthy | 204 | 400 / 404 |
GET /healthie
GET /healthie/intervals
PUT /healthie/MyApp.DatabasePulseChecker/interval?interval=Every30Seconds
PUT /healthie/MyApp.DatabasePulseChecker/threshold?threshold=5
POST /healthie/MyApp.DatabasePulseChecker/start
POST /healthie/MyApp.DatabasePulseChecker/stop
POST /healthie/MyApp.DatabasePulseChecker/trigger
PATCH /healthie/MyApp.DatabasePulseChecker/reset
Note: The
checkerNameis the fully-qualified type name of the pulse checker class (e.g.,MyApp.DatabasePulseChecker), which is derived fromGetType().FullName.
The Healthie.NET.Dashboard package provides a zero-dependency Blazor monitoring dashboard as a Razor Class Library. No third-party UI frameworks required -- pure HTML/CSS with a professional built-in theme.
Key features:
IPulseChecker.StateChanged events and updates individual checker states in-place. No polling, no periodic full re-fetches.1. Install the package:
dotnet add package Healthie.NET.Dashboard
2. Register services:
using Healthie.Dashboard;
builder.Services.AddHealthieUI(options =>
{
options.DashboardTitle = "System Health"; // Default: "System Health"
options.EnableDarkModeToggle = true; // Default: true
});
3. Map the endpoint (standalone hosting):
The dashboard is always served at /healthie/dashboard.
app.MapHealthieUI();
4. (Optional) Require authorization:
app.MapHealthieUI().RequireAuthorization("AdminPolicy");
| Option | Type | Default | Description |
|---|---|---|---|
DashboardTitle | string | "System Health" | Title displayed at the top of the dashboard. |
EnableDarkModeToggle | bool | true | Whether the dark/light mode toggle is visible. |
In a Blazor application, you can embed the dashboard component directly in a Razor page instead of using the standalone endpoint:
@page "/healthie/dashboard"
@using Healthie.Dashboard.Components
<HealthieDashboard />
The dashboard subscribes to IPulseChecker.StateChanged events via SubscribeToStateChanges(Action<string, PulseCheckerState>). When a checker's state changes, only that single entry is updated in the dashboard's dictionary -- no polling, no full state re-fetch. A full GetAllStatesAsync() is only performed on initial load.
Healthie.NET is designed around extensible contracts. You can create custom providers for both scheduling and state storage without modifying library internals.
Implement the IStateProvider interface to store pulse checker state in your preferred backend:
using System.Text.Json;
using Healthie.Abstractions.StateProviding;
public class RedisStateProvider : IStateProvider
{
private readonly IDatabase _redis;
public RedisStateProvider(IDatabase redis) => _redis = redis;
public async Task<TState?> GetStateAsync<TState>(
string name,
CancellationToken cancellationToken = default)
{
var value = await _redis.StringGetAsync(name);
if (value.IsNullOrEmpty) return default;
return JsonSerializer.Deserialize<TState>(value!);
}
public async Task SetStateAsync<TState>(
string name,
TState state,
CancellationToken cancellationToken = default)
{
var json = JsonSerializer.Serialize(state);
await _redis.StringSetAsync(name, json);
}
}
Register it with DI:
builder.Services.AddSingleton<IStateProvider, RedisStateProvider>();
Implement the IPulseScheduler interface to control how individual pulse checkers are scheduled and unscheduled:
using Healthie.Abstractions;
using Healthie.Abstractions.Enums;
using Healthie.Abstractions.Scheduling;
public class HangfirePulseScheduler : IPulseScheduler
{
public Task ScheduleAsync(
IPulseChecker checker,
PulseInterval interval,
CancellationToken cancellationToken = default)
{
RecurringJob.AddOrUpdate(
checker.Name,
() => checker.TriggerAsync(CancellationToken.None),
interval.ToCronExpression());
return Task.CompletedTask;
}
public Task UnscheduleAsync(
IPulseChecker checker,
CancellationToken cancellationToken = default)
{
RecurringJob.RemoveIfExists(checker.Name);
return Task.CompletedTask;
}
}
Register it with DI:
builder.Services.AddSingleton<IPulseScheduler, HangfirePulseScheduler>();
The repository includes three sample applications demonstrating different usage patterns:
| Sample | Description | Path |
|---|---|---|
| Console | Minimal console application with interactive menu | samples/Healthie.Sample.Console |
| WebAPI | ASP.NET Core Web API with REST endpoints and Swagger | samples/Healthie.Sample.WebApi |
| BlazorUI | Blazor Server app with the Healthie.NET UI dashboard | samples/Healthie.Sample.BlazorUI |
Healthie.NET v2.0 is a major version with breaking changes. Follow these steps to upgrade.
| Area | v1.x | v2.0 | Action Required |
|---|---|---|---|
| Sync APIs | IPulseChecker, PulseChecker (sync variants) | Removed | Migrate to async equivalents |
| Async naming | IAsyncPulseChecker, AsyncPulseChecker | Renamed to IPulseChecker, PulseChecker | Update type references |
Check() method | PulseCheckerResult Check() | Task<PulseCheckerResult> CheckAsync(CancellationToken) | Change method signature |
| Scheduling | AddHealthieQuartz(), AddHealthieHangfire() | AddHealthieDefaultScheduler() or AddHealthieQuartz() | Replace registration call |
| State providers | AddHealthieMemoryCache(), AddHealthieSqlServer() | AddHealthieCosmosDb(container) | Switch provider |
| API routes | /sync/* and /async/* prefixes | Fixed at /healthie/* | Update client URLs |
| DI scanning | Scans for both sync and async checkers | Scans only for IPulseChecker (async) | Remove sync checker classes |
| CancellationToken | Not available | Required parameter (with default) on all async methods | Add parameter if overriding |
| Disposal | IDisposable | IAsyncDisposable | Update disposal patterns |
Step 1: Update NuGet packages to v2.0.0-beta.
Step 2: Replace sync pulse checkers. Change the base class and override CheckAsync instead of Check:
// v1.x
public class MyChecker : PulseChecker
{
public MyChecker(IStateProvider provider) : base(provider) { }
public override PulseCheckerResult Check()
=> new(PulseCheckerHealth.Healthy);
}
// v2.0
public class MyChecker : PulseChecker
{
public MyChecker(IStateProvider provider) : base(provider) { }
public override Task<PulseCheckerResult> CheckAsync(
CancellationToken cancellationToken = default)
=> Task.FromResult(new PulseCheckerResult(PulseCheckerHealth.Healthy));
}
Step 3: Replace scheduling registration:
// v1.x
services.AddHealthie(assemblies).AddHealthieQuartz();
// v2.0 -- choose one:
services.AddHealthie(assemblies).AddHealthieDefaultScheduler(); // Built-in timer
services.AddHealthie(assemblies).AddHealthieQuartz(); // Quartz.NET
Step 4: Replace state provider registration:
// v1.x
services.AddHealthieMemoryCache();
// or
services.AddHealthieSqlServer(connectionString);
// v2.0
services.AddHealthieCosmosDb(cosmosContainer);
Step 5: Update API client URLs. Remove /sync/ and /async/ prefixes:
// v1.x
GET /healthie/async
PUT /healthie/async/{name}/interval
// v2.0
GET /healthie
PUT /healthie/{name}/interval
Step 6 (optional): Add the new UI dashboard:
services.AddHealthieUI();
// ...
app.MapHealthieUI(); // Serves at /healthie/dashboard
Releases are automated via GitHub Actions. To publish a new version:
git tag v2.2.0
git push origin v2.2.0
This triggers the CI pipeline which:
For pre-release versions, use a suffix:
git tag v2.2.0-beta
git push origin v2.2.0-beta
Pre-release tags are automatically marked as pre-release on GitHub.
Healthie.NET packages are standard .NET NuGet libraries. They work in Docker containers without any special configuration. When your application runs dotnet restore and dotnet publish inside a Docker build, Healthie.NET packages are restored and included automatically like any other NuGet dependency. The Blazor dashboard static assets (_content/Healthie.NET.Dashboard/) are also included automatically via the Razor Class Library mechanism.
Planned features for future releases:
This project is licensed under the MIT License.