diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f5049cc..d71b041 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -30,7 +30,7 @@ jobs: - name: Install .NET Core uses: actions/setup-dotnet@v4 with: - dotnet-version: '8.x.x' + dotnet-version: '9.x.x' - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index c2ff46c..a1881bb 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -20,7 +20,7 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 8.x.x + 9.x.x - name: Restore dependencies run: dotnet restore diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 306c814..789e3c3 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -27,7 +27,7 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 8.x.x + 9.x.x - name: Restore dependencies run: dotnet restore diff --git a/CleanArchitecture.Api/CleanArchitecture.Api.csproj b/CleanArchitecture.Api/CleanArchitecture.Api.csproj index eae3adb..b6e744f 100644 --- a/CleanArchitecture.Api/CleanArchitecture.Api.csproj +++ b/CleanArchitecture.Api/CleanArchitecture.Api.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable 64377c40-44d6-4989-9662-5d778f8b3b92 Linux @@ -14,17 +14,17 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + diff --git a/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj b/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj index a1277fb..6837676 100644 --- a/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj +++ b/CleanArchitecture.Application.Tests/CleanArchitecture.Application.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable false @@ -9,7 +9,7 @@ - + diff --git a/CleanArchitecture.Application/CleanArchitecture.Application.csproj b/CleanArchitecture.Application/CleanArchitecture.Application.csproj index d258fbf..6bb2987 100644 --- a/CleanArchitecture.Application/CleanArchitecture.Application.csproj +++ b/CleanArchitecture.Application/CleanArchitecture.Application.csproj @@ -1,12 +1,12 @@ - net8.0 + net9.0 enable - + diff --git a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj index 18eae62..194f049 100644 --- a/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj +++ b/CleanArchitecture.Domain.Tests/CleanArchitecture.Domain.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable false @@ -10,7 +10,7 @@ - + diff --git a/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj b/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj index 75271ab..09258a4 100644 --- a/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj +++ b/CleanArchitecture.Domain/CleanArchitecture.Domain.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable @@ -9,10 +9,10 @@ - + - - + + diff --git a/CleanArchitecture.Domain/Extensions/Validation/CustomValidator.cs b/CleanArchitecture.Domain/Extensions/Validation/CustomValidator.cs index cae441a..86ff5c5 100644 --- a/CleanArchitecture.Domain/Extensions/Validation/CustomValidator.cs +++ b/CleanArchitecture.Domain/Extensions/Validation/CustomValidator.cs @@ -14,7 +14,7 @@ public static IRuleBuilderOptions StringMustBeBase64(this IRuleBui private static bool IsBase64String(string base64) { base64 = base64.Trim(); - return base64.Length % 4 == 0 && Base64Regex().IsMatch(base64); + return base64.Length % 4 == 0 && new Regex("^[a-zA-Z0-9\\+/]*={0,3}$").IsMatch(base64); } public static IRuleBuilder Password( @@ -32,7 +32,4 @@ public static IRuleBuilder Password( .Matches("[^a-zA-Z0-9]").WithErrorCode(DomainErrorCodes.User.SpecialCharPassword); return options; } - - [GeneratedRegex("^[a-zA-Z0-9\\+/]*={0,3}$", RegexOptions.None)] - private static partial Regex Base64Regex(); } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/BindQueueToExchange.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/BindQueueToExchange.cs index 72d3f81..fc05e05 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Actions/BindQueueToExchange.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/BindQueueToExchange.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using RabbitMQ.Client; namespace CleanArchitecture.Domain.Rabbitmq.Actions; @@ -15,8 +16,8 @@ public BindQueueToExchange(string queueName, string exchangeName, string routing _queueName = queueName; } - public void Perform(IModel channel) + public async Task Perform(IChannel channel) { - channel.QueueBind(_queueName, _exchangeName, _routingKey); + await channel.QueueBindAsync(_queueName, _exchangeName, _routingKey); } } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/CreateExchange.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/CreateExchange.cs index ee28a42..6b43659 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Actions/CreateExchange.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/CreateExchange.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using RabbitMQ.Client; namespace CleanArchitecture.Domain.Rabbitmq.Actions; @@ -13,8 +14,8 @@ public CreateExchange(string name, string type) _type = type; } - public void Perform(IModel channel) + public async Task Perform(IChannel channel) { - channel.ExchangeDeclare(_name, _type); + await channel.ExchangeDeclareAsync(_name, _type); } } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/CreateQueue.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/CreateQueue.cs index dba3883..0f69898 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Actions/CreateQueue.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/CreateQueue.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using RabbitMQ.Client; namespace CleanArchitecture.Domain.Rabbitmq.Actions; @@ -11,9 +12,9 @@ public CreateQueue(string queueName) QueueName = queueName; } - public void Perform(IModel channel) + public async Task Perform(IChannel channel) { - channel.QueueDeclare( + await channel.QueueDeclareAsync( QueueName, false, false, diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/IRabbitMqAction.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/IRabbitMqAction.cs index 4570bf4..feacd7b 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Actions/IRabbitMqAction.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/IRabbitMqAction.cs @@ -1,8 +1,9 @@ +using System.Threading.Tasks; using RabbitMQ.Client; namespace CleanArchitecture.Domain.Rabbitmq.Actions; public interface IRabbitMqAction { - void Perform(IModel channel); + Task Perform(IChannel channel); } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/RegisterConsumer.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/RegisterConsumer.cs index 52fbf6c..4fcd40d 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Actions/RegisterConsumer.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/RegisterConsumer.cs @@ -1,11 +1,12 @@ using System; +using System.Threading.Tasks; using RabbitMQ.Client; namespace CleanArchitecture.Domain.Rabbitmq.Actions; public sealed class RegisterConsumer : IRabbitMqAction { - private readonly Action _addConsumer; + private readonly Func _addConsumer; private readonly ConsumeEventHandler _consumer; private readonly string _exchange; private readonly string _queue; @@ -16,7 +17,7 @@ public RegisterConsumer( string queue, string routingKey, ConsumeEventHandler consumer, - Action addConsumer) + Func addConsumer) { _exchange = exchange; _queue = queue; @@ -25,8 +26,8 @@ public RegisterConsumer( _addConsumer = addConsumer; } - public void Perform(IModel channel) + public async Task Perform(IChannel channel) { - _addConsumer(_exchange, _queue, _routingKey, _consumer); + await _addConsumer(_exchange, _queue, _routingKey, _consumer); } } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/SendAcknowledgement.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/SendAcknowledgement.cs index 01e3e11..8233d6d 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Actions/SendAcknowledgement.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/SendAcknowledgement.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using RabbitMQ.Client; namespace CleanArchitecture.Domain.Rabbitmq.Actions; @@ -11,8 +12,8 @@ public SendAcknowledgement(ulong deliveryTag) DeliveryTag = deliveryTag; } - public void Perform(IModel channel) + public async Task Perform(IChannel channel) { - channel.BasicAck(DeliveryTag, false); + await channel.BasicAckAsync(DeliveryTag, false); } } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/Actions/SendMessage.cs b/CleanArchitecture.Domain/Rabbitmq/Actions/SendMessage.cs index 213bff4..e407fc6 100644 --- a/CleanArchitecture.Domain/Rabbitmq/Actions/SendMessage.cs +++ b/CleanArchitecture.Domain/Rabbitmq/Actions/SendMessage.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Threading.Tasks; using Newtonsoft.Json; using RabbitMQ.Client; @@ -23,16 +24,15 @@ public SendMessage(string routingKey, string exchange, object message) _message = message; } - public void Perform(IModel channel) + public async Task Perform(IChannel channel) { var json = JsonConvert.SerializeObject(_message, s_serializerSettings); var content = Encoding.UTF8.GetBytes(json); - channel.BasicPublish( + await channel.BasicPublishAsync( _exchange, _routingKey, - null, content); } } \ No newline at end of file diff --git a/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs b/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs index 1e2b48e..4fcb983 100644 --- a/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs +++ b/CleanArchitecture.Domain/Rabbitmq/RabbitMqHandler.cs @@ -13,7 +13,7 @@ namespace CleanArchitecture.Domain.Rabbitmq; public sealed class RabbitMqHandler : BackgroundService { - private readonly IModel? _channel; + private IChannel? _channel; private readonly RabbitMqConfiguration _configuration; private readonly ConcurrentDictionary> _consumers = new(); @@ -28,27 +28,30 @@ public RabbitMqHandler( { _configuration = configuration; _logger = logger; + } - if (!configuration.Enabled) + public override async Task StartAsync(CancellationToken cancellationToken) + { + if (!_configuration.Enabled) { - logger.LogInformation("RabbitMQ is disabled. Connection will not be established"); + _logger.LogInformation("RabbitMQ is disabled. Connection will not be established"); return; } var factory = new ConnectionFactory { AutomaticRecoveryEnabled = true, - HostName = configuration.Host, - Port = configuration.Port, - UserName = configuration.Username, - Password = configuration.Password, - DispatchConsumersAsync = true + HostName = _configuration.Host, + Port = _configuration.Port, + UserName = _configuration.Username, + Password = _configuration.Password, }; - var connection = factory.CreateConnection(); - _channel = connection.CreateModel(); + var connection = await factory.CreateConnectionAsync(cancellationToken); + _channel = await connection.CreateChannelAsync(null, cancellationToken); } + public void InitializeExchange(string exchangeName, string type = ExchangeType.Fanout) { if (!_configuration.Enabled) @@ -126,8 +129,14 @@ public void AddExchangeConsumer(string exchange, string queue, ConsumeEventHandl AddExchangeConsumer(exchange, string.Empty, queue, consumer); } - private void AddEventConsumer(string exchange, string queueName, string routingKey, ConsumeEventHandler consumer) + private async Task AddEventConsumer(string exchange, string queueName, string routingKey, ConsumeEventHandler consumer) { + if (!_configuration.Enabled) + { + _logger.LogInformation("RabbitMQ is disabled. Event consumer will not be added."); + return; + } + var key = $"{exchange}-{routingKey}"; if (!_consumers.TryGetValue(key, out var consumers)) @@ -135,10 +144,10 @@ private void AddEventConsumer(string exchange, string queueName, string routingK consumers = new List(); _consumers.TryAdd(key, consumers); - var eventHandler = new AsyncEventingBasicConsumer(_channel); - eventHandler.Received += CallEventConsumersAsync; + var eventHandler = new AsyncEventingBasicConsumer(_channel!); + eventHandler.ReceivedAsync += CallEventConsumersAsync; - _channel!.BasicConsume(queueName, false, eventHandler); + await _channel!.BasicConsumeAsync(queueName, false, eventHandler); } consumers.Add(consumer); @@ -202,19 +211,19 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) while (true) { - HandleEnqueuedActions(); + await HandleEnqueuedActions(); await Task.Delay(1000, stoppingToken); } } - private void HandleEnqueuedActions() + private async Task HandleEnqueuedActions() { while (_pendingActions.TryDequeue(out var action)) { try { - action.Perform(_channel!); + await action.Perform(_channel!); } catch (Exception ex) { diff --git a/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj b/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj index 9aa2051..7abf9af 100644 --- a/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj +++ b/CleanArchitecture.Infrastructure.Tests/CleanArchitecture.Infrastructure.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable false @@ -9,7 +9,7 @@ - + diff --git a/CleanArchitecture.Infrastructure/CleanArchitecture.Infrastructure.csproj b/CleanArchitecture.Infrastructure/CleanArchitecture.Infrastructure.csproj index fc38b5c..86ff0f5 100644 --- a/CleanArchitecture.Infrastructure/CleanArchitecture.Infrastructure.csproj +++ b/CleanArchitecture.Infrastructure/CleanArchitecture.Infrastructure.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable @@ -12,10 +12,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj index 739bc4f..4cca339 100644 --- a/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj +++ b/CleanArchitecture.IntegrationTests/CleanArchitecture.IntegrationTests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable false @@ -9,9 +9,9 @@ - - - + + + @@ -20,10 +20,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/CleanArchitecture.Proto/CleanArchitecture.Proto.csproj b/CleanArchitecture.Proto/CleanArchitecture.Proto.csproj index 7d630d7..beb1525 100644 --- a/CleanArchitecture.Proto/CleanArchitecture.Proto.csproj +++ b/CleanArchitecture.Proto/CleanArchitecture.Proto.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable diff --git a/CleanArchitecture.Shared/CleanArchitecture.Shared.csproj b/CleanArchitecture.Shared/CleanArchitecture.Shared.csproj index 5b1c9fa..fcab850 100644 --- a/CleanArchitecture.Shared/CleanArchitecture.Shared.csproj +++ b/CleanArchitecture.Shared/CleanArchitecture.Shared.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable diff --git a/CleanArchitecture.gRPC.Tests/CleanArchitecture.gRPC.Tests.csproj b/CleanArchitecture.gRPC.Tests/CleanArchitecture.gRPC.Tests.csproj index dab68d4..14a66df 100644 --- a/CleanArchitecture.gRPC.Tests/CleanArchitecture.gRPC.Tests.csproj +++ b/CleanArchitecture.gRPC.Tests/CleanArchitecture.gRPC.Tests.csproj @@ -1,14 +1,14 @@ - net8.0 + net9.0 enable false - + diff --git a/CleanArchitecture.gRPC/CleanArchitecture.gRPC.csproj b/CleanArchitecture.gRPC/CleanArchitecture.gRPC.csproj index e106542..581e4cb 100644 --- a/CleanArchitecture.gRPC/CleanArchitecture.gRPC.csproj +++ b/CleanArchitecture.gRPC/CleanArchitecture.gRPC.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable diff --git a/Dockerfile b/Dockerfile index 40fb486..3247ed9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/aspnet:9.0 AS base USER $APP_UID WORKDIR /app -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release ARG TARGETARCH WORKDIR /src diff --git a/Readme.md b/Readme.md index d2c35c5..357aa6a 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,4 @@ -# Clean Architecture Dotnet 8 API Project +# Clean Architecture Dotnet 9 API Project ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/alex289/CleanArchitecture/dotnet.yml) @@ -78,4 +78,4 @@ To run the tests, follow these steps: This project uses GitHub Actions to build and test the project on every commit to the main branch. The workflow consists of several steps, including restoring packages, building the project and running tests. ## Conclusion -This project is a sample implementation of the Clean Architecture principles, Onion Architecture, MediatR, and Entity Framework. It demonstrates how to organize a .NET 8 API project into layers, how to use the MediatR library to implement the mediator pattern, and how to use Entity Framework to access data. It also includes unit tests for all layers and integration tests using xUnit. +This project is a sample implementation of the Clean Architecture principles, Onion Architecture, MediatR, and Entity Framework. It demonstrates how to organize a .NET 9 API project into layers, how to use the MediatR library to implement the mediator pattern, and how to use Entity Framework to access data. It also includes unit tests for all layers and integration tests using xUnit.