From 78a83fa09bb51c8b6c72dd29d5894ef645d1b204 Mon Sep 17 00:00:00 2001 From: Angelo Pirola Date: Thu, 18 Dec 2025 10:55:25 +0100 Subject: [PATCH 1/4] Upgrade to .NET 8, async RabbitMQ, and modern C# features Upgraded all projects to .NET 8 and updated NuGet packages for compatibility. Refactored RabbitMQ integration to use new async APIs and modernized consumers. Adopted C# 12 primary constructors in key classes and controllers, improved code style via enhanced .editorconfig, and ensured proper cancellation support. Cleaned up codebase for clarity, maintainability, and alignment with current C#/.NET best practices. --- .editorconfig | 64 +++++++++++++------ .../Abstractions/DefaultMessagingBuilder.cs | 9 +-- .../RabbitMq/MessageManager.cs | 53 +++++++-------- .../RabbitMq/QueueListener.cs | 27 ++------ .../RabbitMq/RabbitMqExtensions.cs | 3 +- .../ServiceBus/QueueListener.cs | 20 ++---- .../WhiteRabbit.Messaging.csproj | 14 ++-- .../WhiteRabbit.WorkerService.csproj | 4 +- WhiteRabbit/Controllers/InvoicesController.cs | 9 +-- WhiteRabbit/Controllers/OrdersController.cs | 11 +--- WhiteRabbit/Program.cs | 3 +- .../Receivers/InvoiceMessageReceiver.cs | 13 ++-- WhiteRabbit/Receivers/OrderMessageReceiver.cs | 15 ++--- WhiteRabbit/WhiteRabbit.csproj | 4 +- 14 files changed, 111 insertions(+), 138 deletions(-) diff --git a/.editorconfig b/.editorconfig index 9848252..8f8512a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,7 @@ tab_width = 4 trim_trailing_whitespace = true # New line preferences -end_of_line = crlf +end_of_line = unset insert_final_newline = unset dotnet_style_null_propagation = true:suggestion @@ -22,6 +22,7 @@ dotnet_style_operator_placement_when_wrapping = beginning_of_line dotnet_style_object_initializer = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion dotnet_style_prefer_simplified_boolean_expressions = true:suggestion dotnet_style_prefer_conditional_expression_over_assignment = false:silent dotnet_style_prefer_conditional_expression_over_return = false:silent @@ -81,6 +82,7 @@ csharp_style_prefer_local_over_anonymous_function = true:silent csharp_style_prefer_extended_property_pattern = true:suggestion csharp_style_implicit_object_creation_when_type_is_apparent = true:silent csharp_style_prefer_tuple_swap = true:silent +csharp_style_prefer_simple_property_accessors = true:suggestion # Field preferences dotnet_style_readonly_field = true:suggestion @@ -98,7 +100,7 @@ csharp_style_var_elsewhere = true:suggestion csharp_style_var_for_built_in_types = true:suggestion csharp_style_var_when_type_is_apparent = true:suggestion -# Expression-bodied members +# Expression-bodied members preferences csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_indexers = true:silent @@ -125,10 +127,13 @@ csharp_prefer_static_local_function = true:suggestion csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent # Code-block preferences +csharp_style_prefer_top_level_statements = true:suggestion +csharp_style_prefer_primary_constructors = true:suggestion csharp_prefer_braces = true:silent csharp_prefer_simple_using_statement = true:suggestion csharp_style_namespace_declarations = file_scoped:suggestion csharp_style_prefer_method_group_conversion = true:silent +csharp_prefer_system_threading_lock = true:suggestion # Expression-level preferences csharp_prefer_simple_default_expression = true:suggestion @@ -138,12 +143,16 @@ csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_style_prefer_index_operator = true:suggestion csharp_style_prefer_range_operator = true:suggestion csharp_style_throw_expression = true:suggestion -csharp_style_unused_value_assignment_preference = discard_variable:suggestion -csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:none +csharp_style_unused_value_expression_statement_preference = discard_variable:none # 'using' directive preferences csharp_using_directive_placement = outside_namespace:suggestion +# Struct preferences +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion + #### C# Formatting Rules #### # New line preferences @@ -157,6 +166,8 @@ csharp_new_line_between_query_expression_clauses = true csharp_style_allow_embedded_statements_on_same_line_experimental = false:error csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:error csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent # Indentation preferences csharp_indent_block_contents = true @@ -193,6 +204,7 @@ csharp_space_between_square_brackets = false # Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true +csharp_style_prefer_utf8_string_literals = true:suggestion #### Naming styles #### @@ -210,6 +222,14 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.constant_fields_should_be_upper_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_upper_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_upper_case.style = pascal_case + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + dotnet_naming_rule.private_or_internal_field_should_be_camel_case.severity = suggestion dotnet_naming_rule.private_or_internal_field_should_be_camel_case.symbols = private_or_internal_field dotnet_naming_rule.private_or_internal_field_should_be_camel_case.style = camel_case @@ -226,23 +246,23 @@ dotnet_naming_rule.async_method_should_be_ends_with_async.style = ends_with_asyn dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.method.applicable_kinds = method dotnet_naming_symbols.method.applicable_accessibilities = public -dotnet_naming_symbols.method.required_modifiers = +dotnet_naming_symbols.method.required_modifiers = dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected -dotnet_naming_symbols.private_or_internal_field.required_modifiers = +dotnet_naming_symbols.private_or_internal_field.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = +dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.non_field_members.required_modifiers = dotnet_naming_symbols.async_method.applicable_kinds = method dotnet_naming_symbols.async_method.applicable_accessibilities = * @@ -250,24 +270,24 @@ dotnet_naming_symbols.async_method.required_modifiers = async # Naming styles -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case -dotnet_naming_style.camel_case.required_prefix = -dotnet_naming_style.camel_case.required_suffix = -dotnet_naming_style.camel_case.word_separator = +dotnet_naming_style.camel_case.required_prefix = +dotnet_naming_style.camel_case.required_suffix = +dotnet_naming_style.camel_case.word_separator = dotnet_naming_style.camel_case.capitalization = camel_case -dotnet_naming_style.ends_with_async.required_prefix = +dotnet_naming_style.ends_with_async.required_prefix = dotnet_naming_style.ends_with_async.required_suffix = Async -dotnet_naming_style.ends_with_async.word_separator = +dotnet_naming_style.ends_with_async.word_separator = dotnet_naming_style.ends_with_async.capitalization = pascal_case # IDE0058: Expression value is never used @@ -278,3 +298,9 @@ dotnet_diagnostic.IDE0010.severity = none # IDE0072: Add missing cases dotnet_diagnostic.IDE0072.severity = none + +# IDE0305: Simplify collection initialization +dotnet_diagnostic.IDE0305.severity = none + +# CA1873: Avoid potentially expensive logging +dotnet_diagnostic.CA1873.severity = none \ No newline at end of file diff --git a/WhiteRabbit.Messaging/Abstractions/DefaultMessagingBuilder.cs b/WhiteRabbit.Messaging/Abstractions/DefaultMessagingBuilder.cs index f738304..96709fa 100644 --- a/WhiteRabbit.Messaging/Abstractions/DefaultMessagingBuilder.cs +++ b/WhiteRabbit.Messaging/Abstractions/DefaultMessagingBuilder.cs @@ -2,12 +2,7 @@ namespace WhiteRabbit.Messaging.Abstractions; -internal class DefaultMessagingBuilder : IMessagingBuilder +internal class DefaultMessagingBuilder(IServiceCollection services) : IMessagingBuilder { - public IServiceCollection Services { get; } - - public DefaultMessagingBuilder(IServiceCollection services) - { - Services = services; - } + public IServiceCollection Services { get; } = services; } diff --git a/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs b/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs index 29f4c3c..a923305 100644 --- a/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs +++ b/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs @@ -11,8 +11,7 @@ internal class MessageManager : IMessageSender, IDisposable private const string MaxPriorityHeader = "x-max-priority"; internal IConnection Connection { get; private set; } - - internal IModel Channel { get; private set; } + internal IChannel Channel { get; private set; } private readonly MessageManagerSettings messageManagerSettings; private readonly QueueSettings queueSettings; @@ -20,26 +19,27 @@ internal class MessageManager : IMessageSender, IDisposable public MessageManager(MessageManagerSettings messageManagerSettings, QueueSettings queueSettings) { var factory = new ConnectionFactory { Uri = new Uri(messageManagerSettings.ConnectionString) }; - Connection = factory.CreateConnection(); - Channel = Connection.CreateModel(); + Connection = factory.CreateConnectionAsync().GetAwaiter().GetResult(); + Channel = Connection.CreateChannelAsync().GetAwaiter().GetResult(); if (messageManagerSettings.QueuePrefetchCount > 0) { - Channel.BasicQos(0, messageManagerSettings.QueuePrefetchCount, false); + Channel.BasicQosAsync(0, messageManagerSettings.QueuePrefetchCount, false); } - Channel.ExchangeDeclare(messageManagerSettings.ExchangeName, ExchangeType.Direct, durable: true); + Channel.ExchangeDeclareAsync(messageManagerSettings.ExchangeName, ExchangeType.Direct, durable: true); - foreach (var queue in queueSettings.Queues) + foreach (var (queue, args) + in from (string Name, Type Type) queue in queueSettings.Queues + let args = new Dictionary + { + [MaxPriorityHeader] = 10 + } + select (queue, args)) { - var args = new Dictionary - { - [MaxPriorityHeader] = 10 - }; - - Channel.QueueDeclare(queue.Name, durable: true, exclusive: false, autoDelete: false, args); - Channel.QueueBind(queue.Name, messageManagerSettings.ExchangeName, queue.Name, null); + Channel.QueueDeclareAsync(queue.Name, durable: true, exclusive: false, autoDelete: false, args); + Channel.QueueBindAsync(queue.Name, messageManagerSettings.ExchangeName, queue.Name, null); } this.messageManagerSettings = messageManagerSettings; @@ -49,24 +49,26 @@ public MessageManager(MessageManagerSettings messageManagerSettings, QueueSettin public Task PublishAsync(T message, int priority = 1) where T : class { var sendBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(message, messageManagerSettings.JsonSerializerOptions ?? JsonOptions.Default)); - var routingKey = queueSettings.Queues.First(q => q.Type == typeof(T)).Name; + return PublishAsync(sendBytes.AsMemory(), routingKey, priority); } private Task PublishAsync(ReadOnlyMemory body, string routingKey, int priority = 1) { - var properties = Channel.CreateBasicProperties(); - properties.Persistent = true; - properties.Priority = Convert.ToByte(priority); + var props = new BasicProperties + { + Persistent = true, + Priority = Convert.ToByte(priority) + }; + + Channel.BasicPublishAsync(messageManagerSettings.ExchangeName, routingKey, true, props, body); - Channel.BasicPublish(messageManagerSettings.ExchangeName, routingKey, properties, body); return Task.CompletedTask; } - public void MarkAsComplete(BasicDeliverEventArgs message) => Channel.BasicAck(message.DeliveryTag, false); - - public void MarkAsRejected(BasicDeliverEventArgs message) => Channel.BasicReject(message.DeliveryTag, false); + public void MarkAsComplete(BasicDeliverEventArgs message) => Channel.BasicAckAsync(message.DeliveryTag, false); + public void MarkAsRejected(BasicDeliverEventArgs message) => Channel.BasicRejectAsync(message.DeliveryTag, false); public void Dispose() { @@ -74,18 +76,19 @@ public void Dispose() { if (Channel.IsOpen) { - Channel.Close(); + Channel.CloseAsync().GetAwaiter().GetResult(); } if (Connection.IsOpen) { - Connection.Close(); + Connection.CloseAsync().GetAwaiter().GetResult(); } } catch { + // Ignore exceptions on dispose } GC.SuppressFinalize(this); } -} +} \ No newline at end of file diff --git a/WhiteRabbit.Messaging/RabbitMq/QueueListener.cs b/WhiteRabbit.Messaging/RabbitMq/QueueListener.cs index 1dcd4ba..74ff978 100644 --- a/WhiteRabbit.Messaging/RabbitMq/QueueListener.cs +++ b/WhiteRabbit.Messaging/RabbitMq/QueueListener.cs @@ -3,29 +3,16 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using RabbitMQ.Client; using RabbitMQ.Client.Events; using WhiteRabbit.Messaging.Abstractions; namespace WhiteRabbit.Messaging.RabbitMq; -internal class QueueListener : BackgroundService where T : class +internal class QueueListener(MessageManager messageManager, MessageManagerSettings messageManagerSettings, QueueSettings settings, + ILogger> logger, IServiceProvider serviceProvider) : BackgroundService where T : class { - private readonly MessageManager messageManager; - private readonly MessageManagerSettings messageManagerSettings; - private readonly ILogger logger; - private readonly IServiceProvider serviceProvider; - private readonly string queueName; - - public QueueListener(MessageManager messageManager, MessageManagerSettings messageManagerSettings, QueueSettings settings, ILogger> logger, IServiceProvider serviceProvider) - { - this.messageManager = messageManager; - this.messageManagerSettings = messageManagerSettings; - this.logger = logger; - this.serviceProvider = serviceProvider; - - queueName = settings.Queues.First(q => q.Type == typeof(T)).Name; - } + private readonly ILogger logger = logger; + private readonly string queueName = settings.Queues.First(q => q.Type == typeof(T)).Name; public override Task StartAsync(CancellationToken cancellationToken) { @@ -45,8 +32,8 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken) { stoppingToken.ThrowIfCancellationRequested(); - var consumer = new EventingBasicConsumer(messageManager.Channel); - consumer.Received += async (_, message) => + var consumer = new AsyncEventingBasicConsumer(messageManager.Channel); + consumer.ReceivedAsync += async (_, message) => { try { @@ -71,7 +58,7 @@ protected override Task ExecuteAsync(CancellationToken stoppingToken) stoppingToken.ThrowIfCancellationRequested(); }; - messageManager.Channel.BasicConsume(queueName, autoAck: false, consumer); + messageManager.Channel.BasicConsumeAsync(queueName, false, null, false, false, null, consumer, stoppingToken); return Task.CompletedTask; } diff --git a/WhiteRabbit.Messaging/RabbitMq/RabbitMqExtensions.cs b/WhiteRabbit.Messaging/RabbitMq/RabbitMqExtensions.cs index e514248..c48f6e3 100644 --- a/WhiteRabbit.Messaging/RabbitMq/RabbitMqExtensions.cs +++ b/WhiteRabbit.Messaging/RabbitMq/RabbitMqExtensions.cs @@ -5,7 +5,8 @@ namespace WhiteRabbit.Messaging.RabbitMq; public static class RabbitMQExtensions { - public static IMessagingBuilder AddRabbitMq(this IServiceCollection services, Action messageManagerConfiguration, Action queuesConfiguration) + public static IMessagingBuilder AddRabbitMq(this IServiceCollection services, Action messageManagerConfiguration, + Action queuesConfiguration) { services.AddSingleton(); services.AddSingleton(provider => provider.GetService()); diff --git a/WhiteRabbit.Messaging/ServiceBus/QueueListener.cs b/WhiteRabbit.Messaging/ServiceBus/QueueListener.cs index 7a11cb0..2bd1ec5 100644 --- a/WhiteRabbit.Messaging/ServiceBus/QueueListener.cs +++ b/WhiteRabbit.Messaging/ServiceBus/QueueListener.cs @@ -8,26 +8,14 @@ namespace WhiteRabbit.Messaging.ServiceBus; -internal class QueueListener : BackgroundService, IAsyncDisposable where T : class +internal class QueueListener(MessageManager messageManager, MessageManagerSettings messageManagerSettings, QueueSettings settings, + ILogger> logger, IServiceProvider serviceProvider) : BackgroundService, IAsyncDisposable where T : class { - private readonly MessageManager messageManager; - private readonly MessageManagerSettings messageManagerSettings; - private readonly ILogger logger; - private readonly IServiceProvider serviceProvider; - private readonly string queueName; + private readonly ILogger logger = logger; + private readonly string queueName = settings.Queues.First(q => q.Type == typeof(T)).Name; private ServiceBusReceiver serviceBusReceiver; - public QueueListener(MessageManager messageManager, MessageManagerSettings messageManagerSettings, QueueSettings settings, ILogger> logger, IServiceProvider serviceProvider) - { - this.messageManager = messageManager; - this.messageManagerSettings = messageManagerSettings; - this.logger = logger; - this.serviceProvider = serviceProvider; - - queueName = settings.Queues.First(q => q.Type == typeof(T)).Name; - } - public override Task StartAsync(CancellationToken cancellationToken) { logger.LogDebug("ServiceBus Listener for {QueueName} started", queueName); diff --git a/WhiteRabbit.Messaging/WhiteRabbit.Messaging.csproj b/WhiteRabbit.Messaging/WhiteRabbit.Messaging.csproj index 2ce5d54..9b52d1d 100644 --- a/WhiteRabbit.Messaging/WhiteRabbit.Messaging.csproj +++ b/WhiteRabbit.Messaging/WhiteRabbit.Messaging.csproj @@ -1,17 +1,17 @@  - net6.0 + net8.0 enable - - - - - - + + + + + + diff --git a/WhiteRabbit.WorkerService/WhiteRabbit.WorkerService.csproj b/WhiteRabbit.WorkerService/WhiteRabbit.WorkerService.csproj index 719865a..16f7a21 100644 --- a/WhiteRabbit.WorkerService/WhiteRabbit.WorkerService.csproj +++ b/WhiteRabbit.WorkerService/WhiteRabbit.WorkerService.csproj @@ -1,12 +1,12 @@  - net6.0 + net8.0 enable - + diff --git a/WhiteRabbit/Controllers/InvoicesController.cs b/WhiteRabbit/Controllers/InvoicesController.cs index a7b53cd..1e52478 100644 --- a/WhiteRabbit/Controllers/InvoicesController.cs +++ b/WhiteRabbit/Controllers/InvoicesController.cs @@ -6,15 +6,8 @@ namespace WhiteRabbit.Controllers; [ApiController] [Route("api/[controller]")] -public class InvoicesController : ControllerBase +public class InvoicesController(IMessageSender messageSender) : ControllerBase { - private readonly IMessageSender messageSender; - - public InvoicesController(IMessageSender messageSender) - { - this.messageSender = messageSender; - } - [HttpPost] [ProducesResponseType(StatusCodes.Status202Accepted)] public async Task Post(Invoice invoice) diff --git a/WhiteRabbit/Controllers/OrdersController.cs b/WhiteRabbit/Controllers/OrdersController.cs index c371ded..37f77b8 100644 --- a/WhiteRabbit/Controllers/OrdersController.cs +++ b/WhiteRabbit/Controllers/OrdersController.cs @@ -6,15 +6,8 @@ namespace WhiteRabbit.Controllers; [ApiController] [Route("api/[controller]")] -public class OrdersController : ControllerBase +public class OrdersController(IMessageSender messageSender) : ControllerBase { - private readonly IMessageSender messageSender; - - public OrdersController(IMessageSender messageSender) - { - this.messageSender = messageSender; - } - [HttpPost] [ProducesResponseType(StatusCodes.Status202Accepted)] public async Task Post(Order order) @@ -22,4 +15,4 @@ public async Task Post(Order order) await messageSender.PublishAsync(order); return Accepted(); } -} +} \ No newline at end of file diff --git a/WhiteRabbit/Program.cs b/WhiteRabbit/Program.cs index 5c57811..62c5003 100644 --- a/WhiteRabbit/Program.cs +++ b/WhiteRabbit/Program.cs @@ -1,4 +1,4 @@ -using Microsoft.OpenApi.Models; +using Microsoft.OpenApi; using WhiteRabbit.Messaging.RabbitMq; using WhiteRabbit.Receivers; using WhiteRabbit.Shared; @@ -55,7 +55,6 @@ void Configure(IApplicationBuilder app, IWebHostEnvironment env) } app.UseHttpsRedirection(); - app.UseRouting(); app.UseEndpoints(endpoints => diff --git a/WhiteRabbit/Receivers/InvoiceMessageReceiver.cs b/WhiteRabbit/Receivers/InvoiceMessageReceiver.cs index e873bff..68e0ccc 100644 --- a/WhiteRabbit/Receivers/InvoiceMessageReceiver.cs +++ b/WhiteRabbit/Receivers/InvoiceMessageReceiver.cs @@ -3,21 +3,16 @@ namespace WhiteRabbit.Receivers; -public class InvoiceMessageReceiver : IMessageReceiver +public class InvoiceMessageReceiver(ILogger logger) : IMessageReceiver { - private readonly ILogger logger; - - public InvoiceMessageReceiver(ILogger logger) - { - this.logger = logger; - } + private readonly ILogger logger = logger; public async Task ReceiveAsync(Invoice message, CancellationToken cancellationToken) { logger.LogInformation("Creating invoice for order {OrderNumber}...", message.OrderNumber); - await Task.Delay(TimeSpan.FromSeconds(10 + Random.Shared.Next(10))); + await Task.Delay(TimeSpan.FromSeconds(10 + Random.Shared.Next(10)), cancellationToken); logger.LogInformation("End creating invoice for order {OrderNumber}", message.OrderNumber); } -} +} \ No newline at end of file diff --git a/WhiteRabbit/Receivers/OrderMessageReceiver.cs b/WhiteRabbit/Receivers/OrderMessageReceiver.cs index c1690cb..09cbf14 100644 --- a/WhiteRabbit/Receivers/OrderMessageReceiver.cs +++ b/WhiteRabbit/Receivers/OrderMessageReceiver.cs @@ -3,25 +3,18 @@ namespace WhiteRabbit.Receivers; -public class OrderMessageReceiver : IMessageReceiver +public class OrderMessageReceiver(ILogger logger, IMessageSender messageSender) : IMessageReceiver { - private readonly ILogger logger; - private readonly IMessageSender messageSender; - - public OrderMessageReceiver(ILogger logger, IMessageSender messageSender) - { - this.logger = logger; - this.messageSender = messageSender; - } + private readonly ILogger logger = logger; public async Task ReceiveAsync(Order message, CancellationToken cancellationToken) { logger.LogInformation("Processing order {OrderNumber}...", message.Number); - await Task.Delay(TimeSpan.FromSeconds(10 + Random.Shared.Next(10))); + await Task.Delay(TimeSpan.FromSeconds(10 + Random.Shared.Next(10)), cancellationToken); logger.LogInformation("End processing order {OrderNumber}", message.Number); await messageSender.PublishAsync(new Invoice { OrderNumber = message.Number }); } -} +} \ No newline at end of file diff --git a/WhiteRabbit/WhiteRabbit.csproj b/WhiteRabbit/WhiteRabbit.csproj index 6cf6635..c5a53e1 100644 --- a/WhiteRabbit/WhiteRabbit.csproj +++ b/WhiteRabbit/WhiteRabbit.csproj @@ -1,12 +1,12 @@ - net6.0 + net8.0 enable - + From d7bb43ec8a9117c109e3b855f8a179f6b3a425a8 Mon Sep 17 00:00:00 2001 From: Angelo Pirola Date: Thu, 18 Dec 2025 11:09:11 +0100 Subject: [PATCH 2/4] Update WhiteRabbit.Messaging/RabbitMq/MessageManager.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- WhiteRabbit.Messaging/RabbitMq/MessageManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs b/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs index a923305..5fa4bfc 100644 --- a/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs +++ b/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs @@ -25,7 +25,7 @@ public MessageManager(MessageManagerSettings messageManagerSettings, QueueSettin if (messageManagerSettings.QueuePrefetchCount > 0) { - Channel.BasicQosAsync(0, messageManagerSettings.QueuePrefetchCount, false); + Channel.BasicQosAsync(0, messageManagerSettings.QueuePrefetchCount, false).GetAwaiter().GetResult(); } Channel.ExchangeDeclareAsync(messageManagerSettings.ExchangeName, ExchangeType.Direct, durable: true); From f83036af821ef4b64dcc30ee4981871229cc80b0 Mon Sep 17 00:00:00 2001 From: Angelo Pirola Date: Thu, 18 Dec 2025 11:13:28 +0100 Subject: [PATCH 3/4] Refactor MessageManager for async init and disposal Refactored MessageManager to use asynchronous initialization and disposal. Introduced a static CreateAsync factory method for async setup of RabbitMQ connections, channels, and queues. Replaced IDisposable with IAsyncDisposable and implemented DisposeAsync to properly await resource cleanup. All setup operations now use async/await for improved resource management and modern .NET practices. --- .../RabbitMq/MessageManager.cs | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs b/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs index a923305..bf026c1 100644 --- a/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs +++ b/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs @@ -6,7 +6,7 @@ namespace WhiteRabbit.Messaging.RabbitMq; -internal class MessageManager : IMessageSender, IDisposable +internal class MessageManager : IMessageSender, IAsyncDisposable { private const string MaxPriorityHeader = "x-max-priority"; @@ -16,19 +16,26 @@ internal class MessageManager : IMessageSender, IDisposable private readonly MessageManagerSettings messageManagerSettings; private readonly QueueSettings queueSettings; - public MessageManager(MessageManagerSettings messageManagerSettings, QueueSettings queueSettings) + private MessageManager(MessageManagerSettings messageManagerSettings, QueueSettings queueSettings) + { + this.messageManagerSettings = messageManagerSettings; + this.queueSettings = queueSettings; + } + + public static async Task CreateAsync(MessageManagerSettings messageManagerSettings, QueueSettings queueSettings) { var factory = new ConnectionFactory { Uri = new Uri(messageManagerSettings.ConnectionString) }; + var manager = new MessageManager(messageManagerSettings, queueSettings); - Connection = factory.CreateConnectionAsync().GetAwaiter().GetResult(); - Channel = Connection.CreateChannelAsync().GetAwaiter().GetResult(); + manager.Connection = await factory.CreateConnectionAsync().ConfigureAwait(false); + manager.Channel = await manager.Connection.CreateChannelAsync().ConfigureAwait(false); if (messageManagerSettings.QueuePrefetchCount > 0) { - Channel.BasicQosAsync(0, messageManagerSettings.QueuePrefetchCount, false); + await manager.Channel.BasicQosAsync(0, messageManagerSettings.QueuePrefetchCount, false).ConfigureAwait(false); } - Channel.ExchangeDeclareAsync(messageManagerSettings.ExchangeName, ExchangeType.Direct, durable: true); + await manager.Channel.ExchangeDeclareAsync(messageManagerSettings.ExchangeName, ExchangeType.Direct, durable: true).ConfigureAwait(false); foreach (var (queue, args) in from (string Name, Type Type) queue in queueSettings.Queues @@ -38,12 +45,11 @@ in from (string Name, Type Type) queue in queueSettings.Queues } select (queue, args)) { - Channel.QueueDeclareAsync(queue.Name, durable: true, exclusive: false, autoDelete: false, args); - Channel.QueueBindAsync(queue.Name, messageManagerSettings.ExchangeName, queue.Name, null); + await manager.Channel.QueueDeclareAsync(queue.Name, durable: true, exclusive: false, autoDelete: false, args).ConfigureAwait(false); + await manager.Channel.QueueBindAsync(queue.Name, messageManagerSettings.ExchangeName, queue.Name, null).ConfigureAwait(false); } - this.messageManagerSettings = messageManagerSettings; - this.queueSettings = queueSettings; + return manager; } public Task PublishAsync(T message, int priority = 1) where T : class @@ -70,18 +76,18 @@ private Task PublishAsync(ReadOnlyMemory body, string routingKey, int prio public void MarkAsComplete(BasicDeliverEventArgs message) => Channel.BasicAckAsync(message.DeliveryTag, false); public void MarkAsRejected(BasicDeliverEventArgs message) => Channel.BasicRejectAsync(message.DeliveryTag, false); - public void Dispose() + public async ValueTask DisposeAsync() { try { - if (Channel.IsOpen) + if (Channel?.IsOpen == true) { - Channel.CloseAsync().GetAwaiter().GetResult(); + await Channel.CloseAsync().ConfigureAwait(false); } - if (Connection.IsOpen) + if (Connection?.IsOpen == true) { - Connection.CloseAsync().GetAwaiter().GetResult(); + await Connection.CloseAsync().ConfigureAwait(false); } } catch From 90853ecbe8b3ba57f4ea8fac832295756187b606 Mon Sep 17 00:00:00 2001 From: Angelo Pirola Date: Thu, 18 Dec 2025 11:47:37 +0100 Subject: [PATCH 4/4] Refactor MessageManager initialization in CreateAsync Refactored the CreateAsync method to set the Connection property of MessageManager directly in the object initializer, removing the redundant assignment line. This improves code conciseness and readability. --- WhiteRabbit.Messaging/RabbitMq/MessageManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs b/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs index bf026c1..bc57d18 100644 --- a/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs +++ b/WhiteRabbit.Messaging/RabbitMq/MessageManager.cs @@ -25,9 +25,11 @@ private MessageManager(MessageManagerSettings messageManagerSettings, QueueSetti public static async Task CreateAsync(MessageManagerSettings messageManagerSettings, QueueSettings queueSettings) { var factory = new ConnectionFactory { Uri = new Uri(messageManagerSettings.ConnectionString) }; - var manager = new MessageManager(messageManagerSettings, queueSettings); + var manager = new MessageManager(messageManagerSettings, queueSettings) + { + Connection = await factory.CreateConnectionAsync().ConfigureAwait(false) + }; - manager.Connection = await factory.CreateConnectionAsync().ConfigureAwait(false); manager.Channel = await manager.Connection.CreateChannelAsync().ConfigureAwait(false); if (messageManagerSettings.QueuePrefetchCount > 0)