From 7b7a1935ffc0796109ad4702f0a007f92af589ca Mon Sep 17 00:00:00 2001 From: "enrique.ortuno" Date: Thu, 3 Mar 2022 23:02:37 -0400 Subject: [PATCH 1/8] github action manual execution added --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 381d977..722aa01 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,6 +1,6 @@ name: Main Workflow -on: [push] +on: [push, workflow_dispatch] jobs: build: From 0840440bc335440133393f13234621cd54f56d1a Mon Sep 17 00:00:00 2001 From: "enrique.ortuno" Date: Thu, 3 Mar 2022 23:14:32 -0400 Subject: [PATCH 2/8] all docker containers running displayed --- .github/workflows/workflow.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 722aa01..d6d6ffa 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -19,6 +19,9 @@ jobs: - name: Build with dotnet run: dotnet build --configuration Release + - name: Listing all docker containers + run: docker ps -all + - name: Init databases run: docker exec codelytv-chsarp_ddd_skeleton-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Your_password123 -d master -i init.sql From 6882d4582df464dbb8c38ea59ee9956c8ab1081c Mon Sep 17 00:00:00 2001 From: "enrique.ortuno" Date: Thu, 3 Mar 2022 23:22:42 -0400 Subject: [PATCH 3/8] wait time added --- .github/workflows/workflow.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index d6d6ffa..b4b6e92 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -19,6 +19,11 @@ jobs: - name: Build with dotnet run: dotnet build --configuration Release + - name: Sleep for 30 seconds + uses: jakejarvis/wait-action@master + with: + time: '45s' + - name: Listing all docker containers run: docker ps -all From e067e5ed5d24524eb776b5940d122099c51b2a6e Mon Sep 17 00:00:00 2001 From: "enrique.ortuno" Date: Thu, 3 Mar 2022 23:29:03 -0400 Subject: [PATCH 4/8] sql server logs reveleaded --- .github/workflows/workflow.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index b4b6e92..32f9f30 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -26,6 +26,9 @@ jobs: - name: Listing all docker containers run: docker ps -all + + - name: SQL Server logs + run: docker logs codelytv-chsarp_ddd_skeleton-mssql - name: Init databases run: docker exec codelytv-chsarp_ddd_skeleton-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Your_password123 -d master -i init.sql From 63277fd9b3d0b521d55e2480136963b16ce75d1c Mon Sep 17 00:00:00 2001 From: "enrique.ortuno" Date: Thu, 3 Mar 2022 23:39:19 -0400 Subject: [PATCH 5/8] mssql ran as root user --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 17f673f..5ec8fbf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,8 @@ version: '3.0' services: mssql: container_name: codelytv-chsarp_ddd_skeleton-mssql - image: mcr.microsoft.com/mssql/server:2019-latest + image: mcr.microsoft.com/mssql/server:2019-latest + user: root ports: - 1433:1433 environment: From f288f2f74faa8d3ca1c81cb96d454d2171ae2451 Mon Sep 17 00:00:00 2001 From: "enrique.ortuno" Date: Fri, 4 Mar 2022 00:09:01 -0400 Subject: [PATCH 6/8] elastic search connection string added --- apps/Mooc/Backend/appsettings.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/Mooc/Backend/appsettings.json b/apps/Mooc/Backend/appsettings.json index 3535bdc..00e03e2 100644 --- a/apps/Mooc/Backend/appsettings.json +++ b/apps/Mooc/Backend/appsettings.json @@ -13,5 +13,11 @@ "Password": "c0d3ly", "Hostname": "localhost", "port": "5630" + }, + "Elasticsearch": + { + "Host": "http://localhost", + "Port": "9200", + "IndexPrefix": "backoffice" } } From 0f443e1101edb1fcb668278639a88ee215532de2 Mon Sep 17 00:00:00 2001 From: Enrique Ortuno Date: Sun, 26 May 2024 22:40:27 -0400 Subject: [PATCH 7/8] initial upgrade changes --- .github/workflows/workflow.yml | 12 +++++------ .gitignore | 1 + CsharpDDDSkeleton.sln | 2 +- apps/Backoffice/Backend/Backend.csproj | 2 +- apps/Backoffice/Frontend/Frontend.csproj | 16 +++++++-------- apps/Mooc/Backend/Backend.csproj | 6 +++--- docker-compose.yml | 2 -- src/Backoffice/Backoffice.csproj | 6 +++--- src/Mooc/Mooc.csproj | 10 +++++----- src/Retention/Retention.csproj | 4 ++-- .../Bus/Event/RabbitMq/RabbitMqEventBus.cs | 7 ++++--- src/Shared/Shared.csproj | 20 +++++++++---------- test/apps/Mooc/Mooc.csproj | 6 +++--- test/src/Backoffice/Backoffice.csproj | 2 +- test/src/Mooc/Mooc.csproj | 8 ++++---- test/src/Shared/Shared.csproj | 10 +++++----- 16 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 32f9f30..4913aae 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -14,28 +14,28 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.x - + dotnet-version: 8.0.x + - name: Build with dotnet run: dotnet build --configuration Release - name: Sleep for 30 seconds uses: jakejarvis/wait-action@master with: - time: '45s' + time: "45s" - name: Listing all docker containers run: docker ps -all - + - name: SQL Server logs run: docker logs codelytv-chsarp_ddd_skeleton-mssql - name: Init databases run: docker exec codelytv-chsarp_ddd_skeleton-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Your_password123 -d master -i init.sql - + - name: Init mooc database run: docker exec codelytv-chsarp_ddd_skeleton-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Your_password123 -d master -i mooc.sql - + - name: Init backoffice database run: docker exec codelytv-chsarp_ddd_skeleton-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Your_password123 -d master -i backoffice.sql diff --git a/.gitignore b/.gitignore index 05f4ae0..514d847 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.swp +*.log *.*~ project.lock.json .DS_Store diff --git a/CsharpDDDSkeleton.sln b/CsharpDDDSkeleton.sln index 14e95bc..0830367 100644 --- a/CsharpDDDSkeleton.sln +++ b/CsharpDDDSkeleton.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.29326.143 +VisualStudioVersion = 18.0.59326.143 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ABF745D7-F9BF-4CEE-8A12-DC03FAB68C9A}" EndProject diff --git a/apps/Backoffice/Backend/Backend.csproj b/apps/Backoffice/Backend/Backend.csproj index 068bc1f..3903436 100644 --- a/apps/Backoffice/Backend/Backend.csproj +++ b/apps/Backoffice/Backend/Backend.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 CodelyTv.Apps.Backoffice.Backend CodelyTv.Apps.Backoffice.Backend diff --git a/apps/Backoffice/Frontend/Frontend.csproj b/apps/Backoffice/Frontend/Frontend.csproj index 9c6db90..5cd303c 100644 --- a/apps/Backoffice/Frontend/Frontend.csproj +++ b/apps/Backoffice/Frontend/Frontend.csproj @@ -1,21 +1,21 @@ - net6.0 + net8.0 CodelyTv.Apps.Backoffice.Frontend CodelyTv.Apps.Backoffice.Frontend - + - - - - + + + + - - + + diff --git a/apps/Mooc/Backend/Backend.csproj b/apps/Mooc/Backend/Backend.csproj index 4137bab..4d0d880 100644 --- a/apps/Mooc/Backend/Backend.csproj +++ b/apps/Mooc/Backend/Backend.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 CodelyTv.Apps.Mooc.Backend CodelyTv.Apps.Mooc.Backend @@ -12,6 +12,6 @@ - - + + diff --git a/docker-compose.yml b/docker-compose.yml index 1d2b02d..134f1c7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.0" - services: mssql: container_name: codelytv-chsarp_ddd_skeleton-mssql diff --git a/src/Backoffice/Backoffice.csproj b/src/Backoffice/Backoffice.csproj index 64a793f..88806a0 100644 --- a/src/Backoffice/Backoffice.csproj +++ b/src/Backoffice/Backoffice.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 CodelyTv.Backoffice CodelyTv.Backoffice @@ -11,8 +11,8 @@ - - + + diff --git a/src/Mooc/Mooc.csproj b/src/Mooc/Mooc.csproj index e05a3df..69aadd4 100644 --- a/src/Mooc/Mooc.csproj +++ b/src/Mooc/Mooc.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 CodelyTv.Mooc CodelyTv.Mooc @@ -11,13 +11,13 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/Retention/Retention.csproj b/src/Retention/Retention.csproj index 9357407..4c09666 100644 --- a/src/Retention/Retention.csproj +++ b/src/Retention/Retention.csproj @@ -1,7 +1,7 @@ - net6.0 - + net8.0 + diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.cs b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.cs index 397c21a..3be1b83 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.cs +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBus.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using CodelyTv.Shared.Domain.Bus.Event; using CodelyTv.Shared.Infrastructure.Bus.Event.MsSql; @@ -22,7 +23,7 @@ public RabbitMqEventBus(RabbitMqPublisher rabbitMqPublisher, MsSqlEventBus failO public async Task Publish(List events) { - events.ForEach(async e => await Publish(e)); + await Task.WhenAll(events.Select(e => Publish(e))); } private async Task Publish(DomainEvent domainEvent) @@ -32,9 +33,9 @@ private async Task Publish(DomainEvent domainEvent) var serializedDomainEvent = DomainEventJsonSerializer.Serialize(domainEvent); _rabbitMqPublisher.Publish(_exchangeName, domainEvent.EventName(), serializedDomainEvent); } - catch (RabbitMQClientException e) + catch (RabbitMQClientException) { - await _failOverPublisher.Publish(new List {domainEvent}); + await _failOverPublisher.Publish(new List { domainEvent }); } } } diff --git a/src/Shared/Shared.csproj b/src/Shared/Shared.csproj index 9eeaf63..2316287 100644 --- a/src/Shared/Shared.csproj +++ b/src/Shared/Shared.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 CodelyTv.Shared CodelyTv.Shared @@ -11,17 +11,17 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + diff --git a/test/apps/Mooc/Mooc.csproj b/test/apps/Mooc/Mooc.csproj index a62ebcd..b3710ba 100644 --- a/test/apps/Mooc/Mooc.csproj +++ b/test/apps/Mooc/Mooc.csproj @@ -1,14 +1,14 @@  - net6.0 + net8.0 false MoocTest.apps MoocTest.apps - + all @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/test/src/Backoffice/Backoffice.csproj b/test/src/Backoffice/Backoffice.csproj index 02aabb4..8fa2b1a 100644 --- a/test/src/Backoffice/Backoffice.csproj +++ b/test/src/Backoffice/Backoffice.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 false CodelyTv.Test.Backoffice CodelyTv.Test.Backoffice diff --git a/test/src/Mooc/Mooc.csproj b/test/src/Mooc/Mooc.csproj index face3ab..9f63d0f 100644 --- a/test/src/Mooc/Mooc.csproj +++ b/test/src/Mooc/Mooc.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false CodelyTv.Test.Mooc CodelyTv.Test.Mooc @@ -9,12 +9,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/src/Shared/Shared.csproj b/test/src/Shared/Shared.csproj index b31b1c3..75a6e46 100644 --- a/test/src/Shared/Shared.csproj +++ b/test/src/Shared/Shared.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 false CodelyTv.Test.Shared CodelyTv.Test.Shared @@ -14,15 +14,15 @@ - - + + - + - + From 36220adf33a97a405cc240c6795be3fbd6e006ee Mon Sep 17 00:00:00 2001 From: Enrique Ortuno Date: Wed, 29 May 2024 22:37:16 -0400 Subject: [PATCH 8/8] dotnet best practices reviewed and added --- Directory.Build.props | 12 ++++ Directory.Packages.props | 27 +++++++ apps/Backoffice/Backend/Backend.csproj | 2 +- .../Backend/Criteria/FilterParam.cs | 6 +- .../Courses/CoursesPostWebModel.cs | 6 +- .../SummaryByPropertyValidatorHtml.cs | 28 +++++++- apps/Backoffice/Frontend/Frontend.csproj | 6 +- apps/Backoffice/Frontend/appsettings.json | 3 +- apps/Mooc/Backend/Backend.csproj | 2 +- docker-compose.yml | 2 +- src/Backoffice/Backoffice.csproj | 4 +- ...BackofficeCoursesByCriteriaQueryHandler.cs | 15 ++-- .../Courses/Domain/BackofficeCourse.cs | 36 ++++++---- ...BackofficeCourseElasticSearchDescriptor.cs | 7 +- ...ElasticsearchBackofficeCourseRepository.cs | 17 ++++- .../Elasticsearch/ElasticsearchExtension.cs | 14 +++- src/Mooc/Courses/Domain/Course.cs | 5 +- src/Mooc/Courses/Domain/CourseRepository.cs | 2 +- .../Infrastructure/FileCourseRepository.cs | 2 +- .../Persistence/MsSqlCourseRepository.cs | 14 +++- .../Find/CoursesCounterResponse.cs | 2 +- .../CoursesCounters/Domain/CoursesCounter.cs | 5 +- .../Domain/CoursesCounterRepository.cs | 2 +- .../MsSqlCoursesCounterRepository.cs | 2 +- src/Mooc/Mooc.csproj | 8 +-- .../DomainEventPrimitiveConfiguration.cs | 16 ++++- .../ExistingCoursesConverter.cs | 2 +- src/Shared/Cli/CommandBuilder.cs | 13 +++- .../Bus/Command/CommandHandlerWrapper.cs | 21 +++++- src/Shared/Domain/Bus/Event/DomainEvent.cs | 8 +-- .../Domain/Bus/Event/DomainEventPrimitive.cs | 10 +-- src/Shared/Domain/Bus/Query/QueryBus.cs | 2 +- .../Domain/Bus/Query/QueryHandlerWrapper.cs | 9 ++- .../Domain/CourseCreatedDomainEvent.cs | 10 +-- .../Domain/FiltersByCriteria/Filters.cs | 2 +- src/Shared/Domain/ReflectionHelper.cs | 17 +++-- .../Domain/ValueObject/IntValueObject.cs | 2 +- .../Domain/ValueObject/StringValueObject.cs | 2 +- src/Shared/Domain/ValueObject/Uuid.cs | 2 +- src/Shared/Domain/ValueObject/ValueObject.cs | 10 ++- src/Shared/DomainEventSubscriberServices.cs | 2 +- .../Bus/Command/InMemoryCommandBus.cs | 34 +++++++-- .../Bus/Event/DomainEventInformation.cs | 18 +++-- .../Bus/Event/DomainEventJsonDeserializer.cs | 71 ++++++++++++++----- .../Event/DomainEventSubscriberInformation.cs | 6 +- .../Bus/Event/InMemoryApplicationEventBus.cs | 10 ++- .../Event/MsSql/MsSqlDomainEventsConsumer.cs | 66 ++++++++++++----- .../Bus/Event/RabbitMq/RabbitMqConfig.cs | 4 +- .../Event/RabbitMq/RabbitMqConfigParams.cs | 6 +- .../RabbitMq/RabbitMqDomainEventsConsumer.cs | 8 ++- .../RabbitMq/RabbitMqEventBusConfiguration.cs | 6 +- .../Bus/Query/InMemoryQueryBus.cs | 33 +++++++-- .../ElasticsearchCriteriaConverter.cs | 18 ++--- .../Elasticsearch/ElasticsearchRepository.cs | 6 +- .../ConvertConfiguration.cs | 18 ++++- src/Shared/Shared.csproj | 18 ++--- .../Validator/Attributes/UuidAttribute.cs | 4 +- test/apps/Mooc/Mooc.csproj | 8 +-- test/src/Backoffice/Backoffice.csproj | 8 +-- .../CoursesCounterModuleUnitTestCase.cs | 2 +- test/src/Mooc/Mooc.csproj | 14 ++-- .../Mooc/MoocContextApplicationTestCase.cs | 27 +++++-- test/src/Mooc/MoocWebApplicationFactory.cs | 7 +- .../Event/RabbitMq/RabbitMqEventBusShould.cs | 48 ++++++++++--- .../Domain/Criterias/FilterOperatorMother.cs | 7 +- .../Shared/Domain/Criterias/OrderByMother.cs | 2 +- .../Domain/Criterias/OrderTypeMother.cs | 7 +- .../Infrastructure/ApplicationTestCase.cs | 2 +- .../Infrastructure/InfrastructureTestCase.cs | 8 ++- test/src/Shared/Shared.csproj | 10 +-- 70 files changed, 602 insertions(+), 231 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..82a6757 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,12 @@ + + + true + true + net8.0 + enable + enable + + + 1591 + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..875cb61 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/Backoffice/Backend/Backend.csproj b/apps/Backoffice/Backend/Backend.csproj index 3903436..dde8298 100644 --- a/apps/Backoffice/Backend/Backend.csproj +++ b/apps/Backoffice/Backend/Backend.csproj @@ -11,7 +11,7 @@ - + diff --git a/apps/Backoffice/Backend/Criteria/FilterParam.cs b/apps/Backoffice/Backend/Criteria/FilterParam.cs index c5d13cf..c096195 100644 --- a/apps/Backoffice/Backend/Criteria/FilterParam.cs +++ b/apps/Backoffice/Backend/Criteria/FilterParam.cs @@ -6,12 +6,12 @@ namespace CodelyTv.Apps.Backoffice.Backend.Criteria public class FiltersParam { [FromQuery(Name = "filters")] - public List> Filters { get; set; } + public List> Filters { get; set; } = new List>(); [FromQuery(Name = "order_by")] - public string OrderBy { get; set; } + public string OrderBy { get; set; } = string.Empty; - public string Order { get; set; } + public string Order { get; set; } = string.Empty; public int? Limit { get; set; } diff --git a/apps/Backoffice/Frontend/Controllers/Courses/CoursesPostWebModel.cs b/apps/Backoffice/Frontend/Controllers/Courses/CoursesPostWebModel.cs index 93745be..57bf048 100644 --- a/apps/Backoffice/Frontend/Controllers/Courses/CoursesPostWebModel.cs +++ b/apps/Backoffice/Frontend/Controllers/Courses/CoursesPostWebModel.cs @@ -7,12 +7,12 @@ public class CoursesPostWebModel { [Uuid] [Required] - public string Id { get; set; } + public string Id { get; set; } = string.Empty; [Required] - public string Name { get; set; } + public string Name { get; set; } = string.Empty; [Required] - public string Duration { get; set; } + public string Duration { get; set; } = string.Empty; } } diff --git a/apps/Backoffice/Frontend/Extension/Validators/SummaryByPropertyValidatorHtml.cs b/apps/Backoffice/Frontend/Extension/Validators/SummaryByPropertyValidatorHtml.cs index b37c87c..8d1282f 100644 --- a/apps/Backoffice/Frontend/Extension/Validators/SummaryByPropertyValidatorHtml.cs +++ b/apps/Backoffice/Frontend/Extension/Validators/SummaryByPropertyValidatorHtml.cs @@ -11,11 +11,35 @@ public static class SummaryByPropertyValidatorHtml public static IHtmlContent ValidationSummaryByProperty(this IHtmlHelper helper, ModelStateDictionary dictionary, string property, string className) { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (string.IsNullOrEmpty(property)) + { + throw new ArgumentNullException(nameof(property)); + } + + if (className == null) + { + throw new ArgumentNullException(nameof(className)); + } + var builder = new StringBuilder(); - if (dictionary[property] != null) - foreach (var modelState in dictionary[property].Errors) + if (dictionary.TryGetValue(property, out var modelStateEntry) && modelStateEntry?.Errors != null) + { + foreach (var modelState in modelStateEntry.Errors) + { builder.Append(CultureInfo.CurrentCulture, $"

{modelState.ErrorMessage}

"); + } + } return new HtmlString(builder.ToString()); } diff --git a/apps/Backoffice/Frontend/Frontend.csproj b/apps/Backoffice/Frontend/Frontend.csproj index 5cd303c..bcbd542 100644 --- a/apps/Backoffice/Frontend/Frontend.csproj +++ b/apps/Backoffice/Frontend/Frontend.csproj @@ -7,9 +7,9 @@
- - - + + + diff --git a/apps/Backoffice/Frontend/appsettings.json b/apps/Backoffice/Frontend/appsettings.json index 86fa1f2..dc56e0d 100644 --- a/apps/Backoffice/Frontend/appsettings.json +++ b/apps/Backoffice/Frontend/appsettings.json @@ -17,8 +17,7 @@ "Hostname": "localhost", "port": "5630" }, - "Elasticsearch": - { + "Elasticsearch": { "Host": "http://localhost", "Port": "9200", "IndexPrefix": "backoffice" diff --git a/apps/Mooc/Backend/Backend.csproj b/apps/Mooc/Backend/Backend.csproj index 4d0d880..ad2b764 100644 --- a/apps/Mooc/Backend/Backend.csproj +++ b/apps/Mooc/Backend/Backend.csproj @@ -12,6 +12,6 @@ - +
diff --git a/docker-compose.yml b/docker-compose.yml index 134f1c7..2c071e9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: mssql: container_name: codelytv-chsarp_ddd_skeleton-mssql - image: mcr.microsoft.com/mssql/server:2019-latest + image: mcr.microsoft.com/mssql/server:2022-latest user: root ports: - 1433:1433 diff --git a/src/Backoffice/Backoffice.csproj b/src/Backoffice/Backoffice.csproj index 88806a0..35d85f7 100644 --- a/src/Backoffice/Backoffice.csproj +++ b/src/Backoffice/Backoffice.csproj @@ -11,8 +11,8 @@ - - + +
diff --git a/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.cs b/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.cs index 462d3d4..20212f9 100644 --- a/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.cs +++ b/src/Backoffice/Courses/Application/SearchByCriteria/SearchBackofficeCoursesByCriteriaQueryHandler.cs @@ -4,21 +4,24 @@ namespace CodelyTv.Backoffice.Courses.Application.SearchByCriteria { - public class - SearchBackofficeCoursesByCriteriaQueryHandler : QueryHandler + public class SearchBackofficeCoursesByCriteriaQueryHandler : QueryHandler { private readonly BackofficeCoursesByCriteriaSearcher _searcher; public SearchBackofficeCoursesByCriteriaQueryHandler(BackofficeCoursesByCriteriaSearcher searcher) { - _searcher = searcher; + _searcher = searcher ?? throw new ArgumentNullException(nameof(searcher)); } public async Task Handle(SearchBackofficeCoursesByCriteriaQuery query) { - var filters = Filters.FromValues(query.Filters); - var order = Order.FromValues(query.OrderBy, query.OrderType); + if (query == null) + { + throw new ArgumentNullException(nameof(query)); + } + + Filters filters = Filters.FromValues(query.Filters) ?? throw new InvalidOperationException("Filters are missing"); + var order = Order.FromValues(query.OrderBy ?? throw new InvalidOperationException("OrderBy is missing"), query.OrderType ?? throw new InvalidOperationException("OrderType is missing")); return await _searcher.Search(filters, order, query.Limit, query.Offset); } diff --git a/src/Backoffice/Courses/Domain/BackofficeCourse.cs b/src/Backoffice/Courses/Domain/BackofficeCourse.cs index 322db05..5a620dc 100644 --- a/src/Backoffice/Courses/Domain/BackofficeCourse.cs +++ b/src/Backoffice/Courses/Domain/BackofficeCourse.cs @@ -11,41 +11,51 @@ public class BackofficeCourse public BackofficeCourse(string id, string name, string duration) { - Id = id; - Name = name; - Duration = duration; + Id = id ?? throw new ArgumentNullException(nameof(id)); + Name = name ?? throw new ArgumentNullException(nameof(name)); + Duration = duration ?? throw new ArgumentNullException(nameof(duration)); } private BackofficeCourse() { + Id = string.Empty; + Name = string.Empty; + Duration = string.Empty; } public Dictionary ToPrimitives() { - var primitives = new Dictionary + return new Dictionary { - {"id", Id}, - {"name", Name}, - {"duration", Duration} + { "id", Id }, + { "name", Name }, + { "duration", Duration } }; - - return primitives; } public static BackofficeCourse FromPrimitives(Dictionary body) { - return new BackofficeCourse(body["id"].ToString(), body["name"].ToString(), body["duration"].ToString()); + if (body == null) + { + throw new ArgumentNullException(nameof(body)); + } + + return new BackofficeCourse( + body["id"]?.ToString() ?? throw new InvalidOperationException("ID is missing"), + body["name"]?.ToString() ?? throw new InvalidOperationException("Name is missing"), + body["duration"]?.ToString() ?? throw new InvalidOperationException("Duration is missing") + ); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (this == obj) return true; + if (obj == null || GetType() != obj.GetType()) return false; var item = obj as BackofficeCourse; if (item == null) return false; - return Id.Equals(item.Id) && Name.Equals(item.Name) && - Duration.Equals(item.Duration); + return Id.Equals(item.Id) && Name.Equals(item.Name) && Duration.Equals(item.Duration); } public override int GetHashCode() diff --git a/src/Backoffice/Courses/Infrastructure/Persistence/Elasticsearch/BackofficeCourseElasticSearchDescriptor.cs b/src/Backoffice/Courses/Infrastructure/Persistence/Elasticsearch/BackofficeCourseElasticSearchDescriptor.cs index 0292a7f..7f722b4 100644 --- a/src/Backoffice/Courses/Infrastructure/Persistence/Elasticsearch/BackofficeCourseElasticSearchDescriptor.cs +++ b/src/Backoffice/Courses/Infrastructure/Persistence/Elasticsearch/BackofficeCourseElasticSearchDescriptor.cs @@ -7,7 +7,12 @@ public static class BackofficeCourseElasticsearchDescriptor { public static CreateIndexDescriptor CreateBackofficeCourseDescriptor(this CreateIndexDescriptor descriptor) { - return descriptor?.Map(m => m.AutoMap().Properties(pr => pr + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + return descriptor.Map(m => m.AutoMap().Properties(pr => pr .Keyword(i => i.Name("id")) .Keyword(i => i.Name("name")) )); diff --git a/src/Backoffice/Courses/Infrastructure/Persistence/Elasticsearch/ElasticsearchBackofficeCourseRepository.cs b/src/Backoffice/Courses/Infrastructure/Persistence/Elasticsearch/ElasticsearchBackofficeCourseRepository.cs index f7cd142..2b29d27 100644 --- a/src/Backoffice/Courses/Infrastructure/Persistence/Elasticsearch/ElasticsearchBackofficeCourseRepository.cs +++ b/src/Backoffice/Courses/Infrastructure/Persistence/Elasticsearch/ElasticsearchBackofficeCourseRepository.cs @@ -18,19 +18,30 @@ public ElasticsearchBackofficeCourseRepository(ElasticsearchClient client) : bas public async Task Save(BackofficeCourse course) { - await Persist(course.Id, JsonConvert.SerializeObject(course?.ToPrimitives())); + if (course == null) + { + throw new ArgumentNullException(nameof(course)); + } + + await Persist(course.Id ?? throw new InvalidOperationException("Course ID is missing"), + JsonConvert.SerializeObject(course.ToPrimitives() ?? throw new InvalidOperationException("Course primitives are missing"))); } public async Task> Matching(Criteria criteria) { + if (criteria == null) + { + throw new ArgumentNullException(nameof(criteria)); + } + var docs = await SearchByCriteria(criteria); - return docs?.Select(BackofficeCourse.FromPrimitives).ToList(); + return docs?.Select(BackofficeCourse.FromPrimitives).ToList() ?? Enumerable.Empty(); } public async Task> SearchAll() { var docs = await SearchAllInElastic(); - return docs?.Select(BackofficeCourse.FromPrimitives).ToList(); + return docs?.Select(BackofficeCourse.FromPrimitives).ToList() ?? Enumerable.Empty(); } protected override string ModuleName() diff --git a/src/Backoffice/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchExtension.cs b/src/Backoffice/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchExtension.cs index e277b09..4eb52eb 100644 --- a/src/Backoffice/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchExtension.cs +++ b/src/Backoffice/Shared/Infrastructure/Persistence/Elasticsearch/ElasticsearchExtension.cs @@ -12,8 +12,18 @@ public static class ElasticsearchExtensions { public static void AddElasticsearch(this IServiceCollection services, IConfiguration configuration) { - var defaultIndex = configuration["Elasticsearch:IndexPrefix"]; - var url = $"{configuration["Elasticsearch:Host"]}:{configuration["Elasticsearch:Port"]}"; + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + var defaultIndex = configuration["Elasticsearch:IndexPrefix"] ?? throw new InvalidOperationException("Elasticsearch index prefix is missing"); + + var host = configuration["Elasticsearch:Host"] ?? throw new InvalidOperationException("Elasticsearch host is missing"); + + var port = configuration["Elasticsearch:Port"] ?? throw new InvalidOperationException("Elasticsearch port is missing"); + + var url = $"{host}:{port}"; var settings = new ConnectionSettings(new Uri(url)).DefaultIndex(defaultIndex); diff --git a/src/Mooc/Courses/Domain/Course.cs b/src/Mooc/Courses/Domain/Course.cs index 79648b7..38eae8f 100644 --- a/src/Mooc/Courses/Domain/Course.cs +++ b/src/Mooc/Courses/Domain/Course.cs @@ -19,6 +19,9 @@ public Course(CourseId id, CourseName name, CourseDuration duration) private Course() { + Id = null!; + Name = null!; + Duration = null!; } public static Course Create(CourseId id, CourseName name, CourseDuration duration) @@ -30,7 +33,7 @@ public static Course Create(CourseId id, CourseName name, CourseDuration duratio return course; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (this == obj) return true; diff --git a/src/Mooc/Courses/Domain/CourseRepository.cs b/src/Mooc/Courses/Domain/CourseRepository.cs index f1083fb..06d31bf 100644 --- a/src/Mooc/Courses/Domain/CourseRepository.cs +++ b/src/Mooc/Courses/Domain/CourseRepository.cs @@ -5,6 +5,6 @@ namespace CodelyTv.Mooc.Courses.Domain public interface CourseRepository { Task Save(Course course); - Task Search(CourseId id); + Task Search(CourseId id); } } diff --git a/src/Mooc/Courses/Infrastructure/FileCourseRepository.cs b/src/Mooc/Courses/Infrastructure/FileCourseRepository.cs index bd13ada..d987677 100644 --- a/src/Mooc/Courses/Infrastructure/FileCourseRepository.cs +++ b/src/Mooc/Courses/Infrastructure/FileCourseRepository.cs @@ -20,7 +20,7 @@ await Task.Run(() => }); } - public async Task Search(CourseId id) + public async Task Search(CourseId id) { if (File.Exists(FileName(id.Value))) { diff --git a/src/Mooc/Courses/Infrastructure/Persistence/MsSqlCourseRepository.cs b/src/Mooc/Courses/Infrastructure/Persistence/MsSqlCourseRepository.cs index b30e386..20907f6 100644 --- a/src/Mooc/Courses/Infrastructure/Persistence/MsSqlCourseRepository.cs +++ b/src/Mooc/Courses/Infrastructure/Persistence/MsSqlCourseRepository.cs @@ -11,17 +11,27 @@ public class MsSqlCourseRepository : CourseRepository public MsSqlCourseRepository(MoocContext context) { - _context = context; + _context = context ?? throw new ArgumentNullException(nameof(context)); } public async Task Save(Course course) { + if (course == null) + { + throw new ArgumentNullException(nameof(course)); + } + await _context.Courses.AddAsync(course); await _context.SaveChangesAsync(); } - public async Task Search(CourseId id) + public async Task Search(CourseId id) { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + return await _context.Courses.FirstOrDefaultAsync(c => c.Id.Equals(id)); } } diff --git a/src/Mooc/CoursesCounters/Application/Find/CoursesCounterResponse.cs b/src/Mooc/CoursesCounters/Application/Find/CoursesCounterResponse.cs index 2acc199..c247189 100644 --- a/src/Mooc/CoursesCounters/Application/Find/CoursesCounterResponse.cs +++ b/src/Mooc/CoursesCounters/Application/Find/CoursesCounterResponse.cs @@ -11,7 +11,7 @@ public CoursesCounterResponse(int total) Total = total; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (this == obj) return true; diff --git a/src/Mooc/CoursesCounters/Domain/CoursesCounter.cs b/src/Mooc/CoursesCounters/Domain/CoursesCounter.cs index dd79378..b00fc85 100644 --- a/src/Mooc/CoursesCounters/Domain/CoursesCounter.cs +++ b/src/Mooc/CoursesCounters/Domain/CoursesCounter.cs @@ -20,6 +20,9 @@ public CoursesCounter(CoursesCounterId id, CoursesCounterTotal total, List Search(); + Task Search(); } } diff --git a/src/Mooc/CoursesCounters/Infrastructure/Persistence/MsSqlCoursesCounterRepository.cs b/src/Mooc/CoursesCounters/Infrastructure/Persistence/MsSqlCoursesCounterRepository.cs index 42b5e0b..07ece12 100644 --- a/src/Mooc/CoursesCounters/Infrastructure/Persistence/MsSqlCoursesCounterRepository.cs +++ b/src/Mooc/CoursesCounters/Infrastructure/Persistence/MsSqlCoursesCounterRepository.cs @@ -24,7 +24,7 @@ public async Task Save(CoursesCounter counter) await _context.SaveChangesAsync(); } - public async Task Search() + public async Task Search() { return await _context.CoursesCounter.SingleOrDefaultAsync(); } diff --git a/src/Mooc/Mooc.csproj b/src/Mooc/Mooc.csproj index 69aadd4..9fbc70e 100644 --- a/src/Mooc/Mooc.csproj +++ b/src/Mooc/Mooc.csproj @@ -11,13 +11,13 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/src/Mooc/Shared/Infrastructure/Persistence/EntityFramework/EntityConfigurations/DomainEventPrimitiveConfiguration.cs b/src/Mooc/Shared/Infrastructure/Persistence/EntityFramework/EntityConfigurations/DomainEventPrimitiveConfiguration.cs index 7e0a0d4..7c57b85 100644 --- a/src/Mooc/Shared/Infrastructure/Persistence/EntityFramework/EntityConfigurations/DomainEventPrimitiveConfiguration.cs +++ b/src/Mooc/Shared/Infrastructure/Persistence/EntityFramework/EntityConfigurations/DomainEventPrimitiveConfiguration.cs @@ -12,19 +12,29 @@ public class DomainEventPrimitiveConfiguration : IEntityTypeConfiguration builder) { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + builder.ToTable(nameof(MoocContext.DomainEvents).ToDatabaseFormat()); builder.HasKey(x => x.AggregateId); builder.Property(x => x.Body) - .HasConversion(v => JsonConvert.SerializeObject(v), - v => JsonConvert.DeserializeObject>(v)); + .HasConversion( + v => JsonConvert.SerializeObject(v) ?? string.Empty, + v => JsonConvert.DeserializeObject>(v) ?? new Dictionary() + ); builder.Property(x => x.AggregateId) .HasColumnName(nameof(DomainEventPrimitive.AggregateId).ToDatabaseFormat()); builder.Property(x => x.OccurredOn) - .HasConversion(v => Utils.StringToDate(v), v => Utils.DateToString(v)) + .HasConversion( + v => Utils.StringToDate(v), + v => Utils.DateToString(v) ?? string.Empty + ) .HasColumnName(nameof(DomainEventPrimitive.OccurredOn).ToDatabaseFormat()); } } diff --git a/src/Mooc/Shared/Infrastructure/Persistence/EntityFramework/ValueConverter/ExistingCoursesConverter.cs b/src/Mooc/Shared/Infrastructure/Persistence/EntityFramework/ValueConverter/ExistingCoursesConverter.cs index 71014d0..3c8d1fa 100644 --- a/src/Mooc/Shared/Infrastructure/Persistence/EntityFramework/ValueConverter/ExistingCoursesConverter.cs +++ b/src/Mooc/Shared/Infrastructure/Persistence/EntityFramework/ValueConverter/ExistingCoursesConverter.cs @@ -7,7 +7,7 @@ namespace CodelyTv.Mooc.Shared.Infrastructure.Persistence.EntityFramework.ValueC { public class ExistingCoursesConverter : ValueConverter, string> { - public ExistingCoursesConverter(ConverterMappingHints mappingHints = null) + public ExistingCoursesConverter(ConverterMappingHints? mappingHints = null) : base(v => ConvertConfiguration.ObjectToJson(v), v => ConvertConfiguration.ObjectFromJson(v), mappingHints diff --git a/src/Shared/Cli/CommandBuilder.cs b/src/Shared/Cli/CommandBuilder.cs index 551978f..32be541 100644 --- a/src/Shared/Cli/CommandBuilder.cs +++ b/src/Shared/Cli/CommandBuilder.cs @@ -11,7 +11,7 @@ public abstract class CommandBuilder private readonly string[] _args; private readonly Dictionary Commands; - protected ServiceProvider Provider { get; set; } + protected ServiceProvider? Provider { get; set; } protected CommandBuilder(string[] args, Dictionary commands) { @@ -24,11 +24,18 @@ protected CommandBuilder(string[] args, Dictionary commands) public virtual void Run() { var command = GetCommand(); - + if (Provider == null) throw new SystemException("Provider is not set"); using var scope = Provider.CreateScope(); var service = scope.ServiceProvider.GetService(command); - ((Command) service).Execute(_args); + if (service is Command commandService) + { + commandService.Execute(_args); + } + else + { + throw new SystemException("Command is not a valid command"); + } } protected Type GetCommand() diff --git a/src/Shared/Domain/Bus/Command/CommandHandlerWrapper.cs b/src/Shared/Domain/Bus/Command/CommandHandlerWrapper.cs index 239371e..796bd10 100644 --- a/src/Shared/Domain/Bus/Command/CommandHandlerWrapper.cs +++ b/src/Shared/Domain/Bus/Command/CommandHandlerWrapper.cs @@ -11,10 +11,25 @@ internal abstract class CommandHandlerWrapper internal class CommandHandlerWrapper : CommandHandlerWrapper where TCommand : Command { - public override async Task Handle(Command domainEvent, IServiceProvider provider) + public override async Task Handle(Command command, IServiceProvider provider) { - var handler = (CommandHandler) provider.GetService(typeof(CommandHandler)); - await handler.Handle((TCommand) domainEvent); + if (command == null) + { + throw new ArgumentNullException(nameof(command)); + } + + if (provider == null) + { + throw new ArgumentNullException(nameof(provider)); + } + + var handler = provider.GetService(typeof(CommandHandler)) as CommandHandler; + if (handler == null) + { + throw new InvalidOperationException($"Handler for {typeof(TCommand).Name} not found"); + } + + await handler.Handle((TCommand)command); } } } diff --git a/src/Shared/Domain/Bus/Event/DomainEvent.cs b/src/Shared/Domain/Bus/Event/DomainEvent.cs index 002a398..e295be4 100644 --- a/src/Shared/Domain/Bus/Event/DomainEvent.cs +++ b/src/Shared/Domain/Bus/Event/DomainEvent.cs @@ -10,16 +10,16 @@ public abstract class DomainEvent public string EventId { get; } public string OccurredOn { get; } - protected DomainEvent(string aggregateId, string eventId, string occurredOn) + protected DomainEvent(string aggregateId, string? eventId, string? occurredOn) { AggregateId = aggregateId; EventId = eventId ?? Uuid.Random().Value; OccurredOn = occurredOn ?? Utils.DateToString(DateTime.Now); } - protected DomainEvent() - { - } + // protected DomainEvent() + // { + // } public abstract string EventName(); public abstract Dictionary ToPrimitives(); diff --git a/src/Shared/Domain/Bus/Event/DomainEventPrimitive.cs b/src/Shared/Domain/Bus/Event/DomainEventPrimitive.cs index e72ffde..e9b5c13 100644 --- a/src/Shared/Domain/Bus/Event/DomainEventPrimitive.cs +++ b/src/Shared/Domain/Bus/Event/DomainEventPrimitive.cs @@ -4,10 +4,10 @@ namespace CodelyTv.Shared.Domain.Bus.Event { public class DomainEventPrimitive { - public string Id { get; set; } - public string AggregateId { get; set; } - public string Name { get; set; } - public string OccurredOn { get; set; } - public Dictionary Body { get; set; } + public string Id { get; set; } = string.Empty; + public string AggregateId { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string OccurredOn { get; set; } = string.Empty; + public Dictionary Body { get; set; } = new Dictionary(); } } diff --git a/src/Shared/Domain/Bus/Query/QueryBus.cs b/src/Shared/Domain/Bus/Query/QueryBus.cs index d8cb4b8..8d9d3c9 100644 --- a/src/Shared/Domain/Bus/Query/QueryBus.cs +++ b/src/Shared/Domain/Bus/Query/QueryBus.cs @@ -4,6 +4,6 @@ namespace CodelyTv.Shared.Domain.Bus.Query { public interface QueryBus { - Task Ask(Query request); + Task Ask(Query request) where TResponse : class; } } diff --git a/src/Shared/Domain/Bus/Query/QueryHandlerWrapper.cs b/src/Shared/Domain/Bus/Query/QueryHandlerWrapper.cs index 6be1d2b..9d5d3f7 100644 --- a/src/Shared/Domain/Bus/Query/QueryHandlerWrapper.cs +++ b/src/Shared/Domain/Bus/Query/QueryHandlerWrapper.cs @@ -13,10 +13,13 @@ internal class QueryHandlerWrapper : QueryHandlerWrapper Handle(Query query, IServiceProvider provider) { - var handler = - (QueryHandler) provider.GetService(typeof(QueryHandler)); + var service = provider.GetService(typeof(QueryHandler)); + if (!(service is QueryHandler handler)) + { + throw new SystemException("Query is not a valid query"); + } - return await handler.Handle((TQuery) query); + return await handler.Handle((TQuery)query); } } } diff --git a/src/Shared/Domain/Courses/Domain/CourseCreatedDomainEvent.cs b/src/Shared/Domain/Courses/Domain/CourseCreatedDomainEvent.cs index 24d8e1e..d9e80ed 100644 --- a/src/Shared/Domain/Courses/Domain/CourseCreatedDomainEvent.cs +++ b/src/Shared/Domain/Courses/Domain/CourseCreatedDomainEvent.cs @@ -9,15 +9,17 @@ public class CourseCreatedDomainEvent : DomainEvent public string Name { get; } public string Duration { get; } - public CourseCreatedDomainEvent(string id, string name, string duration, string eventId = null, - string occurredOn = null) : base(id, eventId, occurredOn) + public CourseCreatedDomainEvent(string id, string name, string duration, string? eventId = null, + string? occurredOn = null) : base(id, eventId, occurredOn) { Name = name; Duration = duration; } - public CourseCreatedDomainEvent() + public CourseCreatedDomainEvent() : base(string.Empty, null, null) { + Name = string.Empty; + Duration = string.Empty; } public override string EventName() @@ -40,7 +42,7 @@ public override DomainEvent FromPrimitives(string aggregateId, Dictionary filters) Values = filters; } - public static Filters FromValues(List> filters) + public static Filters? FromValues(List> filters) { if (filters == null) return null; diff --git a/src/Shared/Domain/ReflectionHelper.cs b/src/Shared/Domain/ReflectionHelper.cs index 8d83f40..7849ea6 100644 --- a/src/Shared/Domain/ReflectionHelper.cs +++ b/src/Shared/Domain/ReflectionHelper.cs @@ -7,17 +7,17 @@ namespace CodelyTv.Shared.Domain { public static class ReflectionHelper { - public static Assembly GetAssemblyByName(string name) + public static Assembly? GetAssemblyByName(string name) { if (name == null) return null; name = name.ToUpper(CultureInfo.InvariantCulture); return AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(x => x.FullName.ToUpper(CultureInfo.InvariantCulture) - .Contains(name, StringComparison.InvariantCulture)); + .FirstOrDefault(x => x.FullName?.ToUpper(CultureInfo.InvariantCulture) + .Contains(name, StringComparison.InvariantCulture) ?? false); } - public static Type GetType(string name) + public static Type? GetType(string name) { if (string.IsNullOrEmpty(name)) return null; @@ -25,16 +25,21 @@ public static Type GetType(string name) .FirstOrDefault(type => type.Name.Equals(name, StringComparison.InvariantCulture)); } - public static Type GetType(string assemblyName, string name) + public static Type? GetType(string assemblyName, string name) { if (string.IsNullOrEmpty(assemblyName) && string.IsNullOrEmpty(name)) return null; var assembly = GetAssemblyByName(assemblyName); + if (assembly == null) + { + throw new ArgumentException("Assembly not found", nameof(assemblyName)); + } + return GetType(assembly, name); } - public static Type GetType(Assembly assembly, string name) + public static Type? GetType(Assembly assembly, string name) { if (assembly == null) return null; diff --git a/src/Shared/Domain/ValueObject/IntValueObject.cs b/src/Shared/Domain/ValueObject/IntValueObject.cs index 85a36f7..92a0075 100644 --- a/src/Shared/Domain/ValueObject/IntValueObject.cs +++ b/src/Shared/Domain/ValueObject/IntValueObject.cs @@ -23,7 +23,7 @@ protected override IEnumerable GetAtomicValues() yield return Value; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (this == obj) return true; diff --git a/src/Shared/Domain/ValueObject/StringValueObject.cs b/src/Shared/Domain/ValueObject/StringValueObject.cs index f5feb95..7d17786 100644 --- a/src/Shared/Domain/ValueObject/StringValueObject.cs +++ b/src/Shared/Domain/ValueObject/StringValueObject.cs @@ -22,7 +22,7 @@ protected override IEnumerable GetAtomicValues() yield return Value; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (this == obj) return true; diff --git a/src/Shared/Domain/ValueObject/Uuid.cs b/src/Shared/Domain/ValueObject/Uuid.cs index 032083c..84cd6bb 100644 --- a/src/Shared/Domain/ValueObject/Uuid.cs +++ b/src/Shared/Domain/ValueObject/Uuid.cs @@ -35,7 +35,7 @@ protected override IEnumerable GetAtomicValues() yield return Value; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (this == obj) return true; diff --git a/src/Shared/Domain/ValueObject/ValueObject.cs b/src/Shared/Domain/ValueObject/ValueObject.cs index 8caf13f..ca72447 100644 --- a/src/Shared/Domain/ValueObject/ValueObject.cs +++ b/src/Shared/Domain/ValueObject/ValueObject.cs @@ -19,20 +19,18 @@ protected static bool NotEqualOperator(ValueObject left, ValueObject right) protected abstract IEnumerable GetAtomicValues(); - public override bool Equals(object obj) + public override bool Equals(object? obj) { - if (obj == null || obj.GetType() != GetType()) return false; + if (obj is not ValueObject other || obj.GetType() != GetType()) return false; - var other = (ValueObject) obj; var thisValues = GetAtomicValues().GetEnumerator(); var otherValues = other.GetAtomicValues().GetEnumerator(); while (thisValues.MoveNext() && otherValues.MoveNext()) { - if (ReferenceEquals(thisValues.Current, null) ^ - ReferenceEquals(otherValues.Current, null)) + if (thisValues.Current is null ^ otherValues.Current is null) return false; - if (thisValues.Current != null && + if (thisValues.Current is not null && !thisValues.Current.Equals(otherValues.Current)) return false; } diff --git a/src/Shared/DomainEventSubscriberServices.cs b/src/Shared/DomainEventSubscriberServices.cs index a9a08f3..2d33aa1 100644 --- a/src/Shared/DomainEventSubscriberServices.cs +++ b/src/Shared/DomainEventSubscriberServices.cs @@ -57,7 +57,7 @@ private static IEnumerable GetLoadableTypes(this Assembly assembly) } catch (ReflectionTypeLoadException e) { - return e.Types.Where(t => t != null); + return e.Types.Where(t => t != null).Cast(); } } } diff --git a/src/Shared/Infrastructure/Bus/Command/InMemoryCommandBus.cs b/src/Shared/Infrastructure/Bus/Command/InMemoryCommandBus.cs index d4a1efc..b6aff18 100644 --- a/src/Shared/Infrastructure/Bus/Command/InMemoryCommandBus.cs +++ b/src/Shared/Infrastructure/Bus/Command/InMemoryCommandBus.cs @@ -17,16 +17,32 @@ public class InMemoryCommandBus : CommandBus public InMemoryCommandBus(IServiceProvider provider) { - _provider = provider; + _provider = provider ?? throw new ArgumentNullException(nameof(provider)); } public async Task Dispatch(Domain.Bus.Command.Command command) { + if (command == null) + { + throw new ArgumentNullException(nameof(command)); + } + var wrappedHandlers = GetWrappedHandlers(command); - if (wrappedHandlers == null) throw new CommandNotRegisteredError(command); + if (wrappedHandlers == null || !wrappedHandlers.Any()) + { + throw new CommandNotRegisteredError(command); + } + + foreach (var handler in wrappedHandlers) + { + if (handler == null) + { + continue; + } - foreach (var handler in wrappedHandlers) await handler.Handle(command, _provider); + await handler.Handle(command, _provider); + } } private IEnumerable GetWrappedHandlers(Domain.Bus.Command.Command command) @@ -34,11 +50,15 @@ private IEnumerable GetWrappedHandlers(Domain.Bus.Command var handlerType = typeof(CommandHandler<>).MakeGenericType(command.GetType()); var wrapperType = typeof(CommandHandlerWrapper<>).MakeGenericType(command.GetType()); - var handlers = - (IEnumerable) _provider.GetService(typeof(IEnumerable<>).MakeGenericType(handlerType)); + var handlers = _provider.GetService(typeof(IEnumerable<>).MakeGenericType(handlerType)) as IEnumerable; + if (handlers == null) + { + throw new InvalidOperationException($"Handlers for command type {command.GetType().Name} not found"); + } - var wrappedHandlers = _commandHandlers.GetOrAdd(command.GetType(), handlers.Cast() - .Select(handler => (CommandHandlerWrapper) Activator.CreateInstance(wrapperType))); + var wrappedHandlers = _commandHandlers.GetOrAdd(command.GetType(), _ => handlers + .Select(handler => (CommandHandlerWrapper)(Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Unable to create instance of {wrapperType.Name}"))) + .ToList()); return wrappedHandlers; } diff --git a/src/Shared/Infrastructure/Bus/Event/DomainEventInformation.cs b/src/Shared/Infrastructure/Bus/Event/DomainEventInformation.cs index fe73800..a5e2bf8 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventInformation.cs +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventInformation.cs @@ -14,9 +14,9 @@ public DomainEventsInformation() GetDomainTypes().ForEach(eventType => IndexedDomainEvents.Add(GetEventName(eventType), eventType)); } - public Type ForName(string name) + public Type? ForName(string name) { - Type value; + Type? value; IndexedDomainEvents.TryGetValue(name, out value); return value; } @@ -28,8 +28,18 @@ public string ForClass(DomainEvent domainEvent) private string GetEventName(Type eventType) { - var instance = (DomainEvent) Activator.CreateInstance(eventType); - return eventType.GetMethod("EventName").Invoke(instance, null).ToString(); + var instance = Activator.CreateInstance(eventType); + var method = eventType.GetMethod("EventName"); + if (method == null) + { + throw new InvalidOperationException($"Type {eventType} does not have a method named 'EventName'."); + } + var result = method.Invoke(instance, null); + if (result == null) + { + throw new InvalidOperationException($"Method 'EventName' on type {eventType} returned null."); + } + return result.ToString() ?? ""; } private List GetDomainTypes() diff --git a/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.cs b/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.cs index 4bd00d7..a7c2142 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.cs +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventJsonDeserializer.cs @@ -12,30 +12,67 @@ public class DomainEventJsonDeserializer public DomainEventJsonDeserializer(DomainEventsInformation information) { - this.information = information; + this.information = information ?? throw new ArgumentNullException(nameof(information)); } public DomainEvent Deserialize(string body) { + if (string.IsNullOrEmpty(body)) + { + throw new ArgumentNullException(nameof(body)); + } + var eventData = JsonConvert.DeserializeObject>>(body); + if (eventData == null || !eventData.ContainsKey("data")) + { + throw new InvalidOperationException("Invalid JSON format"); + } var data = eventData["data"]; - var attributes = JsonConvert.DeserializeObject>(data["attributes"].ToString()); - - var domainEventType = information.ForName((string) data["type"]); - - var instance = (DomainEvent) Activator.CreateInstance(domainEventType); - - var domainEvent = (DomainEvent) domainEventType - .GetTypeInfo() - .GetDeclaredMethod(nameof(DomainEvent.FromPrimitives)) - .Invoke(instance, new object[] - { - attributes["id"], - attributes, - data["id"].ToString(), - data["occurred_on"].ToString() - }); + if (data == null || !data.ContainsKey("attributes") || !data.ContainsKey("type") || !data.ContainsKey("id") || !data.ContainsKey("occurred_on")) + { + throw new InvalidOperationException("Invalid data format"); + } + + var attributes = JsonConvert.DeserializeObject>(data["attributes"]?.ToString() ?? string.Empty); + if (attributes == null) + { + throw new InvalidOperationException("Invalid attributes format"); + } + + var domainEventType = information.ForName(data["type"]?.ToString() ?? throw new InvalidOperationException("Type is missing")); + if (domainEventType == null) + { + throw new InvalidOperationException("Unable to resolve domain event type"); + } + + var instance = Activator.CreateInstance(domainEventType) as DomainEvent; + if (instance == null) + { + throw new InvalidOperationException($"Unable to create instance of type {domainEventType}"); + } + + var fromPrimitivesMethod = domainEventType.GetTypeInfo().GetDeclaredMethod(nameof(DomainEvent.FromPrimitives)); + if (fromPrimitivesMethod == null) + { + throw new InvalidOperationException("FromPrimitives method not found"); + } + + var id = data["id"]?.ToString() ?? throw new InvalidOperationException("ID is missing"); + var occurredOn = data["occurred_on"]?.ToString() ?? throw new InvalidOperationException("Occurred on is missing"); + + var domainEvent = fromPrimitivesMethod.Invoke(instance, new object[] + { + attributes["id"], + attributes, + id, + occurredOn + }) as DomainEvent; + + if (domainEvent == null) + { + throw new InvalidOperationException("Unable to deserialize domain event"); + } return domainEvent; } diff --git a/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberInformation.cs b/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberInformation.cs index 2cb04bc..e241cc6 100644 --- a/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberInformation.cs +++ b/src/Shared/Infrastructure/Bus/Event/DomainEventSubscriberInformation.cs @@ -14,7 +14,7 @@ public string ContextName get { var nameParts = _subscriberClass.FullName?.Split("."); - return nameParts?[1]; + return nameParts?.Length > 1 ? nameParts[1] : string.Empty; } } @@ -23,7 +23,7 @@ public string ModuleName get { var nameParts = _subscriberClass.FullName?.Split("."); - return nameParts?[2]; + return nameParts?.Length > 2 ? nameParts[2] : string.Empty; } } @@ -32,7 +32,7 @@ public string ClassName get { var nameParts = _subscriberClass.FullName?.Split("."); - return nameParts?[^1]; + return nameParts?.Length > 0 ? nameParts[^1] : string.Empty; } } diff --git a/src/Shared/Infrastructure/Bus/Event/InMemoryApplicationEventBus.cs b/src/Shared/Infrastructure/Bus/Event/InMemoryApplicationEventBus.cs index ea5783b..57e0073 100644 --- a/src/Shared/Infrastructure/Bus/Event/InMemoryApplicationEventBus.cs +++ b/src/Shared/Infrastructure/Bus/Event/InMemoryApplicationEventBus.cs @@ -25,11 +25,17 @@ public async Task Publish(List events) { var subscribers = GetSubscribers(@event, scope); - foreach (var subscriber in subscribers) await ((DomainEventSubscriberBase) subscriber).On(@event); + foreach (var subscriber in subscribers) + { + if (subscriber is not null) + { + await ((DomainEventSubscriberBase)subscriber).On(@event); + } + } } } - private static IEnumerable GetSubscribers(DomainEvent @event, IServiceScope scope) + private static IEnumerable GetSubscribers(DomainEvent @event, IServiceScope scope) { var eventType = @event.GetType(); var subscriberType = typeof(DomainEventSubscriber<>).MakeGenericType(eventType); diff --git a/src/Shared/Infrastructure/Bus/Event/MsSql/MsSqlDomainEventsConsumer.cs b/src/Shared/Infrastructure/Bus/Event/MsSql/MsSqlDomainEventsConsumer.cs index 9176635..42a4630 100644 --- a/src/Shared/Infrastructure/Bus/Event/MsSql/MsSqlDomainEventsConsumer.cs +++ b/src/Shared/Infrastructure/Bus/Event/MsSql/MsSqlDomainEventsConsumer.cs @@ -19,36 +19,68 @@ public MsSqlDomainEventsConsumer(InMemoryApplicationEventBus bus, DomainEventsInformation domainEventsInformation, DbContext context) { - _bus = bus; - _domainEventsInformation = domainEventsInformation; - _context = context; + _bus = bus ?? throw new ArgumentNullException(nameof(bus)); + _domainEventsInformation = domainEventsInformation ?? throw new ArgumentNullException(nameof(domainEventsInformation)); + _context = context ?? throw new ArgumentNullException(nameof(context)); } public async Task Consume() { var domainEvents = _context.Set().Take(Chunk).ToList(); + if (domainEvents == null || domainEvents.Count == 0) + { + throw new InvalidOperationException("No domain events found"); + } - foreach (var domainEvent in domainEvents) await ExecuteSubscribers(domainEvent); + foreach (var domainEvent in domainEvents) + { + if (domainEvent == null) + { + continue; + } + await ExecuteSubscribers(domainEvent); + } } private async Task ExecuteSubscribers(DomainEventPrimitive domainEventPrimitive) { - var domainEventType = _domainEventsInformation.ForName(domainEventPrimitive.Name); + if (domainEventPrimitive == null) + { + throw new ArgumentNullException(nameof(domainEventPrimitive)); + } - var instance = (DomainEvent) Activator.CreateInstance(domainEventType); + var domainEventType = _domainEventsInformation.ForName(domainEventPrimitive.Name ?? throw new InvalidOperationException("Event name is missing")); + if (domainEventType == null) + { + throw new InvalidOperationException("Unable to resolve domain event type"); + } - var result = (DomainEvent) domainEventType - .GetTypeInfo() - .GetDeclaredMethod(nameof(DomainEvent.FromPrimitives)) - .Invoke(instance, new object[] - { - domainEventPrimitive.AggregateId, - domainEventPrimitive.Body, - domainEventPrimitive.Id, - domainEventPrimitive.OccurredOn - }); + var instance = Activator.CreateInstance(domainEventType) as DomainEvent; + if (instance == null) + { + throw new InvalidOperationException($"Unable to create instance of type {domainEventType}"); + } + + var fromPrimitivesMethod = domainEventType.GetTypeInfo().GetDeclaredMethod(nameof(DomainEvent.FromPrimitives)); + if (fromPrimitivesMethod == null) + { + throw new InvalidOperationException("FromPrimitives method not found"); + } + + var result = fromPrimitivesMethod.Invoke(instance, new object[] + { + domainEventPrimitive.AggregateId ?? throw new InvalidOperationException("AggregateId is missing"), + domainEventPrimitive.Body ?? throw new InvalidOperationException("Body is missing"), + domainEventPrimitive.Id ?? throw new InvalidOperationException("Id is missing"), + domainEventPrimitive.OccurredOn ?? throw new InvalidOperationException("OccurredOn is missing") + }) as DomainEvent; + + if (result == null) + { + throw new InvalidOperationException("Unable to deserialize domain event"); + } - await _bus.Publish(new List {result}); + await _bus.Publish(new List { result }); _context.Set().Remove(domainEventPrimitive); _context.SaveChanges(); diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConfig.cs b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConfig.cs index e3158dd..f175e55 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConfig.cs +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConfig.cs @@ -6,8 +6,8 @@ namespace CodelyTv.Shared.Infrastructure.Bus.Event.RabbitMq public class RabbitMqConfig { public ConnectionFactory ConnectionFactory { get; } - private static IConnection _connection { get; set; } - private static IModel _channel { get; set; } + private static IConnection? _connection { get; set; } + private static IModel? _channel { get; set; } public RabbitMqConfig(IOptions rabbitMqParams) { diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConfigParams.cs b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConfigParams.cs index 110f488..c029151 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConfigParams.cs +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqConfigParams.cs @@ -2,11 +2,11 @@ namespace CodelyTv.Shared.Infrastructure.Bus.Event.RabbitMq { public class RabbitMqConfigParams { - public string Username { get; set; } + public string Username { get; set; } = string.Empty; - public string Password { get; set; } + public string Password { get; set; } = string.Empty; - public string HostName { get; set; } + public string HostName { get; set; } = string.Empty; public int Port { get; set; } } diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.cs b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.cs index 5cae380..1e8e219 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.cs +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqDomainEventsConsumer.cs @@ -59,7 +59,7 @@ public void ConsumeMessages(string queue, ushort prefetchCount = 10) try { - await ((DomainEventSubscriberBase) subscriber).On(@event); + await ((DomainEventSubscriberBase)subscriber).On(@event); } catch { @@ -83,6 +83,8 @@ private object SubscribeFor(string queue, IServiceScope scope) var subscriberName = queueParts[^1].ToCamelFirstUpper(); var t = ReflectionHelper.GetType(subscriberName); + if (t is null) + throw new Exception($"The subscriber {subscriberName} does not exist"); var subscriber = scope.ServiceProvider.GetRequiredService(t); DomainEventSubscribers.Add(queue, subscriber); @@ -108,7 +110,7 @@ private void HandleConsumptionError(BasicDeliverEventArgs ea, DomainEvent @event private bool HasBeenRedeliveredTooMuch(IDictionary headers) { - return (int) (headers[HeaderRedelivery] ?? 0) >= MaxRetries; + return (int)(headers[HeaderRedelivery] ?? 0) >= MaxRetries; } private void SendToRetry(BasicDeliverEventArgs ea, string queue) @@ -129,7 +131,7 @@ private void SendMessageTo(string exchange, BasicDeliverEventArgs ea, string que var body = ea.Body; var properties = ea.BasicProperties; var headers = ea.BasicProperties.Headers; - headers[HeaderRedelivery] = (int) headers[HeaderRedelivery] + 1; + headers[HeaderRedelivery] = (int)headers[HeaderRedelivery] + 1; properties.Headers = headers; channel.BasicPublish(exchange, diff --git a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusConfiguration.cs b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusConfiguration.cs index 61538ed..10bd3a5 100644 --- a/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusConfiguration.cs +++ b/src/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusConfiguration.cs @@ -72,7 +72,11 @@ private IDictionary RetryQueueArguments(string domainEventExchan private string EventNameSubscribed(DomainEventSubscriberInformation subscriberInformation) { - dynamic domainEvent = Activator.CreateInstance(subscriberInformation.SubscribedEvent); + dynamic? domainEvent = Activator.CreateInstance(subscriberInformation.SubscribedEvent); + if (domainEvent == null) + { + throw new InvalidOperationException("Failed to create instance of event type "); + } return domainEvent.EventName(); } } diff --git a/src/Shared/Infrastructure/Bus/Query/InMemoryQueryBus.cs b/src/Shared/Infrastructure/Bus/Query/InMemoryQueryBus.cs index 8b8341f..7c8f08a 100644 --- a/src/Shared/Infrastructure/Bus/Query/InMemoryQueryBus.cs +++ b/src/Shared/Infrastructure/Bus/Query/InMemoryQueryBus.cs @@ -21,6 +21,7 @@ public InMemoryQueryBus(IServiceProvider provider) } public async Task Ask(Domain.Bus.Query.Query query) + where TResponse : class { var handler = GetWrappedHandlers(query); @@ -31,18 +32,36 @@ public async Task Ask(Domain.Bus.Query.Query query) private QueryHandlerWrapper GetWrappedHandlers(Domain.Bus.Query.Query query) { - Type[] typeArgs = {query.GetType(), typeof(TResponse)}; + Type[] typeArgs = { query.GetType(), typeof(TResponse) }; var handlerType = typeof(QueryHandler<,>).MakeGenericType(typeArgs); var wrapperType = typeof(QueryHandlerWrapper<,>).MakeGenericType(typeArgs); - var handlers = - (IEnumerable) _provider.GetService(typeof(IEnumerable<>).MakeGenericType(handlerType)); + var handlers = _provider.GetService(typeof(IEnumerable<>).MakeGenericType(handlerType)) as IEnumerable; + if (handlers == null) + { + throw new Exception($"The query {query.GetType().Name} has not a query handlers associated"); + } - var wrappedHandlers = (QueryHandlerWrapper) _queryHandlers.GetOrAdd(query.GetType(), - handlers.Cast() - .Select(handler => (QueryHandlerWrapper) Activator.CreateInstance(wrapperType)) - .FirstOrDefault()); + var handler = handlers.Cast() + .Select(h => + { + var instance = Activator.CreateInstance(wrapperType); + if (instance == null) + { + throw new InvalidOperationException($"Unable to create an instance of type {wrapperType.FullName}."); + } + + return (QueryHandlerWrapper)instance; + }) + .FirstOrDefault(); + + if (handler == null) + { + throw new Exception($"The query {query.GetType().Name} has not a query handler associated"); + } + + var wrappedHandlers = (QueryHandlerWrapper)_queryHandlers.GetOrAdd(query.GetType(), handler); return wrappedHandlers; } diff --git a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchCriteriaConverter.cs b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchCriteriaConverter.cs index a286730..1694f67 100644 --- a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchCriteriaConverter.cs +++ b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchCriteriaConverter.cs @@ -8,8 +8,8 @@ namespace CodelyTv.Shared.Infrastructure.Elasticsearch { public class ElasticsearchCriteriaConverter where T : class { - private readonly Dictionary> _queryTransformers = - new Dictionary> + private readonly Dictionary> _queryTransformers = + new Dictionary> { {FilterOperator.EQUAL, TermQuery}, {FilterOperator.NOT_EQUAL, TermQuery}, @@ -37,7 +37,7 @@ public SearchDescriptor Convert(Criteria criteria, string index) return searchDescriptor; } - private SortDescriptor CreateSortDescriptor(SortDescriptor sortDescriptor, Criteria criteria) + private SortDescriptor? CreateSortDescriptor(SortDescriptor sortDescriptor, Criteria criteria) { if (!criteria.HasOrder()) return null; @@ -48,12 +48,12 @@ private SortDescriptor CreateSortDescriptor(SortDescriptor sortDescriptor, return sortDescriptor.Field(f => f.Field(orderByValue).Order(sortOrder)); } - private QueryContainer QueryByCriteria(Criteria criteria) + private QueryContainer? QueryByCriteria(Criteria criteria) { if (!criteria.HasFilters()) return null; - QueryContainer query = null; + QueryContainer? query = null; foreach (var filter in criteria.Filters.Values) { @@ -69,7 +69,7 @@ private static QueryContainer TermQuery(Filter filter) return Query.Term(filter.Field.Value, filter.Value.Value.ToLowerInvariant()); } - private static QueryContainer GreaterThanQueryTransformer(Filter filter) + private static QueryContainer? GreaterThanQueryTransformer(Filter filter) { if (double.TryParse(filter.Value.Value, out var value)) return Query.Range(r => r.Field(filter.Field.Value).GreaterThan(value)); @@ -77,7 +77,7 @@ private static QueryContainer GreaterThanQueryTransformer(Filter filter) return null; } - private static QueryContainer GreaterThanOrEqualQueryTransformer(Filter filter) + private static QueryContainer? GreaterThanOrEqualQueryTransformer(Filter filter) { if (double.TryParse(filter.Value.Value, out var value)) return Query.Range(r => r.Field(filter.Field.Value).GreaterThanOrEquals(value)); @@ -85,7 +85,7 @@ private static QueryContainer GreaterThanOrEqualQueryTransformer(Filter filter) return null; } - private static QueryContainer LessThanQueryTransformer(Filter filter) + private static QueryContainer? LessThanQueryTransformer(Filter filter) { if (double.TryParse(filter.Value.Value, out var value)) return Query.Range(r => r.Field(filter.Field.Value).LessThan(value)); @@ -93,7 +93,7 @@ private static QueryContainer LessThanQueryTransformer(Filter filter) return null; } - private static QueryContainer LessThanOrEqualQueryTransformer(Filter filter) + private static QueryContainer? LessThanOrEqualQueryTransformer(Filter filter) { if (double.TryParse(filter.Value.Value, out var value)) return Query.Range(r => r.Field(filter.Field.Value).LessThanOrEquals(value)); diff --git a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchRepository.cs b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchRepository.cs index 994b5b9..c90f5f4 100644 --- a/src/Shared/Infrastructure/Elasticsearch/ElasticsearchRepository.cs +++ b/src/Shared/Infrastructure/Elasticsearch/ElasticsearchRepository.cs @@ -24,14 +24,16 @@ protected async Task>> SearchAllI searchDescriptor.MatchAll(); searchDescriptor.Index(_client.IndexFor(ModuleName())); - return (await _client.Client.SearchAsync>(searchDescriptor))?.Documents; + var response = await _client.Client.SearchAsync>(searchDescriptor); + return response?.Documents ?? new List>(); } protected async Task>> SearchByCriteria(Criteria criteria) { var searchDescriptor = _criteriaConverter.Convert(criteria, _client.IndexFor(ModuleName())); - return (await _client.Client.SearchAsync>(searchDescriptor))?.Documents; + var response = await _client.Client.SearchAsync>(searchDescriptor); + return response?.Documents ?? new List>(); } protected async Task Persist(string id, string json) diff --git a/src/Shared/Infrastructure/Persistence/EntityFramework/EntityConfigurations/ConvertConfiguration.cs b/src/Shared/Infrastructure/Persistence/EntityFramework/EntityConfigurations/ConvertConfiguration.cs index 79a7971..897e006 100644 --- a/src/Shared/Infrastructure/Persistence/EntityFramework/EntityConfigurations/ConvertConfiguration.cs +++ b/src/Shared/Infrastructure/Persistence/EntityFramework/EntityConfigurations/ConvertConfiguration.cs @@ -11,14 +11,26 @@ public static List ObjectFromJson(string json) where TObject : var jsonList = JsonConvert.DeserializeObject>(json); var type = typeof(TObject); - var ctor = type.GetConstructor(new[] {typeof(string)}); + var ctor = type.GetConstructor(new[] { typeof(string) }); + if (ctor == null) + { + throw new InvalidOperationException($"Type {typeof(TObject).FullName} does not have a constructor that takes a single string parameter."); + } + if (jsonList == null) + { + return new List(); + } - return jsonList.Select(x => (TObject) ctor.Invoke(new object[] {x})).ToList(); + return jsonList.Select(x => (TObject)ctor.Invoke(new object[] { x })).ToList(); } public static string ObjectToJson(List objects) { - return JsonConvert.SerializeObject(objects.Select(x => x.ToString())); + if (objects == null) + { + throw new ArgumentNullException(nameof(objects)); + } + return JsonConvert.SerializeObject(objects.Select(x => x?.ToString())); } } } diff --git a/src/Shared/Shared.csproj b/src/Shared/Shared.csproj index 2316287..047a3cc 100644 --- a/src/Shared/Shared.csproj +++ b/src/Shared/Shared.csproj @@ -11,17 +11,17 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + diff --git a/src/Shared/Validator/Attributes/UuidAttribute.cs b/src/Shared/Validator/Attributes/UuidAttribute.cs index 34c8f27..7102f3b 100644 --- a/src/Shared/Validator/Attributes/UuidAttribute.cs +++ b/src/Shared/Validator/Attributes/UuidAttribute.cs @@ -5,10 +5,10 @@ namespace CodelyTv.Shared.Validator.Attributes { public class UuidAttribute : ValidationAttribute { - protected override ValidationResult IsValid(object value, + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { - var isValid = Guid.TryParse((string) value, out var result); + var isValid = Guid.TryParse(value as string, out var result); if (isValid) return ValidationResult.Success; diff --git a/test/apps/Mooc/Mooc.csproj b/test/apps/Mooc/Mooc.csproj index b3710ba..bbd7cc7 100644 --- a/test/apps/Mooc/Mooc.csproj +++ b/test/apps/Mooc/Mooc.csproj @@ -8,13 +8,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/test/src/Backoffice/Backoffice.csproj b/test/src/Backoffice/Backoffice.csproj index 8fa2b1a..bf60447 100644 --- a/test/src/Backoffice/Backoffice.csproj +++ b/test/src/Backoffice/Backoffice.csproj @@ -9,13 +9,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/src/Mooc/CoursesCounters/CoursesCounterModuleUnitTestCase.cs b/test/src/Mooc/CoursesCounters/CoursesCounterModuleUnitTestCase.cs index fff0eec..6770422 100644 --- a/test/src/Mooc/CoursesCounters/CoursesCounterModuleUnitTestCase.cs +++ b/test/src/Mooc/CoursesCounters/CoursesCounterModuleUnitTestCase.cs @@ -25,7 +25,7 @@ protected void ShouldSearch(CoursesCounter counter) protected void ShouldSearch() { - Repository.Setup(x => x.Search()).ReturnsAsync((CoursesCounter) null); + Repository.Setup(x => x.Search()).ReturnsAsync((CoursesCounter?)null); } } } diff --git a/test/src/Mooc/Mooc.csproj b/test/src/Mooc/Mooc.csproj index 9f63d0f..671a2e7 100644 --- a/test/src/Mooc/Mooc.csproj +++ b/test/src/Mooc/Mooc.csproj @@ -9,16 +9,16 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/test/src/Mooc/MoocContextApplicationTestCase.cs b/test/src/Mooc/MoocContextApplicationTestCase.cs index 57d9d38..b1f22b5 100644 --- a/test/src/Mooc/MoocContextApplicationTestCase.cs +++ b/test/src/Mooc/MoocContextApplicationTestCase.cs @@ -1,3 +1,5 @@ +#nullable enable + using System; using System.Collections.Generic; using System.Net.Http; @@ -14,11 +16,11 @@ namespace CodelyTv.Test.Mooc public class MoocContextApplicationTestCase : IClassFixture> { private readonly MoocWebApplicationFactory _factory; - private HttpClient _client; + private HttpClient? _client; public MoocContextApplicationTestCase(MoocWebApplicationFactory factory) { - _factory = factory; + _factory = factory ?? throw new ArgumentNullException(nameof(factory)); } protected void CreateAnonymousClient() @@ -29,6 +31,11 @@ protected void CreateAnonymousClient() protected async Task AssertRequestWithBody(HttpMethod method, string endpoint, string body, int expectedStatusCode) { + if (_client == null) + { + throw new InvalidOperationException("HttpClient is not initialized. Call CreateAnonymousClient() first."); + } + using (var request = new HttpRequestMessage { Method = method, @@ -38,13 +45,18 @@ protected async Task AssertRequestWithBody(HttpMethod method, string endpoint, s { var response = await _client.SendAsync(request); - Assert.Equal(expectedStatusCode, (int) response.StatusCode); + Assert.Equal(expectedStatusCode, (int)response.StatusCode); } } protected async Task AssertResponse(HttpMethod method, string endpoint, int expectedStatusCode, string expectedResponse) { + if (_client == null) + { + throw new InvalidOperationException("HttpClient is not initialized. Call CreateAnonymousClient() first."); + } + using (var request = new HttpRequestMessage { Method = method, @@ -52,14 +64,19 @@ protected async Task AssertResponse(HttpMethod method, string endpoint, int expe }) { var response = await _client.SendAsync(request); - var result = response.Content.ReadAsStringAsync().Result; - Assert.Equal(expectedStatusCode, (int) response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedStatusCode, (int)response.StatusCode); Assert.Equal(expectedResponse, result); } } protected async Task GivenISendEventsToTheBus(List domainEvents) { + if (domainEvents == null) + { + throw new ArgumentNullException(nameof(domainEvents)); + } + using (var scope = _factory.Server.Host.Services.CreateScope()) { var eventBus = scope.ServiceProvider.GetRequiredService(); diff --git a/test/src/Mooc/MoocWebApplicationFactory.cs b/test/src/Mooc/MoocWebApplicationFactory.cs index 4bf400e..04ef193 100644 --- a/test/src/Mooc/MoocWebApplicationFactory.cs +++ b/test/src/Mooc/MoocWebApplicationFactory.cs @@ -13,6 +13,11 @@ public class MoocWebApplicationFactory : ApplicationTestCase { private string _databaseName; + public MoocWebApplicationFactory() + { + _databaseName = Guid.NewGuid().ToString(); + } + public HttpClient GetAnonymousClient() { SetDatabaseName(); @@ -33,7 +38,7 @@ protected override Action Services() .AddEntityFrameworkInMemoryDatabase() .BuildServiceProvider(); - // Add a database context using an in-memory + // Add a database context using an in-memory // database for testing. services.AddDbContext(options => { diff --git a/test/src/Mooc/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusShould.cs b/test/src/Mooc/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusShould.cs index c608496..c19ddca 100644 --- a/test/src/Mooc/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusShould.cs +++ b/test/src/Mooc/Shared/Infrastructure/Bus/Event/RabbitMq/RabbitMqEventBusShould.cs @@ -42,7 +42,7 @@ public async Task PublishDomainEventFromRabbitMq() { var domainEvent = CourseCreatedDomainEventMother.Random(); - await _bus.Publish(new List {domainEvent}); + await _bus.Publish(new List { domainEvent }); await _consumer.Consume(); @@ -64,18 +64,50 @@ private static DomainEventSubscribersInformation FakeSubscriber() }); } - private static void CreateQueue(IModel channel, - DomainEventSubscribersInformation domainEventSubscribersInformation) + private static void CreateQueue(IModel channel, DomainEventSubscribersInformation domainEventSubscribersInformation) { + if (channel == null) + { + throw new ArgumentNullException(nameof(channel)); + } + + if (domainEventSubscribersInformation == null) + { + throw new ArgumentNullException(nameof(domainEventSubscribersInformation)); + } + foreach (var subscriberInformation in domainEventSubscribersInformation.All()) { + if (subscriberInformation == null) + { + continue; + } + var domainEventsQueueName = RabbitMqQueueNameFormatter.Format(subscriberInformation); var queue = channel.QueueDeclare(domainEventsQueueName, - true, - false, - false); - dynamic domainEvent = Activator.CreateInstance(subscriberInformation.SubscribedEvent); - channel.QueueBind(queue, TestDomainEvents, (string) domainEvent.EventName()); + durable: true, + exclusive: false, + autoDelete: false); + + var domainEvent = Activator.CreateInstance(subscriberInformation.SubscribedEvent); + if (domainEvent == null) + { + throw new InvalidOperationException($"Unable to create instance of {subscriberInformation.SubscribedEvent.FullName}"); + } + + var eventNameMethod = domainEvent.GetType().GetMethod("EventName"); + if (eventNameMethod == null) + { + throw new InvalidOperationException("The 'EventName' method is not found in the subscribed event."); + } + + var eventName = eventNameMethod.Invoke(domainEvent, null) as string; + if (eventName == null) + { + throw new InvalidOperationException("The 'EventName' method returned null or is not a string."); + } + + channel.QueueBind(queue.QueueName, TestDomainEvents, eventName); } } diff --git a/test/src/Shared/Domain/Criterias/FilterOperatorMother.cs b/test/src/Shared/Domain/Criterias/FilterOperatorMother.cs index 7f38cea..51f48b9 100644 --- a/test/src/Shared/Domain/Criterias/FilterOperatorMother.cs +++ b/test/src/Shared/Domain/Criterias/FilterOperatorMother.cs @@ -8,8 +8,13 @@ public static class FilterOperatorMother public static FilterOperator Random() { Array values = Enum.GetValues(typeof(FilterOperator)); + if (values.Length == 0) + { + throw new InvalidOperationException("The FilterOperator enum does not contain any values."); + } + Random random = new Random(); - return (FilterOperator)values.GetValue(random.Next(values.Length)); + return (FilterOperator)(values.GetValue(random.Next(values.Length)) ?? throw new InvalidOperationException("Unable to get a random value from the FilterOperator enum.")); } } } diff --git a/test/src/Shared/Domain/Criterias/OrderByMother.cs b/test/src/Shared/Domain/Criterias/OrderByMother.cs index f69046a..4133c50 100644 --- a/test/src/Shared/Domain/Criterias/OrderByMother.cs +++ b/test/src/Shared/Domain/Criterias/OrderByMother.cs @@ -4,7 +4,7 @@ namespace CodelyTv.Test.Shared.Domain.Criterias { public static class OrderByMother { - public static OrderBy Create(string fieldName = null) + public static OrderBy Create(string? fieldName = null) { return new OrderBy(fieldName ?? WordMother.Random()); } diff --git a/test/src/Shared/Domain/Criterias/OrderTypeMother.cs b/test/src/Shared/Domain/Criterias/OrderTypeMother.cs index 0affc66..8584496 100644 --- a/test/src/Shared/Domain/Criterias/OrderTypeMother.cs +++ b/test/src/Shared/Domain/Criterias/OrderTypeMother.cs @@ -8,8 +8,13 @@ public static class OrderTypeMother public static OrderType Random() { Array values = Enum.GetValues(typeof(OrderType)); + if (values.Length == 0) + { + throw new InvalidOperationException("The OrderType enum does not contain any values."); + } + Random random = new Random(); - return (OrderType)values.GetValue(random.Next(values.Length)); + return (OrderType)(values.GetValue(random.Next(values.Length)) ?? throw new InvalidOperationException("Unable to get a random value from the OrderType enum.")); } } } diff --git a/test/src/Shared/Infrastructure/ApplicationTestCase.cs b/test/src/Shared/Infrastructure/ApplicationTestCase.cs index 9f52f1b..b3193d3 100644 --- a/test/src/Shared/Infrastructure/ApplicationTestCase.cs +++ b/test/src/Shared/Infrastructure/ApplicationTestCase.cs @@ -12,6 +12,6 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) builder.ConfigureServices(Services()); } - protected abstract Action Services(); + protected new abstract Action Services(); } } diff --git a/test/src/Shared/Infrastructure/InfrastructureTestCase.cs b/test/src/Shared/Infrastructure/InfrastructureTestCase.cs index fd03cc3..92ea309 100644 --- a/test/src/Shared/Infrastructure/InfrastructureTestCase.cs +++ b/test/src/Shared/Infrastructure/InfrastructureTestCase.cs @@ -55,7 +55,13 @@ private static IConfigurationRoot Configuration() protected T GetService() { - return _host.Services.GetService(); + var service = _host.Services.GetService(); + if (service == null) + { + throw new Exception($"Service of type {typeof(T)} not found."); + } + + return service; } protected abstract Action Services(); diff --git a/test/src/Shared/Shared.csproj b/test/src/Shared/Shared.csproj index 75a6e46..b308bae 100644 --- a/test/src/Shared/Shared.csproj +++ b/test/src/Shared/Shared.csproj @@ -13,11 +13,11 @@ - - - - - + + + + +