diff --git a/src/BuildingBlocks/Caching/CacheServiceExtensions.cs b/src/BuildingBlocks/Caching/CacheServiceExtensions.cs
index e97d66ced6..b8527254d9 100644
--- a/src/BuildingBlocks/Caching/CacheServiceExtensions.cs
+++ b/src/BuildingBlocks/Caching/CacheServiceExtensions.cs
@@ -1,6 +1,20 @@
-namespace FSH.Framework.Caching;
+namespace FSH.Framework.Caching;
+
+///
+/// Extension methods for providing cache-aside pattern implementations.
+///
public static class CacheServiceExtensions
{
+ ///
+ /// Gets an item from cache, or sets it using the provided callback if not found.
+ /// Implements the cache-aside pattern synchronously.
+ ///
+ /// The type of the cached item.
+ /// The cache service instance.
+ /// The unique cache key.
+ /// A callback function to retrieve the item if not in cache.
+ /// Optional sliding expiration for the cached item.
+ /// The cached item or the newly retrieved and cached item.
public static T? GetOrSet(this ICacheService cache, string key, Func getItemCallback, TimeSpan? slidingExpiration = null)
{
ArgumentNullException.ThrowIfNull(cache);
@@ -23,6 +37,17 @@ public static class CacheServiceExtensions
return value;
}
+ ///
+ /// Asynchronously gets an item from cache, or sets it using the provided task if not found.
+ /// Implements the cache-aside pattern asynchronously.
+ ///
+ /// The type of the cached item.
+ /// The cache service instance.
+ /// The unique cache key.
+ /// An async function to retrieve the item if not in cache.
+ /// Optional sliding expiration for the cached item.
+ /// Cancellation token for the operation.
+ /// The cached item or the newly retrieved and cached item.
public static async Task GetOrSetAsync(this ICacheService cache, string key, Func> task, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(cache);
@@ -44,4 +69,4 @@ public static class CacheServiceExtensions
return value;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/Caching/DistributedCacheService.cs b/src/BuildingBlocks/Caching/DistributedCacheService.cs
index 1e6fd7f00f..839ee52073 100644
--- a/src/BuildingBlocks/Caching/DistributedCacheService.cs
+++ b/src/BuildingBlocks/Caching/DistributedCacheService.cs
@@ -1,10 +1,15 @@
-using Microsoft.Extensions.Caching.Distributed;
+using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text;
using System.Text.Json;
namespace FSH.Framework.Caching;
+
+///
+/// Implementation of using distributed cache (Redis or in-memory).
+/// Provides JSON serialization for cached objects with configurable expiration policies.
+///
public sealed class DistributedCacheService : ICacheService
{
private static readonly Encoding Utf8 = Encoding.UTF8;
@@ -14,6 +19,12 @@ public sealed class DistributedCacheService : ICacheService
private readonly ILogger _logger;
private readonly CachingOptions _opts;
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The underlying distributed cache implementation.
+ /// Logger for cache operations.
+ /// Caching configuration options.
public DistributedCacheService(
IDistributedCache cache,
ILogger logger,
@@ -26,6 +37,7 @@ public DistributedCacheService(
_opts = opts.Value;
}
+ ///
public async Task GetItemAsync(string key, CancellationToken ct = default)
{
key = Normalize(key);
@@ -42,6 +54,7 @@ public DistributedCacheService(
}
}
+ ///
public async Task SetItemAsync(string key, T value, TimeSpan? sliding = default, CancellationToken ct = default)
{
key = Normalize(key);
@@ -57,6 +70,7 @@ public async Task SetItemAsync(string key, T value, TimeSpan? sliding = defau
}
}
+ ///
public async Task RemoveItemAsync(string key, CancellationToken ct = default)
{
key = Normalize(key);
@@ -65,6 +79,7 @@ public async Task RemoveItemAsync(string key, CancellationToken ct = default)
{ _logger.LogWarning(ex, "Cache remove failed for {Key}", key); }
}
+ ///
public async Task RefreshItemAsync(string key, CancellationToken ct = default)
{
key = Normalize(key);
@@ -76,11 +91,24 @@ public async Task RefreshItemAsync(string key, CancellationToken ct = default)
catch (Exception ex) when (ex is not OperationCanceledException)
{ _logger.LogWarning(ex, "Cache refresh failed for {Key}", key); }
}
+
+ ///
public T? GetItem(string key) => GetItemAsync(key).GetAwaiter().GetResult();
+
+ ///
public void SetItem(string key, T value, TimeSpan? sliding = default) => SetItemAsync(key, value, sliding).GetAwaiter().GetResult();
+
+ ///
public void RemoveItem(string key) => RemoveItemAsync(key).GetAwaiter().GetResult();
+
+ ///
public void RefreshItem(string key) => RefreshItemAsync(key).GetAwaiter().GetResult();
+ ///
+ /// Builds cache entry options with configured expiration settings.
+ ///
+ /// Optional sliding expiration override.
+ /// Configured cache entry options.
private DistributedCacheEntryOptions BuildEntryOptions(TimeSpan? sliding)
{
var o = new DistributedCacheEntryOptions();
@@ -96,6 +124,12 @@ private DistributedCacheEntryOptions BuildEntryOptions(TimeSpan? sliding)
return o;
}
+ ///
+ /// Normalizes the cache key by applying the configured prefix.
+ ///
+ /// The original cache key.
+ /// The normalized key with prefix applied.
+ /// Thrown when key is null or whitespace.
private string Normalize(string key)
{
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key));
diff --git a/src/BuildingBlocks/Caching/Extensions.cs b/src/BuildingBlocks/Caching/Extensions.cs
index 4857f6a982..49446091d2 100644
--- a/src/BuildingBlocks/Caching/Extensions.cs
+++ b/src/BuildingBlocks/Caching/Extensions.cs
@@ -1,12 +1,27 @@
-using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using StackExchange.Redis;
namespace FSH.Framework.Caching;
+///
+/// Extension methods for registering caching services in the dependency injection container.
+///
public static class Extensions
{
+ ///
+ /// Adds FullStackHero caching services to the service collection.
+ /// Configures a hybrid L1/L2 cache with in-memory (L1) and Redis or distributed memory (L2).
+ ///
+ /// The service collection to add caching services to.
+ /// The application configuration containing caching options.
+ /// The service collection for chaining.
+ ///
+ /// If Redis connection string is configured in , Redis is used for L2 cache.
+ /// Otherwise, falls back to in-memory distributed cache for L2.
+ /// The is registered to provide both sync and async cache operations.
+ ///
public static IServiceCollection AddHeroCaching(this IServiceCollection services, IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(configuration);
@@ -47,4 +62,4 @@ public static IServiceCollection AddHeroCaching(this IServiceCollection services
return services;
}
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/Caching/HybridCacheService.cs b/src/BuildingBlocks/Caching/HybridCacheService.cs
index ed8eb20a09..bf170ca4de 100644
--- a/src/BuildingBlocks/Caching/HybridCacheService.cs
+++ b/src/BuildingBlocks/Caching/HybridCacheService.cs
@@ -7,6 +7,15 @@
namespace FSH.Framework.Caching;
+///
+/// A hybrid cache implementation combining L1 (in-memory) and L2 (distributed) caching.
+/// Provides fast local access with distributed cache backup for multi-instance scenarios.
+///
+///
+/// The hybrid approach uses memory cache for fast L1 access and automatically populates
+/// it from the L2 distributed cache on cache misses. Write operations update both caches.
+/// Memory cache uses 80% of the distributed cache sliding expiration for faster refresh.
+///
public sealed class HybridCacheService : ICacheService
{
private static readonly Encoding Utf8 = Encoding.UTF8;
@@ -17,6 +26,13 @@ public sealed class HybridCacheService : ICacheService
private readonly ILogger _logger;
private readonly CachingOptions _opts;
+ ///
+ /// Initializes a new instance of .
+ ///
+ /// The L1 in-memory cache.
+ /// The L2 distributed cache (Redis or memory-based).
+ /// Logger for cache operations.
+ /// Caching configuration options.
public HybridCacheService(
IMemoryCache memoryCache,
IDistributedCache distributedCache,
@@ -31,6 +47,11 @@ public HybridCacheService(
_opts = opts.Value;
}
+ ///
+ ///
+ /// First checks L1 memory cache, then falls back to L2 distributed cache.
+ /// If found in L2, the item is automatically populated into L1 for subsequent fast access.
+ ///
public async Task GetItemAsync(string key, CancellationToken ct = default)
{
key = Normalize(key);
@@ -66,6 +87,10 @@ public HybridCacheService(
}
}
+ ///
+ ///
+ /// Writes to both L1 memory cache and L2 distributed cache simultaneously.
+ ///
public async Task SetItemAsync(string key, T value, TimeSpan? sliding = default, CancellationToken ct = default)
{
key = Normalize(key);
@@ -86,6 +111,10 @@ public async Task SetItemAsync(string key, T value, TimeSpan? sliding = defau
}
}
+ ///
+ ///
+ /// Removes from both L1 memory cache and L2 distributed cache.
+ ///
public async Task RemoveItemAsync(string key, CancellationToken ct = default)
{
key = Normalize(key);
@@ -102,6 +131,7 @@ public async Task RemoveItemAsync(string key, CancellationToken ct = default)
}
}
+ ///
public async Task RefreshItemAsync(string key, CancellationToken ct = default)
{
key = Normalize(key);
@@ -116,11 +146,23 @@ public async Task RefreshItemAsync(string key, CancellationToken ct = default)
}
}
+ ///
public T? GetItem(string key) => GetItemAsync(key).GetAwaiter().GetResult();
+
+ ///
public void SetItem(string key, T value, TimeSpan? sliding = default) => SetItemAsync(key, value, sliding).GetAwaiter().GetResult();
+
+ ///
public void RemoveItem(string key) => RemoveItemAsync(key).GetAwaiter().GetResult();
+
+ ///
public void RefreshItem(string key) => RefreshItemAsync(key).GetAwaiter().GetResult();
+ ///
+ /// Builds distributed cache entry options with configured expiration settings.
+ ///
+ /// Optional sliding expiration override.
+ /// Configured distributed cache entry options.
private DistributedCacheEntryOptions BuildDistributedEntryOptions(TimeSpan? sliding)
{
var o = new DistributedCacheEntryOptions();
@@ -136,6 +178,11 @@ private DistributedCacheEntryOptions BuildDistributedEntryOptions(TimeSpan? slid
return o;
}
+ ///
+ /// Gets memory cache expiration options, set to 80% of distributed cache expiration
+ /// for faster refresh cycles.
+ ///
+ /// Memory cache entry options with sliding expiration.
private MemoryCacheEntryOptions GetMemoryCacheExpiration()
{
var options = new MemoryCacheEntryOptions();
@@ -147,6 +194,12 @@ private MemoryCacheEntryOptions GetMemoryCacheExpiration()
return options;
}
+ ///
+ /// Normalizes the cache key by applying the configured prefix.
+ ///
+ /// The original cache key.
+ /// The normalized key with prefix applied.
+ /// Thrown when key is null or whitespace.
private string Normalize(string key)
{
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key));
diff --git a/src/BuildingBlocks/Caching/ICacheService.cs b/src/BuildingBlocks/Caching/ICacheService.cs
index fbc00dfadc..9e8fa95a9e 100644
--- a/src/BuildingBlocks/Caching/ICacheService.cs
+++ b/src/BuildingBlocks/Caching/ICacheService.cs
@@ -1,12 +1,70 @@
-namespace FSH.Framework.Caching;
+namespace FSH.Framework.Caching;
+
+///
+/// Provides caching operations for storing and retrieving items from cache.
+/// Supports both synchronous and asynchronous operations.
+///
public interface ICacheService
{
+ ///
+ /// Asynchronously retrieves an item from the cache.
+ ///
+ /// The type of the cached item.
+ /// The unique cache key.
+ /// Cancellation token for the operation.
+ /// The cached item if found; otherwise, null.
Task GetItemAsync(string key, CancellationToken ct = default);
+
+ ///
+ /// Asynchronously stores an item in the cache.
+ ///
+ /// The type of the item to cache.
+ /// The unique cache key.
+ /// The value to cache.
+ /// Optional sliding expiration. Uses default if not specified.
+ /// Cancellation token for the operation.
Task SetItemAsync(string key, T value, TimeSpan? sliding = default, CancellationToken ct = default);
+
+ ///
+ /// Asynchronously removes an item from the cache.
+ ///
+ /// The unique cache key to remove.
+ /// Cancellation token for the operation.
Task RemoveItemAsync(string key, CancellationToken ct = default);
+
+ ///
+ /// Asynchronously refreshes the sliding expiration of a cached item.
+ ///
+ /// The unique cache key to refresh.
+ /// Cancellation token for the operation.
Task RefreshItemAsync(string key, CancellationToken ct = default);
+
+ ///
+ /// Retrieves an item from the cache synchronously.
+ ///
+ /// The type of the cached item.
+ /// The unique cache key.
+ /// The cached item if found; otherwise, null.
T? GetItem(string key);
+
+ ///
+ /// Stores an item in the cache synchronously.
+ ///
+ /// The type of the item to cache.
+ /// The unique cache key.
+ /// The value to cache.
+ /// Optional sliding expiration. Uses default if not specified.
void SetItem(string key, T value, TimeSpan? sliding = default);
+
+ ///
+ /// Removes an item from the cache synchronously.
+ ///
+ /// The unique cache key to remove.
void RemoveItem(string key);
+
+ ///
+ /// Refreshes the sliding expiration of a cached item synchronously.
+ ///
+ /// The unique cache key to refresh.
void RefreshItem(string key);
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/Core/Abstractions/IAppUser.cs b/src/BuildingBlocks/Core/Abstractions/IAppUser.cs
index d50318d64c..b49e0e6157 100644
--- a/src/BuildingBlocks/Core/Abstractions/IAppUser.cs
+++ b/src/BuildingBlocks/Core/Abstractions/IAppUser.cs
@@ -1,10 +1,37 @@
namespace FSH.Framework.Core.Abstractions;
+
+///
+/// Represents a basic application user with common properties.
+///
public interface IAppUser
{
+ ///
+ /// Gets the first name of the user.
+ ///
string? FirstName { get; }
+
+ ///
+ /// Gets the last name of the user.
+ ///
string? LastName { get; }
+
+ ///
+ /// Gets the URL of the user's profile image.
+ ///
Uri? ImageUrl { get; }
+
+ ///
+ /// Gets a value indicating whether the user account is active.
+ ///
bool IsActive { get; }
+
+ ///
+ /// Gets the refresh token for the user's authentication session.
+ ///
string? RefreshToken { get; }
+
+ ///
+ /// Gets the expiry time of the refresh token.
+ ///
DateTime RefreshTokenExpiryTime { get; }
}
\ No newline at end of file
diff --git a/src/BuildingBlocks/Core/Common/QueryStringKeys.cs b/src/BuildingBlocks/Core/Common/QueryStringKeys.cs
index 8252eb5949..1b724d279a 100644
--- a/src/BuildingBlocks/Core/Common/QueryStringKeys.cs
+++ b/src/BuildingBlocks/Core/Common/QueryStringKeys.cs
@@ -1,6 +1,17 @@
namespace FSH.Framework.Core.Common;
+
+///
+/// Constants for commonly used query string parameter names.
+///
public static class QueryStringKeys
{
+ ///
+ /// Query string key for authentication or verification codes.
+ ///
public const string Code = "code";
+
+ ///
+ /// Query string key for user identifier parameter.
+ ///
public const string UserId = "userId";
}
\ No newline at end of file
diff --git a/src/BuildingBlocks/Core/Context/ICurrentUser.cs b/src/BuildingBlocks/Core/Context/ICurrentUser.cs
index 2784294c1f..2a70135b21 100644
--- a/src/BuildingBlocks/Core/Context/ICurrentUser.cs
+++ b/src/BuildingBlocks/Core/Context/ICurrentUser.cs
@@ -1,19 +1,51 @@
using System.Security.Claims;
namespace FSH.Framework.Core.Context;
+
+///
+/// Represents the current authenticated user context with access to user information and claims.
+///
public interface ICurrentUser
{
+ ///
+ /// Gets the display name of the current user.
+ ///
string? Name { get; }
+ ///
+ /// Gets the unique identifier of the current user.
+ ///
+ /// The user's unique identifier.
Guid GetUserId();
+ ///
+ /// Gets the email address of the current user.
+ ///
+ /// The user's email address, or null if not available.
string? GetUserEmail();
+ ///
+ /// Gets the tenant identifier that the current user belongs to.
+ ///
+ /// The tenant identifier, or null if not in a multi-tenant context.
string? GetTenant();
+ ///
+ /// Determines whether the current user is authenticated.
+ ///
+ /// true if the user is authenticated; otherwise, false.
bool IsAuthenticated();
+ ///
+ /// Determines whether the current user is in the specified role.
+ ///
+ /// The role to check for.
+ /// true if the user is in the specified role; otherwise, false.
bool IsInRole(string role);
+ ///
+ /// Gets all claims associated with the current user.
+ ///
+ /// A collection of user claims, or null if no claims are available.
IEnumerable? GetUserClaims();
}
\ No newline at end of file
diff --git a/src/BuildingBlocks/Core/Context/ICurrentUserInitializer.cs b/src/BuildingBlocks/Core/Context/ICurrentUserInitializer.cs
index 9e94530a0f..2a8dafb9b8 100644
--- a/src/BuildingBlocks/Core/Context/ICurrentUserInitializer.cs
+++ b/src/BuildingBlocks/Core/Context/ICurrentUserInitializer.cs
@@ -1,9 +1,21 @@
using System.Security.Claims;
namespace FSH.Framework.Core.Context;
+
+///
+/// Provides methods to initialize and set the current user context.
+///
public interface ICurrentUserInitializer
{
+ ///
+ /// Sets the current user from a claims principal.
+ ///
+ /// The claims principal representing the authenticated user.
void SetCurrentUser(ClaimsPrincipal user);
+ ///
+ /// Sets the current user identifier directly.
+ ///
+ /// The unique identifier of the user.
void SetCurrentUserId(string userId);
}
\ No newline at end of file
diff --git a/src/BuildingBlocks/Core/Domain/AggregateRoot.cs b/src/BuildingBlocks/Core/Domain/AggregateRoot.cs
index 8f0d2f8349..db4b19c689 100644
--- a/src/BuildingBlocks/Core/Domain/AggregateRoot.cs
+++ b/src/BuildingBlocks/Core/Domain/AggregateRoot.cs
@@ -1,4 +1,9 @@
namespace FSH.Framework.Core.Domain;
+
+///
+/// Represents an aggregate root in the domain model.
+///
+/// The type of the aggregate identifier.
public abstract class AggregateRoot : BaseEntity
{
// Put aggregate-wide behaviors/helpers here if needed
diff --git a/src/BuildingBlocks/Core/Domain/BaseEntity.cs b/src/BuildingBlocks/Core/Domain/BaseEntity.cs
index 18c5b3d2bc..be973c5792 100644
--- a/src/BuildingBlocks/Core/Domain/BaseEntity.cs
+++ b/src/BuildingBlocks/Core/Domain/BaseEntity.cs
@@ -1,15 +1,32 @@
namespace FSH.Framework.Core.Domain;
+
+///
+/// Provides a base implementation for entities with identity and domain events.
+///
+/// The type of the entity identifier.
public abstract class BaseEntity : IEntity, IHasDomainEvents
{
private readonly List _domainEvents = [];
+ ///
+ /// Gets the entity identifier.
+ ///
public TId Id { get; protected set; } = default!;
+ ///
+ /// Gets the domain events raised by this entity.
+ ///
public IReadOnlyCollection DomainEvents => _domainEvents;
- /// Raise and record a domain event for later dispatch.
+ ///
+ /// Raises and records a domain event for later dispatch.
+ ///
+ /// The domain event to add.
protected void AddDomainEvent(IDomainEvent @event)
=> _domainEvents.Add(@event);
+ ///
+ /// Clears all recorded domain events.
+ ///
public void ClearDomainEvents() => _domainEvents.Clear();
}
diff --git a/src/BuildingBlocks/Core/Domain/DomainEvent.cs b/src/BuildingBlocks/Core/Domain/DomainEvent.cs
index 0553965f9b..3fc83733f0 100644
--- a/src/BuildingBlocks/Core/Domain/DomainEvent.cs
+++ b/src/BuildingBlocks/Core/Domain/DomainEvent.cs
@@ -1,5 +1,12 @@
namespace FSH.Framework.Core.Domain;
-/// Base domain event with correlation and tenant context.
+
+///
+/// Base domain event with correlation and tenant context.
+///
+/// The unique event identifier.
+/// The UTC timestamp when the event occurred.
+/// The optional correlation identifier.
+/// The optional tenant identifier.
public abstract record DomainEvent(
Guid EventId,
DateTimeOffset OccurredOnUtc,
@@ -7,6 +14,12 @@ public abstract record DomainEvent(
string? TenantId = null
) : IDomainEvent
{
+ ///
+ /// Creates a new domain event using the provided factory.
+ ///
+ /// The domain event type to create.
+ /// Factory to create the event using the generated id and timestamp.
+ /// The created domain event.
public static T Create(Func factory)
where T : DomainEvent
{
diff --git a/src/BuildingBlocks/Core/Domain/IAuditableEntity.cs b/src/BuildingBlocks/Core/Domain/IAuditableEntity.cs
index 5925f4d426..98538eb2ba 100644
--- a/src/BuildingBlocks/Core/Domain/IAuditableEntity.cs
+++ b/src/BuildingBlocks/Core/Domain/IAuditableEntity.cs
@@ -1,8 +1,27 @@
namespace FSH.Framework.Core.Domain;
+
+///
+/// Defines audit metadata for an entity.
+///
public interface IAuditableEntity
{
+ ///
+ /// Gets the UTC timestamp when the entity was created.
+ ///
DateTimeOffset CreatedOnUtc { get; }
+
+ ///
+ /// Gets the identifier of the creator.
+ ///
string? CreatedBy { get; }
+
+ ///
+ /// Gets the UTC timestamp when the entity was last modified.
+ ///
DateTimeOffset? LastModifiedOnUtc { get; }
+
+ ///
+ /// Gets the identifier of the last modifier.
+ ///
string? LastModifiedBy { get; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/Core/Domain/IDomainEvent.cs b/src/BuildingBlocks/Core/Domain/IDomainEvent.cs
index c886257dbd..3c24ae30d7 100644
--- a/src/BuildingBlocks/Core/Domain/IDomainEvent.cs
+++ b/src/BuildingBlocks/Core/Domain/IDomainEvent.cs
@@ -1,8 +1,27 @@
namespace FSH.Framework.Core.Domain;
+
+///
+/// Represents a domain event with correlation and tenant context.
+///
public interface IDomainEvent
{
+ ///
+ /// Gets the unique event identifier.
+ ///
Guid EventId { get; }
+
+ ///
+ /// Gets the UTC timestamp when the event occurred.
+ ///
DateTimeOffset OccurredOnUtc { get; }
+
+ ///
+ /// Gets the correlation identifier for tracing across boundaries.
+ ///
string? CorrelationId { get; }
+
+ ///
+ /// Gets the tenant identifier associated with the event.
+ ///
string? TenantId { get; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/Core/Domain/IEntity.cs b/src/BuildingBlocks/Core/Domain/IEntity.cs
index adffcdab19..dd23e7a578 100644
--- a/src/BuildingBlocks/Core/Domain/IEntity.cs
+++ b/src/BuildingBlocks/Core/Domain/IEntity.cs
@@ -1,5 +1,13 @@
namespace FSH.Framework.Core.Domain;
+
+///
+/// Represents an entity with a strongly-typed identifier.
+///
+/// The type of the entity identifier.
public interface IEntity
{
+ ///
+ /// Gets the entity identifier.
+ ///
TId Id { get; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/Core/Domain/IHasDomainEvents.cs b/src/BuildingBlocks/Core/Domain/IHasDomainEvents.cs
index 48a3ce0ba2..4d875334f9 100644
--- a/src/BuildingBlocks/Core/Domain/IHasDomainEvents.cs
+++ b/src/BuildingBlocks/Core/Domain/IHasDomainEvents.cs
@@ -1,6 +1,17 @@
namespace FSH.Framework.Core.Domain;
+
+///
+/// Exposes domain events raised by an entity.
+///
public interface IHasDomainEvents
{
+ ///
+ /// Gets the collection of raised domain events.
+ ///
IReadOnlyCollection DomainEvents { get; }
+
+ ///
+ /// Clears the stored domain events.
+ ///
void ClearDomainEvents();
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/Core/Domain/IHasTenant.cs b/src/BuildingBlocks/Core/Domain/IHasTenant.cs
index b7f579b309..70f4c4d007 100644
--- a/src/BuildingBlocks/Core/Domain/IHasTenant.cs
+++ b/src/BuildingBlocks/Core/Domain/IHasTenant.cs
@@ -1,5 +1,12 @@
namespace FSH.Framework.Core.Domain;
+
+///
+/// Associates an entity with a tenant.
+///
public interface IHasTenant
{
+ ///
+ /// Gets the tenant identifier.
+ ///
string TenantId { get; }
-}
\ No newline at end of file
+}
diff --git a/src/BuildingBlocks/Core/Domain/ISoftDeletable.cs b/src/BuildingBlocks/Core/Domain/ISoftDeletable.cs
index 35f841268e..46426403fc 100644
--- a/src/BuildingBlocks/Core/Domain/ISoftDeletable.cs
+++ b/src/BuildingBlocks/Core/Domain/ISoftDeletable.cs
@@ -1,7 +1,22 @@
namespace FSH.Framework.Core.Domain;
+
+///
+/// Marks an entity as supporting soft deletion.
+///
public interface ISoftDeletable
{
+ ///
+ /// Gets a value indicating whether the entity is deleted.
+ ///
bool IsDeleted { get; }
+
+ ///
+ /// Gets the UTC timestamp when the entity was deleted.
+ ///
DateTimeOffset? DeletedOnUtc { get; }
+
+ ///
+ /// Gets the identifier of the user who deleted the entity.
+ ///
string? DeletedBy { get; }
}
diff --git a/src/BuildingBlocks/Core/Exceptions/CustomException.cs b/src/BuildingBlocks/Core/Exceptions/CustomException.cs
index 1c3a51a9b0..48a8a3b41d 100644
--- a/src/BuildingBlocks/Core/Exceptions/CustomException.cs
+++ b/src/BuildingBlocks/Core/Exceptions/CustomException.cs
@@ -19,21 +19,39 @@ public class CustomException : Exception
///
public HttpStatusCode StatusCode { get; }
+ ///
+ /// Initializes a new instance of the class with default message and internal server error status.
+ ///
public CustomException()
: this("An error occurred.", Enumerable.Empty(), HttpStatusCode.InternalServerError)
{
}
+ ///
+ /// Initializes a new instance of the class with specified message.
+ ///
+ /// The error message.
public CustomException(string message)
: this(message, Enumerable.Empty(), HttpStatusCode.InternalServerError)
{
}
+ ///
+ /// Initializes a new instance of the class with specified message and inner exception.
+ ///
+ /// The error message.
+ /// The inner exception that caused this exception.
public CustomException(string message, Exception innerException)
: this(message, innerException, Enumerable.Empty(), HttpStatusCode.InternalServerError)
{
}
+ ///
+ /// Initializes a new instance of the class with message, errors, and status code.
+ ///
+ /// The main error message.
+ /// Collection of detailed error messages.
+ /// The HTTP status code associated with this exception.
public CustomException(
string message,
IEnumerable? errors,
@@ -44,6 +62,13 @@ public CustomException(
StatusCode = statusCode;
}
+ ///
+ /// Initializes a new instance of the class with full parameters.
+ ///
+ /// The main error message.
+ /// The inner exception that caused this exception.
+ /// Collection of detailed error messages.
+ /// The HTTP status code associated with this exception.
public CustomException(
string message,
Exception innerException,
@@ -55,6 +80,12 @@ public CustomException(
StatusCode = statusCode;
}
+ ///
+ /// Initializes a new instance of the class with message, inner exception, and status code.
+ ///
+ /// The main error message.
+ /// The inner exception that caused this exception.
+ /// The HTTP status code associated with this exception.
public CustomException(
string message,
Exception innerException,
diff --git a/src/BuildingBlocks/Core/Exceptions/ForbiddenException.cs b/src/BuildingBlocks/Core/Exceptions/ForbiddenException.cs
index e1ef0705d9..384cdad248 100644
--- a/src/BuildingBlocks/Core/Exceptions/ForbiddenException.cs
+++ b/src/BuildingBlocks/Core/Exceptions/ForbiddenException.cs
@@ -6,21 +6,38 @@ namespace FSH.Framework.Core.Exceptions;
///
public class ForbiddenException : CustomException
{
+ ///
+ /// Initializes a new instance of the class with default message.
+ ///
public ForbiddenException()
: base("Unauthorized access.", Array.Empty(), HttpStatusCode.Forbidden)
{
}
+ ///
+ /// Initializes a new instance of the class with specified message.
+ ///
+ /// The error message describing the forbidden action.
public ForbiddenException(string message)
: base(message, Array.Empty(), HttpStatusCode.Forbidden)
{
}
+ ///
+ /// Initializes a new instance of the class with message and error details.
+ ///
+ /// The main error message.
+ /// Collection of detailed error messages.
public ForbiddenException(string message, IEnumerable errors)
: base(message, errors.ToList(), HttpStatusCode.Forbidden)
{
}
+ ///
+ /// Initializes a new instance of the class with message and inner exception.
+ ///
+ /// The error message describing the forbidden action.
+ /// The inner exception that caused this exception.
public ForbiddenException(string message, Exception innerException)
: base(message, innerException, HttpStatusCode.Forbidden)
{
diff --git a/src/BuildingBlocks/Core/Exceptions/NotFoundException.cs b/src/BuildingBlocks/Core/Exceptions/NotFoundException.cs
index 78b1532b54..53e3f21b64 100644
--- a/src/BuildingBlocks/Core/Exceptions/NotFoundException.cs
+++ b/src/BuildingBlocks/Core/Exceptions/NotFoundException.cs
@@ -7,21 +7,38 @@ namespace FSH.Framework.Core.Exceptions;
///
public class NotFoundException : CustomException
{
+ ///
+ /// Initializes a new instance of the class with default message.
+ ///
public NotFoundException()
: base("Resource not found.", Array.Empty(), HttpStatusCode.NotFound)
{
}
+ ///
+ /// Initializes a new instance of the class with specified message.
+ ///
+ /// The error message describing what resource was not found.
public NotFoundException(string message)
: base(message, Array.Empty(), HttpStatusCode.NotFound)
{
}
+ ///
+ /// Initializes a new instance of the class with message and error details.
+ ///
+ /// The main error message.
+ /// Collection of detailed error messages.
public NotFoundException(string message, IEnumerable errors)
: base(message, errors.ToList(), HttpStatusCode.NotFound)
{
}
+ ///
+ /// Initializes a new instance of the class with message and inner exception.
+ ///
+ /// The error message describing what resource was not found.
+ /// The inner exception that caused this exception.
public NotFoundException(string message, Exception innerException)
: base(message, innerException, HttpStatusCode.NotFound)
{
diff --git a/src/BuildingBlocks/Core/Exceptions/UnauthorizedException.cs b/src/BuildingBlocks/Core/Exceptions/UnauthorizedException.cs
index a6c745b95d..4ed86490d7 100644
--- a/src/BuildingBlocks/Core/Exceptions/UnauthorizedException.cs
+++ b/src/BuildingBlocks/Core/Exceptions/UnauthorizedException.cs
@@ -6,21 +6,38 @@ namespace FSH.Framework.Core.Exceptions;
///
public class UnauthorizedException : CustomException
{
+ ///
+ /// Initializes a new instance of the class with default message.
+ ///
public UnauthorizedException()
: base("Authentication failed.", Array.Empty(), HttpStatusCode.Unauthorized)
{
}
+ ///
+ /// Initializes a new instance of the class with specified message.
+ ///
+ /// The error message describing the authentication failure.
public UnauthorizedException(string message)
: base(message, Array.Empty(), HttpStatusCode.Unauthorized)
{
}
+ ///
+ /// Initializes a new instance of the class with message and error details.
+ ///
+ /// The main error message.
+ /// Collection of detailed error messages.
public UnauthorizedException(string message, IEnumerable errors)
: base(message, errors.ToList(), HttpStatusCode.Unauthorized)
{
}
+ ///
+ /// Initializes a new instance of the class with message and inner exception.
+ ///
+ /// The error message describing the authentication failure.
+ /// The inner exception that caused this exception.
public UnauthorizedException(string message, Exception innerException)
: base(message, innerException, HttpStatusCode.Unauthorized)
{
diff --git a/src/BuildingBlocks/Persistence/ConnectionStringValidator.cs b/src/BuildingBlocks/Persistence/ConnectionStringValidator.cs
index 54a54e0d95..21686f32f2 100644
--- a/src/BuildingBlocks/Persistence/ConnectionStringValidator.cs
+++ b/src/BuildingBlocks/Persistence/ConnectionStringValidator.cs
@@ -6,6 +6,11 @@
namespace FSH.Framework.Persistence;
+///
+/// Validates database connection strings for supported providers (PostgreSQL, SQL Server).
+///
+/// Database configuration options.
+/// Logger instance for error tracking.
public sealed class ConnectionStringValidator(IOptions dbSettings, ILogger logger) : IConnectionStringValidator
{
private readonly DatabaseOptions _dbSettings = dbSettings.Value;
diff --git a/src/BuildingBlocks/Persistence/Context/BaseDbContext.cs b/src/BuildingBlocks/Persistence/Context/BaseDbContext.cs
index 0bbab352d8..be63ccc24c 100644
--- a/src/BuildingBlocks/Persistence/Context/BaseDbContext.cs
+++ b/src/BuildingBlocks/Persistence/Context/BaseDbContext.cs
@@ -9,6 +9,13 @@
namespace FSH.Framework.Persistence.Context;
+///
+/// Base database context with multi-tenancy and soft delete support.
+///
+/// Accessor for multi-tenant context information.
+/// Database context options.
+/// Database configuration settings.
+/// Host environment information.
public class BaseDbContext(IMultiTenantContextAccessor multiTenantContextAccessor,
DbContextOptions options,
IOptions settings,
@@ -17,12 +24,23 @@ public class BaseDbContext(IMultiTenantContextAccessor multiTenan
{
private readonly DatabaseOptions _settings = settings.Value;
+ ///
+ /// Configures the model by applying global query filters for soft delete functionality.
+ ///
+ /// The model builder used to configure the database schema.
+ /// Thrown when modelBuilder is null.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
ArgumentNullException.ThrowIfNull(modelBuilder);
modelBuilder.AppendGlobalQueryFilter(s => !s.IsDeleted);
base.OnModelCreating(modelBuilder);
}
+
+ ///
+ /// Configures the database connection using tenant-specific connection string if available.
+ ///
+ /// The options builder for configuring the database connection.
+ /// Thrown when optionsBuilder is null.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
ArgumentNullException.ThrowIfNull(optionsBuilder);
@@ -36,6 +54,12 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
environment.IsDevelopment());
}
}
+
+ ///
+ /// Saves all changes made in this context to the database with tenant overwrite mode.
+ ///
+ /// A cancellation token to cancel the save operation.
+ /// The number of state entries written to the database.
public override async Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
TenantNotSetMode = TenantNotSetMode.Overwrite;
diff --git a/src/BuildingBlocks/Persistence/DatabaseOptionsStartupLogger.cs b/src/BuildingBlocks/Persistence/DatabaseOptionsStartupLogger.cs
index 16fba87cc6..40c76db210 100644
--- a/src/BuildingBlocks/Persistence/DatabaseOptionsStartupLogger.cs
+++ b/src/BuildingBlocks/Persistence/DatabaseOptionsStartupLogger.cs
@@ -5,11 +5,19 @@
namespace FSH.Framework.Persistence;
+///
+/// Hosted service that logs database configuration options during application startup.
+///
public sealed class DatabaseOptionsStartupLogger : IHostedService
{
private readonly ILogger _logger;
private readonly IOptions _options;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Logger instance for writing startup information.
+ /// Database configuration options.
public DatabaseOptionsStartupLogger(
ILogger logger,
IOptions options)
@@ -18,6 +26,11 @@ public DatabaseOptionsStartupLogger(
_options = options;
}
+ ///
+ /// Logs database configuration information when the service starts.
+ ///
+ /// Cancellation token to cancel the operation.
+ /// A completed task.
public Task StartAsync(CancellationToken cancellationToken)
{
var options = _options.Value;
@@ -27,6 +40,11 @@ public Task StartAsync(CancellationToken cancellationToken)
return Task.CompletedTask;
}
+ ///
+ /// Performs no operation when the service stops.
+ ///
+ /// Cancellation token to cancel the operation.
+ /// A completed task.
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
diff --git a/src/BuildingBlocks/Persistence/IConnectionStringValidator.cs b/src/BuildingBlocks/Persistence/IConnectionStringValidator.cs
index 65ef8612a1..8d3402a712 100644
--- a/src/BuildingBlocks/Persistence/IConnectionStringValidator.cs
+++ b/src/BuildingBlocks/Persistence/IConnectionStringValidator.cs
@@ -1,6 +1,15 @@
namespace FSH.Framework.Persistence;
+///
+/// Interface for validating database connection strings.
+///
public interface IConnectionStringValidator
{
+ ///
+ /// Validates the specified connection string format and accessibility.
+ ///
+ /// The connection string to validate.
+ /// Optional database provider type for provider-specific validation.
+ /// true if the connection string is valid; otherwise, false.
bool TryValidate(string connectionString, string? dbProvider = null);
}
\ No newline at end of file
diff --git a/src/BuildingBlocks/Persistence/IDbInitializer.cs b/src/BuildingBlocks/Persistence/IDbInitializer.cs
index 5aaca67093..e37f8c8610 100644
--- a/src/BuildingBlocks/Persistence/IDbInitializer.cs
+++ b/src/BuildingBlocks/Persistence/IDbInitializer.cs
@@ -1,7 +1,21 @@
namespace FSH.Framework.Persistence;
+///
+/// Interface for database initialization operations including migrations and seeding.
+///
public interface IDbInitializer
{
+ ///
+ /// Applies pending database migrations.
+ ///
+ /// Cancellation token to cancel the operation.
+ /// A task representing the asynchronous operation.
Task MigrateAsync(CancellationToken cancellationToken);
+
+ ///
+ /// Seeds the database with initial data.
+ ///
+ /// Cancellation token to cancel the operation.
+ /// A task representing the asynchronous operation.
Task SeedAsync(CancellationToken cancellationToken);
}
\ No newline at end of file
diff --git a/src/BuildingBlocks/Persistence/Inteceptors/DomainEventsInterceptor.cs b/src/BuildingBlocks/Persistence/Inteceptors/DomainEventsInterceptor.cs
index 8ffdb4483b..4f951828cf 100644
--- a/src/BuildingBlocks/Persistence/Inteceptors/DomainEventsInterceptor.cs
+++ b/src/BuildingBlocks/Persistence/Inteceptors/DomainEventsInterceptor.cs
@@ -5,17 +5,32 @@
namespace FSH.Framework.Persistence.Inteceptors;
+///
+/// Entity Framework interceptor that automatically publishes domain events after saving changes.
+///
public sealed class DomainEventsInterceptor : SaveChangesInterceptor
{
private readonly IPublisher _publisher;
private readonly ILogger _logger;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The mediator publisher for publishing domain events.
+ /// Logger for tracking domain event publication.
public DomainEventsInterceptor(IPublisher publisher, ILogger logger)
{
_publisher = publisher;
_logger = logger;
}
+ ///
+ /// Called before changes are saved to the database.
+ ///
+ /// Contextual information about the DbContext being saved.
+ /// The result to be returned from SaveChanges.
+ /// Cancellation token to cancel the operation.
+ /// A task representing the asynchronous operation.
public override async ValueTask> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult result,
@@ -24,6 +39,14 @@ public override async ValueTask> SavingChangesAsync(
return await base.SavingChangesAsync(eventData, result, cancellationToken);
}
+ ///
+ /// Called after changes have been saved to the database. Publishes all domain events from tracked entities.
+ ///
+ /// Contextual information about the completed save operation.
+ /// The number of state entries written to the database.
+ /// Cancellation token to cancel the operation.
+ /// The number of state entries written to the database.
+ /// Thrown when eventData is null.
public override async ValueTask SavedChangesAsync(
SaveChangesCompletedEventData eventData,
int result,
diff --git a/src/BuildingBlocks/Persistence/ModelBuilderExtensions.cs b/src/BuildingBlocks/Persistence/ModelBuilderExtensions.cs
index 397db064d8..2a7ea67353 100644
--- a/src/BuildingBlocks/Persistence/ModelBuilderExtensions.cs
+++ b/src/BuildingBlocks/Persistence/ModelBuilderExtensions.cs
@@ -4,8 +4,18 @@
namespace FSH.Framework.Persistence;
+///
+/// Internal extension methods for Entity Framework ModelBuilder configuration.
+///
internal static class ModelBuilderExtensions
{
+ ///
+ /// Applies a global query filter to all entities that implement the specified interface.
+ ///
+ /// The interface type to filter entities by.
+ /// The ModelBuilder instance to configure.
+ /// The filter expression to apply to all matching entities.
+ /// The ModelBuilder for method chaining.
public static ModelBuilder AppendGlobalQueryFilter(this ModelBuilder modelBuilder, Expression> filter)
{
// get a list of entities without a baseType that implement the interface TInterface
diff --git a/src/BuildingBlocks/Persistence/OptionsBuilderExtensions.cs b/src/BuildingBlocks/Persistence/OptionsBuilderExtensions.cs
index 4f4e7daf4a..16caf9a45e 100644
--- a/src/BuildingBlocks/Persistence/OptionsBuilderExtensions.cs
+++ b/src/BuildingBlocks/Persistence/OptionsBuilderExtensions.cs
@@ -4,8 +4,22 @@
namespace FSH.Framework.Persistence;
+///
+/// Extension methods for configuring Entity Framework DbContextOptionsBuilder.
+///
public static class OptionsBuilderExtensions
{
+ ///
+ /// Configures the database provider and connection for the Hero framework.
+ ///
+ /// The DbContextOptionsBuilder to configure.
+ /// The database provider (PostgreSQL, MSSQL).
+ /// The database connection string.
+ /// The assembly containing database migrations.
+ /// Whether the application is running in development mode.
+ /// The configured DbContextOptionsBuilder for method chaining.
+ /// Thrown when builder is null or dbProvider is null/whitespace.
+ /// Thrown when an unsupported database provider is specified.
public static DbContextOptionsBuilder ConfigureHeroDatabase(
this DbContextOptionsBuilder builder,
string dbProvider,
diff --git a/src/BuildingBlocks/Persistence/Pagination/PaginationExtensions.cs b/src/BuildingBlocks/Persistence/Pagination/PaginationExtensions.cs
index 4d888ece17..6e1c92ada8 100644
--- a/src/BuildingBlocks/Persistence/Pagination/PaginationExtensions.cs
+++ b/src/BuildingBlocks/Persistence/Pagination/PaginationExtensions.cs
@@ -4,11 +4,23 @@
namespace FSH.Framework.Persistence;
+///
+/// Extension methods for converting IQueryable results to paginated responses.
+///
public static class PaginationExtensions
{
private const int DefaultPageSize = 20;
private const int MaxPageSize = 100;
+ ///
+ /// Converts an IQueryable to a paged response with the specified pagination parameters.
+ ///
+ /// The type of items in the query.
+ /// The queryable source to paginate.
+ /// The pagination parameters including page number and page size.
+ /// Cancellation token to cancel the operation.
+ /// A paged response containing the requested page of data and pagination metadata.
+ /// Thrown when source or pagination is null.
public static Task> ToPagedResponseAsync(
this IQueryable source,
IPagedQuery pagination,
diff --git a/src/BuildingBlocks/Persistence/PersistenceExtensions.cs b/src/BuildingBlocks/Persistence/PersistenceExtensions.cs
index 06f01a9460..ef3f2b83b3 100644
--- a/src/BuildingBlocks/Persistence/PersistenceExtensions.cs
+++ b/src/BuildingBlocks/Persistence/PersistenceExtensions.cs
@@ -8,8 +8,18 @@
namespace FSH.Framework.Persistence;
+///
+/// Extension methods for configuring persistence services and database contexts.
+///
public static class PersistenceExtensions
{
+ ///
+ /// Adds database configuration options to the service collection with validation.
+ ///
+ /// The service collection to add options to.
+ /// The configuration instance containing database settings.
+ /// The service collection for method chaining.
+ /// Thrown when configuration is null.
public static IServiceCollection AddHeroDatabaseOptions(this IServiceCollection services, IConfiguration configuration)
{
ArgumentNullException.ThrowIfNull(configuration);
@@ -22,6 +32,13 @@ public static IServiceCollection AddHeroDatabaseOptions(this IServiceCollection
return services;
}
+ ///
+ /// Adds a configured Entity Framework DbContext to the service collection.
+ ///
+ /// The type of the DbContext to configure.
+ /// The service collection to add the context to.
+ /// The service collection for method chaining.
+ /// Thrown when services is null.
public static IServiceCollection AddHeroDbContext(this IServiceCollection services)
where TContext : DbContext
{
diff --git a/src/BuildingBlocks/Persistence/Specifications/SpecificationEvaluator.cs b/src/BuildingBlocks/Persistence/Specifications/SpecificationEvaluator.cs
index 511f9b0182..308b9d2aad 100644
--- a/src/BuildingBlocks/Persistence/Specifications/SpecificationEvaluator.cs
+++ b/src/BuildingBlocks/Persistence/Specifications/SpecificationEvaluator.cs
@@ -7,6 +7,14 @@ namespace FSH.Framework.Persistence;
///
internal static class SpecificationEvaluator
{
+ ///
+ /// Evaluates a specification against an input query to produce a configured IQueryable.
+ ///
+ /// The entity type.
+ /// The base queryable to apply the specification to.
+ /// The specification containing query configuration.
+ /// A configured queryable with all specification rules applied.
+ /// Thrown when inputQuery or specification is null.
public static IQueryable GetQuery(
IQueryable inputQuery,
ISpecification specification)
@@ -25,6 +33,15 @@ public static IQueryable GetQuery(
return query;
}
+ ///
+ /// Evaluates a specification with projection against an input query.
+ ///
+ /// The entity type.
+ /// The projected result type.
+ /// The base queryable to apply the specification to.
+ /// The specification containing query configuration and projection.
+ /// A configured queryable with specification rules and projection applied.
+ /// Thrown when inputQuery or specification is null.
public static IQueryable GetQuery(
IQueryable inputQuery,
ISpecification specification)