From f3988c4a9ff9a2abf693e6f0dae990f6fcd0bc0d Mon Sep 17 00:00:00 2001 From: Kamran Sadin Date: Fri, 6 Jun 2025 05:46:02 +0330 Subject: [PATCH 1/3] Temp with many unconfirmed changes --- .vs/KSFramework/xs/UserPrefs.xml | 20 +-- .../xs/project-cache/KSFramework-Debug.json | 2 +- KSFramework.sln | 15 ++ README.md | 64 ++++++- Samples/MediatorSampleApp/Mediator.csproj | 4 +- Samples/MediatorSampleApp/Program.cs | 8 +- src/KSFramework/Api/ApiResult.cs | 165 ------------------ .../Domain/AggregatesHelper/IAggregateRoot.cs | 5 - src/KSFramework/Domain/BaseEntity.cs | 36 ---- .../Domain/DomainServices/IDomainService.cs | 5 - src/KSFramework/Domain/IRepository.cs | 8 - src/KSFramework/Domain/IUnitOfWork.cs | 7 - .../Exceptions/KSValidationException.cs | 20 ++- .../GenericRepository/GenericRepository.cs | 21 +++ .../GenericRepository/IGenericRepository.cs | 94 ++++++++++ .../GenericRepository/IRepository.cs | 14 +- .../GenericRepository/IUnitOfWork.cs | 23 +++ .../GenericRepository/Repository.cs | 98 +++++++++-- .../GenericRepository/UnitOfWork.cs | 78 +++++++++ src/KSFramework/KSApi/ApiResult.cs | 165 ++++++++++++++++++ .../AggregatesHelper/IAggregateRoot.cs | 5 + .../AggregatesHelper/ValueObject.cs | 6 +- src/KSFramework/KSDomain/BaseEntity.cs | 59 +++++++ .../DomainServices/DomainService.cs | 4 +- .../KSDomain/DomainServices/IDomainService.cs | 5 + src/KSFramework/KSDomain/Entity.cs | 10 ++ .../{Domain => KSDomain}/Enumeration.cs | 6 +- .../{Domain => KSDomain}/IDomainEvent.cs | 4 +- src/KSFramework/KSDomain/IRepository.cs | 8 + .../AndNotSpecification.cs | 4 +- .../SpecificationsHelpers/AndSpecification.cs | 4 +- .../SpecificationsHelpers/AnySpecification.cs | 4 +- .../CompositeSpecification.cs | 2 +- .../ExpressionFuncExtender.cs | 4 +- .../ExpressionSpecification.cs | 4 +- .../ICompositeSpecification.cs | 2 +- .../SpecificationsHelpers/ISpecification.cs | 4 +- .../ISpecificationParser.cs | 2 +- .../NoneSpecification.cs | 4 +- .../SpecificationsHelpers/NotSpecification.cs | 4 +- .../SpecificationsHelpers/OrSpecification.cs | 4 +- .../ParameterRebinder.cs | 4 +- .../SpecificationsHelpers/Specification.cs | 2 +- .../SpecificationExtensions.cs | 4 +- src/KSFramework/KSFramework.csproj | 20 ++- .../KSMessaging/Abstraction/ICommand.cs | 9 + .../Abstraction/ICommandHandler.cs | 13 ++ .../Abstraction/IMediator.cs | 8 +- .../Abstraction/INotification.cs | 2 +- .../Abstraction/INotificationBehavior.cs | 2 +- .../Abstraction/INotificationHandler.cs | 2 +- .../Abstraction/IPipelineBehavior.cs | 2 +- .../Abstraction/IPostProcessor.cs | 2 +- .../KSMessaging/Abstraction/IQuery.cs | 11 ++ .../KSMessaging/Abstraction/IQueryHandler.cs | 13 ++ .../Abstraction/IRequest.cs | 2 +- .../Abstraction/IRequestHandler.cs | 4 +- .../Abstraction/IRequestPreProcessor.cs | 2 +- .../Abstraction/ISender.cs | 2 +- .../Abstraction/IStreamPipelineBehavior.cs | 2 +- .../Abstraction/IStreamRequest.cs | 2 +- .../Abstraction/IStreamRequestHandler.cs | 2 +- .../Abstraction/Unit.cs | 2 +- .../Behaviors/ExceptionHandlingBehavior.cs | 4 +- .../Behaviors/LoggingBehavior.cs | 4 +- .../Behaviors/LoggingPostProcessor.cs | 4 +- .../Behaviors/LoggingPreProcessor.cs | 4 +- .../Behaviors/NotificationLoggingBehavior.cs | 6 +- .../Behaviors/RequestProcessorBehavior.cs | 4 +- .../Behaviors/RequestValidationBehavior.cs | 12 +- .../Behaviors/StreamLoggingBehavior.cs | 7 +- .../Extensions/RegisterMediatorServices.cs | 6 +- .../{Messaging => KSMessaging}/Mediator.cs | 30 ++-- .../NotificationLoggingBehavior.cs | 4 +- .../Samples/CounterStreamTest.cs | 4 +- .../Samples/LoggingNotificationBehavior.cs | 4 +- .../Samples/MultiplyByTwoRequest.cs | 4 +- .../Samples/NotificationTest.cs | 4 +- .../Samples/UserRegisteredHandler.cs | 4 +- .../Samples/UserRegisteredNotification.cs | 4 +- .../ServiceCollectionExtensions.cs | 47 ----- src/KSFramework/Pagination/PaginatedList.cs | 8 +- .../TagHelpers/ActiveRouteTagHelper.cs | 41 ++--- .../Utilities/IdentityExtensions.cs | 8 +- src/KSFramework/Utilities/LinqExtentions.cs | 4 +- .../Utilities/ModelBuilderExtensions.cs | 21 ++- src/KSFramework/Utilities/StringExtensions.cs | 20 ++- .../GenericRepositoryTests.cs | 87 +++++++++ .../TestGenericRepository.cs | 5 + .../IntegrationTestBase.cs | 67 +++++++ .../KSFramework.IntegrationTests.csproj | 28 +++ .../Messaging/MediatorTests.cs | 77 ++++++++ .../Performance/PerformanceTests.cs | 135 ++++++++++++++ .../KSFramework.IntegrationTests/UnitTest1.cs | 10 ++ .../ExceptionHandlingBehaviorTests.cs | 4 +- .../RequestProcessorBehaviorTests.cs | 4 +- .../RequestValidationBehaviorTests.cs | 4 +- .../AddKSMediatorNotificationTests.cs | 4 +- .../Configuration/AddKSMediatorTests.cs | 10 +- .../ExceptionHandlingBehaviorTests.cs | 4 +- .../KSFramework.UnitTests.csproj | 8 +- .../Mediator/Publish/MediatorPublishTests.cs | 6 +- .../Mediator/Publish/SapmleNotification.cs | 2 +- .../Publish/TestNotificationBehavior.cs | 2 +- .../Publish/TestNotificationHandler.cs | 2 +- .../AddKSMediatorRegistrationTests.cs | 4 +- .../RequestValidationBehaviorTests.cs | 4 +- .../CreateStream/MediatorCreateStreamTests.cs | 8 +- 108 files changed, 1324 insertions(+), 522 deletions(-) delete mode 100644 src/KSFramework/Api/ApiResult.cs delete mode 100644 src/KSFramework/Domain/AggregatesHelper/IAggregateRoot.cs delete mode 100644 src/KSFramework/Domain/BaseEntity.cs delete mode 100644 src/KSFramework/Domain/DomainServices/IDomainService.cs delete mode 100644 src/KSFramework/Domain/IRepository.cs delete mode 100644 src/KSFramework/Domain/IUnitOfWork.cs create mode 100644 src/KSFramework/GenericRepository/GenericRepository.cs create mode 100644 src/KSFramework/GenericRepository/IGenericRepository.cs create mode 100644 src/KSFramework/GenericRepository/IUnitOfWork.cs create mode 100644 src/KSFramework/GenericRepository/UnitOfWork.cs create mode 100644 src/KSFramework/KSApi/ApiResult.cs create mode 100644 src/KSFramework/KSDomain/AggregatesHelper/IAggregateRoot.cs rename src/KSFramework/{Domain => KSDomain}/AggregatesHelper/ValueObject.cs (93%) create mode 100644 src/KSFramework/KSDomain/BaseEntity.cs rename src/KSFramework/{Domain => KSDomain}/DomainServices/DomainService.cs (60%) create mode 100644 src/KSFramework/KSDomain/DomainServices/IDomainService.cs create mode 100644 src/KSFramework/KSDomain/Entity.cs rename src/KSFramework/{Domain => KSDomain}/Enumeration.cs (92%) rename src/KSFramework/{Domain => KSDomain}/IDomainEvent.cs (52%) create mode 100644 src/KSFramework/KSDomain/IRepository.cs rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/AndNotSpecification.cs (93%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/AndSpecification.cs (91%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/AnySpecification.cs (86%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/CompositeSpecification.cs (94%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/ExpressionFuncExtender.cs (96%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/ExpressionSpecification.cs (91%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/ICompositeSpecification.cs (90%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/ISpecification.cs (91%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/ISpecificationParser.cs (93%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/NoneSpecification.cs (86%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/NotSpecification.cs (92%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/OrSpecification.cs (91%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/ParameterRebinder.cs (93%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/Specification.cs (95%) rename src/KSFramework/{Domain => KSDomain}/SpecificationsHelpers/SpecificationExtensions.cs (97%) create mode 100644 src/KSFramework/KSMessaging/Abstraction/ICommand.cs create mode 100644 src/KSFramework/KSMessaging/Abstraction/ICommandHandler.cs rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IMediator.cs (97%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/INotification.cs (72%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/INotificationBehavior.cs (91%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/INotificationHandler.cs (91%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IPipelineBehavior.cs (96%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IPostProcessor.cs (84%) create mode 100644 src/KSFramework/KSMessaging/Abstraction/IQuery.cs create mode 100644 src/KSFramework/KSMessaging/Abstraction/IQueryHandler.cs rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IRequest.cs (65%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IRequestHandler.cs (82%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IRequestPreProcessor.cs (82%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/ISender.cs (92%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IStreamPipelineBehavior.cs (96%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IStreamRequest.cs (84%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/IStreamRequestHandler.cs (89%) rename src/KSFramework/{Messaging => KSMessaging}/Abstraction/Unit.cs (62%) rename src/KSFramework/{Messaging => KSMessaging}/Behaviors/ExceptionHandlingBehavior.cs (92%) rename src/KSFramework/{Messaging => KSMessaging}/Behaviors/LoggingBehavior.cs (91%) rename src/KSFramework/{Messaging => KSMessaging}/Behaviors/LoggingPostProcessor.cs (80%) rename src/KSFramework/{Messaging => KSMessaging}/Behaviors/LoggingPreProcessor.cs (77%) rename src/KSFramework/{Messaging => KSMessaging}/Behaviors/NotificationLoggingBehavior.cs (86%) rename src/KSFramework/{Messaging => KSMessaging}/Behaviors/RequestProcessorBehavior.cs (95%) rename src/KSFramework/{Messaging => KSMessaging}/Behaviors/RequestValidationBehavior.cs (80%) rename src/KSFramework/{Messaging => KSMessaging}/Behaviors/StreamLoggingBehavior.cs (87%) rename src/KSFramework/{Messaging => KSMessaging}/Extensions/RegisterMediatorServices.cs (94%) rename src/KSFramework/{Messaging => KSMessaging}/Mediator.cs (88%) rename src/KSFramework/{Messaging => KSMessaging}/NotificationLoggingBehavior.cs (90%) rename src/KSFramework/{Messaging => KSMessaging}/Samples/CounterStreamTest.cs (87%) rename src/KSFramework/{Messaging => KSMessaging}/Samples/LoggingNotificationBehavior.cs (85%) rename src/KSFramework/{Messaging => KSMessaging}/Samples/MultiplyByTwoRequest.cs (81%) rename src/KSFramework/{Messaging => KSMessaging}/Samples/NotificationTest.cs (82%) rename src/KSFramework/{Messaging => KSMessaging}/Samples/UserRegisteredHandler.cs (88%) rename src/KSFramework/{Messaging => KSMessaging}/Samples/UserRegisteredNotification.cs (76%) delete mode 100644 src/KSFramework/Messaging/Configuration/ServiceCollectionExtensions.cs create mode 100644 tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/GenericRepositoryTests.cs create mode 100644 tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/TestGenericRepository.cs create mode 100644 tests/KSFramework/KSFramework.IntegrationTests/IntegrationTestBase.cs create mode 100644 tests/KSFramework/KSFramework.IntegrationTests/KSFramework.IntegrationTests.csproj create mode 100644 tests/KSFramework/KSFramework.IntegrationTests/Messaging/MediatorTests.cs create mode 100644 tests/KSFramework/KSFramework.IntegrationTests/Performance/PerformanceTests.cs create mode 100644 tests/KSFramework/KSFramework.IntegrationTests/UnitTest1.cs diff --git a/.vs/KSFramework/xs/UserPrefs.xml b/.vs/KSFramework/xs/UserPrefs.xml index 140fba5..a5ec89e 100644 --- a/.vs/KSFramework/xs/UserPrefs.xml +++ b/.vs/KSFramework/xs/UserPrefs.xml @@ -1,29 +1,17 @@  - - - - + + - - - - - - - - - - + - + - \ No newline at end of file diff --git a/.vs/KSFramework/xs/project-cache/KSFramework-Debug.json b/.vs/KSFramework/xs/project-cache/KSFramework-Debug.json index 53cd802..2e19ab1 100644 --- a/.vs/KSFramework/xs/project-cache/KSFramework-Debug.json +++ b/.vs/KSFramework/xs/project-cache/KSFramework-Debug.json @@ -1 +1 @@ -{"Format":1,"ProjectReferences":[],"MetadataReferences":[{"FilePath":"/Users/sadin/.nuget/packages/azure.core/1.38.0/lib/net6.0/Azure.Core.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/azure.identity/1.11.4/lib/netstandard2.0/Azure.Identity.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/fluentvalidation/12.0.0/lib/net8.0/FluentValidation.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/mediatr.contracts/2.0.1/lib/netstandard2.0/MediatR.Contracts.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/mediatr/12.5.0/lib/net6.0/MediatR.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.antiforgery/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Antiforgery.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.authentication.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.authentication.core/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Authentication.Core.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.authorization/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Authorization.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.authorization.policy/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Authorization.Policy.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.cors/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Cors.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.cryptography.internal/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Cryptography.Internal.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.dataprotection.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.DataProtection.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.dataprotection/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.DataProtection.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.diagnostics.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Diagnostics.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.hosting.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.hosting.server.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.html.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Html.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.http.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.http/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Http.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.http.extensions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.http.features/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.jsonpatch/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.JsonPatch.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.localization/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Localization.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.apiexplorer/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.ApiExplorer.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.core/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.Core.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.cors/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.Cors.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.dataannotations/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.DataAnnotations.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.formatters.json/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.Formatters.Json.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.localization/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.Localization.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.razor/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.razor.extensions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.Razor.Extensions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.razorpages/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.RazorPages.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.taghelpers/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.TagHelpers.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.mvc.viewfeatures/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Mvc.ViewFeatures.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.razor/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Razor.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.razor.language/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Razor.Language.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.razor.runtime/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Razor.Runtime.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.responsecaching.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.ResponseCaching.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.routing.abstractions/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Routing.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.routing/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.Routing.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.aspnetcore.webutilities/2.3.0/lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.bcl.asyncinterfaces/1.1.1/ref/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.codeanalysis.csharp/2.8.2/lib/netstandard1.3/Microsoft.CodeAnalysis.CSharp.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.codeanalysis.common/2.8.2/lib/netstandard1.3/Microsoft.CodeAnalysis.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.codeanalysis.razor/2.3.0/lib/netstandard2.0/Microsoft.CodeAnalysis.Razor.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.data.sqlclient/5.1.6/ref/net6.0/Microsoft.Data.SqlClient.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.dotnet.platformabstractions/2.1.0/lib/netstandard1.3/Microsoft.DotNet.PlatformAbstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.entityframeworkcore.abstractions/9.0.5/lib/net8.0/Microsoft.EntityFrameworkCore.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.entityframeworkcore/9.0.5/lib/net8.0/Microsoft.EntityFrameworkCore.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.entityframeworkcore.relational.design/1.1.6/lib/netstandard1.3/Microsoft.EntityFrameworkCore.Relational.Design.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.entityframeworkcore.relational/9.0.5/lib/net8.0/Microsoft.EntityFrameworkCore.Relational.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.entityframeworkcore.sqlserver.design/1.1.6/lib/netstandard1.3/Microsoft.EntityFrameworkCore.SqlServer.Design.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.entityframeworkcore.sqlserver/9.0.5/lib/net8.0/Microsoft.EntityFrameworkCore.SqlServer.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.caching.abstractions/9.0.5/lib/net9.0/Microsoft.Extensions.Caching.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.caching.memory/9.0.5/lib/net9.0/Microsoft.Extensions.Caching.Memory.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.configuration.abstractions/9.0.5/lib/net9.0/Microsoft.Extensions.Configuration.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.configuration.binder/9.0.5/lib/net9.0/Microsoft.Extensions.Configuration.Binder.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.configuration.commandline/9.0.5/lib/net9.0/Microsoft.Extensions.Configuration.CommandLine.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.configuration/9.0.5/lib/net9.0/Microsoft.Extensions.Configuration.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.configuration.environmentvariables/9.0.5/lib/net9.0/Microsoft.Extensions.Configuration.EnvironmentVariables.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.configuration.fileextensions/9.0.5/lib/net9.0/Microsoft.Extensions.Configuration.FileExtensions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.configuration.json/9.0.5/lib/net9.0/Microsoft.Extensions.Configuration.Json.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.configuration.usersecrets/9.0.5/lib/net9.0/Microsoft.Extensions.Configuration.UserSecrets.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.dependencyinjection.abstractions/9.0.5/lib/net9.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.dependencyinjection/9.0.5/lib/net9.0/Microsoft.Extensions.DependencyInjection.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.dependencymodel/2.1.0/lib/netstandard1.6/Microsoft.Extensions.DependencyModel.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.diagnostics.abstractions/9.0.5/lib/net9.0/Microsoft.Extensions.Diagnostics.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.diagnostics/9.0.5/lib/net9.0/Microsoft.Extensions.Diagnostics.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.fileproviders.abstractions/9.0.5/lib/net9.0/Microsoft.Extensions.FileProviders.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.fileproviders.composite/8.0.0/lib/net8.0/Microsoft.Extensions.FileProviders.Composite.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.fileproviders.physical/9.0.5/lib/net9.0/Microsoft.Extensions.FileProviders.Physical.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.filesystemglobbing/9.0.5/lib/net9.0/Microsoft.Extensions.FileSystemGlobbing.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.hosting.abstractions/9.0.5/lib/net9.0/Microsoft.Extensions.Hosting.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.hosting/9.0.5/lib/net9.0/Microsoft.Extensions.Hosting.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.localization.abstractions/8.0.11/lib/net8.0/Microsoft.Extensions.Localization.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.localization/8.0.11/lib/net8.0/Microsoft.Extensions.Localization.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.logging.abstractions/9.0.5/lib/net9.0/Microsoft.Extensions.Logging.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.logging.configuration/9.0.5/lib/net9.0/Microsoft.Extensions.Logging.Configuration.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.logging.console/9.0.5/lib/net9.0/Microsoft.Extensions.Logging.Console.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.logging.debug/9.0.5/lib/net9.0/Microsoft.Extensions.Logging.Debug.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.logging/9.0.5/lib/net9.0/Microsoft.Extensions.Logging.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.logging.eventlog/9.0.5/lib/net9.0/Microsoft.Extensions.Logging.EventLog.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.logging.eventsource/9.0.5/lib/net9.0/Microsoft.Extensions.Logging.EventSource.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.objectpool/8.0.11/lib/net8.0/Microsoft.Extensions.ObjectPool.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.options.configurationextensions/9.0.5/lib/net9.0/Microsoft.Extensions.Options.ConfigurationExtensions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.options/9.0.5/lib/net9.0/Microsoft.Extensions.Options.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.primitives/9.0.5/lib/net9.0/Microsoft.Extensions.Primitives.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.extensions.webencoders/8.0.11/lib/net8.0/Microsoft.Extensions.WebEncoders.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.identity.client/4.61.3/lib/net6.0/Microsoft.Identity.Client.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.identity.client.extensions.msal/4.61.3/lib/net6.0/Microsoft.Identity.Client.Extensions.Msal.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.identitymodel.abstractions/6.35.0/lib/net6.0/Microsoft.IdentityModel.Abstractions.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.identitymodel.jsonwebtokens/6.35.0/lib/net6.0/Microsoft.IdentityModel.JsonWebTokens.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.identitymodel.logging/6.35.0/lib/net6.0/Microsoft.IdentityModel.Logging.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.identitymodel.protocols/6.35.0/lib/net6.0/Microsoft.IdentityModel.Protocols.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.identitymodel.protocols.openidconnect/6.35.0/lib/net6.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.identitymodel.tokens/6.35.0/lib/net6.0/Microsoft.IdentityModel.Tokens.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.net.http.headers/2.3.0/lib/netstandard2.0/Microsoft.Net.Http.Headers.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/microsoft.sqlserver.server/1.0.0/lib/netstandard2.0/Microsoft.SqlServer.Server.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/newtonsoft.json.bson/1.0.2/lib/netstandard2.0/Newtonsoft.Json.Bson.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/newtonsoft.json/13.0.3/lib/net6.0/Newtonsoft.Json.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/pluralize.net/1.0.2/lib/netstandard2.0/Pluralize.NET.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/system.clientmodel/1.0.0/lib/net6.0/System.ClientModel.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/system.componentmodel.annotations/5.0.0/ref/netstandard2.1/System.ComponentModel.Annotations.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/system.diagnostics.eventlog/9.0.5/lib/net9.0/System.Diagnostics.EventLog.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/system.identitymodel.tokens.jwt/6.35.0/lib/net6.0/System.IdentityModel.Tokens.Jwt.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/system.memory.data/1.0.2/lib/netstandard2.0/System.Memory.Data.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/system.security.cryptography.pkcs/8.0.1/lib/net8.0/System.Security.Cryptography.Pkcs.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/system.security.cryptography.protecteddata/6.0.0/lib/net6.0/System.Security.Cryptography.ProtectedData.dll","Aliases":[],"Framework":null},{"FilePath":"/Users/sadin/.nuget/packages/system.security.cryptography.xml/8.0.2/lib/net8.0/System.Security.Cryptography.Xml.dll","Aliases":[],"Framework":null}],"Files":["/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Api/ApiResult.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/AggregatesHelper/IAggregateRoot.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/AggregatesHelper/ValueObject.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/BaseEntity.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/DomainServices/DomainService.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/DomainServices/IDomainService.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/Enumeration.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/IDomainEvent.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/IRepository.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/IUnitOfWork.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/AndNotSpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/AndSpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/AnySpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/CompositeSpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/ExpressionFuncExtender.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/ExpressionSpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/ICompositeSpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/ISpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/ISpecificationParser.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/NoneSpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/NotSpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/OrSpecification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/ParameterRebinder.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/Specification.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Domain/SpecificationsHelpers/SpecificationExtensions.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Enums/ApiResultStatusCode.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Exceptions/KSArgumentNullException.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Exceptions/KSBadRequestException.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Exceptions/KSException.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Exceptions/KSNotFoundException.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Exceptions/KSServerErrorException.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Exceptions/KSUnauthorizedAccessException.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Exceptions/KSValidationException.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/GenericRepository/IRepository.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/GenericRepository/Repository.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Pagination/Paginated.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Pagination/PaginatedList.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Pagination/QueryableExtension.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/BaseResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/FailedResponses/BadRequestResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/FailedResponses/FailedResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/FailedResponses/ForbiddenAccessResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/FailedResponses/FormValidationResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/FailedResponses/NotFoundResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/FailedResponses/ServerErrorResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/FailedResponses/UnauthorizedAccessResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/SuccessResponses/AcceptedResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/SuccessResponses/CreatedResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/SuccessResponses/NoContentResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/SuccessResponses/NonAuthoritativeInformationResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/SuccessResponses/OkResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/SuccessResponses/PartialContentResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Responses/SuccessResponses/ResetContentResponse.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/ResutlMessage.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/TagHelpers/ActiveRouteTagHelper.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/TagHelpers/CheckMarkTagHelper.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/Assert.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/DateTimeUtilityExtensions.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/EnumExcentions.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/IdentityExtensions.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/LinqExtentions.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/ModelBuilderExtensions.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/RandomGenerator.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/SecurityHelper.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/SnakeCasePropertyNamingPolicy.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/StringExtensions.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/Utilities/ValidationExtensions.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net10.0/KSFramework.GlobalUsings.g.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net10.0/KSFramework.AssemblyInfo.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net10.0/KSFramework.AssemblyInfo.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net10.0/KSFramework.AssemblyInfo.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net10.0/KSFramework.AssemblyInfo.cs"],"BuildActions":["Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile","Compile"],"Analyzers":["/usr/local/share/dotnet/sdk/7.0.304/Sdks/Microsoft.NET.Sdk/analyzers/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll","/usr/local/share/dotnet/sdk/7.0.304/Sdks/Microsoft.NET.Sdk/analyzers/Microsoft.CodeAnalysis.NetAnalyzers.dll","/Users/sadin/.nuget/packages/microsoft.codeanalysis.analyzers/1.1.0/analyzers/dotnet/cs/Microsoft.CodeAnalysis.Analyzers.dll","/Users/sadin/.nuget/packages/microsoft.codeanalysis.analyzers/1.1.0/analyzers/dotnet/cs/Microsoft.CodeAnalysis.CSharp.Analyzers.dll","/Users/sadin/.nuget/packages/microsoft.entityframeworkcore.analyzers/9.0.5/analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll","/Users/sadin/.nuget/packages/microsoft.extensions.logging.abstractions/9.0.5/analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Logging.Generators.dll","/Users/sadin/.nuget/packages/microsoft.extensions.options/9.0.5/analyzers/dotnet/roslyn4.4/cs/Microsoft.Extensions.Options.SourceGeneration.dll"],"AdditionalFiles":[],"EditorConfigFiles":["/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net10.0/KSFramework.GeneratedMSBuildEditorConfig.editorconfig"],"DefineConstants":["TRACE","DEBUG","NET","NET10_0","NETCOREAPP","NET5_0_OR_GREATER","NET6_0_OR_GREATER","NET7_0_OR_GREATER","NETCOREAPP1_0_OR_GREATER","NETCOREAPP1_1_OR_GREATER","NETCOREAPP2_0_OR_GREATER","NETCOREAPP2_1_OR_GREATER","NETCOREAPP2_2_OR_GREATER","NETCOREAPP3_0_OR_GREATER","NETCOREAPP3_1_OR_GREATER"],"IntermediateAssembly":"/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net10.0/KSFramework.dll"} \ No newline at end of file +{"Format":1,"ProjectReferences":[],"MetadataReferences":[],"Files":["/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net8.0/KSFramework.AssemblyInfo.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net8.0/KSFramework.AssemblyInfo.cs","/Users/sadin/Projects/Personal Projects/NugetPackages/KSFramework/src/KSFramework/obj/Debug/net8.0/KSFramework.AssemblyInfo.cs"],"BuildActions":["Compile","Compile","Compile"],"Analyzers":[],"AdditionalFiles":[],"EditorConfigFiles":[],"DefineConstants":[],"IntermediateAssembly":""} \ No newline at end of file diff --git a/KSFramework.sln b/KSFramework.sln index 4a7b249..a31a944 100644 --- a/KSFramework.sln +++ b/KSFramework.sln @@ -19,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "KSFramework", "KSFramework" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KSFramework.UnitTests", "tests\KSFramework\KSFramework.UnitTests\KSFramework.UnitTests.csproj", "{48D1B7B2-D99B-4554-BBFB-95E75720B8E5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KSFramework.IntegrationTests", "tests\KSFramework\KSFramework.IntegrationTests\KSFramework.IntegrationTests.csproj", "{C990672B-5ECD-4123-AF33-1F5CE23318E9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,6 +67,18 @@ Global {48D1B7B2-D99B-4554-BBFB-95E75720B8E5}.Release|x64.Build.0 = Release|Any CPU {48D1B7B2-D99B-4554-BBFB-95E75720B8E5}.Release|x86.ActiveCfg = Release|Any CPU {48D1B7B2-D99B-4554-BBFB-95E75720B8E5}.Release|x86.Build.0 = Release|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Debug|x64.ActiveCfg = Debug|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Debug|x64.Build.0 = Debug|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Debug|x86.ActiveCfg = Debug|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Debug|x86.Build.0 = Debug|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Release|Any CPU.Build.0 = Release|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Release|x64.ActiveCfg = Release|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Release|x64.Build.0 = Release|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Release|x86.ActiveCfg = Release|Any CPU + {C990672B-5ECD-4123-AF33-1F5CE23318E9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -75,5 +89,6 @@ Global {D849CD75-73AE-4F1A-80EA-0FC596C80723} = {B36C9166-6687-1700-0084-D88BD073518A} {6F746C7E-99E8-D040-B792-CDA2514E83CE} = {0AB3BF05-4346-4AA6-1389-037BE0695223} {48D1B7B2-D99B-4554-BBFB-95E75720B8E5} = {6F746C7E-99E8-D040-B792-CDA2514E83CE} + {C990672B-5ECD-4123-AF33-1F5CE23318E9} = {6F746C7E-99E8-D040-B792-CDA2514E83CE} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index a47e7cd..5da0538 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ - ✅ Scrutor-based automatic registration - ✅ File-scoped namespaces and XML documentation for every component - ✅ Full unit test coverage using xUnit and Moq +- ✅ Swagger/OpenAPI documentation support +- ✅ Comprehensive XML documentation --- @@ -24,19 +26,71 @@ Add the package reference (once published): ```bash -dotnet add package KSFramework.Messaging -dotnet add package KSFramework.Data +dotnet add package KSFramework.KSMessaging +dotnet add package KSFramework.KSData ``` Or reference the source projects directly in your solution. + +## 📚 API Documentation + +### Swagger/OpenAPI Setup + +1. Add Swagger configuration in your `Program.cs`: +```csharp +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "Your API Name", + Version = "v1", + Description = "API Documentation" + }); + + // Include XML comments + var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + c.IncludeXmlComments(xmlPath); +}); +``` + +2. Enable Swagger UI in your application: +```csharp +app.UseSwagger(); +app.UseSwaggerUI(c => +{ + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Your API V1"); +}); +``` + +### XML Documentation + +The framework is configured to generate XML documentation. Add XML comments to your classes and methods: + +```csharp +/// +/// Represents a generic repository for entity operations. +/// +/// The type of the entity. +public interface IGenericRepository where TEntity : class +{ + /// + /// Retrieves an entity by its identifier. + /// + /// The identifier of the entity. + /// The entity if found; otherwise, null. + Task GetByIdAsync(object id); +} +``` ⸻ 🧠 Project Structure ``` src/ -KSFramework.Messaging/ → Custom mediator, behaviors, contracts -KSFramework.Data/ → Repository, UnitOfWork, Specification -KSFramework.SharedKernel/ → Domain base types, entities, value objects +KSFramework.KSMessaging/ → Custom mediator, behaviors, contracts +KSFramework.KSData/ → Repository, UnitOfWork, Specification +KSFramework.KSSharedKernel/ → Domain base types, entities, value objects tests/ KSFramework.UnitTests/ → xUnit unit tests diff --git a/Samples/MediatorSampleApp/Mediator.csproj b/Samples/MediatorSampleApp/Mediator.csproj index 6a2e3de..7c622c1 100644 --- a/Samples/MediatorSampleApp/Mediator.csproj +++ b/Samples/MediatorSampleApp/Mediator.csproj @@ -1,8 +1,8 @@ - + Exe - net10.0 + net8.0 enable enable diff --git a/Samples/MediatorSampleApp/Program.cs b/Samples/MediatorSampleApp/Program.cs index 8d73d6d..6e5f80d 100644 --- a/Samples/MediatorSampleApp/Program.cs +++ b/Samples/MediatorSampleApp/Program.cs @@ -1,8 +1,8 @@ -using System.Reflection; +using System.Reflection; using FluentValidation; -using KSFramework.Messaging.Abstraction; -using KSFramework.Messaging.Extensions; -using KSFramework.Messaging.Samples; +using KSFramework.KSMessaging.Abstraction; +using KSFramework.KSMessaging.Extensions; +using KSFramework.KSMessaging.Samples; using Microsoft.Extensions.DependencyInjection; var services = new ServiceCollection(); diff --git a/src/KSFramework/Api/ApiResult.cs b/src/KSFramework/Api/ApiResult.cs deleted file mode 100644 index 2f6a3a7..0000000 --- a/src/KSFramework/Api/ApiResult.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Net; -using KSFramework.Utilities; -using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; - -namespace KSFramework.Api; - -public class ApiResult - { - public bool Status { get; set; } - public HttpStatusCode StatusCode { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public string Message { get; set; } - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public List? Errors { get; set; } - - public ApiResult(bool isSucceeded, HttpStatusCode statusCode, List? errors = null, string? message = null) - { - Status = isSucceeded; - StatusCode = statusCode; - Message = message ?? statusCode.ToDisplay(); - Errors = errors; - } - #region Implicit Operators - public static implicit operator ApiResult(OkResult result) - { - return new ApiResult(true, HttpStatusCode.OK, null); - } - - public static implicit operator ApiResult(BadRequestResult result) - { - return new ApiResult(false, HttpStatusCode.BadRequest, null); - } - - public static implicit operator ApiResult(BadRequestObjectResult result) - { - var errorsList = new List(); - if (result.Value is SerializableError errors) - { - errorsList = errors.SelectMany(p => (string[])p.Value).Distinct().ToList(); - } - else - { - errorsList.Add(result.Value.ToString()); - } - return new ApiResult(false, HttpStatusCode.BadRequest, errorsList); - } - - public static implicit operator ApiResult(ContentResult result) - { - return new ApiResult(true, HttpStatusCode.OK, null, result.Content); - } - - public static implicit operator ApiResult(NotFoundResult result) - { - return new ApiResult(false, HttpStatusCode.NotFound, null); - } - - public static implicit operator ApiResult(UnauthorizedResult result) - { - return new ApiResult(false, HttpStatusCode.Unauthorized, null); - } - - public static implicit operator ApiResult(UnauthorizedObjectResult result) - { - var errorsList = new List(); - if (result.Value is SerializableError errors) - { - errorsList = errors.SelectMany(p => (string[])p.Value).Distinct().ToList(); - } - else - { - errorsList.Add(result.Value.ToString()); - } - return new ApiResult(false, HttpStatusCode.Unauthorized, errorsList, null); - } - #endregion - } - - - public class ApiResult : ApiResult - where TData : class - { - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public TData Data { get; set; } - - public ApiResult(bool isSucceeded, HttpStatusCode statusCode, TData data, List? errors = null, string? message = null) - : base(isSucceeded, statusCode, errors, message) - { - Data = data; - } - - #region Implicit Operators - public static implicit operator ApiResult(TData data) - { - return new ApiResult(true, HttpStatusCode.OK, data, null); - } - - public static implicit operator ApiResult(OkResult result) - { - return new ApiResult(true, HttpStatusCode.OK, null, null); - } - - public static implicit operator ApiResult(OkObjectResult result) - { - return new ApiResult(true, HttpStatusCode.OK, (TData)result.Value, null); - } - - public static implicit operator ApiResult(BadRequestResult result) - { - return new ApiResult(false, HttpStatusCode.BadRequest, null, null); - } - - public static implicit operator ApiResult(BadRequestObjectResult result) - { - var errorsList = new List(); - if (result.Value is SerializableError errors) - { - errorsList = errors.SelectMany(p => (string[])p.Value).Distinct().ToList(); - } - else - { - errorsList.Add(result.Value.ToString()); - } - return new ApiResult(false, HttpStatusCode.BadRequest, null, errorsList); - } - - public static implicit operator ApiResult(ContentResult result) - { - return new ApiResult(true, HttpStatusCode.OK, null, null, result.Content); - } - - public static implicit operator ApiResult(NotFoundResult result) - { - return new ApiResult(false, HttpStatusCode.NotFound, null, null); - } - - public static implicit operator ApiResult(NotFoundObjectResult result) - { - var errors = new List(); - errors.Add(result.Value.ToString()); - return new ApiResult(false, HttpStatusCode.NotFound, (TData)result.Value, errors); - } - - - public static implicit operator ApiResult(UnauthorizedResult result) - { - return new ApiResult(false, HttpStatusCode.Unauthorized, null, null); - } - - public static implicit operator ApiResult(UnauthorizedObjectResult result) - { - var errorsList = new List(); - if (result.Value is SerializableError errors) - { - errorsList = errors.SelectMany(p => (string[])p.Value).Distinct().ToList(); - } - else - { - errorsList.Add(result.Value.ToString()); - } - return new ApiResult(false, HttpStatusCode.Unauthorized, null, errorsList); - } - #endregion - } \ No newline at end of file diff --git a/src/KSFramework/Domain/AggregatesHelper/IAggregateRoot.cs b/src/KSFramework/Domain/AggregatesHelper/IAggregateRoot.cs deleted file mode 100644 index 9d7e7f4..0000000 --- a/src/KSFramework/Domain/AggregatesHelper/IAggregateRoot.cs +++ /dev/null @@ -1,5 +0,0 @@ - -namespace KSFramework.Domain.AggregatesHelper; -public interface IAggregateRoot -{ -} \ No newline at end of file diff --git a/src/KSFramework/Domain/BaseEntity.cs b/src/KSFramework/Domain/BaseEntity.cs deleted file mode 100644 index 643280a..0000000 --- a/src/KSFramework/Domain/BaseEntity.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -namespace KSFramework.Domain; -public interface IEntity -{ - -} - -public abstract class BaseEntity : BaseEntity -{ - public new Guid Id { get; set; } = Guid.NewGuid(); -} - -public abstract class BaseEntity : IEntity -{ - public TKey Id { get; set; } - public int Version { get; private set; } = 0; - - private List _domainEvents; - public IReadOnlyCollection DomainEvents => _domainEvents.AsReadOnly(); - - protected void AddDomainEvent(IDomainEvent domainEvent) - { - _domainEvents ??= new List(); - _domainEvents.Add(domainEvent); - } - - protected void IncreaseVersion() - { - Version++; - } - - public void ClearDomainEvents() - { - _domainEvents?.Clear(); - } -} \ No newline at end of file diff --git a/src/KSFramework/Domain/DomainServices/IDomainService.cs b/src/KSFramework/Domain/DomainServices/IDomainService.cs deleted file mode 100644 index ffd5dc1..0000000 --- a/src/KSFramework/Domain/DomainServices/IDomainService.cs +++ /dev/null @@ -1,5 +0,0 @@ -using System; -namespace KSFramework.Domain.DomainServices; -public interface IDomainService -{ -} \ No newline at end of file diff --git a/src/KSFramework/Domain/IRepository.cs b/src/KSFramework/Domain/IRepository.cs deleted file mode 100644 index c107449..0000000 --- a/src/KSFramework/Domain/IRepository.cs +++ /dev/null @@ -1,8 +0,0 @@ -using KSFramework.Domain.AggregatesHelper; - -namespace KSFramework.Domain; - -public interface IRepository where T : IAggregateRoot -{ -} - diff --git a/src/KSFramework/Domain/IUnitOfWork.cs b/src/KSFramework/Domain/IUnitOfWork.cs deleted file mode 100644 index 54aadde..0000000 --- a/src/KSFramework/Domain/IUnitOfWork.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace KSFramework.Domain; - -public interface IUnitOfWork : IDisposable -{ - Task SaveChangesAsync(CancellationToken cancellationToken = default); -} - diff --git a/src/KSFramework/Exceptions/KSValidationException.cs b/src/KSFramework/Exceptions/KSValidationException.cs index 9849343..0067b77 100644 --- a/src/KSFramework/Exceptions/KSValidationException.cs +++ b/src/KSFramework/Exceptions/KSValidationException.cs @@ -4,34 +4,38 @@ namespace KSFramework.Exceptions; public class KSValidationException : KSException { - public IEnumerable Errors; + private IEnumerable? _validationErrors; + public new IEnumerable? Errors + { + get => _validationErrors; + set => _validationErrors = value; + } public KSValidationException() : base(code: 400) { - + } public KSValidationException(string message) : base(code: 400, message) { - + } public KSValidationException(string message, T? errors) : base(code: 400, errors: errors, message) { - + } } -public class KSValidationException: KSException +public class KSValidationException : KSException> { - public IEnumerable Errors; public KSValidationException() : base(code: 400) { - + } public KSValidationException(string message) : base(code: 400, message) { - + } } \ No newline at end of file diff --git a/src/KSFramework/GenericRepository/GenericRepository.cs b/src/KSFramework/GenericRepository/GenericRepository.cs new file mode 100644 index 0000000..ff2b392 --- /dev/null +++ b/src/KSFramework/GenericRepository/GenericRepository.cs @@ -0,0 +1,21 @@ +using System.Linq.Expressions; +using KSFramework.KSDomain.AggregatesHelper; +using KSFramework.Pagination; +using Microsoft.EntityFrameworkCore; + +namespace KSFramework.GenericRepository; + +/// +/// Generic repository implementation for entity operations. +/// +/// The type of the entity. +public class GenericRepository : Repository, IGenericRepository where TEntity : class +{ + /// + /// Initializes a new instance of the GenericRepository class. + /// + /// The database context. + public GenericRepository(DbContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/src/KSFramework/GenericRepository/IGenericRepository.cs b/src/KSFramework/GenericRepository/IGenericRepository.cs new file mode 100644 index 0000000..902c8f2 --- /dev/null +++ b/src/KSFramework/GenericRepository/IGenericRepository.cs @@ -0,0 +1,94 @@ +using System.Linq.Expressions; +using KSFramework.KSDomain.AggregatesHelper; +using KSFramework.Pagination; + +namespace KSFramework.GenericRepository; + +/// +/// Represents a generic repository interface for performing CRUD operations on entities. +/// +/// The type of the entity. +public interface IGenericRepository where TEntity : class +{ + /// + /// Retrieves an entity by its identifier asynchronously. + /// + /// The identifier of the entity. + /// A task that represents the asynchronous operation. The task result contains the entity if found. + ValueTask GetByIdAsync(object id); + + /// + /// Retrieves all entities asynchronously. + /// + /// A task that represents the asynchronous operation. The task result contains the collection of entities. + Task> GetAllAsync(); + + /// + /// Retrieves a paginated list of entities asynchronously based on specified criteria. + /// + /// The index of the page to retrieve (1-based). + /// The size of the page. + /// Optional predicate to filter the entities. + /// Optional property name to order the results by. + /// Optional flag to indicate descending order. + /// A task that represents the asynchronous operation. The task result contains the paginated list of entities. + Task> GetPagedAsync(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false); + + /// + /// Retrieves a paginated list of entities synchronously based on specified criteria. + /// + /// The index of the page to retrieve (1-based). + /// The size of the page. + /// Optional predicate to filter the entities. + /// Optional property name to order the results by. + /// Optional flag to indicate descending order. + /// A paginated list of entities. + PaginatedList GetPaged(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false); + + /// + /// Finds entities that match the specified predicate. + /// + /// The condition to filter entities. + /// A collection of entities that match the condition. + IEnumerable Find(Expression> predicate); + + /// + /// Retrieves a single entity that matches the specified predicate, or null if no match is found. + /// + /// The condition to filter the entity. + /// A task that represents the asynchronous operation. The task result contains the entity if found, otherwise null. + Task SingleOrDefaultAsync(Expression> predicate); + + /// + /// Adds a new entity to the repository asynchronously. + /// + /// The entity to add. + /// A task that represents the asynchronous operation. + Task AddAsync(TEntity entity); + + /// + /// Adds multiple entities to the repository asynchronously. + /// + /// The collection of entities to add. + /// A task that represents the asynchronous operation. + Task AddRangeAsync(IEnumerable entities); + + /// + /// Removes an entity from the repository. + /// + /// The entity to remove. + void Remove(TEntity entity); + + /// + /// Removes multiple entities from the repository. + /// + /// The collection of entities to remove. + void RemoveRange(IEnumerable entities); + + /// + /// Checks if any entity exists that matches the specified predicate. + /// + /// The condition to check for existence. + /// A task that represents the asynchronous operation. The task result contains true if a matching entity exists; otherwise, false. + Task IsExistValueForPropertyAsync(Expression> predicate); +} diff --git a/src/KSFramework/GenericRepository/IRepository.cs b/src/KSFramework/GenericRepository/IRepository.cs index b9db045..ef2950f 100644 --- a/src/KSFramework/GenericRepository/IRepository.cs +++ b/src/KSFramework/GenericRepository/IRepository.cs @@ -1,21 +1,21 @@ using System.Linq.Expressions; -using KSFramework.Domain.AggregatesHelper; +using KSFramework.KSDomain.AggregatesHelper; using KSFramework.Pagination; namespace KSFramework.GenericRepository; -public interface IRepository where TEntity : class, IAggregateRoot +public interface IRepository where TEntity : class { - ValueTask GetByIdAsync(object id); + ValueTask GetByIdAsync(object id); Task> GetAllAsync(); - Task> GetPagedAsync(int pageIndex, int pageSize, Expression> where = null, string orderBy = "", bool desc = false); - PaginatedList GetPaged(int pageIndex, int pageSize, Expression> where = null, string orderBy = "", bool desc = false); + Task> GetPagedAsync(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false); + PaginatedList GetPaged(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false); IEnumerable Find(Expression> predicate); - Task SingleOrDefaultAsync(Expression> predicate); + Task SingleOrDefaultAsync(Expression> predicate); Task AddAsync(TEntity entity); Task AddRangeAsync(IEnumerable entities); void Remove(TEntity entity); void RemoveRange(IEnumerable entities); - Task IsExistValuForPropertyAsync(Expression> predicate); + Task IsExistValueForPropertyAsync(Expression> predicate); } \ No newline at end of file diff --git a/src/KSFramework/GenericRepository/IUnitOfWork.cs b/src/KSFramework/GenericRepository/IUnitOfWork.cs new file mode 100644 index 0000000..516108e --- /dev/null +++ b/src/KSFramework/GenericRepository/IUnitOfWork.cs @@ -0,0 +1,23 @@ +using System; +using System.Threading.Tasks; + +namespace KSFramework.GenericRepository; + +/// +/// Represents the Unit of Work pattern, encapsulating the transaction and saving changes. +/// +public interface IUnitOfWork : IDisposable +{ + /// + /// Saves all changes made in this unit of work to the underlying data store asynchronously. + /// + /// A task that represents the asynchronous save operation. The task result contains the number of state entries written to the database. + Task SaveChangesAsync(); + + /// + /// Gets a generic repository for the specified entity type. + /// + /// The type of the entity. + /// An instance of for the specified entity type. + IGenericRepository GetRepository() where TEntity : class; +} \ No newline at end of file diff --git a/src/KSFramework/GenericRepository/Repository.cs b/src/KSFramework/GenericRepository/Repository.cs index fa44b76..658d90a 100644 --- a/src/KSFramework/GenericRepository/Repository.cs +++ b/src/KSFramework/GenericRepository/Repository.cs @@ -1,70 +1,144 @@ using System.Linq.Expressions; -using KSFramework.Domain.AggregatesHelper; +using KSFramework.KSDomain.AggregatesHelper; using KSFramework.Pagination; using Microsoft.EntityFrameworkCore; namespace KSFramework.GenericRepository; -public abstract class Repository : IRepository where TEntity : class, IAggregateRoot +/// +/// Provides a base implementation of the generic repository pattern for entity operations. +/// +/// The type of the entity. +public abstract class Repository : IRepository where TEntity : class { private readonly DbContext Context; protected DbSet Entity; - public Repository(DbContext context) + + /// + /// Initializes a new instance of the Repository class. + /// + /// The database context. + protected Repository(DbContext context) { this.Context = context; Entity = Context.Set(); } + /// + /// Adds a new entity to the repository asynchronously. + /// + /// The entity to add. + /// A task representing the asynchronous operation. public virtual async Task AddAsync(TEntity entity) { await Entity.AddAsync(entity); } + /// + /// Adds multiple entities to the repository asynchronously. + /// + /// The collection of entities to add. + /// A task representing the asynchronous operation. public virtual async Task AddRangeAsync(IEnumerable entities) { await Entity.AddRangeAsync(entities); } + /// + /// Finds entities that match the specified predicate. + /// + /// The condition to filter entities. + /// A collection of entities that match the condition. public virtual IEnumerable Find(Expression> predicate) { return Entity.Where(predicate); } + /// + /// Retrieves all entities asynchronously. + /// + /// A task representing the asynchronous operation, containing all entities. public virtual async Task> GetAllAsync() { return await Entity.ToListAsync(); } - public async Task> GetPagedAsync(int pageIndex, int pageSize, Expression> where = null, string orderBy = "", bool desc = false) + /// + /// Retrieves a paginated list of entities asynchronously. + /// + /// The index of the page to retrieve. + /// The size of the page. + /// Optional predicate to filter the entities. + /// Optional property name to order the results by. + /// Optional flag to indicate descending order. + /// A task representing the asynchronous operation, containing a paginated list of entities. + public virtual async Task> GetPagedAsync(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false) { - return await PaginatedList.CreateAsync(Entity.AsQueryable(), pageIndex, pageSize, where, orderBy, desc); + var query = Entity.AsQueryable(); + if (where != null) + query = query.Where(where); + + return await PaginatedList.CreateAsync(query, pageIndex, pageSize, where, orderBy, desc); } - public PaginatedList GetPaged(int pageIndex, int pageSize, Expression> where = null, string orderBy = "", bool desc = false) + /// + /// Retrieves a paginated list of entities synchronously. + /// + /// The index of the page to retrieve. + /// The size of the page. + /// Optional predicate to filter the entities. + /// Optional property name to order the results by. + /// Optional flag to indicate descending order. + /// A paginated list of entities. + public virtual PaginatedList GetPaged(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false) { - return PaginatedList.Create(Entity.AsQueryable(), pageIndex, pageSize, where, orderBy, desc); + var query = Entity.AsQueryable(); + return PaginatedList.Create(query, pageIndex, pageSize, where, orderBy, desc); } - public virtual async ValueTask GetByIdAsync(object id) + /// + /// Retrieves an entity by its identifier asynchronously. + /// + /// The identifier of the entity. + /// A task representing the asynchronous operation, containing the entity if found. + public virtual async ValueTask GetByIdAsync(object id) { return await Entity.FindAsync(id); } - public void Remove(TEntity entity) + /// + /// Removes an entity from the repository. + /// + /// The entity to remove. + public virtual void Remove(TEntity entity) { Entity.Remove(entity); } - public void RemoveRange(IEnumerable entities) + /// + /// Removes multiple entities from the repository. + /// + /// The collection of entities to remove. + public virtual void RemoveRange(IEnumerable entities) { Entity.RemoveRange(entities); } - public async Task SingleOrDefaultAsync(Expression> predicate) + /// + /// Retrieves a single entity that matches the specified predicate, or null if no match is found. + /// + /// The condition to filter the entity. + /// A task representing the asynchronous operation, containing the entity if found. + public virtual async Task SingleOrDefaultAsync(Expression> predicate) { return await Entity.SingleOrDefaultAsync(predicate); } - public async Task IsExistValuForPropertyAsync(Expression> predicate) + /// + /// Checks if any entity exists that matches the specified predicate. + /// + /// The condition to check for existence. + /// A task representing the asynchronous operation, containing true if a matching entity exists. + public virtual async Task IsExistValueForPropertyAsync(Expression> predicate) { return await Entity.AnyAsync(predicate); } diff --git a/src/KSFramework/GenericRepository/UnitOfWork.cs b/src/KSFramework/GenericRepository/UnitOfWork.cs new file mode 100644 index 0000000..57ef87c --- /dev/null +++ b/src/KSFramework/GenericRepository/UnitOfWork.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using KSFramework.KSDomain.AggregatesHelper; +using Microsoft.EntityFrameworkCore; + +namespace KSFramework.GenericRepository; + +/// +/// Implements the Unit of Work pattern, managing a DbContext and providing access to generic repositories. +/// +public class UnitOfWork : IUnitOfWork +{ + private readonly DbContext _context; + private readonly Dictionary _repositories; + /// + /// Initializes a new instance of the UnitOfWork class. + /// + /// The database context. + public UnitOfWork(DbContext context) + { + _context = context; + _repositories = new Dictionary(); + } + + /// + /// Saves all changes made in this unit of work to the underlying data store asynchronously. + /// + /// A task that represents the asynchronous save operation. The task result contains the number of state entries written to the database. + public async Task SaveChangesAsync() + { + return await _context.SaveChangesAsync(); + } + + /// + /// Retrieves a generic repository for the specified entity type. + /// + /// The type of the entity. + /// An instance of for the specified entity type. + IGenericRepository IUnitOfWork.GetRepository() where TEntity : class + { + if (_repositories.TryGetValue(typeof(TEntity), out var repository)) + { + return (IGenericRepository)repository; + } + + var newRepository = new GenericRepository((DbContext)_context); + _repositories.Add(typeof(TEntity), newRepository); + return newRepository; + } + + private bool _disposed = false; + + /// + /// Disposes the current instance of the UnitOfWork. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes the current instance of the UnitOfWork. + /// + /// True to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + _context.Dispose(); + } + } + _disposed = true; + } +} \ No newline at end of file diff --git a/src/KSFramework/KSApi/ApiResult.cs b/src/KSFramework/KSApi/ApiResult.cs new file mode 100644 index 0000000..703c32d --- /dev/null +++ b/src/KSFramework/KSApi/ApiResult.cs @@ -0,0 +1,165 @@ +using System.Net; +using KSFramework.Utilities; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + +namespace KSFramework.KSApi; + +public class ApiResult +{ + public bool Status { get; set; } + public HttpStatusCode StatusCode { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public string Message { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public List? Errors { get; set; } + + public ApiResult(bool isSucceeded, HttpStatusCode statusCode, List? errors = null, string? message = null) + { + Status = isSucceeded; + StatusCode = statusCode; + Message = message ?? statusCode.ToDisplay(); + Errors = errors; + } + #region Implicit Operators + public static implicit operator ApiResult(OkResult result) + { + return new ApiResult(true, HttpStatusCode.OK, null); + } + + public static implicit operator ApiResult(BadRequestResult result) + { + return new ApiResult(false, HttpStatusCode.BadRequest, null); + } + + public static implicit operator ApiResult(BadRequestObjectResult result) + { + var errorsList = new List(); + if (result.Value is SerializableError errors) + { + errorsList = errors.SelectMany(p => (string[])p.Value).Distinct().ToList(); + } + else + { + errorsList.Add(result.Value.ToString()); + } + return new ApiResult(false, HttpStatusCode.BadRequest, errorsList); + } + + public static implicit operator ApiResult(ContentResult result) + { + return new ApiResult(true, HttpStatusCode.OK, null, result.Content); + } + + public static implicit operator ApiResult(NotFoundResult result) + { + return new ApiResult(false, HttpStatusCode.NotFound, null); + } + + public static implicit operator ApiResult(UnauthorizedResult result) + { + return new ApiResult(false, HttpStatusCode.Unauthorized, null); + } + + public static implicit operator ApiResult(UnauthorizedObjectResult result) + { + var errorsList = new List(); + if (result.Value is SerializableError errors) + { + errorsList = errors.SelectMany(p => (string[])p.Value).Distinct().ToList(); + } + else + { + errorsList.Add(result.Value.ToString()); + } + return new ApiResult(false, HttpStatusCode.Unauthorized, errorsList, null); + } + #endregion +} + + +public class ApiResult : ApiResult + where TData : class +{ + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public TData Data { get; set; } + + public ApiResult(bool isSucceeded, HttpStatusCode statusCode, TData data, List? errors = null, string? message = null) + : base(isSucceeded, statusCode, errors, message) + { + Data = data; + } + + #region Implicit Operators + public static implicit operator ApiResult(TData data) + { + return new ApiResult(true, HttpStatusCode.OK, data, null); + } + + public static implicit operator ApiResult(OkResult result) + { + return new ApiResult(true, HttpStatusCode.OK, null, null); + } + + public static implicit operator ApiResult(OkObjectResult result) + { + return new ApiResult(true, HttpStatusCode.OK, (TData)result.Value, null); + } + + public static implicit operator ApiResult(BadRequestResult result) + { + return new ApiResult(false, HttpStatusCode.BadRequest, null, null); + } + + public static implicit operator ApiResult(BadRequestObjectResult result) + { + var errorsList = new List(); + if (result.Value is SerializableError errors) + { + errorsList = errors.SelectMany(p => (string[])p.Value).Distinct().ToList(); + } + else + { + errorsList.Add(result.Value.ToString()); + } + return new ApiResult(false, HttpStatusCode.BadRequest, null, errorsList); + } + + public static implicit operator ApiResult(ContentResult result) + { + return new ApiResult(true, HttpStatusCode.OK, null, null, result.Content); + } + + public static implicit operator ApiResult(NotFoundResult result) + { + return new ApiResult(false, HttpStatusCode.NotFound, null, null); + } + + public static implicit operator ApiResult(NotFoundObjectResult result) + { + var errors = new List(); + errors.Add(result.Value.ToString()); + return new ApiResult(false, HttpStatusCode.NotFound, (TData)result.Value, errors); + } + + + public static implicit operator ApiResult(UnauthorizedResult result) + { + return new ApiResult(false, HttpStatusCode.Unauthorized, null, null); + } + + public static implicit operator ApiResult(UnauthorizedObjectResult result) + { + var errorsList = new List(); + if (result.Value is SerializableError errors) + { + errorsList = errors.SelectMany(p => (string[])p.Value).Distinct().ToList(); + } + else + { + errorsList.Add(result.Value.ToString()); + } + return new ApiResult(false, HttpStatusCode.Unauthorized, null, errorsList); + } + #endregion +} \ No newline at end of file diff --git a/src/KSFramework/KSDomain/AggregatesHelper/IAggregateRoot.cs b/src/KSFramework/KSDomain/AggregatesHelper/IAggregateRoot.cs new file mode 100644 index 0000000..3a0f2c4 --- /dev/null +++ b/src/KSFramework/KSDomain/AggregatesHelper/IAggregateRoot.cs @@ -0,0 +1,5 @@ + +namespace KSFramework.KSDomain.AggregatesHelper; +public interface IAggregateRoot +{ +} \ No newline at end of file diff --git a/src/KSFramework/Domain/AggregatesHelper/ValueObject.cs b/src/KSFramework/KSDomain/AggregatesHelper/ValueObject.cs similarity index 93% rename from src/KSFramework/Domain/AggregatesHelper/ValueObject.cs rename to src/KSFramework/KSDomain/AggregatesHelper/ValueObject.cs index 2a4b658..1e9825f 100644 --- a/src/KSFramework/Domain/AggregatesHelper/ValueObject.cs +++ b/src/KSFramework/KSDomain/AggregatesHelper/ValueObject.cs @@ -1,8 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; -namespace KSFramework.Domain.AggregatesHelper; +namespace KSFramework.KSDomain.AggregatesHelper; public abstract class ValueObject { protected static bool EqualOperator(ValueObject left, ValueObject right) @@ -21,7 +21,7 @@ protected static bool NotEqualOperator(ValueObject left, ValueObject right) protected abstract IEnumerable GetEqualityComponents(); - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj == null || obj.GetType() != GetType()) { diff --git a/src/KSFramework/KSDomain/BaseEntity.cs b/src/KSFramework/KSDomain/BaseEntity.cs new file mode 100644 index 0000000..af6a379 --- /dev/null +++ b/src/KSFramework/KSDomain/BaseEntity.cs @@ -0,0 +1,59 @@ +using System; +namespace KSFramework.KSDomain; +/// +/// Represents the base interface for all entities in the domain. +/// +public interface IEntity +{ +} + +/// +/// Base class for entities with GUID as the primary key. +/// +public abstract class BaseEntity : BaseEntity +{ + /// + /// Gets or sets the unique identifier for the entity. + /// + public override required Guid Id { get; set; } = Guid.NewGuid(); +} + +/// +/// Generic base class for entities with a specified key type. +/// +/// The type of the entity's primary key. +public abstract class BaseEntity : IEntity +{ + /// + /// Gets or sets the unique identifier for the entity. + /// + public virtual required TKey Id { get; set; } + + /// + /// Gets the version number of the entity for optimistic concurrency. + /// + public int Version { get; private set; } = 0; + + private List _domainEvents = new List(); + + /// + /// Gets the collection of domain events associated with this entity. + /// + public IReadOnlyCollection DomainEvents => _domainEvents.AsReadOnly(); + + protected void AddDomainEvent(IDomainEvent domainEvent) + { + _domainEvents ??= new List(); + _domainEvents.Add(domainEvent); + } + + protected void IncreaseVersion() + { + Version++; + } + + public void ClearDomainEvents() + { + _domainEvents?.Clear(); + } +} \ No newline at end of file diff --git a/src/KSFramework/Domain/DomainServices/DomainService.cs b/src/KSFramework/KSDomain/DomainServices/DomainService.cs similarity index 60% rename from src/KSFramework/Domain/DomainServices/DomainService.cs rename to src/KSFramework/KSDomain/DomainServices/DomainService.cs index f2ddc4b..707550b 100644 --- a/src/KSFramework/Domain/DomainServices/DomainService.cs +++ b/src/KSFramework/KSDomain/DomainServices/DomainService.cs @@ -1,5 +1,5 @@ -using System; -namespace KSFramework.Domain.DomainServices; +using System; +namespace KSFramework.KSDomain.DomainServices; public abstract class DomainService : IDomainService { public DomainService() diff --git a/src/KSFramework/KSDomain/DomainServices/IDomainService.cs b/src/KSFramework/KSDomain/DomainServices/IDomainService.cs new file mode 100644 index 0000000..b753d7b --- /dev/null +++ b/src/KSFramework/KSDomain/DomainServices/IDomainService.cs @@ -0,0 +1,5 @@ +using System; +namespace KSFramework.KSDomain.DomainServices; +public interface IDomainService +{ +} \ No newline at end of file diff --git a/src/KSFramework/KSDomain/Entity.cs b/src/KSFramework/KSDomain/Entity.cs new file mode 100644 index 0000000..dfbf2f7 --- /dev/null +++ b/src/KSFramework/KSDomain/Entity.cs @@ -0,0 +1,10 @@ +using KSFramework.KSDomain.AggregatesHelper; + +namespace KSFramework.KSDomain; + +/// +/// Base class for entities with GUID as the primary key that can be used with the generic repository. +/// +public abstract class Entity : BaseEntity, IAggregateRoot +{ +} \ No newline at end of file diff --git a/src/KSFramework/Domain/Enumeration.cs b/src/KSFramework/KSDomain/Enumeration.cs similarity index 92% rename from src/KSFramework/Domain/Enumeration.cs rename to src/KSFramework/KSDomain/Enumeration.cs index cac9cdb..6f3c66b 100644 --- a/src/KSFramework/Domain/Enumeration.cs +++ b/src/KSFramework/KSDomain/Enumeration.cs @@ -1,6 +1,6 @@ using System.Reflection; -namespace KSFramework.Domain; +namespace KSFramework.KSDomain; public abstract class Enumeration : IComparable { public int Id { get; private set; } @@ -17,7 +17,7 @@ public static IEnumerable GetAll() where T : Enumeration => .Select(f => f.GetValue(null)) .Cast(); - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is not Enumeration otherValue) { @@ -60,6 +60,6 @@ private static T Parse(K value, string description, Func predicat return matchingItem; } - public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id); + public int CompareTo(object? other) => Id.CompareTo(((Enumeration)other!).Id); } \ No newline at end of file diff --git a/src/KSFramework/Domain/IDomainEvent.cs b/src/KSFramework/KSDomain/IDomainEvent.cs similarity index 52% rename from src/KSFramework/Domain/IDomainEvent.cs rename to src/KSFramework/KSDomain/IDomainEvent.cs index 09c10b2..3a3c530 100644 --- a/src/KSFramework/Domain/IDomainEvent.cs +++ b/src/KSFramework/KSDomain/IDomainEvent.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Domain; +namespace KSFramework.KSDomain; public interface IDomainEvent : INotification { DateTime OccurredOn { get; } diff --git a/src/KSFramework/KSDomain/IRepository.cs b/src/KSFramework/KSDomain/IRepository.cs new file mode 100644 index 0000000..09d491e --- /dev/null +++ b/src/KSFramework/KSDomain/IRepository.cs @@ -0,0 +1,8 @@ +using KSFramework.KSDomain.AggregatesHelper; + +namespace KSFramework.KSDomain; + +public interface IRepository where T : IAggregateRoot +{ +} + diff --git a/src/KSFramework/Domain/SpecificationsHelpers/AndNotSpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/AndNotSpecification.cs similarity index 93% rename from src/KSFramework/Domain/SpecificationsHelpers/AndNotSpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/AndNotSpecification.cs index 033ef51..ef714a6 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/AndNotSpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/AndNotSpecification.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the combined specification which indicates that the first specification /// can be satisifed by the given object whereas the second one cannot. diff --git a/src/KSFramework/Domain/SpecificationsHelpers/AndSpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/AndSpecification.cs similarity index 91% rename from src/KSFramework/Domain/SpecificationsHelpers/AndSpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/AndSpecification.cs index 4bbb6d6..df0c18e 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/AndSpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/AndSpecification.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the combined specification which indicates that both of the given /// specifications should be satisfied by the given object. diff --git a/src/KSFramework/Domain/SpecificationsHelpers/AnySpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/AnySpecification.cs similarity index 86% rename from src/KSFramework/Domain/SpecificationsHelpers/AnySpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/AnySpecification.cs index c2e0352..e2eadf1 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/AnySpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/AnySpecification.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the specification that can be satisfied by the given object /// in any circumstance. diff --git a/src/KSFramework/Domain/SpecificationsHelpers/CompositeSpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/CompositeSpecification.cs similarity index 94% rename from src/KSFramework/Domain/SpecificationsHelpers/CompositeSpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/CompositeSpecification.cs index 1248b98..eef1bff 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/CompositeSpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/CompositeSpecification.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the base class for composite specifications. /// diff --git a/src/KSFramework/Domain/SpecificationsHelpers/ExpressionFuncExtender.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/ExpressionFuncExtender.cs similarity index 96% rename from src/KSFramework/Domain/SpecificationsHelpers/ExpressionFuncExtender.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/ExpressionFuncExtender.cs index 0850c66..7d530d5 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/ExpressionFuncExtender.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/ExpressionFuncExtender.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the extender for Expression[Func[T, bool]] type. /// This is part of the solution which solves diff --git a/src/KSFramework/Domain/SpecificationsHelpers/ExpressionSpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/ExpressionSpecification.cs similarity index 91% rename from src/KSFramework/Domain/SpecificationsHelpers/ExpressionSpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/ExpressionSpecification.cs index 1bf9394..878019a 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/ExpressionSpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/ExpressionSpecification.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the specification which is represented by the corresponding /// LINQ expression. diff --git a/src/KSFramework/Domain/SpecificationsHelpers/ICompositeSpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/ICompositeSpecification.cs similarity index 90% rename from src/KSFramework/Domain/SpecificationsHelpers/ICompositeSpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/ICompositeSpecification.cs index 5622943..9c8e550 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/ICompositeSpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/ICompositeSpecification.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents that the implemented classes are composite specifications. /// diff --git a/src/KSFramework/Domain/SpecificationsHelpers/ISpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/ISpecification.cs similarity index 91% rename from src/KSFramework/Domain/SpecificationsHelpers/ISpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/ISpecification.cs index d0f9e1e..1bc4ed5 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/ISpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/ISpecification.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents that the implemented classes are specifications. For more /// information about the specification pattern, please refer to diff --git a/src/KSFramework/Domain/SpecificationsHelpers/ISpecificationParser.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/ISpecificationParser.cs similarity index 93% rename from src/KSFramework/Domain/SpecificationsHelpers/ISpecificationParser.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/ISpecificationParser.cs index 969f23d..81aaafc 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/ISpecificationParser.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/ISpecificationParser.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents that the implemented classes are specification parsers that /// parses the given specification to a domain specific criteria object, such diff --git a/src/KSFramework/Domain/SpecificationsHelpers/NoneSpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/NoneSpecification.cs similarity index 86% rename from src/KSFramework/Domain/SpecificationsHelpers/NoneSpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/NoneSpecification.cs index 879bdf2..7e55e2c 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/NoneSpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/NoneSpecification.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the specification that can be satisfied by the given object /// in no circumstance. diff --git a/src/KSFramework/Domain/SpecificationsHelpers/NotSpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/NotSpecification.cs similarity index 92% rename from src/KSFramework/Domain/SpecificationsHelpers/NotSpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/NotSpecification.cs index 7ccea8e..620cd38 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/NotSpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/NotSpecification.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the specification which indicates the semantics opposite to the given specification. /// diff --git a/src/KSFramework/Domain/SpecificationsHelpers/OrSpecification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/OrSpecification.cs similarity index 91% rename from src/KSFramework/Domain/SpecificationsHelpers/OrSpecification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/OrSpecification.cs index dda3c63..502c1ac 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/OrSpecification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/OrSpecification.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the combined specification which indicates that either of the given /// specification should be satisfied by the given object. diff --git a/src/KSFramework/Domain/SpecificationsHelpers/ParameterRebinder.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/ParameterRebinder.cs similarity index 93% rename from src/KSFramework/Domain/SpecificationsHelpers/ParameterRebinder.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/ParameterRebinder.cs index e912435..05df649 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/ParameterRebinder.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/ParameterRebinder.cs @@ -1,6 +1,6 @@ -using System.Linq.Expressions; +using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the parameter rebinder used for rebinding the parameters /// for the given expressions. This is part of the solution which solves diff --git a/src/KSFramework/Domain/SpecificationsHelpers/Specification.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/Specification.cs similarity index 95% rename from src/KSFramework/Domain/SpecificationsHelpers/Specification.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/Specification.cs index d5eccd2..9d0e655 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/Specification.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/Specification.cs @@ -1,6 +1,6 @@ using System.Linq.Expressions; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; /// /// Represents the base class for specifications. /// diff --git a/src/KSFramework/Domain/SpecificationsHelpers/SpecificationExtensions.cs b/src/KSFramework/KSDomain/SpecificationsHelpers/SpecificationExtensions.cs similarity index 97% rename from src/KSFramework/Domain/SpecificationsHelpers/SpecificationExtensions.cs rename to src/KSFramework/KSDomain/SpecificationsHelpers/SpecificationExtensions.cs index 6c3ad34..a0eb820 100644 --- a/src/KSFramework/Domain/SpecificationsHelpers/SpecificationExtensions.cs +++ b/src/KSFramework/KSDomain/SpecificationsHelpers/SpecificationExtensions.cs @@ -1,6 +1,6 @@ -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; -namespace KSFramework.Domain.SpecificationsHelpers; +namespace KSFramework.KSDomain.SpecificationsHelpers; public static class SpecificationExtensions { /// diff --git a/src/KSFramework/KSFramework.csproj b/src/KSFramework/KSFramework.csproj index 1bd184e..2ef1e4d 100644 --- a/src/KSFramework/KSFramework.csproj +++ b/src/KSFramework/KSFramework.csproj @@ -1,11 +1,13 @@ - + net8.0 enable enable + true + $(NoWarn);1591 - + KSFramework Kamran Sadin @@ -30,15 +32,19 @@ - - - - - + + + + + + + + + diff --git a/src/KSFramework/KSMessaging/Abstraction/ICommand.cs b/src/KSFramework/KSMessaging/Abstraction/ICommand.cs new file mode 100644 index 0000000..e2e7610 --- /dev/null +++ b/src/KSFramework/KSMessaging/Abstraction/ICommand.cs @@ -0,0 +1,9 @@ +namespace KSFramework.KSMessaging.Abstraction; + +public interface ICommand : IRequest +{ +} + +public interface ICommand : IRequest +{ +} \ No newline at end of file diff --git a/src/KSFramework/KSMessaging/Abstraction/ICommandHandler.cs b/src/KSFramework/KSMessaging/Abstraction/ICommandHandler.cs new file mode 100644 index 0000000..381f89d --- /dev/null +++ b/src/KSFramework/KSMessaging/Abstraction/ICommandHandler.cs @@ -0,0 +1,13 @@ +using KSFramework.KSMessaging.Abstraction; + +namespace KSFramework.KSMessaging.Abstraction; + +public interface ICommandHandler : IRequestHandler + where TCommand : ICommand +{ +} + +public interface ICommandHandler : IRequestHandler + where TCommand : ICommand +{ +} \ No newline at end of file diff --git a/src/KSFramework/Messaging/Abstraction/IMediator.cs b/src/KSFramework/KSMessaging/Abstraction/IMediator.cs similarity index 97% rename from src/KSFramework/Messaging/Abstraction/IMediator.cs rename to src/KSFramework/KSMessaging/Abstraction/IMediator.cs index 1df2a1a..eb63b46 100644 --- a/src/KSFramework/Messaging/Abstraction/IMediator.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IMediator.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Represents a mediator that supports sending requests and publishing notifications. @@ -14,7 +14,7 @@ public interface IMediator : ISender /// A task representing the asynchronous operation. Task Publish(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification; - + /// /// Sends a request without knowing the response type at compile time. /// @@ -22,7 +22,7 @@ Task Publish(TNotification notification, CancellationToken cancel /// Cancellation token. /// A task that represents the response. Task Send(object request, CancellationToken cancellationToken = default); - + /// /// Publishes a notification without knowing the notification type at compile time. /// @@ -30,7 +30,7 @@ Task Publish(TNotification notification, CancellationToken cancel /// Cancellation token. /// A task representing the asynchronous operation. Task Publish(object notification, CancellationToken cancellationToken = default); - + /// /// Creates a stream of responses for a given stream request. /// diff --git a/src/KSFramework/Messaging/Abstraction/INotification.cs b/src/KSFramework/KSMessaging/Abstraction/INotification.cs similarity index 72% rename from src/KSFramework/Messaging/Abstraction/INotification.cs rename to src/KSFramework/KSMessaging/Abstraction/INotification.cs index d49298a..a7674c5 100644 --- a/src/KSFramework/Messaging/Abstraction/INotification.cs +++ b/src/KSFramework/KSMessaging/Abstraction/INotification.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Marker interface to represent a notification message. diff --git a/src/KSFramework/Messaging/Abstraction/INotificationBehavior.cs b/src/KSFramework/KSMessaging/Abstraction/INotificationBehavior.cs similarity index 91% rename from src/KSFramework/Messaging/Abstraction/INotificationBehavior.cs rename to src/KSFramework/KSMessaging/Abstraction/INotificationBehavior.cs index 0fd7dd8..fbb2315 100644 --- a/src/KSFramework/Messaging/Abstraction/INotificationBehavior.cs +++ b/src/KSFramework/KSMessaging/Abstraction/INotificationBehavior.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Delegate that represents the next notification handler or behavior in the pipeline. diff --git a/src/KSFramework/Messaging/Abstraction/INotificationHandler.cs b/src/KSFramework/KSMessaging/Abstraction/INotificationHandler.cs similarity index 91% rename from src/KSFramework/Messaging/Abstraction/INotificationHandler.cs rename to src/KSFramework/KSMessaging/Abstraction/INotificationHandler.cs index ce192f6..b54e2ec 100644 --- a/src/KSFramework/Messaging/Abstraction/INotificationHandler.cs +++ b/src/KSFramework/KSMessaging/Abstraction/INotificationHandler.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Defines a handler for a specific type of notification. diff --git a/src/KSFramework/Messaging/Abstraction/IPipelineBehavior.cs b/src/KSFramework/KSMessaging/Abstraction/IPipelineBehavior.cs similarity index 96% rename from src/KSFramework/Messaging/Abstraction/IPipelineBehavior.cs rename to src/KSFramework/KSMessaging/Abstraction/IPipelineBehavior.cs index 6899e6f..1763fdc 100644 --- a/src/KSFramework/Messaging/Abstraction/IPipelineBehavior.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IPipelineBehavior.cs @@ -1,7 +1,7 @@ using System.Threading; using System.Threading.Tasks; -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Defines a pipeline behavior for requests that can run code before and after the handler is invoked. /// diff --git a/src/KSFramework/Messaging/Abstraction/IPostProcessor.cs b/src/KSFramework/KSMessaging/Abstraction/IPostProcessor.cs similarity index 84% rename from src/KSFramework/Messaging/Abstraction/IPostProcessor.cs rename to src/KSFramework/KSMessaging/Abstraction/IPostProcessor.cs index bcde205..39c4a2a 100644 --- a/src/KSFramework/Messaging/Abstraction/IPostProcessor.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IPostProcessor.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Defines a post-processor that runs after the request handler. diff --git a/src/KSFramework/KSMessaging/Abstraction/IQuery.cs b/src/KSFramework/KSMessaging/Abstraction/IQuery.cs new file mode 100644 index 0000000..8d0ffa7 --- /dev/null +++ b/src/KSFramework/KSMessaging/Abstraction/IQuery.cs @@ -0,0 +1,11 @@ +using KSFramework.KSMessaging.Abstraction; + +namespace KSFramework.KSMessaging.Abstraction; + +public interface IQuery : IRequest +{ +} + +public interface IQuery : IRequest +{ +} \ No newline at end of file diff --git a/src/KSFramework/KSMessaging/Abstraction/IQueryHandler.cs b/src/KSFramework/KSMessaging/Abstraction/IQueryHandler.cs new file mode 100644 index 0000000..0ee5bbd --- /dev/null +++ b/src/KSFramework/KSMessaging/Abstraction/IQueryHandler.cs @@ -0,0 +1,13 @@ +using KSFramework.KSMessaging.Abstraction; + +namespace KSFramework.KSMessaging.Abstraction; + +public interface IQueryHandler : IRequestHandler + where TQuery : IQuery +{ +} + +public interface IQueryHandler : IRequestHandler + where TQuery : IQuery +{ +} \ No newline at end of file diff --git a/src/KSFramework/Messaging/Abstraction/IRequest.cs b/src/KSFramework/KSMessaging/Abstraction/IRequest.cs similarity index 65% rename from src/KSFramework/Messaging/Abstraction/IRequest.cs rename to src/KSFramework/KSMessaging/Abstraction/IRequest.cs index bff54c1..dbe760f 100644 --- a/src/KSFramework/Messaging/Abstraction/IRequest.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IRequest.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; public interface IRequest { diff --git a/src/KSFramework/Messaging/Abstraction/IRequestHandler.cs b/src/KSFramework/KSMessaging/Abstraction/IRequestHandler.cs similarity index 82% rename from src/KSFramework/Messaging/Abstraction/IRequestHandler.cs rename to src/KSFramework/KSMessaging/Abstraction/IRequestHandler.cs index bf13151..8c40e2f 100644 --- a/src/KSFramework/Messaging/Abstraction/IRequestHandler.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IRequestHandler.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; public interface IRequestHandler where TRequest : IRequest @@ -6,7 +6,7 @@ public interface IRequestHandler Task Handle(TRequest request, CancellationToken cancellationToken); } -public interface IRequestHandler : IRequestHandler +public interface IRequestHandler : IRequestHandler where TRequest : IRequest { } diff --git a/src/KSFramework/Messaging/Abstraction/IRequestPreProcessor.cs b/src/KSFramework/KSMessaging/Abstraction/IRequestPreProcessor.cs similarity index 82% rename from src/KSFramework/Messaging/Abstraction/IRequestPreProcessor.cs rename to src/KSFramework/KSMessaging/Abstraction/IRequestPreProcessor.cs index f11aa7f..8580ac6 100644 --- a/src/KSFramework/Messaging/Abstraction/IRequestPreProcessor.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IRequestPreProcessor.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Defines a pre-processor that runs before the request handler. diff --git a/src/KSFramework/Messaging/Abstraction/ISender.cs b/src/KSFramework/KSMessaging/Abstraction/ISender.cs similarity index 92% rename from src/KSFramework/Messaging/Abstraction/ISender.cs rename to src/KSFramework/KSMessaging/Abstraction/ISender.cs index 35b2c7a..8817735 100644 --- a/src/KSFramework/Messaging/Abstraction/ISender.cs +++ b/src/KSFramework/KSMessaging/Abstraction/ISender.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Represents a sender that can send requests and return responses. diff --git a/src/KSFramework/Messaging/Abstraction/IStreamPipelineBehavior.cs b/src/KSFramework/KSMessaging/Abstraction/IStreamPipelineBehavior.cs similarity index 96% rename from src/KSFramework/Messaging/Abstraction/IStreamPipelineBehavior.cs rename to src/KSFramework/KSMessaging/Abstraction/IStreamPipelineBehavior.cs index 33f50e3..11e4e0d 100644 --- a/src/KSFramework/Messaging/Abstraction/IStreamPipelineBehavior.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IStreamPipelineBehavior.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Defines a pipeline behavior for stream requests that can run code before and after the handler is invoked. diff --git a/src/KSFramework/Messaging/Abstraction/IStreamRequest.cs b/src/KSFramework/KSMessaging/Abstraction/IStreamRequest.cs similarity index 84% rename from src/KSFramework/Messaging/Abstraction/IStreamRequest.cs rename to src/KSFramework/KSMessaging/Abstraction/IStreamRequest.cs index fb71bae..68e98a2 100644 --- a/src/KSFramework/Messaging/Abstraction/IStreamRequest.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IStreamRequest.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Marker interface for a request that returns a stream of responses. diff --git a/src/KSFramework/Messaging/Abstraction/IStreamRequestHandler.cs b/src/KSFramework/KSMessaging/Abstraction/IStreamRequestHandler.cs similarity index 89% rename from src/KSFramework/Messaging/Abstraction/IStreamRequestHandler.cs rename to src/KSFramework/KSMessaging/Abstraction/IStreamRequestHandler.cs index 9f354ad..b46cc7e 100644 --- a/src/KSFramework/Messaging/Abstraction/IStreamRequestHandler.cs +++ b/src/KSFramework/KSMessaging/Abstraction/IStreamRequestHandler.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; /// /// Handles stream requests. diff --git a/src/KSFramework/Messaging/Abstraction/Unit.cs b/src/KSFramework/KSMessaging/Abstraction/Unit.cs similarity index 62% rename from src/KSFramework/Messaging/Abstraction/Unit.cs rename to src/KSFramework/KSMessaging/Abstraction/Unit.cs index f8df51d..1cfda23 100644 --- a/src/KSFramework/Messaging/Abstraction/Unit.cs +++ b/src/KSFramework/KSMessaging/Abstraction/Unit.cs @@ -1,4 +1,4 @@ -namespace KSFramework.Messaging.Abstraction; +namespace KSFramework.KSMessaging.Abstraction; public readonly struct Unit { diff --git a/src/KSFramework/Messaging/Behaviors/ExceptionHandlingBehavior.cs b/src/KSFramework/KSMessaging/Behaviors/ExceptionHandlingBehavior.cs similarity index 92% rename from src/KSFramework/Messaging/Behaviors/ExceptionHandlingBehavior.cs rename to src/KSFramework/KSMessaging/Behaviors/ExceptionHandlingBehavior.cs index ebe2639..11547eb 100644 --- a/src/KSFramework/Messaging/Behaviors/ExceptionHandlingBehavior.cs +++ b/src/KSFramework/KSMessaging/Behaviors/ExceptionHandlingBehavior.cs @@ -1,7 +1,7 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.Logging; -namespace KSFramework.Messaging.Behaviors +namespace KSFramework.KSMessaging.Behaviors { /// /// Behavior that catches exceptions during request handling, logs them, and rethrows. diff --git a/src/KSFramework/Messaging/Behaviors/LoggingBehavior.cs b/src/KSFramework/KSMessaging/Behaviors/LoggingBehavior.cs similarity index 91% rename from src/KSFramework/Messaging/Behaviors/LoggingBehavior.cs rename to src/KSFramework/KSMessaging/Behaviors/LoggingBehavior.cs index 6ebce8f..b435767 100644 --- a/src/KSFramework/Messaging/Behaviors/LoggingBehavior.cs +++ b/src/KSFramework/KSMessaging/Behaviors/LoggingBehavior.cs @@ -1,7 +1,7 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.Logging; -namespace KSFramework.Messaging.Behaviors; +namespace KSFramework.KSMessaging.Behaviors; /// /// Logs the request before and after it is handled. diff --git a/src/KSFramework/Messaging/Behaviors/LoggingPostProcessor.cs b/src/KSFramework/KSMessaging/Behaviors/LoggingPostProcessor.cs similarity index 80% rename from src/KSFramework/Messaging/Behaviors/LoggingPostProcessor.cs rename to src/KSFramework/KSMessaging/Behaviors/LoggingPostProcessor.cs index 452e6a4..6491b89 100644 --- a/src/KSFramework/Messaging/Behaviors/LoggingPostProcessor.cs +++ b/src/KSFramework/KSMessaging/Behaviors/LoggingPostProcessor.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Messaging.Behaviors; +namespace KSFramework.KSMessaging.Behaviors; public class LoggingPostProcessor : IRequestPostProcessor { diff --git a/src/KSFramework/Messaging/Behaviors/LoggingPreProcessor.cs b/src/KSFramework/KSMessaging/Behaviors/LoggingPreProcessor.cs similarity index 77% rename from src/KSFramework/Messaging/Behaviors/LoggingPreProcessor.cs rename to src/KSFramework/KSMessaging/Behaviors/LoggingPreProcessor.cs index aa5b028..c4b79db 100644 --- a/src/KSFramework/Messaging/Behaviors/LoggingPreProcessor.cs +++ b/src/KSFramework/KSMessaging/Behaviors/LoggingPreProcessor.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Messaging.Behaviors; +namespace KSFramework.KSMessaging.Behaviors; public class LoggingPreProcessor : IRequestPreProcessor { diff --git a/src/KSFramework/Messaging/Behaviors/NotificationLoggingBehavior.cs b/src/KSFramework/KSMessaging/Behaviors/NotificationLoggingBehavior.cs similarity index 86% rename from src/KSFramework/Messaging/Behaviors/NotificationLoggingBehavior.cs rename to src/KSFramework/KSMessaging/Behaviors/NotificationLoggingBehavior.cs index ab5d181..1dbd1fd 100644 --- a/src/KSFramework/Messaging/Behaviors/NotificationLoggingBehavior.cs +++ b/src/KSFramework/KSMessaging/Behaviors/NotificationLoggingBehavior.cs @@ -1,9 +1,9 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.Logging; -namespace KSFramework.Messaging.Behaviors; +namespace KSFramework.KSMessaging.Behaviors; -public class NotificationLoggingBehavior : INotificationBehavior +public class NotificationLoggingBehavior : INotificationBehavior where TNotification : INotification { private readonly ILogger> _logger; diff --git a/src/KSFramework/Messaging/Behaviors/RequestProcessorBehavior.cs b/src/KSFramework/KSMessaging/Behaviors/RequestProcessorBehavior.cs similarity index 95% rename from src/KSFramework/Messaging/Behaviors/RequestProcessorBehavior.cs rename to src/KSFramework/KSMessaging/Behaviors/RequestProcessorBehavior.cs index fa06a7b..a91b00a 100644 --- a/src/KSFramework/Messaging/Behaviors/RequestProcessorBehavior.cs +++ b/src/KSFramework/KSMessaging/Behaviors/RequestProcessorBehavior.cs @@ -1,7 +1,7 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.Logging; -namespace KSFramework.Messaging.Behaviors; +namespace KSFramework.KSMessaging.Behaviors; /// /// Executes pre-processors and post-processors for the request. diff --git a/src/KSFramework/Messaging/Behaviors/RequestValidationBehavior.cs b/src/KSFramework/KSMessaging/Behaviors/RequestValidationBehavior.cs similarity index 80% rename from src/KSFramework/Messaging/Behaviors/RequestValidationBehavior.cs rename to src/KSFramework/KSMessaging/Behaviors/RequestValidationBehavior.cs index e066f1c..136e113 100644 --- a/src/KSFramework/Messaging/Behaviors/RequestValidationBehavior.cs +++ b/src/KSFramework/KSMessaging/Behaviors/RequestValidationBehavior.cs @@ -1,13 +1,9 @@ -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using FluentValidation; using FluentValidation.Results; using Microsoft.Extensions.Logging; -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Messaging.Behaviors +namespace KSFramework.KSMessaging.Behaviors { /// /// Pipeline behavior for validating requests using FluentValidation validators. @@ -16,7 +12,7 @@ namespace KSFramework.Messaging.Behaviors /// The type of the request. /// The type of the response. public class RequestValidationBehavior : IPipelineBehavior - where TRequest : IRequest // مطمئن شو این شرط در درخواستت وجود دارد + where TRequest : IRequest { private readonly IEnumerable> Validators; private readonly ILogger> Logger; @@ -49,7 +45,7 @@ public async Task Handle(TRequest request, CancellationToken cancella } } - // اگر ولیدیتور نبود یا اعتبارسنجی رد نشد، اجرای هندر ادامه پیدا کند + // If there is no validator or validation passes, continue handler execution return await next(); } } diff --git a/src/KSFramework/Messaging/Behaviors/StreamLoggingBehavior.cs b/src/KSFramework/KSMessaging/Behaviors/StreamLoggingBehavior.cs similarity index 87% rename from src/KSFramework/Messaging/Behaviors/StreamLoggingBehavior.cs rename to src/KSFramework/KSMessaging/Behaviors/StreamLoggingBehavior.cs index 93212e5..a8084a6 100644 --- a/src/KSFramework/Messaging/Behaviors/StreamLoggingBehavior.cs +++ b/src/KSFramework/KSMessaging/Behaviors/StreamLoggingBehavior.cs @@ -1,7 +1,8 @@ -using KSFramework.Messaging.Abstraction; +using System.Runtime.CompilerServices; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.Logging; -namespace KSFramework.Messaging.Behaviors; +namespace KSFramework.KSMessaging.Behaviors; /// /// A stream pipeline behavior that logs before and after streaming a request. @@ -25,7 +26,7 @@ public StreamLoggingBehavior(ILogger> /// public async IAsyncEnumerable Handle( TRequest request, - CancellationToken cancellationToken, + [EnumeratorCancellation] CancellationToken cancellationToken, StreamHandlerDelegate next) { _logger.LogInformation("Start streaming: {RequestType}", typeof(TRequest).Name); diff --git a/src/KSFramework/Messaging/Extensions/RegisterMediatorServices.cs b/src/KSFramework/KSMessaging/Extensions/RegisterMediatorServices.cs similarity index 94% rename from src/KSFramework/Messaging/Extensions/RegisterMediatorServices.cs rename to src/KSFramework/KSMessaging/Extensions/RegisterMediatorServices.cs index 40e1450..e6a187e 100644 --- a/src/KSFramework/Messaging/Extensions/RegisterMediatorServices.cs +++ b/src/KSFramework/KSMessaging/Extensions/RegisterMediatorServices.cs @@ -1,9 +1,9 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.DependencyInjection; using System.Reflection; -using KSFramework.Messaging.Behaviors; +using KSFramework.KSMessaging.Behaviors; -namespace KSFramework.Messaging.Extensions; +namespace KSFramework.KSMessaging.Extensions; /// /// Extension methods for registering mediator handlers and behaviors. diff --git a/src/KSFramework/Messaging/Mediator.cs b/src/KSFramework/KSMessaging/Mediator.cs similarity index 88% rename from src/KSFramework/Messaging/Mediator.cs rename to src/KSFramework/KSMessaging/Mediator.cs index cfc10b9..b3de658 100644 --- a/src/KSFramework/Messaging/Mediator.cs +++ b/src/KSFramework/KSMessaging/Mediator.cs @@ -1,7 +1,7 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.DependencyInjection; -namespace KSFramework.Messaging; +namespace KSFramework.KSMessaging; /// /// The default implementation of the mediator pattern. @@ -52,15 +52,16 @@ public async Task Send(IRequest request, Cancel var next = handlerDelegate; handlerDelegate = () => { - var method = behaviorType.GetMethod("Handle"); - return (Task)method.Invoke(currentBehavior, new object[] { request, cancellationToken, next }); + var method = behaviorType.GetMethod("Handle") ?? throw new InvalidOperationException("Handle method not found on behavior type."); + var result = method.Invoke(currentBehavior, new object[] { request, cancellationToken, next }) ?? throw new InvalidOperationException("Handle method returned null."); + return (Task)result; }; } // Execute the composed pipeline return await handlerDelegate(); } - + public async Task Send(object request, CancellationToken cancellationToken = default) { if (request == null) @@ -100,7 +101,7 @@ public async Task Send(IRequest request, Cancel var resultProperty = task.GetType().GetProperty("Result"); return resultProperty?.GetValue(task); } - + /// public async Task Publish(object notification, CancellationToken cancellationToken = default) { @@ -126,7 +127,7 @@ public async Task Publish(object notification, CancellationToken cancellationTok var genericMethod = method.MakeGenericMethod(notificationType); await (Task)genericMethod.Invoke(this, new object[] { notification, cancellationToken })!; } - + // /// // public async Task Send(object request, CancellationToken cancellationToken = default) // { @@ -135,7 +136,6 @@ public async Task Publish(object notification, CancellationToken cancellationTok // // var requestType = request.GetType(); // - // // پیدا کردن اینترفیس IRequest که روی request اعمال شده // var interfaceType = requestType // .GetInterfaces() // .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequest<>)); @@ -145,7 +145,6 @@ public async Task Publish(object notification, CancellationToken cancellationTok // // var responseType = interfaceType.GetGenericArguments()[0]; // - // // ساختن متد جنریک Send(IRequest) // var method = typeof(IMediator) // .GetMethods() // .FirstOrDefault(m => @@ -195,14 +194,15 @@ public async Task Publish(TNotification notification, Cancellatio var next = handlerDelegate; handlerDelegate = () => { - var method = behavior.GetType().GetMethod("Handle"); - return (Task)method.Invoke(currentBehavior, new object[] { notification, cancellationToken, next }); + var method = behavior.GetType().GetMethod("Handle") ?? throw new InvalidOperationException("Handle method not found on behavior type."); + var result = method.Invoke(currentBehavior, new object[] { notification, cancellationToken, next }) ?? throw new InvalidOperationException("Handle method returned null."); + return (Task)result; }; } await handlerDelegate(); } - + public IAsyncEnumerable CreateStream( IStreamRequest request, CancellationToken cancellationToken = default) @@ -238,12 +238,14 @@ public IAsyncEnumerable CreateStream( // Compose behaviors foreach (var behavior in behaviors) { + if (behavior == null) throw new ArgumentNullException(nameof(behavior)); var current = behavior; var next = handlerDelegate; handlerDelegate = () => { - var method = behavior.GetType().GetMethod("Handle"); - return (IAsyncEnumerable)method.Invoke(current, new object[] { request, cancellationToken, next })!; + var method = current.GetType().GetMethod("Handle") ?? throw new InvalidOperationException("Handle method not found on behavior type."); + var result = method.Invoke(current, new object[] { request, cancellationToken, next }) ?? throw new InvalidOperationException("Handle method returned null."); + return (IAsyncEnumerable)result; }; } diff --git a/src/KSFramework/Messaging/NotificationLoggingBehavior.cs b/src/KSFramework/KSMessaging/NotificationLoggingBehavior.cs similarity index 90% rename from src/KSFramework/Messaging/NotificationLoggingBehavior.cs rename to src/KSFramework/KSMessaging/NotificationLoggingBehavior.cs index 54c0838..b2f8a76 100644 --- a/src/KSFramework/Messaging/NotificationLoggingBehavior.cs +++ b/src/KSFramework/KSMessaging/NotificationLoggingBehavior.cs @@ -1,7 +1,7 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.Logging; -namespace KSFramework.Messaging; +namespace KSFramework.KSMessaging; public class NotificationLoggingBehavior : INotificationBehavior where TNotification : INotification diff --git a/src/KSFramework/Messaging/Samples/CounterStreamTest.cs b/src/KSFramework/KSMessaging/Samples/CounterStreamTest.cs similarity index 87% rename from src/KSFramework/Messaging/Samples/CounterStreamTest.cs rename to src/KSFramework/KSMessaging/Samples/CounterStreamTest.cs index dfde75b..7889388 100644 --- a/src/KSFramework/Messaging/Samples/CounterStreamTest.cs +++ b/src/KSFramework/KSMessaging/Samples/CounterStreamTest.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Messaging.Samples; +namespace KSFramework.KSMessaging.Samples; public class CounterStreamRequest : IStreamRequest { diff --git a/src/KSFramework/Messaging/Samples/LoggingNotificationBehavior.cs b/src/KSFramework/KSMessaging/Samples/LoggingNotificationBehavior.cs similarity index 85% rename from src/KSFramework/Messaging/Samples/LoggingNotificationBehavior.cs rename to src/KSFramework/KSMessaging/Samples/LoggingNotificationBehavior.cs index 70c4ec0..dbb0659 100644 --- a/src/KSFramework/Messaging/Samples/LoggingNotificationBehavior.cs +++ b/src/KSFramework/KSMessaging/Samples/LoggingNotificationBehavior.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Messaging.Samples; +namespace KSFramework.KSMessaging.Samples; public class LoggingNotificationBehavior : INotificationBehavior where TNotification : INotification { diff --git a/src/KSFramework/Messaging/Samples/MultiplyByTwoRequest.cs b/src/KSFramework/KSMessaging/Samples/MultiplyByTwoRequest.cs similarity index 81% rename from src/KSFramework/Messaging/Samples/MultiplyByTwoRequest.cs rename to src/KSFramework/KSMessaging/Samples/MultiplyByTwoRequest.cs index 9116a5a..69ecc26 100644 --- a/src/KSFramework/Messaging/Samples/MultiplyByTwoRequest.cs +++ b/src/KSFramework/KSMessaging/Samples/MultiplyByTwoRequest.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Messaging.Samples; +namespace KSFramework.KSMessaging.Samples; public class MultiplyByTwoRequest : IRequest { diff --git a/src/KSFramework/Messaging/Samples/NotificationTest.cs b/src/KSFramework/KSMessaging/Samples/NotificationTest.cs similarity index 82% rename from src/KSFramework/Messaging/Samples/NotificationTest.cs rename to src/KSFramework/KSMessaging/Samples/NotificationTest.cs index c84f2a3..ae6659a 100644 --- a/src/KSFramework/Messaging/Samples/NotificationTest.cs +++ b/src/KSFramework/KSMessaging/Samples/NotificationTest.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Messaging.Samples; +namespace KSFramework.KSMessaging.Samples; public class TestNotification : INotification { diff --git a/src/KSFramework/Messaging/Samples/UserRegisteredHandler.cs b/src/KSFramework/KSMessaging/Samples/UserRegisteredHandler.cs similarity index 88% rename from src/KSFramework/Messaging/Samples/UserRegisteredHandler.cs rename to src/KSFramework/KSMessaging/Samples/UserRegisteredHandler.cs index 3bbd816..ae9c9d8 100644 --- a/src/KSFramework/Messaging/Samples/UserRegisteredHandler.cs +++ b/src/KSFramework/KSMessaging/Samples/UserRegisteredHandler.cs @@ -1,7 +1,7 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.Logging; -namespace KSFramework.Messaging.Samples; +namespace KSFramework.KSMessaging.Samples; public class SendWelcomeEmailHandler : INotificationHandler { diff --git a/src/KSFramework/Messaging/Samples/UserRegisteredNotification.cs b/src/KSFramework/KSMessaging/Samples/UserRegisteredNotification.cs similarity index 76% rename from src/KSFramework/Messaging/Samples/UserRegisteredNotification.cs rename to src/KSFramework/KSMessaging/Samples/UserRegisteredNotification.cs index fcfba17..63f60cd 100644 --- a/src/KSFramework/Messaging/Samples/UserRegisteredNotification.cs +++ b/src/KSFramework/KSMessaging/Samples/UserRegisteredNotification.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; -namespace KSFramework.Messaging.Samples; +namespace KSFramework.KSMessaging.Samples; /// /// Notification that is triggered when a user registers. diff --git a/src/KSFramework/Messaging/Configuration/ServiceCollectionExtensions.cs b/src/KSFramework/Messaging/Configuration/ServiceCollectionExtensions.cs deleted file mode 100644 index 81889ec..0000000 --- a/src/KSFramework/Messaging/Configuration/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -// using KSFramework.Messaging.Abstraction; -// using Microsoft.Extensions.DependencyInjection; -// using System.Reflection; -// -// namespace KSFramework.Messaging.Configuration -// { -// /// -// /// Extension methods to register messaging components into the service collection. -// /// -// public static class ServiceCollectionExtensions -// { -// /// -// /// Registers mediator handlers and behaviors from specified assemblies with automatic discovery. -// /// -// public static IServiceCollection AddKSMediator(this IServiceCollection services, params Assembly[] assemblies) -// { -// services.AddScoped(); -// services.AddScoped(sp => sp.GetRequiredService()); -// -// services.Scan(scan => scan -// .FromAssemblies(assemblies) -// -// // Request Handlers -// .AddClasses(c => c.AssignableTo(typeof(IRequestHandler<,>))) -// .AsImplementedInterfaces() -// .WithScopedLifetime() -// -// // Notification Handlers -// .AddClasses(c => c.AssignableTo(typeof(INotificationHandler<>))) -// .AsImplementedInterfaces() -// .WithScopedLifetime() -// -// // Pipeline Behaviors -// .AddClasses(c => c.AssignableTo(typeof(IPipelineBehavior<,>))) -// .AsImplementedInterfaces() -// .WithScopedLifetime() -// -// // Notification Behaviors -// .AddClasses(c => c.AssignableTo(typeof(INotificationBehavior<>))) -// .AsImplementedInterfaces() -// .WithScopedLifetime() -// ); -// -// return services; -// } -// } -// } \ No newline at end of file diff --git a/src/KSFramework/Pagination/PaginatedList.cs b/src/KSFramework/Pagination/PaginatedList.cs index c61a392..7a053a8 100644 --- a/src/KSFramework/Pagination/PaginatedList.cs +++ b/src/KSFramework/Pagination/PaginatedList.cs @@ -37,8 +37,8 @@ public bool HasNextPage } } - public static async Task> CreateAsync(IQueryable source, int pageIndex, int pageSize,Expression> where = null, - string orderBy = "", bool desc = false) + public static async Task> CreateAsync(IQueryable source, int pageIndex, int pageSize, Expression>? where = null, + string? orderBy = "", bool desc = false) { if(where is null) where = x => true; @@ -59,8 +59,8 @@ public static async Task> CreateAsync(IQueryable source, int return new PaginatedList(items, count, pageIndex, pageSize); } - public static PaginatedList Create(IQueryable source, int pageIndex, int pageSize,Expression> where = null, - string orderBy = "", bool desc = false) + public static PaginatedList Create(IQueryable source, int pageIndex, int pageSize, Expression>? where = null, + string? orderBy = "", bool desc = false) { if(where is null) where = x => true; diff --git a/src/KSFramework/TagHelpers/ActiveRouteTagHelper.cs b/src/KSFramework/TagHelpers/ActiveRouteTagHelper.cs index a080a69..95268cd 100644 --- a/src/KSFramework/TagHelpers/ActiveRouteTagHelper.cs +++ b/src/KSFramework/TagHelpers/ActiveRouteTagHelper.cs @@ -11,18 +11,18 @@ public class ActiveRouteTagHelper : TagHelper private const string ActiveRouteClassValue = "active-route-class"; [HtmlAttributeName(ActiveRouteClassValue)] - public string ActiveRouteCssClass { get; set; } - private IDictionary _routeValues; + public string ActiveRouteCssClass { get; set; } = string.Empty; + private IDictionary? _routeValues; /// The name of the action method. /// Must be null if is non-null. [HtmlAttributeName("asp-action")] - public string Action { get; set; } + public string? Action { get; set; } /// The name of the controller. /// Must be null if is non-null. [HtmlAttributeName("asp-controller")] - public string Controller { get; set; } + public string? Controller { get; set; } /// Additional parameters for the route. [HtmlAttributeName("asp-all-route-data", DictionaryAttributePrefix = "asp-route-")] @@ -30,14 +30,10 @@ public IDictionary RouteValues { get { - if (this._routeValues == null) - this._routeValues = (IDictionary)new Dictionary((IEqualityComparer)StringComparer.OrdinalIgnoreCase); - return this._routeValues; - } - set - { - this._routeValues = value; + _routeValues ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + return _routeValues; } + set => _routeValues = value; } /// @@ -45,7 +41,7 @@ public IDictionary RouteValues /// [HtmlAttributeNotBound] [ViewContext] - public ViewContext ViewContext { get; set; } + public required ViewContext ViewContext { get; set; } public override void Process(TagHelperContext context, TagHelperOutput output) { @@ -61,15 +57,15 @@ public override void Process(TagHelperContext context, TagHelperOutput output) private bool ShouldBeActive() { - string currentController = ViewContext.RouteData.Values["Controller"].ToString(); - string currentAction = ViewContext.RouteData.Values["Action"].ToString(); + var currentController = ViewContext.RouteData.Values["Controller"]?.ToString() ?? string.Empty; + var currentAction = ViewContext.RouteData.Values["Action"]?.ToString() ?? string.Empty; - if (!string.IsNullOrWhiteSpace(Controller) && Controller.ToLower() != currentController.ToLower()) + if (!string.IsNullOrWhiteSpace(Controller) && !string.Equals(Controller, currentController, StringComparison.OrdinalIgnoreCase)) { return false; } - if (!string.IsNullOrWhiteSpace(Action) && Action.ToLower() != currentAction.ToLower()) + if (!string.IsNullOrWhiteSpace(Action) && !string.Equals(Action, currentAction, StringComparison.OrdinalIgnoreCase)) { return false; } @@ -94,11 +90,16 @@ private void MakeActive(TagHelperOutput output) classAttr = new TagHelperAttribute("class", ActiveRouteCssClass); output.Attributes.Add(classAttr); } - else if (classAttr.Value == null || classAttr.Value.ToString().IndexOf(ActiveRouteCssClass) < 0) + else { - output.Attributes.SetAttribute("class", classAttr.Value == null - ? ActiveRouteCssClass - : classAttr.Value.ToString() + " " + ActiveRouteCssClass); + var currentClasses = classAttr.Value?.ToString() ?? string.Empty; + if (!currentClasses.Contains(ActiveRouteCssClass)) + { + var newClasses = string.IsNullOrEmpty(currentClasses) + ? ActiveRouteCssClass + : $"{currentClasses} {ActiveRouteCssClass}"; + output.Attributes.SetAttribute("class", newClasses); + } } } } diff --git a/src/KSFramework/Utilities/IdentityExtensions.cs b/src/KSFramework/Utilities/IdentityExtensions.cs index 22da535..3a106f6 100644 --- a/src/KSFramework/Utilities/IdentityExtensions.cs +++ b/src/KSFramework/Utilities/IdentityExtensions.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Security.Claims; using System.Security.Principal; @@ -16,17 +16,17 @@ public static class IdentityExtensions return claimsIdentity?.FindFirstValue(claimType); } - public static string? GetUserId(this IIdentity identity) + public static string? GetUserId(this IIdentity? identity) { return identity?.FindFirstValue(ClaimTypes.NameIdentifier); } - public static T GetUserId(this IIdentity identity) where T : IConvertible + public static T? GetUserId(this IIdentity? identity) where T : IConvertible { var userId = identity?.GetUserId(); return userId.HasValue() ? (T)Convert.ChangeType(userId, typeof(T), CultureInfo.InvariantCulture) - : default(T); + : default; } public static string? GetUserName(this IIdentity identity) diff --git a/src/KSFramework/Utilities/LinqExtentions.cs b/src/KSFramework/Utilities/LinqExtentions.cs index 713dca4..53b0146 100644 --- a/src/KSFramework/Utilities/LinqExtentions.cs +++ b/src/KSFramework/Utilities/LinqExtentions.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; namespace KSFramework.Utilities; public static class ExtendLinq @@ -39,5 +39,5 @@ public int Skip public class ResponseCollection { public int Count { get; set; } - public List Result { get; set; } + public List Result { get; set; } = new List(); } \ No newline at end of file diff --git a/src/KSFramework/Utilities/ModelBuilderExtensions.cs b/src/KSFramework/Utilities/ModelBuilderExtensions.cs index 5efe99a..11bab21 100644 --- a/src/KSFramework/Utilities/ModelBuilderExtensions.cs +++ b/src/KSFramework/Utilities/ModelBuilderExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Pluralize.NET; using System.Reflection; @@ -15,8 +15,11 @@ public static void AddSingularizingTableNameConvention(this ModelBuilder modelBu Pluralizer pluralizer = new Pluralizer(); foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) { - string tableName = entityType.GetTableName(); - entityType.SetTableName(pluralizer.Singularize(tableName)); + string? tableName = entityType.GetTableName(); + if (tableName != null) + { + entityType.SetTableName(pluralizer.Singularize(tableName)); + } } } @@ -29,8 +32,11 @@ public static void AddPluralizingTableNameConvention(this ModelBuilder modelBuil Pluralizer pluralizer = new Pluralizer(); foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) { - string tableName = entityType.GetTableName(); - entityType.SetTableName(pluralizer.Pluralize(tableName)); + string? tableName = entityType.GetTableName(); + if (tableName != null) + { + entityType.SetTableName(pluralizer.Pluralize(tableName)); + } } } @@ -55,7 +61,7 @@ public static void AddDefaultValueSqlConvention(this ModelBuilder modelBuilder, { foreach (IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) { - IMutableProperty property = entityType.GetProperties().SingleOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); + var property = entityType.GetProperties().FirstOrDefault(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)); if (property != null && property.ClrType == propertyType) property.SetDefaultValueSql(defaultValueSql); } @@ -93,7 +99,8 @@ public static void RegisterEntityTypeConfiguration(this ModelBuilder modelBuilde if (iface.IsConstructedGenericType && iface.GetGenericTypeDefinition() == typeof(IEntityTypeConfiguration<>)) { MethodInfo applyConcreteMethod = applyGenericMethod.MakeGenericMethod(iface.GenericTypeArguments[0]); - applyConcreteMethod.Invoke(modelBuilder, new object[] { Activator.CreateInstance(type) }); + var instance = Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of type {type.FullName}"); + applyConcreteMethod.Invoke(modelBuilder, new object[] { instance }); } } } diff --git a/src/KSFramework/Utilities/StringExtensions.cs b/src/KSFramework/Utilities/StringExtensions.cs index 8c31a67..d3a0075 100644 --- a/src/KSFramework/Utilities/StringExtensions.cs +++ b/src/KSFramework/Utilities/StringExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Net; using System.Text; @@ -74,7 +74,7 @@ public static string ToSnakeCase(this string str) return string.Concat(str.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower(); } - public static bool HasValue(this string value, bool ignoreWhiteSpace = true) + public static bool HasValue(this string? value, bool ignoreWhiteSpace = true) { return ignoreWhiteSpace ? !string.IsNullOrWhiteSpace(value) : !string.IsNullOrEmpty(value); } @@ -101,8 +101,8 @@ public static string ToNumeric(this decimal value) public static string ToCurrency(this int value) { - //fa-IR => current culture currency symbol => ریال - //123456 => "123,123ریال" + //fa-IR => current culture currency symbol +//123456 => "123,123" return value.ToString("C0"); } @@ -180,11 +180,13 @@ public static string FixPersianChars(this string str) .Replace("ي", "ی") .Replace(" ", " ") .Replace("‌", " ") - .Replace("ھ", "ه");//.Replace("ئ", "ی"); + .Replace("ھ", "ه"); } - public static string CleanString(this string str) + public static string? CleanString(this string? str) { + if (string.IsNullOrEmpty(str)) + return null; return str.Trim().FixPersianChars().Fa2En().NullIfEmpty(); } @@ -222,9 +224,11 @@ public static string TruncateString(this string str, int start, int maxLength) } #endregion - public static string NullIfEmpty(this string str) + public static string? NullIfEmpty(this string? str) { - return str?.Length == 0 ? null : str; + if (string.IsNullOrEmpty(str)) + return null; + return str; } public static string HtmlToPlainText(this string str) diff --git a/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/GenericRepositoryTests.cs b/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/GenericRepositoryTests.cs new file mode 100644 index 0000000..d77951b --- /dev/null +++ b/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/GenericRepositoryTests.cs @@ -0,0 +1,87 @@ +using KSFramework.GenericRepository; +using KSFramework.KSDomain; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace KSFramework.IntegrationTests.GenericRepository; + +public class TestEntity : Entity, KSDomain.AggregatesHelper.IAggregateRoot +{ + public string Name { get; set; } = string.Empty; +} + +public class TestDbContext : DbContext +{ + public TestDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet TestEntities { get; set; } = null!; +} + +public class GenericRepositoryTests : IntegrationTestBase +{ + public GenericRepositoryTests() + { + } + + protected override void ConfigureServices(IServiceCollection services) + { + base.ConfigureServices(services); + + + + services.AddScoped, TestGenericRepository>(); + } + + [Fact] + public async Task AddAsync_ShouldAddEntityToDatabase() + { + using var scope = CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var repository = scope.ServiceProvider.GetRequiredService>(); + var unitOfWork = scope.ServiceProvider.GetRequiredService(); + + // Arrange + var entity = new TestEntity + { + Id = Guid.NewGuid(), + Name = "Test Entity" + }; + + // Act + await repository.AddAsync(entity); + await unitOfWork.SaveChangesAsync(); + + // Assert + var savedEntity = await dbContext.TestEntities.FirstOrDefaultAsync(e => e.Id == entity.Id); + Assert.NotNull(savedEntity); + Assert.Equal("Test Entity", savedEntity.Name); + } + + [Fact] + public async Task GetByIdAsync_ShouldReturnEntity() + { + using var scope = CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var repository = scope.ServiceProvider.GetRequiredService>(); + var unitOfWork = scope.ServiceProvider.GetRequiredService(); + + // Arrange + var entity = new TestEntity + { + Id = Guid.NewGuid(), + Name = "Test Entity" + }; + await dbContext.TestEntities.AddAsync(entity); + await dbContext.SaveChangesAsync(); + + // Act + var result = await repository.GetByIdAsync(entity.Id); + + // Assert + Assert.NotNull(result); + Assert.Equal(entity.Id, result.Id); + Assert.Equal("Test Entity", result.Name); + } +} \ No newline at end of file diff --git a/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/TestGenericRepository.cs b/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/TestGenericRepository.cs new file mode 100644 index 0000000..f5b86e8 --- /dev/null +++ b/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/TestGenericRepository.cs @@ -0,0 +1,5 @@ +using KSFramework.GenericRepository; + +namespace KSFramework.IntegrationTests.GenericRepository; + +public class TestGenericRepository(TestDbContext context) : GenericRepository(context); \ No newline at end of file diff --git a/tests/KSFramework/KSFramework.IntegrationTests/IntegrationTestBase.cs b/tests/KSFramework/KSFramework.IntegrationTests/IntegrationTestBase.cs new file mode 100644 index 0000000..654b482 --- /dev/null +++ b/tests/KSFramework/KSFramework.IntegrationTests/IntegrationTestBase.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.EntityFrameworkCore; +using KSFramework.GenericRepository; +using KSFramework.IntegrationTests.GenericRepository; + +namespace KSFramework.IntegrationTests; + +public abstract class IntegrationTestBase : IDisposable +{ + protected readonly Lazy _serviceProviderLazy; + protected readonly TestServer TestServer; + + protected IntegrationTestBase() + { + var webHostBuilder = new WebHostBuilder() + .Configure(app => + { + // Configure the application here if needed + }) + .ConfigureServices(services => + { + this.ConfigureServices(services); + }); + + TestServer = new TestServer(webHostBuilder); + _serviceProviderLazy = new Lazy(() => TestServer.Services); + } + + protected virtual void ConfigureServices(IServiceCollection services) + { + // Base configuration for all tests + services.AddLogging(); + + // Configure in-memory database for testing + services.AddDbContext(options => + { + options.UseInMemoryDatabase("TestDatabase"); + }); + + + + + + services.AddScoped(); + + // Register UnitOfWork and IUnitOfWork + services.AddScoped(); + } + + protected IServiceScope CreateScope() + { + return _serviceProviderLazy.Value.CreateScope(); + } + + protected T GetService() where T : class + { + using var scope = CreateScope(); + return scope.ServiceProvider.GetRequiredService(); + } + + public void Dispose() + { + TestServer?.Dispose(); + } +} \ No newline at end of file diff --git a/tests/KSFramework/KSFramework.IntegrationTests/KSFramework.IntegrationTests.csproj b/tests/KSFramework/KSFramework.IntegrationTests/KSFramework.IntegrationTests.csproj new file mode 100644 index 0000000..648a852 --- /dev/null +++ b/tests/KSFramework/KSFramework.IntegrationTests/KSFramework.IntegrationTests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/KSFramework/KSFramework.IntegrationTests/Messaging/MediatorTests.cs b/tests/KSFramework/KSFramework.IntegrationTests/Messaging/MediatorTests.cs new file mode 100644 index 0000000..70025e4 --- /dev/null +++ b/tests/KSFramework/KSFramework.IntegrationTests/Messaging/MediatorTests.cs @@ -0,0 +1,77 @@ +using KSFramework.KSMessaging; +using KSFramework.KSMessaging.Abstraction; +using Microsoft.Extensions.DependencyInjection; + +namespace KSFramework.IntegrationTests.Messaging; + +public record TestCommand : ICommand +{ + public string Data { get; init; } = string.Empty; +} + +public class TestCommandHandler : ICommandHandler +{ + public Task Handle(TestCommand command, CancellationToken cancellationToken = default) + { + return Task.FromResult($"Handled: {command.Data}"); + } +} + +public record TestQuery : IQuery +{ + public string Data { get; init; } = string.Empty; +} + +public class TestQueryHandler : IQueryHandler +{ + public Task Handle(TestQuery query, CancellationToken cancellationToken = default) + { + return Task.FromResult($"Queried: {query.Data}"); + } +} + +public class MediatorTests : IntegrationTestBase +{ + public MediatorTests() + { + } + + protected override void ConfigureServices(IServiceCollection services) + { + base.ConfigureServices(services); + + services.AddScoped, TestCommandHandler>(); + services.AddScoped, TestQueryHandler>(); + services.AddScoped(); + } + + [Fact] + public async Task SendCommand_ShouldExecuteCommandHandler() + { + // Arrange + using var scope = CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + var command = new TestCommand { Data = "Test Command" }; + + // Act + var result = await mediator.Send(command); + + // Assert + Assert.Equal("Handled: Test Command", result); + } + + [Fact] + public async Task SendQuery_ShouldExecuteQueryHandler() + { + // Arrange + using var scope = CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + var query = new TestQuery { Data = "Test Query" }; + + // Act + var result = await mediator.Send(query); + + // Assert + Assert.Equal("Queried: Test Query", result); + } +} \ No newline at end of file diff --git a/tests/KSFramework/KSFramework.IntegrationTests/Performance/PerformanceTests.cs b/tests/KSFramework/KSFramework.IntegrationTests/Performance/PerformanceTests.cs new file mode 100644 index 0000000..b05314b --- /dev/null +++ b/tests/KSFramework/KSFramework.IntegrationTests/Performance/PerformanceTests.cs @@ -0,0 +1,135 @@ +using System.Diagnostics; +using KSFramework.GenericRepository; +using KSFramework.KSDomain.AggregatesHelper; +using KSFramework.IntegrationTests.GenericRepository; +using KSFramework.KSDomain; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace KSFramework.IntegrationTests.Performance; + +public class PerformanceTests : IntegrationTestBase +{ + private readonly IGenericRepository _repository; + private readonly TestDbContext _dbContext; + private readonly Stopwatch _stopwatch; + private readonly IUnitOfWork _unitOfWork; + + public PerformanceTests() + { + using var scope = CreateScope(); + _dbContext = scope.ServiceProvider.GetRequiredService(); + _repository = scope.ServiceProvider.GetRequiredService>(); + _stopwatch = new Stopwatch(); + _unitOfWork = scope.ServiceProvider.GetRequiredService(); + } + + protected override void ConfigureServices(IServiceCollection services) + { + base.ConfigureServices(services); + + services.AddDbContext(options => + options.UseInMemoryDatabase("PerformanceTestDb")); + + services.AddScoped, GenericRepository>(); + } + + [Fact] + public async Task BulkInsert_ShouldCompleteWithinTimeLimit() + { + // Arrange + const int numberOfEntities = 1000; + const int acceptableMilliseconds = 2000; // 2 seconds + var entities = Enumerable.Range(1, numberOfEntities) + .Select(i => new TestEntity + { + Id = Guid.NewGuid(), + Name = $"Performance Test Entity {i}" + }) + .ToList(); + + // Act + _stopwatch.Start(); + + foreach (var entity in entities) + { + await _repository.AddAsync(entity); + } + await _unitOfWork.SaveChangesAsync(); + + _stopwatch.Stop(); + + // Assert + Assert.True(_stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, + $"Bulk insert took {_stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); + } + + [Fact] + public async Task QueryPerformance_ShouldCompleteWithinTimeLimit() + { + // Arrange + const int numberOfQueries = 100; + const int acceptableMilliseconds = 1000; // 1 second + var entity = new TestEntity + { + Id = Guid.NewGuid(), + Name = "Performance Query Test Entity" + }; + await _repository.AddAsync(entity); + await _unitOfWork.SaveChangesAsync(); + + // Act + _stopwatch.Start(); + + for (int i = 0; i < numberOfQueries; i++) + { + await _repository.GetByIdAsync(entity.Id); + } + + _stopwatch.Stop(); + + // Assert + Assert.True(_stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, + $"Multiple queries took {_stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); + } + + [Fact] + public async Task ConcurrentOperations_ShouldHandleEfficiently() + { + // Arrange + const int numberOfTasks = 50; + const int acceptableMilliseconds = 3000; // 3 seconds + var tasks = new List(); + + // Act + _stopwatch.Start(); + + for (int i = 0; i < numberOfTasks; i++) + { + tasks.Add(Task.Run(async () => + { + using (var scope = CreateScope()) + { + var scopedUnitOfWork = scope.ServiceProvider.GetRequiredService(); + var scopedRepository = scope.ServiceProvider.GetRequiredService>(); + + var entity = new TestEntity + { + Id = Guid.NewGuid(), + Name = $"Concurrent Test Entity {Guid.NewGuid()}" + }; + await scopedRepository.AddAsync(entity); + await scopedUnitOfWork.SaveChangesAsync(); + await scopedRepository.GetByIdAsync(entity.Id); + } + })); + } + + await Task.WhenAll(tasks); + _stopwatch.Stop(); + + // Assert + Assert.True(_stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, + $"Concurrent operations took {_stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); + } +} \ No newline at end of file diff --git a/tests/KSFramework/KSFramework.IntegrationTests/UnitTest1.cs b/tests/KSFramework/KSFramework.IntegrationTests/UnitTest1.cs new file mode 100644 index 0000000..a493178 --- /dev/null +++ b/tests/KSFramework/KSFramework.IntegrationTests/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace KSFramework.IntegrationTests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} diff --git a/tests/KSFramework/KSFramework.UnitTests/Behaviors/ExceptionHandling/ExceptionHandlingBehaviorTests.cs b/tests/KSFramework/KSFramework.UnitTests/Behaviors/ExceptionHandling/ExceptionHandlingBehaviorTests.cs index 8985cf5..e627133 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Behaviors/ExceptionHandling/ExceptionHandlingBehaviorTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Behaviors/ExceptionHandling/ExceptionHandlingBehaviorTests.cs @@ -1,5 +1,5 @@ -using KSFramework.Messaging.Abstraction; -using KSFramework.Messaging.Behaviors; +using KSFramework.KSMessaging.Abstraction; +using KSFramework.KSMessaging.Behaviors; using Microsoft.Extensions.Logging; using Moq; diff --git a/tests/KSFramework/KSFramework.UnitTests/Behaviors/RequestProcessing/RequestProcessorBehaviorTests.cs b/tests/KSFramework/KSFramework.UnitTests/Behaviors/RequestProcessing/RequestProcessorBehaviorTests.cs index 7b38757..698aef0 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Behaviors/RequestProcessing/RequestProcessorBehaviorTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Behaviors/RequestProcessing/RequestProcessorBehaviorTests.cs @@ -1,5 +1,5 @@ -using KSFramework.Messaging.Abstraction; -using KSFramework.Messaging.Behaviors; +using KSFramework.KSMessaging.Abstraction; +using KSFramework.KSMessaging.Behaviors; using Microsoft.Extensions.Logging; using Moq; diff --git a/tests/KSFramework/KSFramework.UnitTests/Behaviors/RequestValidation/RequestValidationBehaviorTests.cs b/tests/KSFramework/KSFramework.UnitTests/Behaviors/RequestValidation/RequestValidationBehaviorTests.cs index 747785d..5e014d1 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Behaviors/RequestValidation/RequestValidationBehaviorTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Behaviors/RequestValidation/RequestValidationBehaviorTests.cs @@ -1,7 +1,7 @@ using FluentValidation; using FluentValidation.Results; -using KSFramework.Messaging.Abstraction; -using KSFramework.Messaging.Behaviors; +using KSFramework.KSMessaging.Abstraction; +using KSFramework.KSMessaging.Behaviors; using Microsoft.Extensions.Logging; using Moq; diff --git a/tests/KSFramework/KSFramework.UnitTests/Configuration/AddKSMediatorNotificationTests.cs b/tests/KSFramework/KSFramework.UnitTests/Configuration/AddKSMediatorNotificationTests.cs index 0ea5736..045e36d 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Configuration/AddKSMediatorNotificationTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Configuration/AddKSMediatorNotificationTests.cs @@ -1,5 +1,5 @@ -using KSFramework.Messaging.Abstraction; -using KSFramework.Messaging.Extensions; +using KSFramework.KSMessaging.Abstraction; +using KSFramework.KSMessaging.Extensions; using Microsoft.Extensions.DependencyInjection; namespace KSFramework.UnitTests.Configuration; diff --git a/tests/KSFramework/KSFramework.UnitTests/Configuration/AddKSMediatorTests.cs b/tests/KSFramework/KSFramework.UnitTests/Configuration/AddKSMediatorTests.cs index df048c5..32be322 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Configuration/AddKSMediatorTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Configuration/AddKSMediatorTests.cs @@ -1,6 +1,6 @@ -using KSFramework.Messaging.Abstraction; -using KSFramework.Messaging.Extensions; -using KSFramework.Messaging.Samples; +using KSFramework.KSMessaging.Abstraction; +using KSFramework.KSMessaging.Extensions; +using KSFramework.KSMessaging.Samples; using Microsoft.Extensions.DependencyInjection; namespace KSFramework.UnitTests.Configuration; @@ -8,7 +8,7 @@ namespace KSFramework.UnitTests.Configuration; public class AddKSMediatorTests { [Fact] - public void AddKSMediator_RegistersHandlersAndBehaviorsFromAssembly() + public async Task AddKSMediator_RegistersHandlersAndBehaviorsFromAssembly() { // Arrange var services = new ServiceCollection(); @@ -23,7 +23,7 @@ public void AddKSMediator_RegistersHandlersAndBehaviorsFromAssembly() // Assert Assert.NotNull(handler); - var result = handler.Handle(new MultiplyByTwoRequest(5), CancellationToken.None).Result; + var result = await handler.Handle(new MultiplyByTwoRequest(5), CancellationToken.None); Assert.Equal(10, result); } } \ No newline at end of file diff --git a/tests/KSFramework/KSFramework.UnitTests/ExceptionHandlingBehaviorTests.cs b/tests/KSFramework/KSFramework.UnitTests/ExceptionHandlingBehaviorTests.cs index 7475f5d..3e77c7d 100644 --- a/tests/KSFramework/KSFramework.UnitTests/ExceptionHandlingBehaviorTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/ExceptionHandlingBehaviorTests.cs @@ -1,8 +1,8 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; namespace KSFramework.UnitTests; -using KSFramework.Messaging.Behaviors; +using KSFramework.KSMessaging.Behaviors; using Microsoft.Extensions.Logging; using Moq; using System; diff --git a/tests/KSFramework/KSFramework.UnitTests/KSFramework.UnitTests.csproj b/tests/KSFramework/KSFramework.UnitTests/KSFramework.UnitTests.csproj index 0d84793..202bda2 100644 --- a/tests/KSFramework/KSFramework.UnitTests/KSFramework.UnitTests.csproj +++ b/tests/KSFramework/KSFramework.UnitTests/KSFramework.UnitTests.csproj @@ -1,7 +1,7 @@ - + - net10.0 + net8.0 enable enable false @@ -20,8 +20,8 @@ - - + + diff --git a/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/MediatorPublishTests.cs b/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/MediatorPublishTests.cs index cf20ce2..2809482 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/MediatorPublishTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/MediatorPublishTests.cs @@ -1,4 +1,4 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.DependencyInjection; namespace KSFramework.UnitTests.Mediator.Publish; @@ -56,7 +56,7 @@ public async Task Publish_InvokesNotificationHandlers() services.AddSingleton>(handler); services.AddLogging(); - services.AddSingleton(); + services.AddSingleton(); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); @@ -79,7 +79,7 @@ public async Task Publish_ExecutesNotificationBehavior() services.AddSingleton>(handler); services.AddSingleton>(behavior); services.AddLogging(); - services.AddSingleton(); + services.AddSingleton(); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService(); diff --git a/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/SapmleNotification.cs b/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/SapmleNotification.cs index b5569b3..62f3255 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/SapmleNotification.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/SapmleNotification.cs @@ -1,4 +1,4 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; namespace KSFramework.UnitTests.Mediator.Publish; diff --git a/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/TestNotificationBehavior.cs b/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/TestNotificationBehavior.cs index 18857fe..5bdb107 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/TestNotificationBehavior.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/TestNotificationBehavior.cs @@ -1,4 +1,4 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; namespace KSFramework.UnitTests.Mediator.Publish; diff --git a/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/TestNotificationHandler.cs b/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/TestNotificationHandler.cs index 06b3d9d..e9a92ea 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/TestNotificationHandler.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Mediator/Publish/TestNotificationHandler.cs @@ -1,4 +1,4 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; namespace KSFramework.UnitTests.Mediator.Publish; diff --git a/tests/KSFramework/KSFramework.UnitTests/Mediator/Registration/AddKSMediatorRegistrationTests.cs b/tests/KSFramework/KSFramework.UnitTests/Mediator/Registration/AddKSMediatorRegistrationTests.cs index dd98ef5..901dedb 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Mediator/Registration/AddKSMediatorRegistrationTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Mediator/Registration/AddKSMediatorRegistrationTests.cs @@ -1,5 +1,5 @@ -using KSFramework.Messaging.Abstraction; -using KSFramework.Messaging.Extensions; +using KSFramework.KSMessaging.Abstraction; +using KSFramework.KSMessaging.Extensions; using Microsoft.Extensions.DependencyInjection; namespace KSFramework.UnitTests.Mediator.Registration; diff --git a/tests/KSFramework/KSFramework.UnitTests/RequestValidationBehaviorTests.cs b/tests/KSFramework/KSFramework.UnitTests/RequestValidationBehaviorTests.cs index cdf7810..1592269 100644 --- a/tests/KSFramework/KSFramework.UnitTests/RequestValidationBehaviorTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/RequestValidationBehaviorTests.cs @@ -2,8 +2,8 @@ using FluentValidation.Results; using Microsoft.Extensions.Logging; using Moq; -using KSFramework.Messaging.Behaviors; -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Behaviors; +using KSFramework.KSMessaging.Abstraction; namespace KSFramework.UnitTests { diff --git a/tests/KSFramework/KSFramework.UnitTests/Stream/CreateStream/MediatorCreateStreamTests.cs b/tests/KSFramework/KSFramework.UnitTests/Stream/CreateStream/MediatorCreateStreamTests.cs index c4fc82a..f0e89ff 100644 --- a/tests/KSFramework/KSFramework.UnitTests/Stream/CreateStream/MediatorCreateStreamTests.cs +++ b/tests/KSFramework/KSFramework.UnitTests/Stream/CreateStream/MediatorCreateStreamTests.cs @@ -1,4 +1,4 @@ -using KSFramework.Messaging.Abstraction; +using KSFramework.KSMessaging.Abstraction; using Microsoft.Extensions.DependencyInjection; namespace KSFramework.UnitTests.Stream.CreateStream; @@ -17,7 +17,7 @@ public class StreamRequest : IStreamRequest /// /// A handler for that streams integers from 1 to Count. -/// + /// public class StreamHandler : IStreamRequestHandler { public async IAsyncEnumerable Handle(StreamRequest request, CancellationToken cancellationToken) @@ -32,7 +32,7 @@ public async IAsyncEnumerable Handle(StreamRequest request, CancellationTok /// /// A simple logging behavior for streams that logs before and after streaming. -/// + /// public class LoggingBehavior : IStreamPipelineBehavior { public List Logs { get; } = new(); @@ -60,7 +60,7 @@ public async Task CreateStream_Should_InvokeHandler_AndBehavior() var behavior = new LoggingBehavior(); - services.AddSingleton(); + services.AddSingleton(); services.AddScoped, StreamHandler>(); services.AddScoped>(_ => behavior); From b77c30866fe6762610385f2b6f62b1f87a0afe60 Mon Sep 17 00:00:00 2001 From: Kamran Sadin Date: Fri, 6 Jun 2025 22:46:17 +0330 Subject: [PATCH 2/3] Fixes --- README.md | 391 +++++++++--------- .../GenericRepository/IGenericRepository.cs | 69 ++-- .../GenericRepository/IRepository.cs | 31 +- .../GenericRepository/Repository.cs | 88 +--- .../GenericRepositoryTests.cs | 3 - .../IntegrationTestBase.cs | 6 +- .../Messaging/MediatorTests.cs | 5 +- .../Performance/PerformanceTests.cs | 109 ++--- 8 files changed, 322 insertions(+), 380 deletions(-) diff --git a/README.md b/README.md index 5da0538..3647e97 100644 --- a/README.md +++ b/README.md @@ -1,280 +1,289 @@ -# KSFramework 🧩 +# KSFramework -**KSFramework** is a clean, extensible, and testable foundation for building scalable .NET Core applications. It provides a custom implementation of the MediatR pattern, along with well-known enterprise patterns such as Repository, Unit of Work, and Specification. It is designed to be modular, testable, and ready for production use. +**KSFramework** is a lightweight, extensible .NET framework designed to accelerate clean architecture and domain-driven development. It offers generic repository support, pagination utilities, and helpful abstractions to simplify the implementation of enterprise-grade backend systems using modern .NET patterns. --- ## ✨ Features -- ✅ Custom Mediator pattern implementation (Send / Publish / Stream) -- ✅ Pipeline behaviors (Validation, Logging, Exception handling, Pre/Post-processors) -- ✅ FluentValidation integration -- ✅ Notification pipeline behaviors -- ✅ Repository Pattern -- ✅ Unit of Work Pattern -- ✅ Specification Pattern -- ✅ Scrutor-based automatic registration -- ✅ File-scoped namespaces and XML documentation for every component -- ✅ Full unit test coverage using xUnit and Moq -- ✅ Swagger/OpenAPI documentation support -- ✅ Comprehensive XML documentation +- 🧱 **Domain-Driven Design (DDD) Friendly** +- 🧼 **Clean Architecture Support** +- 🧰 **Generic Repository Pattern** +- 📄 **Built-in Pagination Support** +- 🧪 **Unit Testable Core Interfaces** +- 🧩 **Customizable and Extensible by Design** --- ## 📦 Installation -Add the package reference (once published): +Install via NuGet: ```bash -dotnet add package KSFramework.KSMessaging -dotnet add package KSFramework.KSData +dotnet add package KSFramework ``` -Or reference the source projects directly in your solution. +Or via the Package Manager Console: -## 📚 API Documentation +```powershell +Install-Package KSFramework +``` + +--- + +## 📂 Project Structure + +The KSFramework package is modular and consists of: + +- `KSFramework.GenericRepository` — Contains generic repository interfaces and base implementations. +- `KSFramework.Pagination` — Provides interfaces and classes for paginated queries. +- `KSFramework.KSDomain` — Base types for entities, aggregate roots, and value objects. -### Swagger/OpenAPI Setup +--- + +## 🚀 Getting Started + +### 1. Define Your Entities -1. Add Swagger configuration in your `Program.cs`: ```csharp -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(c => +public class BlogPost { - c.SwaggerDoc("v1", new OpenApiInfo - { - Title = "Your API Name", - Version = "v1", - Description = "API Documentation" - }); - - // Include XML comments - var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); - c.IncludeXmlComments(xmlPath); -}); + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime PublishedAt { get; set; } +} ``` -2. Enable Swagger UI in your application: +### 2. Setup Your DbContext + ```csharp -app.UseSwagger(); -app.UseSwaggerUI(c => +public class AppDbContext : DbContext { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "Your API V1"); -}); + public DbSet BlogPosts { get; set; } + + public AppDbContext(DbContextOptions options) : base(options) { } +} ``` -### XML Documentation +### 3. Create Your Repository -The framework is configured to generate XML documentation. Add XML comments to your classes and methods: +You can use the generic repository directly: ```csharp -/// -/// Represents a generic repository for entity operations. -/// -/// The type of the entity. -public interface IGenericRepository where TEntity : class +public class BlogPostRepository : Repository { - /// - /// Retrieves an entity by its identifier. - /// - /// The identifier of the entity. - /// The entity if found; otherwise, null. - Task GetByIdAsync(object id); + public BlogPostRepository(AppDbContext context) : base(context) { } } ``` -⸻ -🧠 Project Structure +Or inject `IRepository` if you're using dependency injection with Scrutor or your own DI container. + +--- + +## 🧪 Common Repository Operations + +```csharp +await _repository.AddAsync(new BlogPost { Title = "Welcome", Content = "This is the first post." }); + +var post = await _repository.GetByIdAsync(1); + +await _repository.UpdateAsync(post); + +await _repository.DeleteAsync(post); + +var allPosts = await _repository.GetAllAsync(); + +var filtered = _repository.Find(p => p.PublishedAt > DateTime.UtcNow.AddDays(-30)); +``` + +--- + +## 📘 Pagination Example + +```csharp +var query = _repository.Query(); +var paginated = await query.PaginateAsync(pageNumber: 1, pageSize: 10); ``` -src/ -KSFramework.KSMessaging/ → Custom mediator, behaviors, contracts -KSFramework.KSData/ → Repository, UnitOfWork, Specification -KSFramework.KSSharedKernel/ → Domain base types, entities, value objects -tests/ -KSFramework.UnitTests/ → xUnit unit tests +`PaginateAsync` is an extension method provided by the `KSFramework.Pagination` namespace. + +--- + +## 📚 Full Example: Building a Blog with Weekly Newsletter + +### Project Overview + +This sample demonstrates how to build a simple blog platform using `KSFramework` with: + +- Post publishing +- Subscriber registration +- Weekly newsletter delivery to subscribers -samples/ -MediatorSampleApp/ → Console app to demonstrate usage -``` +--- -### 🚀 Mediator Usage +### Step 1: Define Entities -### 1. Define a request ```csharp -public class MultiplyByTwoRequest : IRequest +public class BlogPost { - public int Input { get; } - public MultiplyByTwoRequest(int input) => Input = input; + public int Id { get; set; } + public string Title { get; set; } + public string Content { get; set; } + public DateTime PublishedAt { get; set; } } -``` -### 2. Create a handler -```csharp -public class MultiplyByTwoHandler : IRequestHandler +public class Subscriber { - public Task Handle(MultiplyByTwoRequest request, CancellationToken cancellationToken) - => Task.FromResult(request.Input * 2); + public int Id { get; set; } + public string Email { get; set; } } -``` +``` -### 3. Send the request -```csharp -var result = await mediator.Send(new MultiplyByTwoRequest(5)); -Console.WriteLine(result); // Output: 10 -``` +--- -### 📤 Notifications +### Step 2: Repositories -### Define a notification and handler ```csharp -public class UserRegisteredNotification : INotification +public class BlogPostRepository : Repository { - public string Username { get; } - public UserRegisteredNotification(string username) => Username = username; + public BlogPostRepository(AppDbContext context) : base(context) { } } -public class SendWelcomeEmailHandler : INotificationHandler +public class SubscriberRepository : Repository { - public Task Handle(UserRegisteredNotification notification, CancellationToken cancellationToken) - { - Console.WriteLine($"Welcome email sent to {notification.Username}"); - return Task.CompletedTask; - } + public SubscriberRepository(AppDbContext context) : base(context) { } } -``` +``` -### Publish the notification -```csharp -await mediator.Publish(new UserRegisteredNotification("john")); -``` +--- -### 🔁 Streaming +### Step 3: API Controllers -### Define a stream request and handler ```csharp -public class CounterStreamRequest : IStreamRequest +[ApiController] +[Route("api/[controller]")] +public class BlogPostsController : ControllerBase { - public int Count { get; init; } -} + private readonly IRepository _repository; -public class CounterStreamHandler : IStreamRequestHandler -{ - public async IAsyncEnumerable Handle(CounterStreamRequest request, [EnumeratorCancellation] CancellationToken cancellationToken) + public BlogPostsController(IRepository repository) { - for (int i = 1; i <= request.Count; i++) - { - yield return i; - await Task.Delay(10, cancellationToken); - } + _repository = repository; } -} -``` - -### Consume the stream -```csharp -await foreach (var number in mediator.CreateStream(new CounterStreamRequest { Count = 3 })) -{ - Console.WriteLine($"Streamed: {number}"); -} -``` -## 🧩 Built-in Pipeline Behaviors + [HttpGet] + public async Task> GetAll() => await _repository.GetAllAsync(); -### All behaviors are automatically registered via AddKSMediator(). + [HttpPost] + public async Task Create(BlogPost post) + { + post.PublishedAt = DateTime.UtcNow; + await _repository.AddAsync(post); + return Ok(post); + } +} ``` -| Behavior | Description | -|---------------------------|-------------------------------------------------| -| RequestValidationBehavior | Validates incoming requests using FluentValidation | -| ExceptionHandlingBehavior | Logs and rethrows exceptions from handlers | -| RequestProcessorBehavior | Executes pre- and post-processors | -| LoggingBehavior | Logs request and response types | -| NotificationLoggingBehavior | Logs notification handling stages | -``` - -## 🧰 Configuration - -## Register services in Program.cs -```csharp -services.AddLogging(); -services.AddValidatorsFromAssembly(typeof(Program).Assembly); -services.AddKSMediator(Assembly.GetExecutingAssembly()); -``` -## 🧪 Unit Testing +--- + +### Step 4: Subscription Controller -### Example behavior test ```csharp -[Fact] -public async Task Handle_WithInvalidRequest_ThrowsValidationException() +[ApiController] +[Route("api/[controller]")] +public class NewsletterController : ControllerBase { - var validator = new Mock>(); - validator.Setup(v => v.ValidateAsync(It.IsAny(), It.IsAny())) - .ReturnsAsync(new ValidationResult(new[] { new ValidationFailure("Name", "Required") })); - - var logger = new Mock>>(); + private readonly IRepository _subRepo; - var behavior = new RequestValidationBehavior( - new[] { validator.Object }, logger.Object); + public NewsletterController(IRepository subRepo) + { + _subRepo = subRepo; + } - await Assert.ThrowsAsync(() => - behavior.Handle(new TestRequest(), CancellationToken.None, () => Task.FromResult(new TestResponse()))); + [HttpPost("subscribe")] + public async Task Subscribe(string email) + { + await _subRepo.AddAsync(new Subscriber { Email = email }); + return Ok("Subscribed"); + } } -``` +``` + +--- + +### Step 5: Weekly Newsletter Job (Example) + +You can use [Hangfire](https://www.hangfire.io/) or any scheduler to run this task weekly: -## 📦 Repository & Unit of Work ```csharp -public class ProductService +public class WeeklyNewsletterJob { - private readonly IRepository _repository; - private readonly IUnitOfWork _unitOfWork; + private readonly IRepository _subRepo; + private readonly IRepository _postRepo; - public ProductService(IRepository repository, IUnitOfWork unitOfWork) + public WeeklyNewsletterJob(IRepository subRepo, IRepository postRepo) { - _repository = repository; - _unitOfWork = unitOfWork; + _subRepo = subRepo; + _postRepo = postRepo; } - public async Task AddAsync(Product product) + public async Task Send() { - await _repository.AddAsync(product); - await _unitOfWork.CommitAsync(); + var lastWeek = DateTime.UtcNow.AddDays(-7); + var posts = (await _postRepo.GetAllAsync()) + .Where(p => p.PublishedAt > lastWeek) + .ToList(); + + var subscribers = await _subRepo.GetAllAsync(); + + foreach (var sub in subscribers) + { + // send email (via SMTP or 3rd party) + await EmailSender.Send(sub.Email, "Weekly Newsletter", RenderPosts(posts)); + } } -} -``` -## 🔍 Specification Pattern -```csharp -public class ActiveProductSpec : Specification -{ - public ActiveProductSpec() => Criteria = p => p.IsActive; + private string RenderPosts(IEnumerable posts) + { + return string.Join(" + +", posts.Select(p => $"{p.Title} +{p.Content}")); + } } -``` +``` -```csharp -var products = await _repository.ListAsync(new ActiveProductSpec()); -``` +--- -## ✅ Test Coverage Summary +## 📌 Roadmap -``` -| Component | Test Status | -|------------------------|-------------| -| Request handling | ✅ | -| Notification publishing| ✅ | -| Streaming requests | ✅ | -| Pipeline behaviors | ✅ | -| Validation | ✅ | -| Exception handling | ✅ | -| Logging | ✅ | -| Repository/UoW/Spec | ✅ | -``` +- [x] Generic Repository +- [x] Pagination +- [ ] Specification Pattern +- [ ] Auditing Support +- [ ] Integration with MediatR & CQRS + +--- + +## 🤝 Contributing -## 📚 License +Contributions are welcome! -### This project is licensed under the MIT License. +1. Fork the repository +2. Create a feature branch +3. Submit a pull request + +--- + +## 📄 License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +--- -## 👥 Contributing +## 🔗 Related Projects -### Feel free to fork and submit PRs or issues. Contributions are always welcome! \ No newline at end of file +- [MediatR](https://github.com/jbogard/MediatR) +- [Scrutor](https://github.com/khellang/Scrutor) +- [Hangfire](https://www.hangfire.io/) \ No newline at end of file diff --git a/src/KSFramework/GenericRepository/IGenericRepository.cs b/src/KSFramework/GenericRepository/IGenericRepository.cs index 902c8f2..9e13440 100644 --- a/src/KSFramework/GenericRepository/IGenericRepository.cs +++ b/src/KSFramework/GenericRepository/IGenericRepository.cs @@ -1,76 +1,73 @@ using System.Linq.Expressions; -using KSFramework.KSDomain.AggregatesHelper; using KSFramework.Pagination; namespace KSFramework.GenericRepository; /// -/// Represents a generic repository interface for performing CRUD operations on entities. +/// Represents a generic repository contract for performing common data access operations on entities. /// /// The type of the entity. public interface IGenericRepository where TEntity : class { /// - /// Retrieves an entity by its identifier asynchronously. + /// Asynchronously retrieves an entity by its unique identifier. /// - /// The identifier of the entity. - /// A task that represents the asynchronous operation. The task result contains the entity if found. + /// The unique identifier of the entity. + /// A task representing the asynchronous operation, containing the entity if found; otherwise, null. ValueTask GetByIdAsync(object id); /// - /// Retrieves all entities asynchronously. + /// Asynchronously retrieves all entities. /// - /// A task that represents the asynchronous operation. The task result contains the collection of entities. + /// A task representing the asynchronous operation, containing a collection of all entities. Task> GetAllAsync(); /// - /// Retrieves a paginated list of entities asynchronously based on specified criteria. + /// Asynchronously retrieves a paginated list of entities with optional filtering and ordering. /// - /// The index of the page to retrieve (1-based). - /// The size of the page. - /// Optional predicate to filter the entities. - /// Optional property name to order the results by. - /// Optional flag to indicate descending order. - /// A task that represents the asynchronous operation. The task result contains the paginated list of entities. + /// The index of the page (starting from 1). + /// The number of items per page. + /// Optional filter expression. + /// Optional property name to order by. + /// Indicates if the order should be descending. + /// A task representing the asynchronous operation, containing a paginated list of entities. Task> GetPagedAsync(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false); /// - /// Retrieves a paginated list of entities synchronously based on specified criteria. + /// Synchronously retrieves a paginated list of entities with optional filtering and ordering. /// - /// The index of the page to retrieve (1-based). - /// The size of the page. - /// Optional predicate to filter the entities. - /// Optional property name to order the results by. - /// Optional flag to indicate descending order. + /// The index of the page (starting from 1). + /// The number of items per page. + /// Optional filter expression. + /// Optional property name to order by. + /// Indicates if the order should be descending. /// A paginated list of entities. PaginatedList GetPaged(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false); /// - /// Finds entities that match the specified predicate. + /// Finds entities matching the given predicate. /// - /// The condition to filter entities. - /// A collection of entities that match the condition. + /// The condition to match. + /// A collection of matching entities. IEnumerable Find(Expression> predicate); /// - /// Retrieves a single entity that matches the specified predicate, or null if no match is found. + /// Asynchronously returns a single entity matching the given predicate, or null if none match. /// - /// The condition to filter the entity. - /// A task that represents the asynchronous operation. The task result contains the entity if found, otherwise null. + /// The condition to match. + /// A task representing the asynchronous operation, containing a single entity or null. Task SingleOrDefaultAsync(Expression> predicate); /// - /// Adds a new entity to the repository asynchronously. + /// Asynchronously adds an entity to the repository. /// /// The entity to add. - /// A task that represents the asynchronous operation. Task AddAsync(TEntity entity); /// - /// Adds multiple entities to the repository asynchronously. + /// Asynchronously adds multiple entities to the repository. /// - /// The collection of entities to add. - /// A task that represents the asynchronous operation. + /// The entities to add. Task AddRangeAsync(IEnumerable entities); /// @@ -82,13 +79,13 @@ public interface IGenericRepository where TEntity : class /// /// Removes multiple entities from the repository. /// - /// The collection of entities to remove. + /// The entities to remove. void RemoveRange(IEnumerable entities); /// - /// Checks if any entity exists that matches the specified predicate. + /// Asynchronously checks whether any entity matches the given predicate. /// - /// The condition to check for existence. - /// A task that represents the asynchronous operation. The task result contains true if a matching entity exists; otherwise, false. + /// The condition to match. + /// A task representing the asynchronous operation, containing true if any entity matches; otherwise, false. Task IsExistValueForPropertyAsync(Expression> predicate); -} +} \ No newline at end of file diff --git a/src/KSFramework/GenericRepository/IRepository.cs b/src/KSFramework/GenericRepository/IRepository.cs index ef2950f..75bdaef 100644 --- a/src/KSFramework/GenericRepository/IRepository.cs +++ b/src/KSFramework/GenericRepository/IRepository.cs @@ -1,21 +1,34 @@ using System.Linq.Expressions; -using KSFramework.KSDomain.AggregatesHelper; using KSFramework.Pagination; namespace KSFramework.GenericRepository; + +/// +/// Defines the contract for a generic repository to handle basic CRUD and query operations. +/// +/// The entity type. public interface IRepository where TEntity : class { - ValueTask GetByIdAsync(object id); - Task> GetAllAsync(); - Task> GetPagedAsync(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false); - PaginatedList GetPaged(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false); - IEnumerable Find(Expression> predicate); - Task SingleOrDefaultAsync(Expression> predicate); Task AddAsync(TEntity entity); Task AddRangeAsync(IEnumerable entities); - + IEnumerable Find(Expression> predicate, bool asNoTracking = true); + Task> GetAllAsync(bool asNoTracking = true); + Task> GetPagedAsync( + int pageIndex, + int pageSize, + Expression>? where = null, + string? orderBy = "", + bool desc = false); + PaginatedList GetPaged( + int pageIndex, + int pageSize, + Expression>? where = null, + string? orderBy = "", + bool desc = false); + ValueTask GetByIdAsync(object id); void Remove(TEntity entity); void RemoveRange(IEnumerable entities); + Task SingleOrDefaultAsync(Expression> predicate); Task IsExistValueForPropertyAsync(Expression> predicate); - + void Update(TEntity entity); } \ No newline at end of file diff --git a/src/KSFramework/GenericRepository/Repository.cs b/src/KSFramework/GenericRepository/Repository.cs index 658d90a..d43d42b 100644 --- a/src/KSFramework/GenericRepository/Repository.cs +++ b/src/KSFramework/GenericRepository/Repository.cs @@ -1,76 +1,56 @@ using System.Linq.Expressions; -using KSFramework.KSDomain.AggregatesHelper; using KSFramework.Pagination; using Microsoft.EntityFrameworkCore; namespace KSFramework.GenericRepository; + /// -/// Provides a base implementation of the generic repository pattern for entity operations. +/// Provides a base implementation of the interface using Entity Framework Core. /// /// The type of the entity. -public abstract class Repository : IRepository where TEntity : class +public abstract class Repository : IGenericRepository where TEntity : class { private readonly DbContext Context; - protected DbSet Entity; + /// + /// Gets the underlying for querying and persisting entities. + /// + protected readonly DbSet Entity; /// - /// Initializes a new instance of the Repository class. + /// Initializes a new instance of the class. /// /// The database context. protected Repository(DbContext context) { - this.Context = context; - Entity = Context.Set(); + Context = context; + Entity = context.Set(); } - /// - /// Adds a new entity to the repository asynchronously. - /// - /// The entity to add. - /// A task representing the asynchronous operation. + /// public virtual async Task AddAsync(TEntity entity) { await Entity.AddAsync(entity); } - /// - /// Adds multiple entities to the repository asynchronously. - /// - /// The collection of entities to add. - /// A task representing the asynchronous operation. + /// public virtual async Task AddRangeAsync(IEnumerable entities) { await Entity.AddRangeAsync(entities); } - /// - /// Finds entities that match the specified predicate. - /// - /// The condition to filter entities. - /// A collection of entities that match the condition. + /// public virtual IEnumerable Find(Expression> predicate) { return Entity.Where(predicate); } - /// - /// Retrieves all entities asynchronously. - /// - /// A task representing the asynchronous operation, containing all entities. + /// public virtual async Task> GetAllAsync() { return await Entity.ToListAsync(); } - /// - /// Retrieves a paginated list of entities asynchronously. - /// - /// The index of the page to retrieve. - /// The size of the page. - /// Optional predicate to filter the entities. - /// Optional property name to order the results by. - /// Optional flag to indicate descending order. - /// A task representing the asynchronous operation, containing a paginated list of entities. + /// public virtual async Task> GetPagedAsync(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false) { var query = Entity.AsQueryable(); @@ -80,64 +60,38 @@ public virtual async Task> GetPagedAsync(int pageIndex, i return await PaginatedList.CreateAsync(query, pageIndex, pageSize, where, orderBy, desc); } - /// - /// Retrieves a paginated list of entities synchronously. - /// - /// The index of the page to retrieve. - /// The size of the page. - /// Optional predicate to filter the entities. - /// Optional property name to order the results by. - /// Optional flag to indicate descending order. - /// A paginated list of entities. + /// public virtual PaginatedList GetPaged(int pageIndex, int pageSize, Expression>? where = null, string? orderBy = "", bool desc = false) { var query = Entity.AsQueryable(); return PaginatedList.Create(query, pageIndex, pageSize, where, orderBy, desc); } - /// - /// Retrieves an entity by its identifier asynchronously. - /// - /// The identifier of the entity. - /// A task representing the asynchronous operation, containing the entity if found. + /// public virtual async ValueTask GetByIdAsync(object id) { return await Entity.FindAsync(id); } - /// - /// Removes an entity from the repository. - /// - /// The entity to remove. + /// public virtual void Remove(TEntity entity) { Entity.Remove(entity); } - /// - /// Removes multiple entities from the repository. - /// - /// The collection of entities to remove. + /// public virtual void RemoveRange(IEnumerable entities) { Entity.RemoveRange(entities); } - /// - /// Retrieves a single entity that matches the specified predicate, or null if no match is found. - /// - /// The condition to filter the entity. - /// A task representing the asynchronous operation, containing the entity if found. + /// public virtual async Task SingleOrDefaultAsync(Expression> predicate) { return await Entity.SingleOrDefaultAsync(predicate); } - /// - /// Checks if any entity exists that matches the specified predicate. - /// - /// The condition to check for existence. - /// A task representing the asynchronous operation, containing true if a matching entity exists. + /// public virtual async Task IsExistValueForPropertyAsync(Expression> predicate) { return await Entity.AnyAsync(predicate); diff --git a/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/GenericRepositoryTests.cs b/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/GenericRepositoryTests.cs index d77951b..0d2431c 100644 --- a/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/GenericRepositoryTests.cs +++ b/tests/KSFramework/KSFramework.IntegrationTests/GenericRepository/GenericRepositoryTests.cs @@ -28,9 +28,6 @@ public GenericRepositoryTests() protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); - - - services.AddScoped, TestGenericRepository>(); } diff --git a/tests/KSFramework/KSFramework.IntegrationTests/IntegrationTestBase.cs b/tests/KSFramework/KSFramework.IntegrationTests/IntegrationTestBase.cs index 654b482..8045cde 100644 --- a/tests/KSFramework/KSFramework.IntegrationTests/IntegrationTestBase.cs +++ b/tests/KSFramework/KSFramework.IntegrationTests/IntegrationTestBase.cs @@ -39,11 +39,7 @@ protected virtual void ConfigureServices(IServiceCollection services) options.UseInMemoryDatabase("TestDatabase"); }); - - - - - services.AddScoped(); + services.AddScoped(provider => (DbContext)provider.GetRequiredService()); // Register UnitOfWork and IUnitOfWork services.AddScoped(); diff --git a/tests/KSFramework/KSFramework.IntegrationTests/Messaging/MediatorTests.cs b/tests/KSFramework/KSFramework.IntegrationTests/Messaging/MediatorTests.cs index 70025e4..2f2e321 100644 --- a/tests/KSFramework/KSFramework.IntegrationTests/Messaging/MediatorTests.cs +++ b/tests/KSFramework/KSFramework.IntegrationTests/Messaging/MediatorTests.cs @@ -1,5 +1,6 @@ using KSFramework.KSMessaging; using KSFramework.KSMessaging.Abstraction; +using KSFramework.KSMessaging.Extensions; using Microsoft.Extensions.DependencyInjection; namespace KSFramework.IntegrationTests.Messaging; @@ -40,9 +41,7 @@ protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); - services.AddScoped, TestCommandHandler>(); - services.AddScoped, TestQueryHandler>(); - services.AddScoped(); + services.AddKSMediator(typeof(MediatorTests).Assembly); } [Fact] diff --git a/tests/KSFramework/KSFramework.IntegrationTests/Performance/PerformanceTests.cs b/tests/KSFramework/KSFramework.IntegrationTests/Performance/PerformanceTests.cs index b05314b..bc4b108 100644 --- a/tests/KSFramework/KSFramework.IntegrationTests/Performance/PerformanceTests.cs +++ b/tests/KSFramework/KSFramework.IntegrationTests/Performance/PerformanceTests.cs @@ -1,29 +1,12 @@ using System.Diagnostics; using KSFramework.GenericRepository; -using KSFramework.KSDomain.AggregatesHelper; +using KSFramework.IntegrationTests; using KSFramework.IntegrationTests.GenericRepository; -using KSFramework.KSDomain; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; -namespace KSFramework.IntegrationTests.Performance; - public class PerformanceTests : IntegrationTestBase { - private readonly IGenericRepository _repository; - private readonly TestDbContext _dbContext; - private readonly Stopwatch _stopwatch; - private readonly IUnitOfWork _unitOfWork; - - public PerformanceTests() - { - using var scope = CreateScope(); - _dbContext = scope.ServiceProvider.GetRequiredService(); - _repository = scope.ServiceProvider.GetRequiredService>(); - _stopwatch = new Stopwatch(); - _unitOfWork = scope.ServiceProvider.GetRequiredService(); - } - protected override void ConfigureServices(IServiceCollection services) { base.ConfigureServices(services); @@ -37,9 +20,13 @@ protected override void ConfigureServices(IServiceCollection services) [Fact] public async Task BulkInsert_ShouldCompleteWithinTimeLimit() { - // Arrange + using var scope = CreateScope(); + var repository = scope.ServiceProvider.GetRequiredService>(); + var unitOfWork = scope.ServiceProvider.GetRequiredService(); + var stopwatch = new Stopwatch(); + const int numberOfEntities = 1000; - const int acceptableMilliseconds = 2000; // 2 seconds + const int acceptableMilliseconds = 2000; var entities = Enumerable.Range(1, numberOfEntities) .Select(i => new TestEntity { @@ -48,88 +35,78 @@ public async Task BulkInsert_ShouldCompleteWithinTimeLimit() }) .ToList(); - // Act - _stopwatch.Start(); - + stopwatch.Start(); foreach (var entity in entities) { - await _repository.AddAsync(entity); + await repository.AddAsync(entity); } - await _unitOfWork.SaveChangesAsync(); + await unitOfWork.SaveChangesAsync(); + stopwatch.Stop(); - _stopwatch.Stop(); - - // Assert - Assert.True(_stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, - $"Bulk insert took {_stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); + Assert.True(stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, + $"Bulk insert took {stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); } [Fact] public async Task QueryPerformance_ShouldCompleteWithinTimeLimit() { - // Arrange + using var scope = CreateScope(); + var repository = scope.ServiceProvider.GetRequiredService>(); + var unitOfWork = scope.ServiceProvider.GetRequiredService(); + var stopwatch = new Stopwatch(); + const int numberOfQueries = 100; - const int acceptableMilliseconds = 1000; // 1 second + const int acceptableMilliseconds = 1000; var entity = new TestEntity { Id = Guid.NewGuid(), Name = "Performance Query Test Entity" }; - await _repository.AddAsync(entity); - await _unitOfWork.SaveChangesAsync(); - - // Act - _stopwatch.Start(); + await repository.AddAsync(entity); + await unitOfWork.SaveChangesAsync(); + stopwatch.Start(); for (int i = 0; i < numberOfQueries; i++) { - await _repository.GetByIdAsync(entity.Id); + await repository.GetByIdAsync(entity.Id); } + stopwatch.Stop(); - _stopwatch.Stop(); - - // Assert - Assert.True(_stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, - $"Multiple queries took {_stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); + Assert.True(stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, + $"Multiple queries took {stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); } [Fact] public async Task ConcurrentOperations_ShouldHandleEfficiently() { - // Arrange const int numberOfTasks = 50; - const int acceptableMilliseconds = 3000; // 3 seconds + const int acceptableMilliseconds = 3000; + var stopwatch = new Stopwatch(); var tasks = new List(); - // Act - _stopwatch.Start(); - + stopwatch.Start(); for (int i = 0; i < numberOfTasks; i++) { tasks.Add(Task.Run(async () => { - using (var scope = CreateScope()) - { - var scopedUnitOfWork = scope.ServiceProvider.GetRequiredService(); - var scopedRepository = scope.ServiceProvider.GetRequiredService>(); + using var scope = CreateScope(); + var scopedRepository = scope.ServiceProvider.GetRequiredService>(); + var scopedUnitOfWork = scope.ServiceProvider.GetRequiredService(); - var entity = new TestEntity - { - Id = Guid.NewGuid(), - Name = $"Concurrent Test Entity {Guid.NewGuid()}" - }; - await scopedRepository.AddAsync(entity); - await scopedUnitOfWork.SaveChangesAsync(); - await scopedRepository.GetByIdAsync(entity.Id); - } + var entity = new TestEntity + { + Id = Guid.NewGuid(), + Name = $"Concurrent Test Entity {Guid.NewGuid()}" + }; + await scopedRepository.AddAsync(entity); + await scopedUnitOfWork.SaveChangesAsync(); + await scopedRepository.GetByIdAsync(entity.Id); })); } - await Task.WhenAll(tasks); - _stopwatch.Stop(); + stopwatch.Stop(); - // Assert - Assert.True(_stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, - $"Concurrent operations took {_stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); + Assert.True(stopwatch.ElapsedMilliseconds <= acceptableMilliseconds, + $"Concurrent operations took {stopwatch.ElapsedMilliseconds}ms, which is more than acceptable {acceptableMilliseconds}ms"); } } \ No newline at end of file From 0268d5064cf3f200c653e8420d397439c995b6cd Mon Sep 17 00:00:00 2001 From: Kamran Sadin Date: Sat, 7 Jun 2025 10:04:13 +0330 Subject: [PATCH 3/3] Update README file and implement improvements --- .vs/KSFramework/xs/UserPrefs.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.vs/KSFramework/xs/UserPrefs.xml b/.vs/KSFramework/xs/UserPrefs.xml index a5ec89e..e04c12d 100644 --- a/.vs/KSFramework/xs/UserPrefs.xml +++ b/.vs/KSFramework/xs/UserPrefs.xml @@ -1,17 +1,17 @@  - - + - + + \ No newline at end of file