Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ self.serverHandledUrls = [
/\/hangfire/,
/\/healthchecks-ui/,
/\/healthz/,
/\/health/,
/\/alive/,
/\/swagger/,
/\/signin-/,
/\/.well-known/,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
<PackageVersion Include="EmbedIO" Version="3.5.2" />
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.8" />
<PackageVersion Include="AspNetCore.HealthChecks.Hangfire" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.System" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.UI.Client" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.8" />
<PackageVersion Include="Microsoft.Extensions.Options.DataAnnotations" Version="9.0.8" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="9.0.100" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
<PackageReference Condition=" '$(sentry)' == 'true' OR '$(sentry)' == '' " Include="Sentry.AspNetCore" />
<PackageReference Condition="'$(signalR)' == 'true' OR '$(signalR)' == ''" Include="Microsoft.Azure.SignalR" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" />
<PackageReference Include="AspNetCore.HealthChecks.Hangfire" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Microsoft.Identity.Web" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ private static void ConfigureMiddlewares(this WebApplication app)

app.UseAntiforgery();

app.MappAppHealthChecks();
app.MapAppHealthChecks();

app.UseSwagger();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
using Boilerplate.Server.Api.Services.Jobs;
using Boilerplate.Server.Api.Models.Identity;
using Boilerplate.Server.Api.Services.Identity;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Boilerplate.Server.Api;

Expand All @@ -52,6 +53,12 @@ public static void AddServerApiProjectServices(this WebApplicationBuilder builde

builder.AddServerSharedServices();

builder.AddDefaultHealthChecks()
.AddDbContextCheck<AppDbContext>(tags: ["live"])
.AddHangfire(setup => setup.MinimumAvailableServers = 1, tags: ["live"])
.AddCheck<AppStorageHealthCheck>("storage", tags: ["live"]);
// TODO: Sms, Email, Push notification, AI, Google reCaptcha, Cloudflare

ServerApiSettings appSettings = new();
configuration.Bind(appSettings);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using FluentStorage.Blobs;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Boilerplate.Server.Api.Services;

/// <summary>
/// Checks underlying S3, Azure blob storage, or local file system storage is healthy.
/// </summary>
public partial class AppStorageHealthCheck : IHealthCheck
{
[AutoInject] private IBlobStorage blobStorage = default!;
[AutoInject] private ServerApiSettings settings = default!;

public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
_ = await blobStorage.ExistsAsync(settings.UserProfileImagesDir, cancellationToken);

return HealthCheckResult.Healthy("Storage is healthy");
}
catch (Exception exp)
{
return HealthCheckResult.Unhealthy("Storage is unhealthy", exp);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
using Projects;
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

// Check out appsettings.json for credential settings.

//#if (database == "SqlServer")
var sqlServerPassword = builder.AddParameter("SqlServerPassword", secret: true);
var sqlDatabase = builder.AddSqlServer("sqlserver", password: sqlServerPassword)
var sqlDatabase = builder.AddSqlServer("sqlserver", password: sqlServerPassword, port: 1433)
.WithDbGate(config => config.WithLifetime(ContainerLifetime.Persistent).WithDataVolume())
.WithLifetime(ContainerLifetime.Persistent)
.WithDataVolume()
Expand All @@ -18,7 +19,7 @@

//#elif (database == "PostgreSql")
var postgresPassword = builder.AddParameter("PostgresPassword", secret: true);
var postgresDatabase = builder.AddPostgres("postgresserver", password: postgresPassword)
var postgresDatabase = builder.AddPostgres("postgresserver", password: postgresPassword, port: 5432)
.WithPgAdmin(config => config.WithLifetime(ContainerLifetime.Persistent).WithVolume("/var/lib/pgadmin/Boilerplate/data"))
.WithLifetime(ContainerLifetime.Persistent)
.WithDataVolume()
Expand All @@ -27,7 +28,7 @@

//#elif (database == "MySql")
var mySqlPassword = builder.AddParameter("MySqlPassword", secret: true);
var mySqlDatabase = builder.AddMySql("mysqlserver", password: mySqlPassword)
var mySqlDatabase = builder.AddMySql("mysqlserver", password: mySqlPassword, port: 3306)
.WithPhpMyAdmin(config => config.WithLifetime(ContainerLifetime.Persistent).WithVolume("/var/lib/phpMyAdmin/Boilerplate/data"))
.WithLifetime(ContainerLifetime.Persistent)
.WithDataVolume()
Expand All @@ -40,6 +41,9 @@
{
azurite
.WithLifetime(ContainerLifetime.Persistent)
.WithBlobPort(10000)
.WithQueuePort(10001)
.WithTablePort(10002)
.WithDataVolume();
})
.AddBlobs("blobs");
Expand All @@ -53,10 +57,24 @@
var serverWebProject = builder.AddProject<Boilerplate_Server_Web>("serverweb") // Replace . with _ if needed to ensure the project builds successfully.
.WithExternalHttpEndpoints();

// Adding health checks endpoints to applications in non-development environments has security implications.
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
if (builder.Environment.IsDevelopment())
{
serverWebProject.WithHttpHealthCheck("/alive");
}

//#if (api == "Standalone")
var serverApiProject = builder.AddProject<Boilerplate_Server_Api>("serverapi") // Replace . with _ if needed to ensure the project builds successfully.
.WithExternalHttpEndpoints();

// Adding health checks endpoints to applications in non-development environments has security implications.
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
if (builder.Environment.IsDevelopment())
{
serverApiProject.WithHttpHealthCheck("/alive");
}

serverWebProject.WithReference(serverApiProject).WaitFor(serverApiProject);
//#if (database == "SqlServer")
serverApiProject.WithReference(sqlDatabase, "SqlServerConnectionString").WaitFor(sqlDatabase);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="AspNetCore.HealthChecks.System" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ private static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder)
{
builder.ConfigureOpenTelemetry();

builder.AddDefaultHealthChecks();

builder.Services.AddServiceDiscovery();

builder.Services.ConfigureHttpClientDefaults(http =>
Expand Down Expand Up @@ -191,13 +189,14 @@ private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builde
return builder;
}

private static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder)
public static IHealthChecksBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.Services.AddHealthChecks()
// Add a default liveness check to ensure app is responsive
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
builder.Services.AddOutputCache(configureOptions: static caching =>
caching.AddPolicy("HealthChecks",
build: static policy => policy.Expire(TimeSpan.FromSeconds(10))));

return builder;
return builder.Services.AddHealthChecks()
.AddDiskStorageHealthCheck(opt => opt.AddDrive(Path.GetPathRoot(Directory.GetCurrentDirectory())!, minimumFreeMegabytes: 5 * 1024), tags: ["live"]);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//+:cnd:noEmit
using HealthChecks.UI.Client;
using Boilerplate.Server.Shared;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Localization.Routing;
Expand All @@ -8,19 +9,32 @@ namespace Microsoft.AspNetCore.Builder;

public static class WebApplicationExtensions
{
public static WebApplication MappAppHealthChecks(this WebApplication app)
public static WebApplication MapAppHealthChecks(this WebApplication app)
{
// Adding health checks endpoints to applications in non-development environments has security implications.
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
if (app.Environment.IsDevelopment())
{
// All health checks must pass for app to be considered ready to accept traffic after starting
app.MapHealthChecks("/health");
var healthChecks = app.MapGroup("");

// Only health checks tagged with the "live" tag must pass for app to be considered alive
app.MapHealthChecks("/alive", new HealthCheckOptions
healthChecks
.CacheOutput("HealthChecks");

// All health checks must pass for app to be
// considered ready to accept traffic after starting
healthChecks.MapHealthChecks("/health");

// Only health checks tagged with the "live" tag
// must pass for app to be considered alive
healthChecks.MapHealthChecks("/alive", new()
{
Predicate = static r => r.Tags.Contains("live")
});

healthChecks.MapHealthChecks("/healthz", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public static void ConfigureMiddlewares(this WebApplication app)

app.UseAntiforgery();

app.MappAppHealthChecks();
app.MapAppHealthChecks();

//#if (api == "Integrated")
app.UseSwagger();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static void AddServerWebProjectServices(this WebApplicationBuilder builde
/*
//#endif
builder.AddServerSharedServices();
builder.AddDefaultHealthChecks();
services.AddAuthentication(options =>
{
options.DefaultScheme = Microsoft.AspNetCore.Identity.IdentityConstants.BearerScheme;
Expand Down
2 changes: 2 additions & 0 deletions src/Templates/Boilerplate/Bit.Boilerplate/vs-spell.dic
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,5 @@ svenska
español
français
Sqlite
healthchecks
healthz
Loading