diff --git a/.github/workflows/pipelines.yml b/.github/workflows/pipelines.yml
index 608db0c..725fda9 100644
--- a/.github/workflows/pipelines.yml
+++ b/.github/workflows/pipelines.yml
@@ -18,6 +18,9 @@ on:
- Debug
- Release
+permissions:
+ contents: read
+
jobs:
build:
name: 🛠️ Build
@@ -130,6 +133,8 @@ jobs:
name: call-codeql
needs: [build,test]
uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1
+ permissions:
+ security-events: write
deploy:
if: github.event_name != 'pull_request'
@@ -137,6 +142,9 @@ jobs:
runs-on: ubuntu-24.04
needs: [build, pack, test, sonarcloud, codecov, codeql]
environment: Production
+ permissions:
+ contents: write
+ packages: write
steps:
- uses: codebeltnet/nuget-push@v1
with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a71d378..813554e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder.
-## [4.0.0] - TBD
+## [4.0.0] - 2025-04-12
This major release revisits and refines some of the earlier design decisions to offer a more consistent and flexible API. It also brings forward improvements to reliability and maintainability.
diff --git a/Directory.Build.props b/Directory.Build.props
index 6e78383..1ffd242 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -32,6 +32,9 @@
true
true
$(MSBuildThisFileDirectory)bootstrapper.snk
+ true
+ latest
+ Recommended
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 3839aae..c916437 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,7 @@
-
+
diff --git a/app/Codebelt.Bootstrapper.WebApp.App/Properties/launchSettings.json b/app/Codebelt.Bootstrapper.WebApp.App/Properties/launchSettings.json
index 7d5d862..f8cb865 100644
--- a/app/Codebelt.Bootstrapper.WebApp.App/Properties/launchSettings.json
+++ b/app/Codebelt.Bootstrapper.WebApp.App/Properties/launchSettings.json
@@ -1,14 +1,14 @@
{
"profiles": {
"Codebelt.Bootstrapper.WebApp.App": {
- "commandName": "Project",
- "dotnetRunMessages": "true",
- "launchBrowser": true,
- "applicationUrl": "https://localhost:5001;http://localhost:5000",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "LocalDevelopment",
- "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
- }
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:7173;http://localhost:5249",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "LocalDevelopment",
+ "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
+ }
}
}
}
diff --git a/app/Codebelt.Bootstrapper.WebMvc.App/Properties/launchSettings.json b/app/Codebelt.Bootstrapper.WebMvc.App/Properties/launchSettings.json
index 9f66391..fa94b70 100644
--- a/app/Codebelt.Bootstrapper.WebMvc.App/Properties/launchSettings.json
+++ b/app/Codebelt.Bootstrapper.WebMvc.App/Properties/launchSettings.json
@@ -1,14 +1,14 @@
{
"profiles": {
"Codebelt.Bootstrapper.WebMvc.App": {
- "commandName": "Project",
- "dotnetRunMessages": "true",
- "launchBrowser": true,
- "applicationUrl": "https://localhost:5001;http://localhost:5000",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "LocalDevelopment",
- "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
- }
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:7173;http://localhost:5249",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "LocalDevelopment",
+ "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
+ }
}
}
}
diff --git a/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs b/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs
index 02f3396..896e5a4 100644
--- a/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs
+++ b/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs
@@ -1,9 +1,10 @@
-using System;
-using System.Threading;
-using System.Threading.Tasks;
+using Cuemon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
namespace Codebelt.Bootstrapper.Console
{
@@ -55,13 +56,13 @@ public Task StartAsync(CancellationToken cancellationToken)
{
try
{
- _logger.LogInformation("RunAsync started.");
+ Decorator.EncloseToExpose(_logger, false).RunAsyncStarted();
await startup.RunAsync(_provider, cancellationToken).ConfigureAwait(false);
_ranToCompletion = true;
}
catch (Exception e)
{
- _logger.LogCritical(e, "Fatal error occurred while activating {TypeFullName}.", typeof(TStartup).FullName);
+ Decorator.EncloseToExpose(_logger, false).FatalErrorActivating(typeof(TStartup).FullName, e);
}
}, cancellationToken);
@@ -70,7 +71,7 @@ public Task StartAsync(CancellationToken cancellationToken)
}
else
{
- _logger.LogWarning("Unable to activate an instance of {TypeFullName}.", typeof(TStartup).FullName);
+ Decorator.EncloseToExpose(_logger, false).UnableToActivateInstance(typeof(TStartup).FullName);
}
return Task.CompletedTask;
@@ -91,11 +92,11 @@ public Task StopAsync(CancellationToken cancellationToken)
{
if (!_ranToCompletion)
{
- _logger?.LogInformation("RunAsync ended prematurely.");
+ Decorator.EncloseToExpose(_logger, false)?.RunAsyncPrematureEnd();
}
else
{
- _logger?.LogInformation("RunAsync completed successfully.");
+ Decorator.EncloseToExpose(_logger, false)?.RunAsyncCompleted();
}
return Task.CompletedTask;
diff --git a/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs b/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs
index 67e68c9..0596bb0 100644
--- a/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs
+++ b/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs
@@ -4,6 +4,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using Cuemon;
namespace Codebelt.Bootstrapper.Console
{
@@ -57,18 +58,18 @@ public Task StartAsync(CancellationToken cancellationToken)
{
if (program != null)
{
- _logger.LogInformation("RunAsync started.");
+ Decorator.EncloseToExpose(_logger, false).RunAsyncStarted();
await program.RunAsync(_provider, cancellationToken).ConfigureAwait(false);
_ranToCompletion = true;
}
else
{
- _logger.LogWarning("Unable to activate an instance of {TypeFullName}.", programType.FullName);
+ Decorator.EncloseToExpose(_logger, false).UnableToActivateInstance(programType.FullName);
}
}
catch (Exception e)
{
- _logger.LogCritical(e, "Fatal error occurred while activating {TypeFullName}.", programType.FullName);
+ Decorator.EncloseToExpose(_logger, false).FatalErrorActivating(programType.FullName, e);
}
}, cancellationToken);
@@ -93,11 +94,11 @@ public Task StopAsync(CancellationToken cancellationToken)
{
if (!_ranToCompletion)
{
- _logger?.LogInformation("RunAsync ended prematurely.");
+ Decorator.EncloseToExpose(_logger, false)?.RunAsyncPrematureEnd();
}
else
{
- _logger?.LogInformation("RunAsync completed successfully.");
+ Decorator.EncloseToExpose(_logger, false)?.RunAsyncCompleted();
}
return Task.CompletedTask;
diff --git a/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs b/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs
index 24a5160..31be448 100644
--- a/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs
+++ b/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs
@@ -87,7 +87,7 @@ private void OnApplicationStopping()
}
///
- /// Called when this object is being disposed by either or having disposing set to true and is false.
+ /// Called when this object is being disposed by either or having disposing set to true and is false.
///
protected override void OnDisposeManagedResources()
{
diff --git a/src/Codebelt.Bootstrapper/BootstrapperLogMessages.cs b/src/Codebelt.Bootstrapper/BootstrapperLogMessages.cs
new file mode 100644
index 0000000..92eb249
--- /dev/null
+++ b/src/Codebelt.Bootstrapper/BootstrapperLogMessages.cs
@@ -0,0 +1,73 @@
+using System;
+using Cuemon;
+using Microsoft.Extensions.Logging;
+
+namespace Codebelt.Bootstrapper
+{
+ ///
+ /// Provides centralized logging messages for the Bootstrapper SDK using the LoggerMessage pattern.
+ /// This API supports the product infrastructure and is not intended to be used directly from your code.
+ ///
+ /// https://learn.microsoft.com/en-us/dotnet/core/extensions/high-performance-logging
+ public static class BootstrapperLogMessages
+ {
+ // Information messages
+ private static readonly Action RunAsyncStartedDefinition = LoggerMessage.Define(LogLevel.Information, new EventId(1000, nameof(RunAsyncStarted)), "RunAsync started.");
+ private static readonly Action RunAsyncPrematureEndDefinition = LoggerMessage.Define(LogLevel.Information, new EventId(1001, nameof(RunAsyncPrematureEnd)), "RunAsync ended prematurely.");
+ private static readonly Action RunAsyncCompletedDefinition = LoggerMessage.Define(LogLevel.Information, new EventId(1002, nameof(RunAsyncCompleted)), "RunAsync completed successfully.");
+
+ // Warning messages
+ private static readonly Action UnableToActivateInstanceDefinition = LoggerMessage.Define(LogLevel.Warning, new EventId(2000, nameof(UnableToActivateInstance)), "Unable to activate an instance of {TypeFullName}.");
+
+ // Critical messages
+ private static readonly Action FatalErrorActivatingDefinition = LoggerMessage.Define(LogLevel.Critical, new EventId(3000, nameof(FatalErrorActivating)), "Fatal error occurred while activating {TypeFullName}.");
+
+ ///
+ /// Logs that RunAsync has started.
+ ///
+ /// The logger instance.
+ public static void RunAsyncStarted(this IDecorator logger)
+ {
+ RunAsyncStartedDefinition(logger.Inner, null);
+ }
+
+ ///
+ /// Logs that RunAsync ended prematurely.
+ ///
+ /// The logger instance.
+ public static void RunAsyncPrematureEnd(this IDecorator logger)
+ {
+ RunAsyncPrematureEndDefinition(logger.Inner, null);
+ }
+
+ ///
+ /// Logs that RunAsync completed successfully.
+ ///
+ /// The logger instance.
+ public static void RunAsyncCompleted(this IDecorator logger)
+ {
+ RunAsyncCompletedDefinition(logger.Inner, null);
+ }
+
+ ///
+ /// Logs an inability to activate the specified type.
+ ///
+ /// The logger instance.
+ /// The full name of the type that couldn't be activated.
+ public static void UnableToActivateInstance(this IDecorator logger, string typeFullName)
+ {
+ UnableToActivateInstanceDefinition(logger.Inner, typeFullName, null);
+ }
+
+ ///
+ /// Logs a fatal error that occurred while activating a type.
+ ///
+ /// The logger instance.
+ /// The full name of the type being activated.
+ /// The exception that occurred.
+ public static void FatalErrorActivating(this IDecorator logger, string typeFullName, Exception exception)
+ {
+ FatalErrorActivatingDefinition(logger.Inner, typeFullName, exception);
+ }
+ }
+}