From 4785a680da7d1e24df3b4b63045c09c4200752cc Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Wed, 18 Dec 2024 23:28:18 +0330 Subject: [PATCH 1/9] feat: update dotnet to version 9 --- .config/dotnet-tools.json | 4 +- .editorconfig | 5 + Directory.Build.props | 14 +-- README.md | 25 ++-- global.json | 2 +- src/BuildingBlocks/BuildingBlocks.csproj | 111 ++++++++++-------- src/BuildingBlocks/Core/EventDispatcher.cs | 61 ++++------ src/BuildingBlocks/EFCore/AppDbContextBase.cs | 15 ++- .../EFCore/DesignTimeDbContextFactoryBase.cs | 2 - src/BuildingBlocks/EFCore/EfTxBehavior.cs | 57 ++++----- src/BuildingBlocks/EFCore/Extensions.cs | 60 +++++----- src/BuildingBlocks/EFCore/IDataSeeder.cs | 4 + src/BuildingBlocks/EFCore/ISeedManager.cs | 6 + src/BuildingBlocks/EFCore/SeedManagers.cs | 39 ++++++ src/BuildingBlocks/MassTransit/Extensions.cs | 2 - src/BuildingBlocks/Mongo/MongoDbContext.cs | 8 ++ src/BuildingBlocks/Mongo/MongoRepository.cs | 1 + src/BuildingBlocks/OpenApi/Extensions.cs | 55 +++++++++ .../SecuritySchemeDocumentTransformer.cs | 44 +++++++ .../PersistMessageConfiguration.cs | 33 ------ .../PersistMessageProcessor/Extensions.cs | 70 ++++++----- .../IPersistMessageDbContext.cs | 2 +- .../MessageDeliveryType.cs | 3 +- .../PersistMessageProcessor/MessageStatus.cs | 1 + .../PersistMessageProcessor/PersistMessage.cs | 2 +- .../PersistMessageBackgroundService.cs | 31 ++--- .../{Data => }/PersistMessageDbContext.cs | 23 ++-- .../PersistMessageProcessor.cs | 14 +-- .../Swagger/ConfigureSwaggerOptions.cs | 90 -------------- .../Swagger/ServiceCollectionExtensions.cs | 107 ----------------- .../Swagger/SwaggerDefaultValues.cs | 70 ----------- src/BuildingBlocks/Swagger/SwaggerOptions.cs | 9 -- src/BuildingBlocks/TestBase/TestBase.cs | 55 ++++----- .../Booking/src/Booking/Booking.csproj | 2 +- .../InfrastructureExtensions.cs | 10 +- .../IntegrationTest/Integration.Test.csproj | 4 +- .../FlightDbContextModelSnapshot.cs | 2 +- .../src/Flight/Data/Seed/FlightDataSeeder.cs | 64 ++++------ .../InfrastructureExtensions.cs | 12 +- src/Services/Flight/src/Flight/Flight.csproj | 8 +- .../DeletingFlight/V1/DeleteFlight.cs | 54 ++++++--- .../V1/GetAvailableFlights.cs | 2 + .../GettingFlightById/V1/GetFlightById.cs | 6 +- .../V1/GetAvailableSeats.cs | 2 + .../tests/EndToEndTest/EndToEnd.Test.csproj | 4 +- .../EndToEndTest/FlightTestDataSeeder.cs | 85 ++++++++++++++ .../Flight/Features/CreateFlightTests.cs | 1 - .../IntegrationTest/FlightTestDataSeeder.cs | 85 ++++++++++++++ .../IntegrationTest/Integration.Test.csproj | 4 +- .../Flight/tests/UnitTest/Unit.Test.csproj | 4 +- .../InfrastructureExtensions.cs | 16 ++- .../Identity/src/Identity/Identity.csproj | 2 +- .../IntegrationTest/IdentityTestDataSeeder.cs | 63 ++++++++++ .../IntegrationTest/Integration.Test.csproj | 4 +- .../InfrastructureExtensions.cs | 10 +- .../Passenger/src/Passenger/Passenger.csproj | 4 +- .../IntegrationTest/Integration.Test.csproj | 4 +- 57 files changed, 766 insertions(+), 711 deletions(-) create mode 100644 src/BuildingBlocks/EFCore/ISeedManager.cs create mode 100644 src/BuildingBlocks/EFCore/SeedManagers.cs create mode 100644 src/BuildingBlocks/OpenApi/Extensions.cs create mode 100644 src/BuildingBlocks/OpenApi/SecuritySchemeDocumentTransformer.cs delete mode 100644 src/BuildingBlocks/PersistMessageProcessor/Data/Configurations/PersistMessageConfiguration.cs rename src/BuildingBlocks/PersistMessageProcessor/{Data => }/PersistMessageDbContext.cs (85%) delete mode 100644 src/BuildingBlocks/Swagger/ConfigureSwaggerOptions.cs delete mode 100644 src/BuildingBlocks/Swagger/ServiceCollectionExtensions.cs delete mode 100644 src/BuildingBlocks/Swagger/SwaggerDefaultValues.cs delete mode 100644 src/BuildingBlocks/Swagger/SwaggerOptions.cs create mode 100644 src/Services/Flight/tests/EndToEndTest/FlightTestDataSeeder.cs create mode 100644 src/Services/Flight/tests/IntegrationTest/FlightTestDataSeeder.cs create mode 100644 src/Services/Identity/tests/IntegrationTest/IdentityTestDataSeeder.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index a4d1f263..776a0bf0 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -10,11 +10,11 @@ "rollForward": false }, "dotnet-ef": { - "version": "8.0.8", + "version": "9.0.0", "commands": [ "dotnet-ef" ], "rollForward": false } } -} +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index b388e303..d36b9a29 100644 --- a/.editorconfig +++ b/.editorconfig @@ -241,6 +241,11 @@ dotnet_diagnostic.IDE0055.severity = suggestion # CS1574: XML comment on 'construct' has syntactically incorrect cref attribute 'name' dotnet_diagnostic.CS1574.severity = error +# IDE0160, IDE0161: Report violations when block-scoped namespaces are used +dotnet_diagnostic.IDE0160.severity = none +dotnet_diagnostic.IDE0161.severity = none + + ################################################################################## # https://jetbrains.com.xy2401.com/help/resharper/EditorConfig_Index.html # https://jetbrains.com.xy2401.com/help/resharper/Reference__Code_Inspections_CSHARP.html diff --git a/Directory.Build.props b/Directory.Build.props index dcca6505..b19e86e1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -10,22 +10,22 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/README.md b/README.md index b40dd661..bcdd1f16 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ - :sparkle: Using `End-To-End Testing` and `Integration Testing` for testing `features` with all dependencies using `testcontainers`. - :sparkle: Using `Fluent Validation` and a `Validation Pipeline Behaviour` on top of `MediatR`. - :sparkle: Using `Minimal API` for all endpoints. +- :sparkle: Using `AspNetCore OpenApi` for `generating` built-in support `OpenAPI documentation` in ASP.NET Core. - :sparkle: Using `Health Check` for `reporting` the `health` of app infrastructure components. - :sparkle: Using `Docker-Compose` and `Kubernetes` for our deployment mechanism. - :sparkle: Using `Kibana` on top of `Serilog` for `logging`. @@ -85,24 +86,26 @@ High-level plan is represented in the table ## Technologies - Libraries -- ✔️ **[`.NET 7`](https://dotnet.microsoft.com/download)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core -- ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core -- ✔️ **[`EF Core`](https://github.com/dotnet/efcore)** - Modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations +- ✔️ **[`.NET 7`](https://dotnet.microsoft.com/download)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core. +- ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core. +- ✔️ **[`EF Core`](https://github.com/dotnet/efcore)** - Modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations. +- ✔️ **[`AspNetCore OpenApi`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-9.0&tabs=visual-studio#configure-openapi-document-generation)** - Provides built-in support for OpenAPI document generation in ASP.NET Core. - ✔️ **[`Masstransit`](https://github.com/MassTransit/MassTransit)** - Distributed Application Framework for .NET. - ✔️ **[`MediatR`](https://github.com/jbogard/MediatR)** - Simple, unambitious mediator implementation in .NET. -- ✔️ **[`FluentValidation`](https://github.com/FluentValidation/FluentValidation)** - Popular .NET validation library for building strongly-typed validation rules -- ✔️ **[`Swagger & Swagger UI`](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)** - Swagger tools for documenting API's built on ASP.NET Core +- ✔️ **[`FluentValidation`](https://github.com/FluentValidation/FluentValidation)** - Popular .NET validation library for building strongly-typed validation rules. +- ✔️ **[`Scalar`](https://github.com/scalar/scalar/tree/main/packages/scalar.aspnetcore)** - Scalar provides an easy way to render beautiful API references based on OpenAPI/Swagger documents. +- ✔️ **[`Swagger UI`](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)** - Swagger tools for documenting API's built on ASP.NET Core. - ✔️ **[`Serilog`](https://github.com/serilog/serilog)** - Simple .NET logging with fully-structured events -- ✔️ **[`Polly`](https://github.com/App-vNext/Polly)** - Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner +- ✔️ **[`Polly`](https://github.com/App-vNext/Polly)** - Polly is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, and Fallback in a fluent and thread-safe manner. - ✔️ **[`Scrutor`](https://github.com/khellang/Scrutor)** - Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection - ✔️ **[`Opentelemetry-dotnet`](https://github.com/open-telemetry/opentelemetry-dotnet)** - The OpenTelemetry .NET Client -- ✔️ **[`DuendeSoftware IdentityServer`](https://github.com/DuendeSoftware/IdentityServer)** - The most flexible and standards-compliant OpenID Connect and OAuth 2.x framework for ASP.NET Core +- ✔️ **[`DuendeSoftware IdentityServer`](https://github.com/DuendeSoftware/IdentityServer)** - The most flexible and standards-compliant OpenID Connect and OAuth 2.x framework for ASP.NET Core. - ✔️ **[`EasyCaching`](https://github.com/dotnetcore/EasyCaching)** - Open source caching library that contains basic usages and some advanced usages of caching which can help us to handle caching more easier. - ✔️ **[`Mapster`](https://github.com/MapsterMapper/Mapster)** - Convention-based object-object mapper in .NET. -- ✔️ **[`Hellang.Middleware.ProblemDetails`](https://github.com/khellang/Middleware/tree/master/src/ProblemDetails)** - A middleware for handling exception in .Net Core -- ✔️ **[`NewId`](https://github.com/phatboyg/NewId)** - NewId can be used as an embedded unique ID generator that produces 128 bit (16 bytes) sequential IDs -- ✔️ **[`Yarp`](https://github.com/microsoft/reverse-proxy)** - Reverse proxy toolkit for building fast proxy servers in .NET -- ✔️ **[`Tye`](https://github.com/dotnet/tye)** - Developer tool that makes developing, testing, and deploying microservices and distributed applications easier +- ✔️ **[`Hellang.Middleware.ProblemDetails`](https://github.com/khellang/Middleware/tree/master/src/ProblemDetails)** - A middleware for handling exception in .Net Core. +- ✔️ **[`NewId`](https://github.com/phatboyg/NewId)** - NewId can be used as an embedded unique ID generator that produces 128 bit (16 bytes) sequential IDs. +- ✔️ **[`Yarp`](https://github.com/microsoft/reverse-proxy)** - Reverse proxy toolkit for building fast proxy servers in .NET. +- ✔️ **[`Tye`](https://github.com/dotnet/tye)** - Developer tool that makes developing, testing, and deploying microservices and distributed applications easier. - ✔️ **[`gRPC-dotnet`](https://github.com/grpc/grpc-dotnet)** - gRPC functionality for .NET. - ✔️ **[`EventStore`](https://github.com/EventStore/EventStore)** - The open-source, functional database with Complex Event Processing. - ✔️ **[`MongoDB.Driver`](https://github.com/mongodb/mongo-csharp-driver)** - .NET Driver for MongoDB. diff --git a/global.json b/global.json index 5cf96894..ccd7812e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.401", + "version": "9.0.100", "rollForward": "latestFeature" } } diff --git a/src/BuildingBlocks/BuildingBlocks.csproj b/src/BuildingBlocks/BuildingBlocks.csproj index 59523cd8..2a34911d 100644 --- a/src/BuildingBlocks/BuildingBlocks.csproj +++ b/src/BuildingBlocks/BuildingBlocks.csproj @@ -1,11 +1,12 @@ + - + - + @@ -19,41 +20,42 @@ - + - + - - - - - - + + + + + + - + - - - - - - - - - - - - + + + + + + + + + + + + - - - - + + + + + @@ -63,48 +65,53 @@ - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + - + - + - + - - + + - + - - + + - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/BuildingBlocks/Core/EventDispatcher.cs b/src/BuildingBlocks/Core/EventDispatcher.cs index 36f278c0..b10f295f 100644 --- a/src/BuildingBlocks/Core/EventDispatcher.cs +++ b/src/BuildingBlocks/Core/EventDispatcher.cs @@ -9,30 +9,17 @@ namespace BuildingBlocks.Core; -public sealed class EventDispatcher : IEventDispatcher +public sealed class EventDispatcher( + IServiceScopeFactory serviceScopeFactory, + IEventMapper eventMapper, + ILogger logger, + IPersistMessageProcessor persistMessageProcessor, + IHttpContextAccessor httpContextAccessor +) + : IEventDispatcher { - private readonly IEventMapper _eventMapper; - private readonly ILogger _logger; - private readonly IPersistMessageProcessor _persistMessageProcessor; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IServiceScopeFactory _serviceScopeFactory; - - public EventDispatcher(IServiceScopeFactory serviceScopeFactory, - IEventMapper eventMapper, - ILogger logger, - IPersistMessageProcessor persistMessageProcessor, - IHttpContextAccessor httpContextAccessor) - { - _serviceScopeFactory = serviceScopeFactory; - _eventMapper = eventMapper; - _logger = logger; - _persistMessageProcessor = persistMessageProcessor; - _httpContextAccessor = httpContextAccessor; - } - - public async Task SendAsync(IReadOnlyList events, Type type = null, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default) where T : IEvent { if (events.Count > 0) @@ -45,7 +32,7 @@ async Task PublishIntegrationEvent(IReadOnlyList integrationE { foreach (var integrationEvent in integrationEvents) { - await _persistMessageProcessor.PublishMessageAsync( + await persistMessageProcessor.PublishMessageAsync( new MessageEnvelope(integrationEvent, SetHeaders()), cancellationToken); } @@ -74,7 +61,7 @@ await _persistMessageProcessor.PublishMessageAsync( foreach (var internalMessage in internalMessages) { - await _persistMessageProcessor.AddInternalMessageAsync(internalMessage, cancellationToken); + await persistMessageProcessor.AddInternalMessageAsync(internalMessage, cancellationToken); } } } @@ -89,20 +76,20 @@ public async Task SendAsync(T @event, Type type = null, private Task> MapDomainEventToIntegrationEventAsync( IReadOnlyList events) { - _logger.LogTrace("Processing integration events start..."); + logger.LogTrace("Processing integration events start..."); var wrappedIntegrationEvents = GetWrappedIntegrationEvents(events.ToList())?.ToList(); if (wrappedIntegrationEvents?.Count > 0) return Task.FromResult>(wrappedIntegrationEvents); var integrationEvents = new List(); - using var scope = _serviceScopeFactory.CreateScope(); + using var scope = serviceScopeFactory.CreateScope(); foreach (var @event in events) { var eventType = @event.GetType(); - _logger.LogTrace($"Handling domain event: {eventType.Name}"); + logger.LogTrace($"Handling domain event: {eventType.Name}"); - var integrationEvent = _eventMapper.MapToIntegrationEvent(@event); + var integrationEvent = eventMapper.MapToIntegrationEvent(@event); if (integrationEvent is null) continue; @@ -110,7 +97,7 @@ private Task> MapDomainEventToIntegrationEventA integrationEvents.Add(integrationEvent); } - _logger.LogTrace("Processing integration events done..."); + logger.LogTrace("Processing integration events done..."); return Task.FromResult>(integrationEvents); } @@ -119,16 +106,16 @@ private Task> MapDomainEventToIntegrationEventA private Task> MapDomainEventToInternalCommandAsync( IReadOnlyList events) { - _logger.LogTrace("Processing internal message start..."); + logger.LogTrace("Processing internal message start..."); var internalCommands = new List(); - using var scope = _serviceScopeFactory.CreateScope(); + using var scope = serviceScopeFactory.CreateScope(); foreach (var @event in events) { var eventType = @event.GetType(); - _logger.LogTrace($"Handling domain event: {eventType.Name}"); + logger.LogTrace($"Handling domain event: {eventType.Name}"); - var integrationEvent = _eventMapper.MapToInternalCommand(@event); + var integrationEvent = eventMapper.MapToInternalCommand(@event); if (integrationEvent is null) continue; @@ -136,7 +123,7 @@ private Task> MapDomainEventToInternalCommandAsy internalCommands.Add(integrationEvent); } - _logger.LogTrace("Processing internal message done..."); + logger.LogTrace("Processing internal message done..."); return Task.FromResult>(internalCommands); } @@ -159,9 +146,9 @@ private IEnumerable GetWrappedIntegrationEvents(IReadOnlyList private IDictionary SetHeaders() { var headers = new Dictionary(); - headers.Add("CorrelationId", _httpContextAccessor?.HttpContext?.GetCorrelationId()); - headers.Add("UserId", _httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier)); - headers.Add("UserName", _httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.Name)); + headers.Add("CorrelationId", httpContextAccessor?.HttpContext?.GetCorrelationId()); + headers.Add("UserId", httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier)); + headers.Add("UserName", httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.Name)); return headers; } diff --git a/src/BuildingBlocks/EFCore/AppDbContextBase.cs b/src/BuildingBlocks/EFCore/AppDbContextBase.cs index 1fd548ec..0dcd9c10 100644 --- a/src/BuildingBlocks/EFCore/AppDbContextBase.cs +++ b/src/BuildingBlocks/EFCore/AppDbContextBase.cs @@ -1,15 +1,14 @@ -namespace BuildingBlocks.EFCore; - using System.Collections.Immutable; -using Core.Event; -using Core.Model; +using BuildingBlocks.Core.Event; +using BuildingBlocks.Core.Model; +using BuildingBlocks.Web; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; -using Web; -using Exception = System.Exception; using IsolationLevel = System.Data.IsolationLevel; +namespace BuildingBlocks.EFCore; + public abstract class AppDbContextBase : DbContext, IDbContext { private readonly ICurrentUserProvider? _currentUserProvider; @@ -174,9 +173,9 @@ private void OnBeforeSaving() } } } - catch (Exception ex) + catch (System.Exception ex) { - throw new Exception("try for find IAggregate", ex); + throw new System.Exception("try for find IAggregate", ex); } } } diff --git a/src/BuildingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs b/src/BuildingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs index a1bd716c..177441cb 100644 --- a/src/BuildingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs +++ b/src/BuildingBlocks/EFCore/DesignTimeDbContextFactoryBase.cs @@ -1,5 +1,3 @@ -using System; -using System.IO; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Configuration; diff --git a/src/BuildingBlocks/EFCore/EfTxBehavior.cs b/src/BuildingBlocks/EFCore/EfTxBehavior.cs index 3323f946..cb429072 100644 --- a/src/BuildingBlocks/EFCore/EfTxBehavior.cs +++ b/src/BuildingBlocks/EFCore/EfTxBehavior.cs @@ -2,49 +2,38 @@ using BuildingBlocks.Core; using MediatR; using Microsoft.Extensions.Logging; +using System.Transactions; +using BuildingBlocks.PersistMessageProcessor; +using BuildingBlocks.Polly; namespace BuildingBlocks.EFCore; -using System.Transactions; -using PersistMessageProcessor; -using Polly; -public class EfTxBehavior : IPipelineBehavior - where TRequest : notnull, IRequest - where TResponse : notnull +public class EfTxBehavior( + ILogger> logger, + IDbContext dbContextBase, + IPersistMessageDbContext persistMessageDbContext, + IEventDispatcher eventDispatcher +) + : IPipelineBehavior +where TRequest : notnull, IRequest +where TResponse : notnull { - private readonly ILogger> _logger; - private readonly IDbContext _dbContextBase; - private readonly IPersistMessageDbContext _persistMessageDbContext; - private readonly IEventDispatcher _eventDispatcher; - - public EfTxBehavior( - ILogger> logger, - IDbContext dbContextBase, - IPersistMessageDbContext persistMessageDbContext, - IEventDispatcher eventDispatcher) - { - _logger = logger; - _dbContextBase = dbContextBase; - _persistMessageDbContext = persistMessageDbContext; - _eventDispatcher = eventDispatcher; - } - public async Task Handle(TRequest request, RequestHandlerDelegate next, - CancellationToken cancellationToken) + CancellationToken cancellationToken) { - _logger.LogInformation( + logger.LogInformation( "{Prefix} Handled command {MediatrRequest}", nameof(EfTxBehavior), typeof(TRequest).FullName); - _logger.LogDebug( + logger.LogDebug( "{Prefix} Handled command {MediatrRequest} with content {RequestContent}", nameof(EfTxBehavior), typeof(TRequest).FullName, JsonSerializer.Serialize(request)); - _logger.LogInformation( + logger.LogInformation( "{Prefix} Open the transaction for {MediatrRequest}", nameof(EfTxBehavior), typeof(TRequest).FullName); @@ -56,32 +45,32 @@ public async Task Handle(TRequest request, RequestHandlerDelegate), typeof(TRequest).FullName); while (true) { - var domainEvents = _dbContextBase.GetDomainEvents(); + var domainEvents = dbContextBase.GetDomainEvents(); if (domainEvents is null || !domainEvents.Any()) { return response; } - await _eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken); + await eventDispatcher.SendAsync(domainEvents.ToArray(), typeof(TRequest), cancellationToken); // Save data to database with some retry policy in distributed transaction - await _dbContextBase.RetryOnFailure(async () => + await dbContextBase.RetryOnFailure(async () => { - await _dbContextBase.SaveChangesAsync(cancellationToken); + await dbContextBase.SaveChangesAsync(cancellationToken); }); // Save data to database with some retry policy in distributed transaction - await _persistMessageDbContext.RetryOnFailure(async () => + await persistMessageDbContext.RetryOnFailure(async () => { - await _persistMessageDbContext.SaveChangesAsync(cancellationToken); + await persistMessageDbContext.SaveChangesAsync(cancellationToken); }); scope.Complete(); diff --git a/src/BuildingBlocks/EFCore/Extensions.cs b/src/BuildingBlocks/EFCore/Extensions.cs index 11a21a87..282de70b 100644 --- a/src/BuildingBlocks/EFCore/Extensions.cs +++ b/src/BuildingBlocks/EFCore/Extensions.cs @@ -1,19 +1,20 @@ using System.Linq.Expressions; +using Ardalis.GuardClauses; using BuildingBlocks.Core.Model; using BuildingBlocks.Web; +using Humanizer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; namespace BuildingBlocks.EFCore; -using Ardalis.GuardClauses; -using Humanizer; -using Microsoft.EntityFrameworkCore.Metadata; - public static class Extensions { public static IServiceCollection AddCustomDbContext(this IServiceCollection services) @@ -36,32 +37,30 @@ public static IServiceCollection AddCustomDbContext(this IServiceColle { dbOptions.MigrationsAssembly(typeof(TContext).Assembly.GetName().Name); }) - // https://github.com/efcore/EFCore.NamingConventions .UseSnakeCaseNamingConvention(); + + // Suppress warnings for pending model changes + options.ConfigureWarnings( + w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); }); - services.AddScoped(provider => provider.GetService()); + services.AddScoped(); + services.AddScoped(sp => sp.GetRequiredService()); return services; } - public static IApplicationBuilder UseMigration( - this IApplicationBuilder app, - IWebHostEnvironment env - ) + + public static IApplicationBuilder UseMigration(this IApplicationBuilder app) where TContext : DbContext, IDbContext { - MigrateDatabaseAsync(app.ApplicationServices).GetAwaiter().GetResult(); + MigrateAsync(app.ApplicationServices).GetAwaiter().GetResult(); - if (!env.IsEnvironment("test")) - { - SeedDataAsync(app.ApplicationServices).GetAwaiter().GetResult(); - } + SeedAsync(app.ApplicationServices).GetAwaiter().GetResult(); return app; } - // ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs public static void FilterSoftDeletedProperties(this ModelBuilder modelBuilder) { @@ -114,23 +113,30 @@ public static void ToSnakeCaseTables(this ModelBuilder modelBuilder) } } - private static async Task MigrateDatabaseAsync(IServiceProvider serviceProvider) + private static async Task MigrateAsync(IServiceProvider serviceProvider) where TContext : DbContext, IDbContext { - using var scope = serviceProvider.CreateScope(); - + await using var scope = serviceProvider.CreateAsyncScope(); var context = scope.ServiceProvider.GetRequiredService(); - await context.Database.MigrateAsync(); - } + var logger = scope.ServiceProvider.GetRequiredService>(); - private static async Task SeedDataAsync(IServiceProvider serviceProvider) - { - using var scope = serviceProvider.CreateScope(); - var seeders = scope.ServiceProvider.GetServices(); + var pendingMigrations = await context.Database.GetPendingMigrationsAsync(); - foreach (var seeder in seeders) + if (pendingMigrations.Any()) { - await seeder.SeedAllAsync(); + logger.LogInformation("Applying {Count} pending migrations...", pendingMigrations.Count()); + + await context.Database.MigrateAsync(); + logger.LogInformation("Migrations applied successfully."); } } + + private static async Task SeedAsync(IServiceProvider serviceProvider) + { + await using var scope = serviceProvider.CreateAsyncScope(); + + var seedersManager = scope.ServiceProvider.GetRequiredService(); + + await seedersManager.ExecuteAsync(); + } } diff --git a/src/BuildingBlocks/EFCore/IDataSeeder.cs b/src/BuildingBlocks/EFCore/IDataSeeder.cs index 81829683..f09d485e 100644 --- a/src/BuildingBlocks/EFCore/IDataSeeder.cs +++ b/src/BuildingBlocks/EFCore/IDataSeeder.cs @@ -4,4 +4,8 @@ public interface IDataSeeder { Task SeedAllAsync(); } + + public interface ITestDataSeeder : IDataSeeder + { + } } diff --git a/src/BuildingBlocks/EFCore/ISeedManager.cs b/src/BuildingBlocks/EFCore/ISeedManager.cs new file mode 100644 index 00000000..c6f0e790 --- /dev/null +++ b/src/BuildingBlocks/EFCore/ISeedManager.cs @@ -0,0 +1,6 @@ +namespace BuildingBlocks.EFCore; + +public interface ISeedManager +{ + Task ExecuteAsync(); +} diff --git a/src/BuildingBlocks/EFCore/SeedManagers.cs b/src/BuildingBlocks/EFCore/SeedManagers.cs new file mode 100644 index 00000000..cf91b0de --- /dev/null +++ b/src/BuildingBlocks/EFCore/SeedManagers.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace BuildingBlocks.EFCore; + +public class SeedManager( +IServiceProvider serviceProvider +) + : ISeedManager +{ + public async Task ExecuteAsync() + { + await using var scope = serviceProvider.CreateAsyncScope(); + var logger = scope.ServiceProvider.GetRequiredService>(); + var env = scope.ServiceProvider.GetRequiredService(); + var dataSeeders = scope.ServiceProvider.GetServices(); + + if (env.IsEnvironment("test")) + { + foreach (var seeder in dataSeeders.Where(x => x is ITestDataSeeder)) + { + logger.LogInformation("Test Seed {SeederName} is started.", seeder.GetType().Name); + await seeder.SeedAllAsync(); + logger.LogInformation("Test Seed {SeederName} is completed.", seeder.GetType().Name); + } + } + else + { + foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder)) + { + logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); + await seeder.SeedAllAsync(); + logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name); + } + } + } +} diff --git a/src/BuildingBlocks/MassTransit/Extensions.cs b/src/BuildingBlocks/MassTransit/Extensions.cs index 1f466545..b264bbd4 100644 --- a/src/BuildingBlocks/MassTransit/Extensions.cs +++ b/src/BuildingBlocks/MassTransit/Extensions.cs @@ -1,7 +1,5 @@ using System.Reflection; -using BuildingBlocks.Core.Event; using BuildingBlocks.Web; -using Humanizer; using MassTransit; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; diff --git a/src/BuildingBlocks/Mongo/MongoDbContext.cs b/src/BuildingBlocks/Mongo/MongoDbContext.cs index fbbce103..8f25d25e 100644 --- a/src/BuildingBlocks/Mongo/MongoDbContext.cs +++ b/src/BuildingBlocks/Mongo/MongoDbContext.cs @@ -1,7 +1,9 @@ using System.Globalization; using Microsoft.Extensions.Options; using MongoDB.Bson; +using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; +using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver; namespace BuildingBlocks.Mongo; @@ -14,6 +16,12 @@ public class MongoDbContext : IMongoDbContext public IMongoDatabase Database { get; } public IMongoClient MongoClient { get; } protected readonly IList> _commands; + private static readonly bool _isSerializerRegisterd; + + static MongoDbContext() + { + BsonSerializer.RegisterSerializer(new GuidSerializer(BsonType.String)); + } public MongoDbContext(IOptions options) { diff --git a/src/BuildingBlocks/Mongo/MongoRepository.cs b/src/BuildingBlocks/Mongo/MongoRepository.cs index 458cdea8..f479e107 100644 --- a/src/BuildingBlocks/Mongo/MongoRepository.cs +++ b/src/BuildingBlocks/Mongo/MongoRepository.cs @@ -1,6 +1,7 @@ using System.Linq.Expressions; using BuildingBlocks.Core.Model; using MongoDB.Driver; +using MongoDB.Driver.Linq; namespace BuildingBlocks.Mongo; diff --git a/src/BuildingBlocks/OpenApi/Extensions.cs b/src/BuildingBlocks/OpenApi/Extensions.cs new file mode 100644 index 00000000..32077e9f --- /dev/null +++ b/src/BuildingBlocks/OpenApi/Extensions.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Scalar.AspNetCore; + +namespace BuildingBlocks.OpenApi +{ + public static class Extensions + { + // ref: https://github.com/dotnet/eShop/blob/main/src/eShop.ServiceDefaults/OpenApi.Extensions.cs + public static IServiceCollection AddAspnetOpenApi(this IServiceCollection services) + { + string[] versions = ["v1"]; + + foreach (var description in versions) + { + services.AddOpenApi( + description, + options => + { + options.AddDocumentTransformer(); + }); + } + + return services; + } + + public static IApplicationBuilder UseAspnetOpenApi(this WebApplication app) + { + app.MapOpenApi(); + + app.UseSwaggerUI( + options => + { + var descriptions = app.DescribeApiVersions(); + + // build a swagger endpoint for each discovered API version + foreach (var description in descriptions) + { + var openApiUrl = $"/openapi/{description.GroupName}.json"; + var name = description.GroupName.ToUpperInvariant(); + options.SwaggerEndpoint(openApiUrl, name); + } + }); + + // Add scalar ui + app.MapScalarApiReference( + redocOptions => + { + redocOptions.WithOpenApiRoutePattern("/openapi/{documentName}.json"); + }); + + return app; + } + } +} diff --git a/src/BuildingBlocks/OpenApi/SecuritySchemeDocumentTransformer.cs b/src/BuildingBlocks/OpenApi/SecuritySchemeDocumentTransformer.cs new file mode 100644 index 00000000..0fec484e --- /dev/null +++ b/src/BuildingBlocks/OpenApi/SecuritySchemeDocumentTransformer.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.OpenApi; +using Microsoft.OpenApi.Models; + +public class SecuritySchemeDocumentTransformer : IOpenApiDocumentTransformer +{ + public Task TransformAsync( + OpenApiDocument document, + OpenApiDocumentTransformerContext context, + CancellationToken cancellationToken + ) + { + document.Components ??= new(); + + // Bearer token scheme + document.Components.SecuritySchemes.Add( + "Bearer", + new OpenApiSecurityScheme + { + Name = "Authorization", + Type = SecuritySchemeType.Http, + Scheme = "bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = + "Enter 'Bearer' [space] and your token in the text input below.\n\nExample: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'", + } + ); + + // API Key scheme + document.Components.SecuritySchemes.Add( + "ApiKey", + new OpenApiSecurityScheme + { + Name = "X-API-KEY", + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header, + Description = + "Enter your API key in the text input below.\n\nExample: '12345-abcdef'", + } + ); + + return Task.CompletedTask; + } +} diff --git a/src/BuildingBlocks/PersistMessageProcessor/Data/Configurations/PersistMessageConfiguration.cs b/src/BuildingBlocks/PersistMessageProcessor/Data/Configurations/PersistMessageConfiguration.cs deleted file mode 100644 index 8395030b..00000000 --- a/src/BuildingBlocks/PersistMessageProcessor/Data/Configurations/PersistMessageConfiguration.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata.Builders; - -namespace BuildingBlocks.PersistMessageProcessor.Data.Configurations; - -public class PersistMessageConfiguration : IEntityTypeConfiguration -{ - public void Configure(EntityTypeBuilder builder) - { - builder.ToTable(nameof(PersistMessage)); - - builder.HasKey(x => x.Id); - - builder.Property(r => r.Id) - .IsRequired().ValueGeneratedNever(); - - // // ref: https://learn.microsoft.com/en-us/ef/core/saving/concurrency?tabs=fluent-api - builder.Property(r => r.Version).IsConcurrencyToken(); - - builder.Property(x => x.DeliveryType) - .HasDefaultValue(MessageDeliveryType.Outbox) - .HasConversion( - x => x.ToString(), - x => (MessageDeliveryType)Enum.Parse(typeof(MessageDeliveryType), x)); - - - builder.Property(x => x.MessageStatus) - .HasDefaultValue(MessageStatus.InProgress) - .HasConversion( - v => v.ToString(), - v => (MessageStatus)Enum.Parse(typeof(MessageStatus), v)); - } -} diff --git a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs index 3ef16900..5aef3604 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs @@ -1,51 +1,57 @@ -using BuildingBlocks.PersistMessageProcessor.Data; using BuildingBlocks.Web; +using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.DependencyInjection; namespace BuildingBlocks.PersistMessageProcessor; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; - public static class Extensions { - public static IServiceCollection AddPersistMessageProcessor(this IServiceCollection services, - IWebHostEnvironment env) + public static IServiceCollection AddPersistMessageProcessor( + this IServiceCollection services, + IWebHostEnvironment env + ) { AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); services.AddValidateOptions(); - services.AddDbContext((sp, options) => - { - var persistMessageOptions = sp.GetRequiredService(); - - options.UseNpgsql(persistMessageOptions.ConnectionString, - dbOptions => - { - dbOptions.MigrationsAssembly(typeof(PersistMessageDbContext).Assembly.GetName().Name); - }) - // https://github.com/efcore/EFCore.NamingConventions - .UseSnakeCaseNamingConvention(); - }); - - services.AddScoped(provider => - { - var persistMessageDbContext = provider.GetRequiredService(); - - persistMessageDbContext.Database.EnsureCreated(); - persistMessageDbContext.CreatePersistMessageTable(); - - return persistMessageDbContext; - }); + services.AddDbContext( + (sp, options) => + { + var persistMessageOptions = sp.GetRequiredService(); + + options.UseNpgsql( + persistMessageOptions.ConnectionString, + dbOptions => + { + dbOptions.MigrationsAssembly( + typeof(PersistMessageDbContext).Assembly.GetName().Name); + }) + // https://github.com/efcore/EFCore.NamingConventions + .UseSnakeCaseNamingConvention(); + + // Todo: follow up the issues of .net 9 to use better approach taht will provide by .net! + options.ConfigureWarnings( + w => w.Ignore(RelationalEventId.PendingModelChangesWarning)); + }); + + services.AddScoped( + provider => + { + var persistMessageDbContext = + provider.GetRequiredService(); + + persistMessageDbContext.Database.EnsureCreated(); + persistMessageDbContext.CreatePersistMessageTableIfNotExists(); + + return persistMessageDbContext; + }); services.AddScoped(); - if (env.EnvironmentName != "test") - { - services.AddHostedService(); - } + services.AddHostedService(); return services; } diff --git a/src/BuildingBlocks/PersistMessageProcessor/IPersistMessageDbContext.cs b/src/BuildingBlocks/PersistMessageProcessor/IPersistMessageDbContext.cs index b91888ec..c67b8fa6 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/IPersistMessageDbContext.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/IPersistMessageDbContext.cs @@ -4,7 +4,7 @@ namespace BuildingBlocks.PersistMessageProcessor; public interface IPersistMessageDbContext { - DbSet PersistMessages { get; } + DbSet PersistMessage { get; } Task SaveChangesAsync(CancellationToken cancellationToken = default); Task ExecuteTransactionalAsync(CancellationToken cancellationToken = default); } diff --git a/src/BuildingBlocks/PersistMessageProcessor/MessageDeliveryType.cs b/src/BuildingBlocks/PersistMessageProcessor/MessageDeliveryType.cs index 8d690aad..52aa7977 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/MessageDeliveryType.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/MessageDeliveryType.cs @@ -3,7 +3,8 @@ namespace BuildingBlocks.PersistMessageProcessor; [Flags] public enum MessageDeliveryType { + Unknown = 0, Outbox = 1, Inbox = 2, - Internal = 4 + Internal = 3 } diff --git a/src/BuildingBlocks/PersistMessageProcessor/MessageStatus.cs b/src/BuildingBlocks/PersistMessageProcessor/MessageStatus.cs index 039d65e7..3b40c605 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/MessageStatus.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/MessageStatus.cs @@ -2,6 +2,7 @@ namespace BuildingBlocks.PersistMessageProcessor; public enum MessageStatus { + Unknown = 0, InProgress = 1, Processed = 2 } diff --git a/src/BuildingBlocks/PersistMessageProcessor/PersistMessage.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessage.cs index 3e2110f2..551777fe 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/PersistMessage.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessage.cs @@ -1,6 +1,6 @@ +using BuildingBlocks.Core.Model; namespace BuildingBlocks.PersistMessageProcessor; -using Core.Model; public class PersistMessage : IVersion { diff --git a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs index 0c0caf1e..68f40925 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs @@ -5,27 +5,20 @@ namespace BuildingBlocks.PersistMessageProcessor; -public class PersistMessageBackgroundService : BackgroundService +public class PersistMessageBackgroundService( + ILogger logger, + IServiceProvider serviceProvider, + IOptions options +) + : BackgroundService { - private readonly ILogger _logger; - private readonly IServiceProvider _serviceProvider; - private PersistMessageOptions _options; + private PersistMessageOptions _options = options.Value; private Task? _executingTask; - public PersistMessageBackgroundService( - ILogger logger, - IServiceProvider serviceProvider, - IOptions options) - { - _logger = logger; - _serviceProvider = serviceProvider; - _options = options.Value; - } - protected override Task ExecuteAsync(CancellationToken stoppingToken) { - _logger.LogInformation("PersistMessage Background Service Start"); + logger.LogInformation("PersistMessage Background Service Start"); _executingTask = ProcessAsync(stoppingToken); @@ -34,7 +27,7 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken) public override Task StopAsync(CancellationToken cancellationToken) { - _logger.LogInformation("PersistMessage Background Service Stop"); + logger.LogInformation("PersistMessage Background Service Stop"); return base.StopAsync(cancellationToken); } @@ -43,15 +36,15 @@ private async Task ProcessAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { - await using (var scope = _serviceProvider.CreateAsyncScope()) + await using (var scope = serviceProvider.CreateAsyncScope()) { var service = scope.ServiceProvider.GetRequiredService(); await service.ProcessAllAsync(stoppingToken); } var delay = _options.Interval is { } - ? TimeSpan.FromSeconds((int)_options.Interval) - : TimeSpan.FromSeconds(30); + ? TimeSpan.FromSeconds((int)_options.Interval) + : TimeSpan.FromSeconds(30); await Task.Delay(delay, stoppingToken); } diff --git a/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageDbContext.cs similarity index 85% rename from src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs rename to src/BuildingBlocks/PersistMessageProcessor/PersistMessageDbContext.cs index 4e569a46..27636a7d 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/Data/PersistMessageDbContext.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageDbContext.cs @@ -1,14 +1,11 @@ +using BuildingBlocks.Core.Model; using BuildingBlocks.EFCore; using Microsoft.EntityFrameworkCore; - -namespace BuildingBlocks.PersistMessageProcessor.Data; - -using Configurations; -using Core.Model; using Microsoft.Extensions.Logging; -using Exception = System.Exception; using IsolationLevel = System.Data.IsolationLevel; +namespace BuildingBlocks.PersistMessageProcessor; + public class PersistMessageDbContext : DbContext, IPersistMessageDbContext { private readonly ILogger? _logger; @@ -20,11 +17,10 @@ public PersistMessageDbContext(DbContextOptions options _logger = logger; } - public DbSet PersistMessages => Set(); + public DbSet PersistMessage => Set(); protected override void OnModelCreating(ModelBuilder builder) { - builder.ApplyConfiguration(new PersistMessageConfiguration()); base.OnModelCreating(builder); builder.ToSnakeCaseTables(); } @@ -79,13 +75,8 @@ public override async Task SaveChangesAsync(CancellationToken cancellationT } } - public void CreatePersistMessageTable() + public void CreatePersistMessageTableIfNotExists() { - if (Database.GetPendingMigrations().Any()) - { - throw new InvalidOperationException("Cannot create table if there are pending migrations."); - } - string createTableSql = @" create table if not exists persist_message ( id uuid not null, @@ -120,9 +111,9 @@ private void OnBeforeSaving() } } } - catch (Exception ex) + catch (System.Exception ex) { - throw new Exception("try for find IVersion", ex); + throw new System.Exception("try for find IVersion", ex); } } } diff --git a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs index 7042b5c8..faa4b84a 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs @@ -54,13 +54,13 @@ await SavePersistMessageAsync(new MessageEnvelope(internalCommand), MessageDeliv public async Task> GetByFilterAsync(Expression> predicate, CancellationToken cancellationToken = default) { - return (await _persistMessageDbContext.PersistMessages.Where(predicate).ToListAsync(cancellationToken)) + return (await _persistMessageDbContext.PersistMessage.Where(predicate).ToListAsync(cancellationToken)) .AsReadOnly(); } public Task ExistMessageAsync(Guid messageId, CancellationToken cancellationToken = default) { - return _persistMessageDbContext.PersistMessages.FirstOrDefaultAsync(x => + return _persistMessageDbContext.PersistMessage.FirstOrDefaultAsync(x => x.Id == messageId && x.DeliveryType == MessageDeliveryType.Inbox && x.MessageStatus == MessageStatus.Processed, @@ -73,7 +73,7 @@ public async Task ProcessAsync( CancellationToken cancellationToken = default) { var message = - await _persistMessageDbContext.PersistMessages.FirstOrDefaultAsync( + await _persistMessageDbContext.PersistMessage.FirstOrDefaultAsync( x => x.Id == messageId && x.DeliveryType == deliveryType, cancellationToken); if (message is null) @@ -109,7 +109,7 @@ await _persistMessageDbContext.PersistMessages.FirstOrDefaultAsync( public async Task ProcessAllAsync(CancellationToken cancellationToken = default) { - var messages = await _persistMessageDbContext.PersistMessages + var messages = await _persistMessageDbContext.PersistMessage .Where(x => x.MessageStatus != MessageStatus.Processed) .ToListAsync(cancellationToken); @@ -121,7 +121,7 @@ public async Task ProcessAllAsync(CancellationToken cancellationToken = default) public async Task ProcessInboxAsync(Guid messageId, CancellationToken cancellationToken = default) { - var message = await _persistMessageDbContext.PersistMessages.FirstOrDefaultAsync( + var message = await _persistMessageDbContext.PersistMessage.FirstOrDefaultAsync( x => x.Id == messageId && x.DeliveryType == MessageDeliveryType.Inbox && x.MessageStatus == MessageStatus.InProgress, @@ -193,7 +193,7 @@ private async Task SavePersistMessageAsync( else id = NewId.NextGuid(); - await _persistMessageDbContext.PersistMessages.AddAsync( + await _persistMessageDbContext.PersistMessage.AddAsync( new PersistMessage( id, messageEnvelope.Message.GetType().ToString(), @@ -215,7 +215,7 @@ private async Task ChangeMessageStatusAsync(PersistMessage message, Cancellation { message.ChangeState(MessageStatus.Processed); - _persistMessageDbContext.PersistMessages.Update(message); + _persistMessageDbContext.PersistMessage.Update(message); await _persistMessageDbContext.SaveChangesAsync(cancellationToken); } diff --git a/src/BuildingBlocks/Swagger/ConfigureSwaggerOptions.cs b/src/BuildingBlocks/Swagger/ConfigureSwaggerOptions.cs deleted file mode 100644 index cee88423..00000000 --- a/src/BuildingBlocks/Swagger/ConfigureSwaggerOptions.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System.Text; -using Asp.Versioning; -using Asp.Versioning.ApiExplorer; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace BuildingBlocks.Swagger; - -public class ConfigureSwaggerOptions : IConfigureOptions -{ - private readonly IApiVersionDescriptionProvider provider; - private readonly SwaggerOptions? _options; - - /// - /// Initializes a new instance of the class. - /// - /// The provider used to generate Swagger documents. - public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider, IOptions options) - { - this.provider = provider; - _options = options.Value; - } - - /// - public void Configure(SwaggerGenOptions options) - { - // add a swagger document for each discovered API version - // note: you might choose to skip or document deprecated API versions differently - foreach (var description in provider.ApiVersionDescriptions) - { - options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)); - } - } - - private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description) - { - var text = new StringBuilder("An example application with OpenAPI, Swashbuckle, and API versioning."); - var info = new OpenApiInfo - { - Version = description.ApiVersion.ToString(), - Title = _options?.Title ?? "APIs", - Description = "An application with Swagger, Swashbuckle, and API versioning.", - Contact = new OpenApiContact { Name = "", Email = "" }, - License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") } - }; - - if (description.IsDeprecated) - { - text.Append("This API version has been deprecated."); - } - - if (description.SunsetPolicy is SunsetPolicy policy) - { - if (policy.Date is DateTimeOffset when) - { - text.Append(" The API will be sunset on ") - .Append(when.Date.ToShortDateString()) - .Append('.'); - } - - if (policy.HasLinks) - { - text.AppendLine(); - - for (var i = 0; i < policy.Links.Count; i++) - { - var link = policy.Links[i]; - - if (link.Type == "text/html") - { - text.AppendLine(); - - if (link.Title.HasValue) - { - text.Append(link.Title.Value).Append(": "); - } - - text.Append(link.LinkTarget.OriginalString); - } - } - } - } - - info.Description = text.ToString(); - - return info; - } -} diff --git a/src/BuildingBlocks/Swagger/ServiceCollectionExtensions.cs b/src/BuildingBlocks/Swagger/ServiceCollectionExtensions.cs deleted file mode 100644 index 44f3b8aa..00000000 --- a/src/BuildingBlocks/Swagger/ServiceCollectionExtensions.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Reflection; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; -using Unchase.Swashbuckle.AspNetCore.Extensions.Extensions; - -namespace BuildingBlocks.Swagger; - -public static class ServiceCollectionExtensions -{ - public const string HeaderName = "X-Api-Key"; - public const string HeaderVersion = "api-version"; - - // https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/README.md - // https://github.com/dotnet/aspnet-api-versioning/tree/88323136a97a59fcee24517a514c1a445530c7e2/examples/AspNetCore/WebApi/MinimalOpenApiExample - public static IServiceCollection AddCustomSwagger(this IServiceCollection services, - IConfiguration configuration, - params Assembly[] assemblies) - { - services.AddTransient, ConfigureSwaggerOptions>(); - services.AddOptions().Bind(configuration.GetSection(nameof(SwaggerOptions))) - .ValidateDataAnnotations(); - - services.AddSwaggerGen( - options => - { - options.OperationFilter(); - - foreach (var assembly in assemblies) - { - var xmlFile = XmlCommentsFilePath(assembly); - - if (File.Exists(xmlFile)) - options.IncludeXmlComments(xmlFile); - } - - options.AddEnumsWithValuesFixFilters(); - - options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - BearerFormat = "JWT", - Scheme = "oauth2", - Name = "Bearer", - In = ParameterLocation.Header - }); - - options.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type=ReferenceType.SecurityScheme, - Id="Bearer" - } - }, - new string[]{} - } - }); - - - options.ResolveConflictingActions(apiDescriptions => apiDescriptions.First()); - - ////https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/467 - // options.OperationFilter(); - // options.OperationFilter(); - - // Enables Swagger annotations (SwaggerOperationAttribute, SwaggerParameterAttribute etc.) - // options.EnableAnnotations(); - }); - - // services.Configure(o => o.InferSecuritySchemes = true); - - static string XmlCommentsFilePath(Assembly assembly) - { - var basePath = Path.GetDirectoryName(assembly.Location); - var fileName = assembly.GetName().Name + ".xml"; - return Path.Combine(basePath, fileName); - } - - return services; - } - - public static IApplicationBuilder UseCustomSwagger(this WebApplication app) - { - app.UseSwagger(); - app.UseSwaggerUI( - options => - { - var descriptions = app.DescribeApiVersions(); - - // build a swagger endpoint for each discovered API version - foreach (var description in descriptions) - { - var url = $"/swagger/{description.GroupName}/swagger.json"; - var name = description.GroupName.ToUpperInvariant(); - options.SwaggerEndpoint(url, name); - } - }); - - return app; - } -} diff --git a/src/BuildingBlocks/Swagger/SwaggerDefaultValues.cs b/src/BuildingBlocks/Swagger/SwaggerDefaultValues.cs deleted file mode 100644 index 42cbaefb..00000000 --- a/src/BuildingBlocks/Swagger/SwaggerDefaultValues.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Linq; -using Humanizer; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.OpenApi.Models; -using Newtonsoft.Json; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace BuildingBlocks.Swagger -{ - public class SwaggerDefaultValues : IOperationFilter - { - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - - var apiDescription = context.ApiDescription; - - operation.Deprecated |= apiDescription.IsDeprecated(); - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/1752#issue-663991077 - foreach (var responseType in context.ApiDescription.SupportedResponseTypes) - { - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/b7cf75e7905050305b115dd96640ddd6e74c7ac9/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L383-L387 - var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString(); - var response = operation.Responses[responseKey]; - - foreach (var contentType in response.Content.Keys) - { - if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType)) - { - response.Content.Remove(contentType); - } - } - } - - if (operation.Parameters == null) - { - return; - } - - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/412 - // REF: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/pull/413 - foreach (var parameter in operation.Parameters) - { - var description = apiDescription.ParameterDescriptions.FirstOrDefault(p => p.Name == parameter.Name); - - if (description is null) - { - return; - } - - if (parameter.Description == null) - { - parameter.Description = description.ModelMetadata?.Description; - } - - parameter.Name = description.Name.Camelize(); - - if (parameter.Schema.Default == null && description.DefaultValue != null) - { - // REF: https://github.com/Microsoft/aspnet-api-versioning/issues/429#issuecomment-605402330 - var json = JsonConvert.SerializeObject(description.DefaultValue, description.ModelMetadata - .ModelType, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }); - parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json); - } - - parameter.Required |= description.IsRequired; - } - } - } -} diff --git a/src/BuildingBlocks/Swagger/SwaggerOptions.cs b/src/BuildingBlocks/Swagger/SwaggerOptions.cs deleted file mode 100644 index 82edaf86..00000000 --- a/src/BuildingBlocks/Swagger/SwaggerOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BuildingBlocks.Swagger -{ - public class SwaggerOptions - { - public string Title { get; set; } - public string Name { get; set; } - public string Version { get; set; } - } -} diff --git a/src/BuildingBlocks/TestBase/TestBase.cs b/src/BuildingBlocks/TestBase/TestBase.cs index a339b814..4f788832 100644 --- a/src/BuildingBlocks/TestBase/TestBase.cs +++ b/src/BuildingBlocks/TestBase/TestBase.cs @@ -1,3 +1,4 @@ +using System.Globalization; using System.Net; using System.Security.Claims; using Ardalis.GuardClauses; @@ -9,7 +10,6 @@ using BuildingBlocks.Web; using EasyNetQ.Management.Client; using Grpc.Net.Client; -using MassTransit; using MassTransit.Testing; using MediatR; using Microsoft.AspNetCore.Hosting; @@ -18,10 +18,17 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; using MongoDB.Driver; +using Npgsql; using NSubstitute; using Respawn; using Serilog; +using Testcontainers.EventStoreDb; +using Testcontainers.MongoDb; +using Testcontainers.PostgreSql; +using Testcontainers.RabbitMq; using WebMotions.Fake.Authentication.JwtBearer; using Xunit; using Xunit.Abstractions; @@ -29,12 +36,6 @@ namespace BuildingBlocks.TestBase; -using System.Globalization; -using Npgsql; -using Testcontainers.EventStoreDb; -using Testcontainers.MongoDb; -using Testcontainers.PostgreSql; -using Testcontainers.RabbitMq; public class TestFixture : IAsyncLifetime where TEntryPoint : class @@ -50,8 +51,7 @@ public class TestFixture : IAsyncLifetime public EventStoreDbContainer EventStoreDbTestContainer; public CancellationTokenSource CancellationTokenSource; - public PersistMessageBackgroundService PersistMessageBackgroundService => - ServiceProvider.GetRequiredService(); + public PersistMessageBackgroundService PersistMessageBackgroundService => ServiceProvider.GetRequiredService(); public HttpClient HttpClient { @@ -95,9 +95,17 @@ protected TestFixture() { TestRegistrationServices?.Invoke(services); services.ReplaceSingleton(AddHttpContextAccessorMock); + services.RemoveAll(); services.AddSingleton(); + // Register all ITestDataSeeder implementations dynamically + services.Scan(scan => scan + .FromApplicationDependencies() // Scan the current app and its dependencies + .AddClasses(classes => classes.AssignableTo()) // Find classes that implement ITestDataSeeder + .AsImplementedInterfaces() + .WithScopedLifetime()); + // add authentication using a fake jwt bearer - we can use SetAdminUser method to set authenticate user to existing HttContextAccessor // https://github.com/webmotions/fake-authentication-jwtbearer // https://github.com/webmotions/fake-authentication-jwtbearer/issues/14 @@ -200,14 +208,8 @@ public async Task WaitForPublishing( var result = await WaitUntilConditionMet( async () => { - var published = - await TestHarness.Published.Any(cancellationToken); - - var faulty = - await TestHarness.Published.Any>( - cancellationToken); - - return published && faulty == false; + var published = await TestHarness.Published.Any(cancellationToken); + return published; }); return result; @@ -224,10 +226,7 @@ public async Task WaitForConsuming( var consumed = await TestHarness.Consumed.Any(cancellationToken); - var faulty = - await TestHarness.Consumed.Any>(cancellationToken); - - return consumed && faulty == false; + return consumed; }); return result; @@ -611,8 +610,6 @@ await Fixture.PersistMessageBackgroundService.StartAsync( _reSpawnerDefaultDb = await Respawner.CreateAsync( DefaultDbConnection, new RespawnerOptions { DbAdapter = DbAdapter.Postgres }); - - await SeedDataAsync(); } } @@ -680,18 +677,6 @@ private async Task ResetRabbitMqAsync(CancellationToken cancellationToken = defa protected virtual void RegisterTestsServices(IServiceCollection services) { } - - private async Task SeedDataAsync() - { - using var scope = Fixture.ServiceProvider.CreateScope(); - - var seeders = scope.ServiceProvider.GetServices(); - - foreach (var seeder in seeders) - { - await seeder.SeedAllAsync(); - } - } } public abstract class TestReadBase : TestFixtureCore diff --git a/src/Services/Booking/src/Booking/Booking.csproj b/src/Services/Booking/src/Booking/Booking.csproj index 74b242f2..6f6d9128 100644 --- a/src/Services/Booking/src/Booking/Booking.csproj +++ b/src/Services/Booking/src/Booking/Booking.csproj @@ -5,7 +5,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs index c3d82f1d..56c4b155 100644 --- a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -8,9 +8,10 @@ using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; +using BuildingBlocks.OpenApi; using BuildingBlocks.OpenTelemetry; using BuildingBlocks.PersistMessageProcessor; -using BuildingBlocks.Swagger; +using BuildingBlocks.ProblemDetails; using BuildingBlocks.Web; using Figgle; using FluentValidation; @@ -19,13 +20,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Prometheus; using Serilog; namespace Booking.Extensions.Infrastructure; -using BuildingBlocks.ProblemDetails; - public static class InfrastructureExtensions { public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder) @@ -67,7 +65,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.AddCustomSerilog(env); builder.Services.AddJwt(); builder.Services.AddHttpContextAccessor(); - builder.Services.AddCustomSwagger(configuration, typeof(BookingRoot).Assembly); + builder.Services.AddAspnetOpenApi(); builder.Services.AddCustomVersioning(); builder.Services.AddCustomMediatR(); builder.Services.AddValidatorsFromAssembly(typeof(BookingRoot).Assembly); @@ -106,7 +104,7 @@ public static WebApplication UseInfrastructure(this WebApplication app) if (env.IsDevelopment()) { - app.UseCustomSwagger(); + app.UseAspnetOpenApi(); } return app; diff --git a/src/Services/Booking/tests/IntegrationTest/Integration.Test.csproj b/src/Services/Booking/tests/IntegrationTest/Integration.Test.csproj index 29cb4ccc..660ce90c 100644 --- a/src/Services/Booking/tests/IntegrationTest/Integration.Test.csproj +++ b/src/Services/Booking/tests/IntegrationTest/Integration.Test.csproj @@ -7,8 +7,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Services/Flight/src/Flight/Data/Migrations/FlightDbContextModelSnapshot.cs b/src/Services/Flight/src/Flight/Data/Migrations/FlightDbContextModelSnapshot.cs index 26192d89..930ce7a0 100644 --- a/src/Services/Flight/src/Flight/Data/Migrations/FlightDbContextModelSnapshot.cs +++ b/src/Services/Flight/src/Flight/Data/Migrations/FlightDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.2") + .HasAnnotation("ProductVersion", "9.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); diff --git a/src/Services/Flight/src/Flight/Data/Seed/FlightDataSeeder.cs b/src/Services/Flight/src/Flight/Data/Seed/FlightDataSeeder.cs index 977199da..3307ed89 100644 --- a/src/Services/Flight/src/Flight/Data/Seed/FlightDataSeeder.cs +++ b/src/Services/Flight/src/Flight/Data/Seed/FlightDataSeeder.cs @@ -1,8 +1,7 @@ -using System.Collections.Generic; -using System.Threading.Tasks; using BuildingBlocks.EFCore; using Flight.Aircrafts.Models; using Flight.Airports.Models; +using Flight.Flights.Models; using Flight.Seats.Models; using MapsterMapper; using Microsoft.EntityFrameworkCore; @@ -11,23 +10,12 @@ namespace Flight.Data.Seed; -using Flights.Models; - -public class FlightDataSeeder : IDataSeeder +public class FlightDataSeeder( + FlightDbContext flightDbContext, + FlightReadDbContext flightReadDbContext, + IMapper mapper +) : IDataSeeder { - private readonly FlightDbContext _flightDbContext; - private readonly FlightReadDbContext _flightReadDbContext; - private readonly IMapper _mapper; - - public FlightDataSeeder(FlightDbContext flightDbContext, - FlightReadDbContext flightReadDbContext, - IMapper mapper) - { - _flightDbContext = flightDbContext; - _flightReadDbContext = flightReadDbContext; - _mapper = mapper; - } - public async Task SeedAllAsync() { await SeedAirportAsync(); @@ -38,28 +26,28 @@ public async Task SeedAllAsync() private async Task SeedAirportAsync() { - if (!await _flightDbContext.Airports.AnyAsync()) + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Airports)) { - await _flightDbContext.Airports.AddRangeAsync(InitialData.Airports); - await _flightDbContext.SaveChangesAsync(); + await flightDbContext.Airports.AddRangeAsync(InitialData.Airports); + await flightDbContext.SaveChangesAsync(); - if (!await _flightReadDbContext.Airport.AsQueryable().AnyAsync()) + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Airport.AsQueryable())) { - await _flightReadDbContext.Airport.InsertManyAsync(_mapper.Map>(InitialData.Airports)); + await flightReadDbContext.Airport.InsertManyAsync(mapper.Map>(InitialData.Airports)); } } } private async Task SeedAircraftAsync() { - if (!await _flightDbContext.Aircraft.AnyAsync()) + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Aircraft)) { - await _flightDbContext.Aircraft.AddRangeAsync(InitialData.Aircrafts); - await _flightDbContext.SaveChangesAsync(); + await flightDbContext.Aircraft.AddRangeAsync(InitialData.Aircrafts); + await flightDbContext.SaveChangesAsync(); - if (!await _flightReadDbContext.Aircraft.AsQueryable().AnyAsync()) + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Aircraft.AsQueryable())) { - await _flightReadDbContext.Aircraft.InsertManyAsync(_mapper.Map>(InitialData.Aircrafts)); + await flightReadDbContext.Aircraft.InsertManyAsync(mapper.Map>(InitialData.Aircrafts)); } } } @@ -67,28 +55,28 @@ private async Task SeedAircraftAsync() private async Task SeedSeatAsync() { - if (!await _flightDbContext.Seats.AnyAsync()) + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Seats)) { - await _flightDbContext.Seats.AddRangeAsync(InitialData.Seats); - await _flightDbContext.SaveChangesAsync(); + await flightDbContext.Seats.AddRangeAsync(InitialData.Seats); + await flightDbContext.SaveChangesAsync(); - if (!await _flightReadDbContext.Seat.AsQueryable().AnyAsync()) + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Seat.AsQueryable())) { - await _flightReadDbContext.Seat.InsertManyAsync(_mapper.Map>(InitialData.Seats)); + await flightReadDbContext.Seat.InsertManyAsync(mapper.Map>(InitialData.Seats)); } } } private async Task SeedFlightAsync() { - if (!await _flightDbContext.Flights.AnyAsync()) + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Flights)) { - await _flightDbContext.Flights.AddRangeAsync(InitialData.Flights); - await _flightDbContext.SaveChangesAsync(); + await flightDbContext.Flights.AddRangeAsync(InitialData.Flights); + await flightDbContext.SaveChangesAsync(); - if (!await _flightReadDbContext.Flight.AsQueryable().AnyAsync()) + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Flight.AsQueryable())) { - await _flightReadDbContext.Flight.InsertManyAsync(_mapper.Map>(InitialData.Flights)); + await flightReadDbContext.Flight.InsertManyAsync(mapper.Map>(InitialData.Flights)); } } } diff --git a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs index 77f040e6..e75c24d3 100644 --- a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.RateLimiting; using BuildingBlocks.Core; using BuildingBlocks.EFCore; @@ -9,9 +8,10 @@ using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; +using BuildingBlocks.OpenApi; using BuildingBlocks.OpenTelemetry; using BuildingBlocks.PersistMessageProcessor; -using BuildingBlocks.Swagger; +using BuildingBlocks.ProblemDetails; using BuildingBlocks.Web; using Figgle; using Flight.Data; @@ -27,7 +27,6 @@ namespace Flight.Extensions.Infrastructure; -using BuildingBlocks.ProblemDetails; public static class InfrastructureExtensions { @@ -38,6 +37,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.Configure(options => { @@ -72,7 +72,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.Services.AddEndpointsApiExplorer(); builder.AddCustomSerilog(env); builder.Services.AddJwt(); - builder.Services.AddCustomSwagger(configuration, typeof(FlightRoot).Assembly); + builder.Services.AddAspnetOpenApi(); builder.Services.AddCustomVersioning(); builder.Services.AddValidatorsFromAssembly(typeof(FlightRoot).Assembly); builder.Services.AddCustomMapster(typeof(FlightRoot).Assembly); @@ -105,7 +105,7 @@ public static WebApplication UseInfrastructure(this WebApplication app) options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; }); app.UseCorrelationId(); - app.UseMigration(env); + app.UseMigration(); app.UseCustomHealthCheck(); app.MapGrpcService(); app.UseRateLimiter(); @@ -113,7 +113,7 @@ public static WebApplication UseInfrastructure(this WebApplication app) if (env.IsDevelopment()) { - app.UseCustomSwagger(); + app.UseAspnetOpenApi(); } return app; diff --git a/src/Services/Flight/src/Flight/Flight.csproj b/src/Services/Flight/src/Flight/Flight.csproj index bc49ff99..77a2826c 100644 --- a/src/Services/Flight/src/Flight/Flight.csproj +++ b/src/Services/Flight/src/Flight/Flight.csproj @@ -1,13 +1,13 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Services/Flight/src/Flight/Flights/Features/DeletingFlight/V1/DeleteFlight.cs b/src/Services/Flight/src/Flight/Flights/Features/DeletingFlight/V1/DeleteFlight.cs index ead41c2f..3f82d405 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/DeletingFlight/V1/DeleteFlight.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/DeletingFlight/V1/DeleteFlight.cs @@ -1,38 +1,44 @@ -namespace Flight.Flights.Features.DeletingFlight.V1; - -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Ardalis.GuardClauses; using BuildingBlocks.Core.CQRS; using BuildingBlocks.Core.Event; using BuildingBlocks.Web; -using Data; using Duende.IdentityServer.EntityFramework.Entities; -using Exceptions; +using Flight.Data; +using Flight.Flights.Exceptions; using FluentValidation; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; -using MongoDB.Driver.Linq; -using ValueObjects; + +namespace Flight.Flights.Features.DeletingFlight.V1; public record DeleteFlight(Guid Id) : ICommand, IInternalCommand; public record DeleteFlightResult(Guid Id); -public record FlightDeletedDomainEvent(Guid Id, string FlightNumber, Guid AircraftId, DateTime DepartureDate, - Guid DepartureAirportId, DateTime ArriveDate, Guid ArriveAirportId, decimal DurationMinutes, - DateTime FlightDate, Enums.FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent; +public record FlightDeletedDomainEvent( + Guid Id, + string FlightNumber, + Guid AircraftId, + DateTime DepartureDate, + Guid DepartureAirportId, + DateTime ArriveDate, + Guid ArriveAirportId, + decimal DurationMinutes, + DateTime FlightDate, + Enums.FlightStatus Status, + decimal Price, + bool IsDeleted +) : IDomainEvent; public class DeleteFlightEndpoint : IMinimalEndpoint { public IEndpointRouteBuilder MapEndpoint(IEndpointRouteBuilder builder) { - builder.MapDelete($"{EndpointConfig.BaseApiPath}/flight/{{id}}", + builder.MapDelete( + $"{EndpointConfig.BaseApiPath}/flight/{{id}}", async (Guid id, IMediator mediator, CancellationToken cancellationToken) => { await mediator.Send(new DeleteFlight(id), cancellationToken); @@ -70,7 +76,10 @@ public DeleteFlightHandler(FlightDbContext flightDbContext) _flightDbContext = flightDbContext; } - public async Task Handle(DeleteFlight request, CancellationToken cancellationToken) + public async Task Handle( + DeleteFlight request, + CancellationToken cancellationToken + ) { Guard.Against.Null(request, nameof(request)); @@ -81,9 +90,18 @@ public async Task Handle(DeleteFlight request, CancellationT throw new FlightNotFountException(); } - flight.Delete(flight.Id, flight.FlightNumber, flight.AircraftId, flight.DepartureAirportId, - flight.DepartureDate, flight.ArriveDate, flight.ArriveAirportId, flight.DurationMinutes, - flight.FlightDate, flight.Status, flight.Price); + flight.Delete( + flight.Id, + flight.FlightNumber, + flight.AircraftId, + flight.DepartureAirportId, + flight.DepartureDate, + flight.ArriveDate, + flight.ArriveAirportId, + flight.DurationMinutes, + flight.FlightDate, + flight.Status, + flight.Price); var deleteFlight = _flightDbContext.Flights.Update(flight).Entity; diff --git a/src/Services/Flight/src/Flight/Flights/Features/GettingAvailableFlights/V1/GetAvailableFlights.cs b/src/Services/Flight/src/Flight/Flights/Features/GettingAvailableFlights/V1/GetAvailableFlights.cs index 5a27210b..73d435e4 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/GettingAvailableFlights/V1/GetAvailableFlights.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/GettingAvailableFlights/V1/GetAvailableFlights.cs @@ -1,3 +1,5 @@ +using MongoDB.Driver.Linq; + namespace Flight.Flights.Features.GettingAvailableFlights.V1; using System; diff --git a/src/Services/Flight/src/Flight/Flights/Features/GettingFlightById/V1/GetFlightById.cs b/src/Services/Flight/src/Flight/Flights/Features/GettingFlightById/V1/GetFlightById.cs index ea6bf120..5f01b14b 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/GettingFlightById/V1/GetFlightById.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/GettingFlightById/V1/GetFlightById.cs @@ -76,9 +76,9 @@ public async Task Handle(GetFlightById request, Cancellatio { Guard.Against.Null(request, nameof(request)); - var flight = - await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync(x => x.FlightId == request.Id && - !x.IsDeleted, cancellationToken); + var flight = await _flightReadDbContext.Flight.AsQueryable().SingleOrDefaultAsync( + x => x.FlightId == request.Id && + !x.IsDeleted, cancellationToken); if (flight is null) { diff --git a/src/Services/Flight/src/Flight/Seats/Features/GettingAvailableSeats/V1/GetAvailableSeats.cs b/src/Services/Flight/src/Flight/Seats/Features/GettingAvailableSeats/V1/GetAvailableSeats.cs index 24b0f562..2470176c 100644 --- a/src/Services/Flight/src/Flight/Seats/Features/GettingAvailableSeats/V1/GetAvailableSeats.cs +++ b/src/Services/Flight/src/Flight/Seats/Features/GettingAvailableSeats/V1/GetAvailableSeats.cs @@ -1,3 +1,5 @@ +using MongoDB.Driver.Linq; + namespace Flight.Seats.Features.GettingAvailableSeats.V1; using System; diff --git a/src/Services/Flight/tests/EndToEndTest/EndToEnd.Test.csproj b/src/Services/Flight/tests/EndToEndTest/EndToEnd.Test.csproj index 6e00b08a..571041a3 100644 --- a/src/Services/Flight/tests/EndToEndTest/EndToEnd.Test.csproj +++ b/src/Services/Flight/tests/EndToEndTest/EndToEnd.Test.csproj @@ -7,8 +7,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Services/Flight/tests/EndToEndTest/FlightTestDataSeeder.cs b/src/Services/Flight/tests/EndToEndTest/FlightTestDataSeeder.cs new file mode 100644 index 00000000..862356f9 --- /dev/null +++ b/src/Services/Flight/tests/EndToEndTest/FlightTestDataSeeder.cs @@ -0,0 +1,85 @@ +using BuildingBlocks.EFCore; +using Flight.Aircrafts.Models; +using Flight.Airports.Models; +using Flight.Data; +using Flight.Data.Seed; +using Flight.Flights.Models; +using Flight.Seats.Models; +using MapsterMapper; +using Microsoft.EntityFrameworkCore; +using MongoDB.Driver; +using MongoDB.Driver.Linq; + +namespace EndToEnd.Test; + +public class FlightTestDataSeeder( + FlightDbContext flightDbContext, + FlightReadDbContext flightReadDbContext, + IMapper mapper +) : ITestDataSeeder +{ + public async Task SeedAllAsync() + { + await SeedAirportAsync(); + await SeedAircraftAsync(); + await SeedFlightAsync(); + await SeedSeatAsync(); + } + + private async Task SeedAirportAsync() + { + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Airports)) + { + await flightDbContext.Airports.AddRangeAsync(InitialData.Airports); + await flightDbContext.SaveChangesAsync(); + + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Airport.AsQueryable())) + { + await flightReadDbContext.Airport.InsertManyAsync(mapper.Map>(InitialData.Airports)); + } + } + } + + private async Task SeedAircraftAsync() + { + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Aircraft)) + { + await flightDbContext.Aircraft.AddRangeAsync(InitialData.Aircrafts); + await flightDbContext.SaveChangesAsync(); + + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Aircraft.AsQueryable())) + { + await flightReadDbContext.Aircraft.InsertManyAsync(mapper.Map>(InitialData.Aircrafts)); + } + } + } + + + private async Task SeedSeatAsync() + { + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Seats)) + { + await flightDbContext.Seats.AddRangeAsync(InitialData.Seats); + await flightDbContext.SaveChangesAsync(); + + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Seat.AsQueryable())) + { + await flightReadDbContext.Seat.InsertManyAsync(mapper.Map>(InitialData.Seats)); + } + } + } + + private async Task SeedFlightAsync() + { + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Flights)) + { + await flightDbContext.Flights.AddRangeAsync(InitialData.Flights); + await flightDbContext.SaveChangesAsync(); + + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Flight.AsQueryable())) + { + await flightReadDbContext.Flight.InsertManyAsync(mapper.Map>(InitialData.Flights)); + } + } + } +} diff --git a/src/Services/Flight/tests/IntegrationTest/Flight/Features/CreateFlightTests.cs b/src/Services/Flight/tests/IntegrationTest/Flight/Features/CreateFlightTests.cs index d7b05761..87dacc88 100644 --- a/src/Services/Flight/tests/IntegrationTest/Flight/Features/CreateFlightTests.cs +++ b/src/Services/Flight/tests/IntegrationTest/Flight/Features/CreateFlightTests.cs @@ -1,4 +1,3 @@ -using System.Threading.Tasks; using BuildingBlocks.Contracts.EventBus.Messages; using BuildingBlocks.TestBase; using Flight.Api; diff --git a/src/Services/Flight/tests/IntegrationTest/FlightTestDataSeeder.cs b/src/Services/Flight/tests/IntegrationTest/FlightTestDataSeeder.cs new file mode 100644 index 00000000..3121315e --- /dev/null +++ b/src/Services/Flight/tests/IntegrationTest/FlightTestDataSeeder.cs @@ -0,0 +1,85 @@ +using BuildingBlocks.EFCore; +using Flight.Aircrafts.Models; +using Flight.Airports.Models; +using Flight.Data; +using Flight.Data.Seed; +using Flight.Flights.Models; +using Flight.Seats.Models; +using MapsterMapper; +using Microsoft.EntityFrameworkCore; +using MongoDB.Driver; +using MongoDB.Driver.Linq; + +namespace Integration.Test; + +public class FlightTestDataSeeder( + FlightDbContext flightDbContext, + FlightReadDbContext flightReadDbContext, + IMapper mapper +) : ITestDataSeeder +{ + public async Task SeedAllAsync() + { + await SeedAirportAsync(); + await SeedAircraftAsync(); + await SeedFlightAsync(); + await SeedSeatAsync(); + } + + private async Task SeedAirportAsync() + { + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Airports)) + { + await flightDbContext.Airports.AddRangeAsync(InitialData.Airports); + await flightDbContext.SaveChangesAsync(); + + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Airport.AsQueryable())) + { + await flightReadDbContext.Airport.InsertManyAsync(mapper.Map>(InitialData.Airports)); + } + } + } + + private async Task SeedAircraftAsync() + { + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Aircraft)) + { + await flightDbContext.Aircraft.AddRangeAsync(InitialData.Aircrafts); + await flightDbContext.SaveChangesAsync(); + + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Aircraft.AsQueryable())) + { + await flightReadDbContext.Aircraft.InsertManyAsync(mapper.Map>(InitialData.Aircrafts)); + } + } + } + + + private async Task SeedSeatAsync() + { + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Seats)) + { + await flightDbContext.Seats.AddRangeAsync(InitialData.Seats); + await flightDbContext.SaveChangesAsync(); + + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Seat.AsQueryable())) + { + await flightReadDbContext.Seat.InsertManyAsync(mapper.Map>(InitialData.Seats)); + } + } + } + + private async Task SeedFlightAsync() + { + if (!await EntityFrameworkQueryableExtensions.AnyAsync(flightDbContext.Flights)) + { + await flightDbContext.Flights.AddRangeAsync(InitialData.Flights); + await flightDbContext.SaveChangesAsync(); + + if (!await MongoQueryable.AnyAsync(flightReadDbContext.Flight.AsQueryable())) + { + await flightReadDbContext.Flight.InsertManyAsync(mapper.Map>(InitialData.Flights)); + } + } + } +} diff --git a/src/Services/Flight/tests/IntegrationTest/Integration.Test.csproj b/src/Services/Flight/tests/IntegrationTest/Integration.Test.csproj index 6e00b08a..571041a3 100644 --- a/src/Services/Flight/tests/IntegrationTest/Integration.Test.csproj +++ b/src/Services/Flight/tests/IntegrationTest/Integration.Test.csproj @@ -7,8 +7,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Services/Flight/tests/UnitTest/Unit.Test.csproj b/src/Services/Flight/tests/UnitTest/Unit.Test.csproj index 6e00b08a..571041a3 100644 --- a/src/Services/Flight/tests/UnitTest/Unit.Test.csproj +++ b/src/Services/Flight/tests/UnitTest/Unit.Test.csproj @@ -7,8 +7,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs index 38dc7e84..b4efd409 100644 --- a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.RateLimiting; using BuildingBlocks.Core; using BuildingBlocks.EFCore; @@ -6,27 +5,26 @@ using BuildingBlocks.Logging; using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; +using BuildingBlocks.OpenApi; using BuildingBlocks.OpenTelemetry; using BuildingBlocks.PersistMessageProcessor; -using BuildingBlocks.Swagger; +using BuildingBlocks.ProblemDetails; using BuildingBlocks.Web; using Figgle; using FluentValidation; +using Identity.Configurations; using Identity.Data; using Identity.Data.Seed; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Prometheus; using Serilog; namespace Identity.Extensions.Infrastructure; -using BuildingBlocks.ProblemDetails; -using Configurations; -using Microsoft.AspNetCore.HttpOverrides; public static class InfrastructureExtensions { @@ -67,7 +65,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.Services.AddCustomDbContext(); builder.Services.AddScoped(); builder.AddCustomSerilog(env); - builder.Services.AddCustomSwagger(configuration, typeof(IdentityRoot).Assembly); + builder.Services.AddAspnetOpenApi(); builder.Services.AddCustomVersioning(); builder.Services.AddCustomMediatR(); builder.Services.AddValidatorsFromAssembly(typeof(IdentityRoot).Assembly); @@ -104,8 +102,8 @@ public static WebApplication UseInfrastructure(this WebApplication app) { options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; }); - app.UseMigration(env); app.UseCorrelationId(); + app.UseMigration(); app.UseCustomHealthCheck(); app.UseIdentityServer(); @@ -113,7 +111,7 @@ public static WebApplication UseInfrastructure(this WebApplication app) if (env.IsDevelopment()) { - app.UseCustomSwagger(); + app.UseAspnetOpenApi(); } return app; diff --git a/src/Services/Identity/src/Identity/Identity.csproj b/src/Services/Identity/src/Identity/Identity.csproj index 4e816923..2ac9aa6f 100644 --- a/src/Services/Identity/src/Identity/Identity.csproj +++ b/src/Services/Identity/src/Identity/Identity.csproj @@ -1,7 +1,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Services/Identity/tests/IntegrationTest/IdentityTestDataSeeder.cs b/src/Services/Identity/tests/IntegrationTest/IdentityTestDataSeeder.cs new file mode 100644 index 00000000..2d2a7bf2 --- /dev/null +++ b/src/Services/Identity/tests/IntegrationTest/IdentityTestDataSeeder.cs @@ -0,0 +1,63 @@ +using BuildingBlocks.Contracts.EventBus.Messages; +using BuildingBlocks.Core; +using BuildingBlocks.EFCore; +using Identity.Data.Seed; +using Identity.Identity.Constants; +using Identity.Identity.Models; +using Microsoft.AspNetCore.Identity; + +namespace Integration.Test; + +public class IdentityDataSeeder( + UserManager userManager, + RoleManager roleManager, + IEventDispatcher eventDispatcher +) + : IDataSeeder +{ + public async Task SeedAllAsync() + { + await SeedRoles(); + await SeedUsers(); + } + + private async Task SeedRoles() + { + if (await roleManager.RoleExistsAsync(Constants.Role.Admin) == false) + { + await roleManager.CreateAsync(new Role { Name = Constants.Role.Admin }); + } + + if (await roleManager.RoleExistsAsync(Constants.Role.User) == false) + { + await roleManager.CreateAsync(new Role { Name = Constants.Role.User }); + } + } + + private async Task SeedUsers() + { + if (await userManager.FindByNameAsync("samh") == null) + { + var result = await userManager.CreateAsync(InitialData.Users.First(), "Admin@123456"); + + if (result.Succeeded) + { + await userManager.AddToRoleAsync(InitialData.Users.First(), Constants.Role.Admin); + + await eventDispatcher.SendAsync(new UserCreated(InitialData.Users.First().Id, InitialData.Users.First().FirstName + " " + InitialData.Users.First().LastName, InitialData.Users.First().PassPortNumber)); + } + } + + if (await userManager.FindByNameAsync("meysamh2") == null) + { + var result = await userManager.CreateAsync(InitialData.Users.Last(), "User@123456"); + + if (result.Succeeded) + { + await userManager.AddToRoleAsync(InitialData.Users.Last(), Constants.Role.User); + + await eventDispatcher.SendAsync(new UserCreated(InitialData.Users.Last().Id, InitialData.Users.Last().FirstName + " " + InitialData.Users.Last().LastName, InitialData.Users.Last().PassPortNumber)); + } + } + } +} diff --git a/src/Services/Identity/tests/IntegrationTest/Integration.Test.csproj b/src/Services/Identity/tests/IntegrationTest/Integration.Test.csproj index 360c1055..e3230589 100644 --- a/src/Services/Identity/tests/IntegrationTest/Integration.Test.csproj +++ b/src/Services/Identity/tests/IntegrationTest/Integration.Test.csproj @@ -7,8 +7,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs index ef9bd451..024250eb 100644 --- a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -8,9 +8,10 @@ using BuildingBlocks.Mapster; using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; +using BuildingBlocks.OpenApi; using BuildingBlocks.OpenTelemetry; using BuildingBlocks.PersistMessageProcessor; -using BuildingBlocks.Swagger; +using BuildingBlocks.ProblemDetails; using BuildingBlocks.Web; using Figgle; using FluentValidation; @@ -25,7 +26,6 @@ namespace Passenger.Extensions.Infrastructure; -using BuildingBlocks.ProblemDetails; public static class InfrastructureExtensions { @@ -67,7 +67,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.AddCustomSerilog(env); builder.Services.AddJwt(); builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddCustomSwagger(configuration, typeof(PassengerRoot).Assembly); + builder.Services.AddAspnetOpenApi(); builder.Services.AddCustomVersioning(); builder.Services.AddCustomMediatR(); builder.Services.AddValidatorsFromAssembly(typeof(PassengerRoot).Assembly); @@ -98,15 +98,15 @@ public static WebApplication UseInfrastructure(this WebApplication app) { options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest; }); - app.UseMigration(env); app.UseCorrelationId(); + app.UseMigration(); app.UseCustomHealthCheck(); app.MapGrpcService(); app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name)); if (env.IsDevelopment()) { - app.UseCustomSwagger(); + app.UseAspnetOpenApi(); } return app; diff --git a/src/Services/Passenger/src/Passenger/Passenger.csproj b/src/Services/Passenger/src/Passenger/Passenger.csproj index 015938e8..db24d609 100644 --- a/src/Services/Passenger/src/Passenger/Passenger.csproj +++ b/src/Services/Passenger/src/Passenger/Passenger.csproj @@ -1,8 +1,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Services/Passenger/tests/IntegrationTest/Integration.Test.csproj b/src/Services/Passenger/tests/IntegrationTest/Integration.Test.csproj index 0ae4574d..ac65886b 100644 --- a/src/Services/Passenger/tests/IntegrationTest/Integration.Test.csproj +++ b/src/Services/Passenger/tests/IntegrationTest/Integration.Test.csproj @@ -7,8 +7,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all From 671816cd63100bf095e1f05c050b731d3502bc88 Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Thu, 19 Dec 2024 01:05:16 +0330 Subject: [PATCH 2/9] chore: update .net version in deployments --- .github/actions/build/action.yml | 6 ++++-- .github/workflows/ci.yml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 2747b426..494bfb0d 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -25,14 +25,16 @@ runs: # https://devblogs.microsoft.com/dotnet/dotnet-loves-github-actions/ # https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net#caching-dependencies - name: Cache NuGet Packages - uses: actions/cache@v3 + uses: actions/cache@v4 if: success() with: path: ~/.nuget/packages key: ${{ runner.os }}-dotnet-nuget - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.x.x' # https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools - name: Restore .NET Tools diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d5d0952..fd35f60f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and Test Flight uses: ./.github/actions/build-test From e871a37217108b3f4e8548e04ea3c87ef902f62a Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Thu, 19 Dec 2024 01:14:25 +0330 Subject: [PATCH 3/9] fix: fix .net format issue --- src/BuildingBlocks/EFCore/EfTxBehavior.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BuildingBlocks/EFCore/EfTxBehavior.cs b/src/BuildingBlocks/EFCore/EfTxBehavior.cs index cb429072..96aed26d 100644 --- a/src/BuildingBlocks/EFCore/EfTxBehavior.cs +++ b/src/BuildingBlocks/EFCore/EfTxBehavior.cs @@ -1,10 +1,10 @@ using System.Text.Json; -using BuildingBlocks.Core; -using MediatR; -using Microsoft.Extensions.Logging; using System.Transactions; +using BuildingBlocks.Core; using BuildingBlocks.PersistMessageProcessor; using BuildingBlocks.Polly; +using MediatR; +using Microsoft.Extensions.Logging; namespace BuildingBlocks.EFCore; From bd345ae949a9b4e47b58853a82f604742449d747 Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Thu, 19 Dec 2024 01:33:06 +0330 Subject: [PATCH 4/9] fix: try to fix failed tests in ci --- src/BuildingBlocks/PersistMessageProcessor/Extensions.cs | 5 ++++- .../PersistMessageBackgroundService.cs | 7 ++----- src/BuildingBlocks/TestBase/TestBase.cs | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs index 5aef3604..1efc84a4 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs @@ -51,7 +51,10 @@ IWebHostEnvironment env services.AddScoped(); - services.AddHostedService(); + if (env.EnvironmentName != "test") + { + services.AddHostedService(); + } return services; } diff --git a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs index 68f40925..7ec8f646 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs @@ -14,15 +14,12 @@ IOptions options { private PersistMessageOptions _options = options.Value; - private Task? _executingTask; - - protected override Task ExecuteAsync(CancellationToken stoppingToken) + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { logger.LogInformation("PersistMessage Background Service Start"); - _executingTask = ProcessAsync(stoppingToken); + await ProcessAsync(stoppingToken); - return _executingTask; } public override Task StopAsync(CancellationToken cancellationToken) diff --git a/src/BuildingBlocks/TestBase/TestBase.cs b/src/BuildingBlocks/TestBase/TestBase.cs index 4f788832..20c3c0b0 100644 --- a/src/BuildingBlocks/TestBase/TestBase.cs +++ b/src/BuildingBlocks/TestBase/TestBase.cs @@ -95,8 +95,7 @@ protected TestFixture() { TestRegistrationServices?.Invoke(services); services.ReplaceSingleton(AddHttpContextAccessorMock); - services.RemoveAll(); - + // services.RemoveAll(); services.AddSingleton(); // Register all ITestDataSeeder implementations dynamically From cc8989a6b34ccf3bcb8faddc46fd935797334337 Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Thu, 19 Dec 2024 03:09:50 +0330 Subject: [PATCH 5/9] fix: fix db-update exception in tests --- src/BuildingBlocks/EFCore/Extensions.cs | 2 +- src/BuildingBlocks/EFCore/ISeedManager.cs | 3 +- src/BuildingBlocks/EFCore/SeedManagers.cs | 41 +++++++++---------- .../PersistMessageProcessor/Extensions.cs | 5 +-- src/BuildingBlocks/TestBase/TestBase.cs | 5 ++- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/BuildingBlocks/EFCore/Extensions.cs b/src/BuildingBlocks/EFCore/Extensions.cs index 282de70b..17c98d33 100644 --- a/src/BuildingBlocks/EFCore/Extensions.cs +++ b/src/BuildingBlocks/EFCore/Extensions.cs @@ -137,6 +137,6 @@ private static async Task SeedAsync(IServiceProvider serviceProvider) var seedersManager = scope.ServiceProvider.GetRequiredService(); - await seedersManager.ExecuteAsync(); + await seedersManager.ExecuteSeedAsync(); } } diff --git a/src/BuildingBlocks/EFCore/ISeedManager.cs b/src/BuildingBlocks/EFCore/ISeedManager.cs index c6f0e790..0ded6aa4 100644 --- a/src/BuildingBlocks/EFCore/ISeedManager.cs +++ b/src/BuildingBlocks/EFCore/ISeedManager.cs @@ -2,5 +2,6 @@ namespace BuildingBlocks.EFCore; public interface ISeedManager { - Task ExecuteAsync(); + Task ExecuteSeedAsync(); + Task ExecuteTestSeedAsync(); } diff --git a/src/BuildingBlocks/EFCore/SeedManagers.cs b/src/BuildingBlocks/EFCore/SeedManagers.cs index cf91b0de..6602d4f4 100644 --- a/src/BuildingBlocks/EFCore/SeedManagers.cs +++ b/src/BuildingBlocks/EFCore/SeedManagers.cs @@ -1,39 +1,38 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace BuildingBlocks.EFCore; public class SeedManager( -IServiceProvider serviceProvider -) - : ISeedManager + ILogger logger, + IWebHostEnvironment env, + IServiceProvider serviceProvider +) : ISeedManager { - public async Task ExecuteAsync() + public async Task ExecuteSeedAsync() { await using var scope = serviceProvider.CreateAsyncScope(); - var logger = scope.ServiceProvider.GetRequiredService>(); - var env = scope.ServiceProvider.GetRequiredService(); var dataSeeders = scope.ServiceProvider.GetServices(); - if (env.IsEnvironment("test")) + foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder)) { - foreach (var seeder in dataSeeders.Where(x => x is ITestDataSeeder)) - { - logger.LogInformation("Test Seed {SeederName} is started.", seeder.GetType().Name); - await seeder.SeedAllAsync(); - logger.LogInformation("Test Seed {SeederName} is completed.", seeder.GetType().Name); - } + logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); + await seeder.SeedAllAsync(); + logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name); } - else + } + + public async Task ExecuteTestSeedAsync() + { + await using var scope = serviceProvider.CreateAsyncScope(); + var dataSeeders = scope.ServiceProvider.GetServices(); + + foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder)) { - foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder)) - { - logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); - await seeder.SeedAllAsync(); - logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name); - } + logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); + await seeder.SeedAllAsync(); + logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name); } } } diff --git a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs index 1efc84a4..5aef3604 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs @@ -51,10 +51,7 @@ IWebHostEnvironment env services.AddScoped(); - if (env.EnvironmentName != "test") - { - services.AddHostedService(); - } + services.AddHostedService(); return services; } diff --git a/src/BuildingBlocks/TestBase/TestBase.cs b/src/BuildingBlocks/TestBase/TestBase.cs index 20c3c0b0..ba54b4a4 100644 --- a/src/BuildingBlocks/TestBase/TestBase.cs +++ b/src/BuildingBlocks/TestBase/TestBase.cs @@ -52,6 +52,7 @@ public class TestFixture : IAsyncLifetime public CancellationTokenSource CancellationTokenSource; public PersistMessageBackgroundService PersistMessageBackgroundService => ServiceProvider.GetRequiredService(); + public ISeedManager SeedManager => ServiceProvider.GetRequiredService(); public HttpClient HttpClient { @@ -95,7 +96,7 @@ protected TestFixture() { TestRegistrationServices?.Invoke(services); services.ReplaceSingleton(AddHttpContextAccessorMock); - // services.RemoveAll(); + services.RemoveAll(); services.AddSingleton(); // Register all ITestDataSeeder implementations dynamically @@ -609,6 +610,8 @@ await Fixture.PersistMessageBackgroundService.StartAsync( _reSpawnerDefaultDb = await Respawner.CreateAsync( DefaultDbConnection, new RespawnerOptions { DbAdapter = DbAdapter.Postgres }); + + await Fixture.SeedManager.ExecuteTestSeedAsync(); } } From b9aa18a0437956c8647dfb78078c74b4391e7ae2 Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Thu, 19 Dec 2024 03:59:40 +0330 Subject: [PATCH 6/9] fix: fix issue in seed-manager --- src/BuildingBlocks/EFCore/SeedManagers.cs | 14 +++++++++----- .../IntegrationTest/IdentityTestDataSeeder.cs | 4 ++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/BuildingBlocks/EFCore/SeedManagers.cs b/src/BuildingBlocks/EFCore/SeedManagers.cs index 6602d4f4..9ba1a3de 100644 --- a/src/BuildingBlocks/EFCore/SeedManagers.cs +++ b/src/BuildingBlocks/EFCore/SeedManagers.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace BuildingBlocks.EFCore; @@ -15,11 +16,14 @@ public async Task ExecuteSeedAsync() await using var scope = serviceProvider.CreateAsyncScope(); var dataSeeders = scope.ServiceProvider.GetServices(); - foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder)) + if (!env.IsEnvironment("test")) { - logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); - await seeder.SeedAllAsync(); - logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name); + foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder)) + { + logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); + await seeder.SeedAllAsync(); + logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name); + } } } @@ -28,7 +32,7 @@ public async Task ExecuteTestSeedAsync() await using var scope = serviceProvider.CreateAsyncScope(); var dataSeeders = scope.ServiceProvider.GetServices(); - foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder)) + foreach (var seeder in dataSeeders.Where(x => x is ITestDataSeeder)) { logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); await seeder.SeedAllAsync(); diff --git a/src/Services/Identity/tests/IntegrationTest/IdentityTestDataSeeder.cs b/src/Services/Identity/tests/IntegrationTest/IdentityTestDataSeeder.cs index 2d2a7bf2..c7a8ab5b 100644 --- a/src/Services/Identity/tests/IntegrationTest/IdentityTestDataSeeder.cs +++ b/src/Services/Identity/tests/IntegrationTest/IdentityTestDataSeeder.cs @@ -8,12 +8,12 @@ namespace Integration.Test; -public class IdentityDataSeeder( +public class IdentityTestDataSeeder( UserManager userManager, RoleManager roleManager, IEventDispatcher eventDispatcher ) - : IDataSeeder + : ITestDataSeeder { public async Task SeedAllAsync() { From 321b7ce901aa30237579de02d6b0f42ca0177472 Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:20:58 +0330 Subject: [PATCH 7/9] fix: fix issue in registration persist message background service in test base --- src/BuildingBlocks/EFCore/IDataSeeder.cs | 3 ++- src/BuildingBlocks/EFCore/SeedManagers.cs | 6 +++--- src/BuildingBlocks/MassTransit/Extensions.cs | 4 ++-- .../PersistMessageProcessor/Extensions.cs | 5 +---- .../PersistMessageBackgroundService.cs | 1 - .../PersistMessageProcessor/PersistMessageProcessor.cs | 1 - src/BuildingBlocks/TestBase/TestBase.cs | 10 ++++++++-- .../Infrastructure/InfrastructureExtensions.cs | 2 +- .../Infrastructure/InfrastructureExtensions.cs | 2 +- .../Infrastructure/InfrastructureExtensions.cs | 2 +- .../Infrastructure/InfrastructureExtensions.cs | 2 +- 11 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/BuildingBlocks/EFCore/IDataSeeder.cs b/src/BuildingBlocks/EFCore/IDataSeeder.cs index f09d485e..a3083ff0 100644 --- a/src/BuildingBlocks/EFCore/IDataSeeder.cs +++ b/src/BuildingBlocks/EFCore/IDataSeeder.cs @@ -5,7 +5,8 @@ public interface IDataSeeder Task SeedAllAsync(); } - public interface ITestDataSeeder : IDataSeeder + public interface ITestDataSeeder { + Task SeedAllAsync(); } } diff --git a/src/BuildingBlocks/EFCore/SeedManagers.cs b/src/BuildingBlocks/EFCore/SeedManagers.cs index 9ba1a3de..e50715aa 100644 --- a/src/BuildingBlocks/EFCore/SeedManagers.cs +++ b/src/BuildingBlocks/EFCore/SeedManagers.cs @@ -18,7 +18,7 @@ public async Task ExecuteSeedAsync() if (!env.IsEnvironment("test")) { - foreach (var seeder in dataSeeders.Where(x => x is not ITestDataSeeder)) + foreach (var seeder in dataSeeders) { logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); await seeder.SeedAllAsync(); @@ -30,9 +30,9 @@ public async Task ExecuteSeedAsync() public async Task ExecuteTestSeedAsync() { await using var scope = serviceProvider.CreateAsyncScope(); - var dataSeeders = scope.ServiceProvider.GetServices(); + var dataSeeders = scope.ServiceProvider.GetServices(); - foreach (var seeder in dataSeeders.Where(x => x is ITestDataSeeder)) + foreach (var seeder in dataSeeders) { logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); await seeder.SeedAllAsync(); diff --git a/src/BuildingBlocks/MassTransit/Extensions.cs b/src/BuildingBlocks/MassTransit/Extensions.cs index b264bbd4..ae09548c 100644 --- a/src/BuildingBlocks/MassTransit/Extensions.cs +++ b/src/BuildingBlocks/MassTransit/Extensions.cs @@ -12,7 +12,7 @@ namespace BuildingBlocks.MassTransit; public static class Extensions { public static IServiceCollection AddCustomMassTransit(this IServiceCollection services, - IWebHostEnvironment env, Assembly assembly) + IWebHostEnvironment env, params Assembly[] assembly) { services.AddValidateOptions(); @@ -32,7 +32,7 @@ public static IServiceCollection AddCustomMassTransit(this IServiceCollection se } private static void SetupMasstransitConfigurations(IServiceCollection services, - IBusRegistrationConfigurator configure, Assembly assembly) + IBusRegistrationConfigurator configure, params Assembly[] assembly) { configure.AddConsumers(assembly); configure.AddSagaStateMachines(assembly); diff --git a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs index 5aef3604..ed62cbde 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/Extensions.cs @@ -8,10 +8,7 @@ namespace BuildingBlocks.PersistMessageProcessor; public static class Extensions { - public static IServiceCollection AddPersistMessageProcessor( - this IServiceCollection services, - IWebHostEnvironment env - ) + public static IServiceCollection AddPersistMessageProcessor(this IServiceCollection services) { AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); diff --git a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs index 7ec8f646..35ee175f 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageBackgroundService.cs @@ -19,7 +19,6 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) logger.LogInformation("PersistMessage Background Service Start"); await ProcessAsync(stoppingToken); - } public override Task StopAsync(CancellationToken cancellationToken) diff --git a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs index faa4b84a..ecb53ca0 100644 --- a/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs +++ b/src/BuildingBlocks/PersistMessageProcessor/PersistMessageProcessor.cs @@ -10,7 +10,6 @@ namespace BuildingBlocks.PersistMessageProcessor; using Microsoft.EntityFrameworkCore; -using Polly; public class PersistMessageProcessor : IPersistMessageProcessor { diff --git a/src/BuildingBlocks/TestBase/TestBase.cs b/src/BuildingBlocks/TestBase/TestBase.cs index ba54b4a4..ee5513fa 100644 --- a/src/BuildingBlocks/TestBase/TestBase.cs +++ b/src/BuildingBlocks/TestBase/TestBase.cs @@ -1,15 +1,18 @@ using System.Globalization; using System.Net; +using System.Reflection; using System.Security.Claims; using Ardalis.GuardClauses; using BuildingBlocks.Core.Event; using BuildingBlocks.Core.Model; using BuildingBlocks.EFCore; +using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; using BuildingBlocks.PersistMessageProcessor; using BuildingBlocks.Web; using EasyNetQ.Management.Client; using Grpc.Net.Client; +using MassTransit; using MassTransit.Testing; using MediatR; using Microsoft.AspNetCore.Hosting; @@ -96,8 +99,11 @@ protected TestFixture() { TestRegistrationServices?.Invoke(services); services.ReplaceSingleton(AddHttpContextAccessorMock); - services.RemoveAll(); - services.AddSingleton(); + + services.RemoveHostedService(); + services.AddSingleton(); // Register as a singleton + services.AddHostedService(provider => provider.GetRequiredService()); // Use the same instance for hosted service + // Register all ITestDataSeeder implementations dynamically services.Scan(scan => scan diff --git a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs index 56c4b155..18f8ba8d 100644 --- a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -58,7 +58,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder })); }); - builder.Services.AddPersistMessageProcessor(env); + builder.Services.AddPersistMessageProcessor(); builder.Services.AddMongoDbContext(configuration); builder.Services.AddEndpointsApiExplorer(); diff --git a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs index e75c24d3..fd06f53b 100644 --- a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -67,7 +67,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.Services.AddCustomDbContext(); builder.Services.AddScoped(); builder.Services.AddMongoDbContext(configuration); - builder.Services.AddPersistMessageProcessor(env); + builder.Services.AddPersistMessageProcessor(); builder.Services.AddEndpointsApiExplorer(); builder.AddCustomSerilog(env); diff --git a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs index b4efd409..8960e461 100644 --- a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -61,7 +61,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder.Services.AddEndpointsApiExplorer(); builder.Services.AddControllers(); - builder.Services.AddPersistMessageProcessor(env); + builder.Services.AddPersistMessageProcessor(); builder.Services.AddCustomDbContext(); builder.Services.AddScoped(); builder.AddCustomSerilog(env); diff --git a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs index 024250eb..cbcadd8e 100644 --- a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -60,7 +60,7 @@ public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder })); }); - builder.Services.AddPersistMessageProcessor(env); + builder.Services.AddPersistMessageProcessor(); builder.Services.AddCustomDbContext(); builder.Services.AddMongoDbContext(configuration); From 68a91850705735c7c9821e3160e2073f7bd2969d Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Thu, 19 Dec 2024 19:27:40 +0330 Subject: [PATCH 8/9] docs: update documentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bcdd1f16..b1d5dafb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ -> 🚀 **A practical and imaginary microservices for implementing an infrastructure for up and running distributed system with the latest technology and architecture like Vertical Slice Architecture, Event Sourcing, CQRS, DDD, gRpc, MongoDB, RabbitMq, Masstransit in .Net 8.** +> 🚀 **A practical and imaginary microservices for implementing an infrastructure for up and running distributed system with the latest technology and architecture like Vertical Slice Architecture, Event Sourcing, CQRS, DDD, gRpc, MongoDB, RabbitMq, Masstransit in .Net 9.** > 💡 **This project is not business-oriented and most of my focus was in the thechnical part for implement a distributed system with a sample project. In this project I implemented some concept in microservices like Messaging, Tracing, Event Driven Architecture, Vertical Slice Architecture, Event Sourcing, CQRS, DDD and gRpc.** @@ -86,7 +86,7 @@ High-level plan is represented in the table ## Technologies - Libraries -- ✔️ **[`.NET 7`](https://dotnet.microsoft.com/download)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core. +- ✔️ **[`.NET 9`](https://github.com/dotnet/aspnetcore)** - .NET Framework and .NET Core, including ASP.NET and ASP.NET Core. - ✔️ **[`MVC Versioning API`](https://github.com/microsoft/aspnet-api-versioning)** - Set of libraries which add service API versioning to ASP.NET Web API, OData with ASP.NET Web API, and ASP.NET Core. - ✔️ **[`EF Core`](https://github.com/dotnet/efcore)** - Modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations. - ✔️ **[`AspNetCore OpenApi`](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/aspnetcore-openapi?view=aspnetcore-9.0&tabs=visual-studio#configure-openapi-document-generation)** - Provides built-in support for OpenAPI document generation in ASP.NET Core. From 6c9d183970d14cc9a16f7e5a57f9ce13f525e1e8 Mon Sep 17 00:00:00 2001 From: Meysam Hadeli <35596795+meysamhadeli@users.noreply.github.com> Date: Thu, 19 Dec 2024 20:53:08 +0330 Subject: [PATCH 9/9] fix: fix test issues --- src/BuildingBlocks/EFCore/SeedManagers.cs | 16 +++++----- src/BuildingBlocks/TestBase/TestBase.cs | 38 +++++++++++++---------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/BuildingBlocks/EFCore/SeedManagers.cs b/src/BuildingBlocks/EFCore/SeedManagers.cs index e50715aa..75e72b04 100644 --- a/src/BuildingBlocks/EFCore/SeedManagers.cs +++ b/src/BuildingBlocks/EFCore/SeedManagers.cs @@ -13,11 +13,11 @@ IServiceProvider serviceProvider { public async Task ExecuteSeedAsync() { - await using var scope = serviceProvider.CreateAsyncScope(); - var dataSeeders = scope.ServiceProvider.GetServices(); - if (!env.IsEnvironment("test")) { + await using var scope = serviceProvider.CreateAsyncScope(); + var dataSeeders = scope.ServiceProvider.GetServices(); + foreach (var seeder in dataSeeders) { logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); @@ -30,13 +30,13 @@ public async Task ExecuteSeedAsync() public async Task ExecuteTestSeedAsync() { await using var scope = serviceProvider.CreateAsyncScope(); - var dataSeeders = scope.ServiceProvider.GetServices(); + var testDataSeeders = scope.ServiceProvider.GetServices(); - foreach (var seeder in dataSeeders) + foreach (var testSeeder in testDataSeeders) { - logger.LogInformation("Seed {SeederName} is started.", seeder.GetType().Name); - await seeder.SeedAllAsync(); - logger.LogInformation("Seed {SeederName} is completed.", seeder.GetType().Name); + logger.LogInformation("Seed {SeederName} is started.", testSeeder.GetType().Name); + await testSeeder.SeedAllAsync(); + logger.LogInformation("Seed {SeederName} is completed.", testSeeder.GetType().Name); } } } diff --git a/src/BuildingBlocks/TestBase/TestBase.cs b/src/BuildingBlocks/TestBase/TestBase.cs index ee5513fa..9c22a974 100644 --- a/src/BuildingBlocks/TestBase/TestBase.cs +++ b/src/BuildingBlocks/TestBase/TestBase.cs @@ -1,12 +1,9 @@ -using System.Globalization; using System.Net; -using System.Reflection; using System.Security.Claims; using Ardalis.GuardClauses; using BuildingBlocks.Core.Event; using BuildingBlocks.Core.Model; using BuildingBlocks.EFCore; -using BuildingBlocks.MassTransit; using BuildingBlocks.Mongo; using BuildingBlocks.PersistMessageProcessor; using BuildingBlocks.Web; @@ -21,17 +18,10 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; using MongoDB.Driver; -using Npgsql; using NSubstitute; using Respawn; using Serilog; -using Testcontainers.EventStoreDb; -using Testcontainers.MongoDb; -using Testcontainers.PostgreSql; -using Testcontainers.RabbitMq; using WebMotions.Fake.Authentication.JwtBearer; using Xunit; using Xunit.Abstractions; @@ -39,6 +29,12 @@ namespace BuildingBlocks.TestBase; +using System.Globalization; +using Npgsql; +using Testcontainers.EventStoreDb; +using Testcontainers.MongoDb; +using Testcontainers.PostgreSql; +using Testcontainers.RabbitMq; public class TestFixture : IAsyncLifetime where TEntryPoint : class @@ -54,8 +50,8 @@ public class TestFixture : IAsyncLifetime public EventStoreDbContainer EventStoreDbTestContainer; public CancellationTokenSource CancellationTokenSource; - public PersistMessageBackgroundService PersistMessageBackgroundService => ServiceProvider.GetRequiredService(); - public ISeedManager SeedManager => ServiceProvider.GetRequiredService(); + public PersistMessageBackgroundService PersistMessageBackgroundService => + ServiceProvider.GetRequiredService(); public HttpClient HttpClient { @@ -100,10 +96,8 @@ protected TestFixture() TestRegistrationServices?.Invoke(services); services.ReplaceSingleton(AddHttpContextAccessorMock); + services.AddSingleton(); services.RemoveHostedService(); - services.AddSingleton(); // Register as a singleton - services.AddHostedService(provider => provider.GetRequiredService()); // Use the same instance for hosted service - // Register all ITestDataSeeder implementations dynamically services.Scan(scan => scan @@ -214,7 +208,9 @@ public async Task WaitForPublishing( var result = await WaitUntilConditionMet( async () => { - var published = await TestHarness.Published.Any(cancellationToken); + var published = + await TestHarness.Published.Any(cancellationToken); + return published; }); @@ -617,7 +613,7 @@ await Fixture.PersistMessageBackgroundService.StartAsync( DefaultDbConnection, new RespawnerOptions { DbAdapter = DbAdapter.Postgres }); - await Fixture.SeedManager.ExecuteTestSeedAsync(); + await SeedDataAsync(); } } @@ -685,6 +681,14 @@ private async Task ResetRabbitMqAsync(CancellationToken cancellationToken = defa protected virtual void RegisterTestsServices(IServiceCollection services) { } + + private async Task SeedDataAsync() + { + using var scope = Fixture.ServiceProvider.CreateScope(); + + var seedManager = scope.ServiceProvider.GetService(); + await seedManager.ExecuteTestSeedAsync(); + } } public abstract class TestReadBase : TestFixtureCore