From 5add866733850c95e47a13350e06f6d669fec34e Mon Sep 17 00:00:00 2001 From: jarvis Date: Sat, 31 Jan 2026 01:58:02 +0000 Subject: [PATCH 1/8] docs: Add XML documentation to Caching module - ICacheService: Document all interface methods - CacheServiceExtensions: Document GetOrSet patterns - Extensions: Document AddHeroCaching DI registration - DistributedCacheService: Document implementation details - HybridCacheService: Document L1/L2 caching strategy Part of #1176 --- .../Caching/CacheServiceExtensions.cs | 29 ++++++++- .../Caching/DistributedCacheService.cs | 36 ++++++++++- src/BuildingBlocks/Caching/Extensions.cs | 19 +++++- .../Caching/HybridCacheService.cs | 53 ++++++++++++++++ src/BuildingBlocks/Caching/ICacheService.cs | 62 ++++++++++++++++++- 5 files changed, 192 insertions(+), 7 deletions(-) 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 +} From 598967993569b0e902178979b29a70e30b5436a3 Mon Sep 17 00:00:00 2001 From: jarvis Date: Sat, 31 Jan 2026 02:01:54 +0000 Subject: [PATCH 2/8] docs: Add XML documentation to Core/Domain module - IEntity, BaseEntity, AggregateRoot: Entity and DDD base types - IDomainEvent, DomainEvent, IHasDomainEvents: Domain event patterns - IAuditableEntity: Audit metadata interface - ISoftDeletable: Soft delete support - IHasTenant: Multi-tenancy marker Generated by Codex CLI with --full-auto flag Part of #1176 --- .../Core/Domain/AggregateRoot.cs | 5 +++++ src/BuildingBlocks/Core/Domain/BaseEntity.cs | 19 ++++++++++++++++- src/BuildingBlocks/Core/Domain/DomainEvent.cs | 15 ++++++++++++- .../Core/Domain/IAuditableEntity.cs | 21 ++++++++++++++++++- .../Core/Domain/IDomainEvent.cs | 21 ++++++++++++++++++- src/BuildingBlocks/Core/Domain/IEntity.cs | 10 ++++++++- .../Core/Domain/IHasDomainEvents.cs | 13 +++++++++++- src/BuildingBlocks/Core/Domain/IHasTenant.cs | 9 +++++++- .../Core/Domain/ISoftDeletable.cs | 15 +++++++++++++ 9 files changed, 121 insertions(+), 7 deletions(-) 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; } } From d50f7b80ef924a84951577f8d4c5b9a4d8350325 Mon Sep 17 00:00:00 2001 From: jarvis Date: Sat, 31 Jan 2026 02:08:14 +0000 Subject: [PATCH 3/8] docs: Add XML documentation to Exceptions module --- .../Core/Exceptions/CustomException.cs | 31 +++++++++++++++++++ .../Core/Exceptions/ForbiddenException.cs | 17 ++++++++++ .../Core/Exceptions/NotFoundException.cs | 17 ++++++++++ .../Core/Exceptions/UnauthorizedException.cs | 17 ++++++++++ 4 files changed, 82 insertions(+) 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) { From 8f387dd56227051862ef383f49a66a0ad7881d70 Mon Sep 17 00:00:00 2001 From: jarvis Date: Sat, 31 Jan 2026 02:09:00 +0000 Subject: [PATCH 4/8] docs: Add XML documentation to Context module --- .../Core/Context/ICurrentUser.cs | 32 +++++++++++++++++++ .../Core/Context/ICurrentUserInitializer.cs | 12 +++++++ 2 files changed, 44 insertions(+) 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 From 5922504cf84ae61c406cf3a251b1c81dc6e8e166 Mon Sep 17 00:00:00 2001 From: jarvis Date: Sat, 31 Jan 2026 02:09:25 +0000 Subject: [PATCH 5/8] docs: Add XML documentation to Common module --- src/BuildingBlocks/Core/Common/QueryStringKeys.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 From 83fd58ccf126375cce49f07f88c8eeb18724236c Mon Sep 17 00:00:00 2001 From: jarvis Date: Sat, 31 Jan 2026 02:09:43 +0000 Subject: [PATCH 6/8] docs: Add XML documentation to Abstractions module --- .../Core/Abstractions/IAppUser.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) 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 From b012bc232b2123d7d85bd86797133e574a38207f Mon Sep 17 00:00:00 2001 From: jarvis Date: Sat, 31 Jan 2026 02:11:47 +0000 Subject: [PATCH 7/8] docs: Add XML documentation to Persistence core module --- .../Persistence/ConnectionStringValidator.cs | 5 ++++ .../Persistence/Context/BaseDbContext.cs | 24 +++++++++++++++++++ .../DatabaseOptionsStartupLogger.cs | 18 ++++++++++++++ .../Persistence/IConnectionStringValidator.cs | 9 +++++++ .../Persistence/IDbInitializer.cs | 14 +++++++++++ .../Persistence/ModelBuilderExtensions.cs | 10 ++++++++ .../Persistence/OptionsBuilderExtensions.cs | 14 +++++++++++ .../Persistence/PersistenceExtensions.cs | 17 +++++++++++++ 8 files changed, 111 insertions(+) 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/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/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 { From 5c83da2b253409aadffccb57cdc2d4325254ee50 Mon Sep 17 00:00:00 2001 From: jarvis Date: Sat, 31 Jan 2026 02:13:37 +0000 Subject: [PATCH 8/8] docs: Add XML documentation to Persistence specifications, pagination, and interceptors --- .../Inteceptors/DomainEventsInterceptor.cs | 23 +++++++++++++++++++ .../Pagination/PaginationExtensions.cs | 12 ++++++++++ .../Specifications/SpecificationEvaluator.cs | 17 ++++++++++++++ 3 files changed, 52 insertions(+) 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/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/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)