0
0
mirror of https://github.com/alex289/CleanArchitecture.git synced 2025-06-29 18:21:08 +00:00

feat: dotnet 9

This commit is contained in:
alex289 2024-11-13 14:00:12 +01:00
parent 18f458332f
commit 804679d676
No known key found for this signature in database
GPG Key ID: 573F77CD2D87F863
26 changed files with 95 additions and 83 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<UserSecretsId>64377c40-44d6-4989-9662-5d778f8b3b92</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
@ -14,17 +14,17 @@
<PackageReference Include="AspNetCore.HealthChecks.SqlServer" Version="8.0.2" />
<PackageReference Include="AspNetCore.HealthChecks.UI.Client" Version="8.0.1" />
<PackageReference Include="Grpc.AspNetCore.Server.Reflection" Version="2.66.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.9.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="7.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="MockQueryable.NSubstitute" Version="7.0.3" />
<PackageReference Include="NSubstitute" Version="5.3.0" />

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
</ItemGroup>
<ItemGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.2" />

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
@ -9,10 +9,10 @@
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="FluentValidation" Version="11.10.0" />
<PackageReference Include="MediatR" Version="12.4.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2" />
<PackageReference Include="RabbitMQ.Client" Version="7.0.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.2.0" />
</ItemGroup>
<ItemGroup>

View File

@ -14,7 +14,7 @@ public static partial class CustomValidator
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<T, string> Password<T>(
@ -32,7 +32,4 @@ public static partial class CustomValidator
.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();
}

View File

@ -1,3 +1,4 @@
using System.Threading.Tasks;
using RabbitMQ.Client;
namespace CleanArchitecture.Domain.Rabbitmq.Actions;
@ -15,8 +16,8 @@ public sealed class BindQueueToExchange : IRabbitMqAction
_queueName = queueName;
}
public void Perform(IModel channel)
public async Task Perform(IChannel channel)
{
channel.QueueBind(_queueName, _exchangeName, _routingKey);
await channel.QueueBindAsync(_queueName, _exchangeName, _routingKey);
}
}

View File

@ -1,3 +1,4 @@
using System.Threading.Tasks;
using RabbitMQ.Client;
namespace CleanArchitecture.Domain.Rabbitmq.Actions;
@ -13,8 +14,8 @@ public sealed class CreateExchange : IRabbitMqAction
_type = type;
}
public void Perform(IModel channel)
public async Task Perform(IChannel channel)
{
channel.ExchangeDeclare(_name, _type);
await channel.ExchangeDeclareAsync(_name, _type);
}
}

View File

@ -1,3 +1,4 @@
using System.Threading.Tasks;
using RabbitMQ.Client;
namespace CleanArchitecture.Domain.Rabbitmq.Actions;
@ -11,9 +12,9 @@ public sealed class CreateQueue : IRabbitMqAction
QueueName = queueName;
}
public void Perform(IModel channel)
public async Task Perform(IChannel channel)
{
channel.QueueDeclare(
await channel.QueueDeclareAsync(
QueueName,
false,
false,

View File

@ -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);
}

View File

@ -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<string, string, string, ConsumeEventHandler> _addConsumer;
private readonly Func<string, string, string, ConsumeEventHandler, Task> _addConsumer;
private readonly ConsumeEventHandler _consumer;
private readonly string _exchange;
private readonly string _queue;
@ -16,7 +17,7 @@ public sealed class RegisterConsumer : IRabbitMqAction
string queue,
string routingKey,
ConsumeEventHandler consumer,
Action<string, string, string, ConsumeEventHandler> addConsumer)
Func<string, string, string, ConsumeEventHandler, Task> addConsumer)
{
_exchange = exchange;
_queue = queue;
@ -25,8 +26,8 @@ public sealed class RegisterConsumer : IRabbitMqAction
_addConsumer = addConsumer;
}
public void Perform(IModel channel)
public async Task Perform(IChannel channel)
{
_addConsumer(_exchange, _queue, _routingKey, _consumer);
await _addConsumer(_exchange, _queue, _routingKey, _consumer);
}
}

View File

@ -1,3 +1,4 @@
using System.Threading.Tasks;
using RabbitMQ.Client;
namespace CleanArchitecture.Domain.Rabbitmq.Actions;
@ -11,8 +12,8 @@ public sealed class SendAcknowledgement : IRabbitMqAction
DeliveryTag = deliveryTag;
}
public void Perform(IModel channel)
public async Task Perform(IChannel channel)
{
channel.BasicAck(DeliveryTag, false);
await channel.BasicAckAsync(DeliveryTag, false);
}
}

View File

@ -1,4 +1,5 @@
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RabbitMQ.Client;
@ -23,16 +24,15 @@ public sealed class SendMessage : IRabbitMqAction
_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);
}
}

View File

@ -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<string, List<ConsumeEventHandler>> _consumers = new();
@ -28,27 +28,30 @@ public sealed class RabbitMqHandler : BackgroundService
{
_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 sealed class RabbitMqHandler : BackgroundService
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 @@ public sealed class RabbitMqHandler : BackgroundService
consumers = new List<ConsumeEventHandler>();
_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 @@ public sealed class RabbitMqHandler : BackgroundService
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)
{

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="xunit" Version="2.9.2" />

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
@ -12,10 +12,10 @@
<ItemGroup>
<PackageReference Include="MediatR" Version="12.4.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.10" />
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="NUnit" Version="4.2.2" />
<PackageReference Include="NUnit.Analyzers" Version="4.3.0">
@ -20,10 +20,10 @@
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="Respawn" Version="6.2.1" />
<PackageReference Include="Testcontainers" Version="3.10.0" />
<PackageReference Include="Testcontainers.MsSql" Version="3.10.0" />
<PackageReference Include="Testcontainers.RabbitMq" Version="3.10.0" />
<PackageReference Include="Testcontainers.Redis" Version="3.10.0" />
<PackageReference Include="Testcontainers" Version="4.0.0" />
<PackageReference Include="Testcontainers.MsSql" Version="4.0.0" />
<PackageReference Include="Testcontainers.RabbitMq" Version="4.0.0" />
<PackageReference Include="Testcontainers.Redis" Version="4.0.0" />
<PackageReference Include="coverlet.collector" Version="6.0.2">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.1" />
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="MockQueryable.NSubstitute" Version="7.0.3" />
<PackageReference Include="NSubstitute" Version="5.3.0" />

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -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

View File

@ -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.