diff --git a/Readme.md b/Readme.md
index d4c9ec6..dee8c8b 100644
--- a/Readme.md
+++ b/Readme.md
@@ -23,6 +23,7 @@ This package currently supports:
- **Cors Configuration** with easy configuration options.
- **Resilience Pipelines** for `HttpClient` operations.
- **Controller Extensions** for mapping old-style MVC controllers.
+- **SignalR Extensions** for adding simple SignalR or distributed SignalR backed with Redis.
- **OpenTelemetry Integration** for tracking metrics, traces, and logging.
- **Health Checks** with default endpoints and startup validation.
- Various **Extensions and Utilities**, including enumerable, string, and queryable extensions.
@@ -121,6 +122,7 @@ Follow this example to set up your project with all the features provided by thi
},
"RepositoryName": "be-lib-sharedkernel",
"ConnectionStrings": {
+ "Redis": "localhost:6379",
"PersistentStorage": "/persistence"
},
"Security": {
@@ -143,14 +145,15 @@ builder
.AddResponseCrafter(NamingConvention.ToSnakeCase)
.AddOpenApi()
.AddOpenTelemetry()
- .AddEndpoints(AssemblyRegistry.ToArray())
+ .AddMapMinimalApis(AssemblyRegistry.ToArray())
.AddControllers(AssemblyRegistry.ToArray())
.AddMediatrWithBehaviors(AssemblyRegistry.ToArray())
.AddResilienceDefaultPipeline()
.MapDefaultTimeZone()
- .AddCors();
-
-builder.Services.AddHealthChecks();
+ .AddRedis(KeyPrefix.AssemblyNamePrefix)
+ .AddDistributedSignalR("DistributedSignalR") // or .AddSignalR()
+ .AddCors()
+ .AddHealthChecks();
var app = builder.Build();
@@ -159,11 +162,12 @@ app
.UseRequestResponseLogging()
.UseResponseCrafter()
.UseCors()
- .MapEndpoints()
+ .MapMinimalApis()
.MapDefaultEndpoints()
.EnsureHealthy()
.ClearAssemblyRegistry()
- .UseOpenApi();
+ .UseOpenApi()
+ .MapControllers();
app.LogStartSuccess();
app.Run();
@@ -531,11 +535,16 @@ The default resilience pipeline includes the following policies:
For mapping old style MVC controllers, use `builder.AddControllers()`.
The `AddControllers()` method can also accept assembly names as parameters to scan for controllers.
+The `MapControllers()` method maps the controllers to the application.
Example:
```csharp
+var builder = WebApplication.CreateBuilder(args);
builder.AddControllers([typeof(Program).Assembly]);
+var app = builder.Build();
+app.MapControllers();
+app.Run();
```
## OpenTelemetry
@@ -598,6 +607,7 @@ This package includes various extensions and utilities to aid development:
- **Pandatech.FluentMinimalApiMapper:** Simplifies mapping in minimal APIs.
- **Pandatech.RegexBox:** A collection of useful regular expressions.
- **Pandatech.ResponseCrafter:** A utility for crafting consistent API responses.
+- **Pandatech.DistributedCache:** A distributed cache provider for Redis.
## License
diff --git a/src/SharedKernel/Extensions/ConfigurationExtensions.cs b/src/SharedKernel/Extensions/ConfigurationExtensions.cs
index 5163066..018734c 100644
--- a/src/SharedKernel/Extensions/ConfigurationExtensions.cs
+++ b/src/SharedKernel/Extensions/ConfigurationExtensions.cs
@@ -8,6 +8,7 @@ internal static class ConfigurationExtensions
private const string PersistentConfigurationPath = "PersistentStorage";
private const string RepositoryNameConfigurationPath = "RepositoryName";
private const string TimeZoneConfigurationPath = "DefaultTimeZone";
+ private const string RedisConfigurationPath = "Redis";
internal static string GetAllowedCorsOrigins(this IConfiguration configuration)
{
@@ -52,4 +53,15 @@ public static string GetDefaultTimeZone(this IConfiguration configuration)
return timeZone;
}
+
+ public static string GetRedisUrl(this IConfiguration configuration)
+ {
+ var redisConnectionString = configuration.GetConnectionString(RedisConfigurationPath);
+ if (redisConnectionString is null)
+ {
+ throw new InvalidOperationException("Redis connection string is not configured.");
+ }
+
+ return redisConnectionString;
+ }
}
\ No newline at end of file
diff --git a/src/SharedKernel/Extensions/DistributedCacheExtension.cs b/src/SharedKernel/Extensions/DistributedCacheExtension.cs
new file mode 100644
index 0000000..5094cba
--- /dev/null
+++ b/src/SharedKernel/Extensions/DistributedCacheExtension.cs
@@ -0,0 +1,19 @@
+using DistributedCache.Extensions;
+using DistributedCache.Options;
+using Microsoft.AspNetCore.Builder;
+
+namespace SharedKernel.Extensions;
+
+public static class DistributedCacheExtension
+{
+ public static WebApplicationBuilder AddRedis(this WebApplicationBuilder builder, KeyPrefix keyPrefix)
+ {
+ builder.AddDistributedCache(options =>
+ {
+ options.RedisConnectionString = builder.Configuration.GetRedisUrl();
+ options.KeyPrefixForIsolation = keyPrefix;
+ });
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/SharedKernel/Extensions/HealthCheckRunnerExtension.cs b/src/SharedKernel/Extensions/HealthCheckExtensions.cs
similarity index 87%
rename from src/SharedKernel/Extensions/HealthCheckRunnerExtension.cs
rename to src/SharedKernel/Extensions/HealthCheckExtensions.cs
index 4eeaa4b..fc66850 100644
--- a/src/SharedKernel/Extensions/HealthCheckRunnerExtension.cs
+++ b/src/SharedKernel/Extensions/HealthCheckExtensions.cs
@@ -5,7 +5,7 @@
namespace SharedKernel.Extensions;
-public static class HealthCheckRunnerExtension
+public static class HealthCheckExtensions
{
public static WebApplication EnsureHealthy(this WebApplication app)
{
@@ -42,4 +42,10 @@ public static WebApplication EnsureHealthy(this WebApplication app)
var message = $"Unhealthy services detected: {string.Join(", ", unhealthyChecks)}";
throw new ServiceUnavailableException(message);
}
+
+ public static WebApplicationBuilder AddHealthChecks(this WebApplicationBuilder builder)
+ {
+ builder.Services.AddHealthChecks();
+ return builder;
+ }
}
\ No newline at end of file
diff --git a/src/SharedKernel/Extensions/SignalRExtensions.cs b/src/SharedKernel/Extensions/SignalRExtensions.cs
new file mode 100644
index 0000000..7c856f5
--- /dev/null
+++ b/src/SharedKernel/Extensions/SignalRExtensions.cs
@@ -0,0 +1,35 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using StackExchange.Redis;
+
+namespace SharedKernel.Extensions;
+
+public static class SignalRExtensions
+{
+
+ public static WebApplicationBuilder AddSignalR(this WebApplicationBuilder builder)
+ {
+ builder
+ .Services
+ .AddSignalR()
+ .AddMessagePackProtocol();
+
+ return builder;
+ }
+
+ public static WebApplicationBuilder AddDistributedSignalR(this WebApplicationBuilder builder, string redisChannelName)
+ {
+ builder
+ .Services
+ .AddSignalR()
+ .AddMessagePackProtocol()
+ .AddStackExchangeRedis(builder.Configuration.GetRedisUrl(),
+ options =>
+ {
+ options.Configuration.ChannelPrefix = RedisChannel.Literal("FinHub:SignalR:");
+ });
+
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/src/SharedKernel/Logging/StartupLoggerExtensions.cs b/src/SharedKernel/Logging/StartupLoggerExtensions.cs
index a052ed9..a3fb2c5 100644
--- a/src/SharedKernel/Logging/StartupLoggerExtensions.cs
+++ b/src/SharedKernel/Logging/StartupLoggerExtensions.cs
@@ -1,6 +1,6 @@
using System.Globalization;
+using System.Text.Json;
using Microsoft.AspNetCore.Builder;
-using Newtonsoft.Json;
namespace SharedKernel.Logging;
@@ -11,7 +11,7 @@ public static class StartupLoggerExtensions
public static WebApplicationBuilder LogStartAttempt(this WebApplicationBuilder builder)
{
var now = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
- Console.WriteLine(JsonConvert.SerializeObject(new
+ Console.WriteLine(JsonSerializer.Serialize(new
{
Timestamp = now,
Event = "ApplicationStartAttempt",
@@ -27,7 +27,7 @@ public static WebApplication LogStartSuccess(this WebApplication app)
.TotalMilliseconds;
var deltaInSeconds = Math.Round(delta / 1000, 2);
var now = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
- Console.WriteLine(JsonConvert.SerializeObject(new
+ Console.WriteLine(JsonSerializer.Serialize(new
{
Timestamp = now,
Event = "ApplicationStartSuccess",
@@ -40,7 +40,7 @@ public static WebApplicationBuilder LogModuleRegistrationSuccess(this WebApplica
string moduleName)
{
var now = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
- Console.WriteLine(JsonConvert.SerializeObject(new
+ Console.WriteLine(JsonSerializer.Serialize(new
{
Timestamp = now,
Event = "ModuleRegistrationSuccess",
@@ -52,7 +52,7 @@ public static WebApplicationBuilder LogModuleRegistrationSuccess(this WebApplica
public static WebApplication LogModuleUseSuccess(this WebApplication app, string moduleName)
{
var now = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture);
- Console.WriteLine(JsonConvert.SerializeObject(new
+ Console.WriteLine(JsonSerializer.Serialize(new
{
Timestamp = now,
Event = "ModuleUseSuccess",
@@ -60,4 +60,4 @@ public static WebApplication LogModuleUseSuccess(this WebApplication app, string
}));
return app;
}
-}
\ No newline at end of file
+}
diff --git a/src/SharedKernel/SharedKernel.csproj b/src/SharedKernel/SharedKernel.csproj
index 7909080..bc7222a 100644
--- a/src/SharedKernel/SharedKernel.csproj
+++ b/src/SharedKernel/SharedKernel.csproj
@@ -8,13 +8,13 @@