├── .github └── workflows │ ├── codeql-analysis.yml │ └── github-action.yml ├── .gitignore ├── LICENSE.txt ├── RabbitMQ.Client.Core.DependencyInjection.sln ├── docs ├── advanced-usage.md ├── changelog.md ├── exchange-configuration.md ├── images │ └── delayed-message-model.png ├── message-consumption.md ├── message-production.md ├── rabbit-configuration.md └── readme.md ├── examples ├── Examples.AdvancedConfiguration │ ├── Controllers │ │ └── ExampleController.cs │ ├── Examples.AdvancedConfiguration.csproj │ ├── MessageHandlers │ │ ├── CustomAsyncMessageHandler.cs │ │ └── CustomMessageHandler.cs │ ├── Program.cs │ ├── Services │ │ └── ConsumingHostedService.cs │ ├── Startup.cs │ └── appsettings.json ├── Examples.BatchMessageHandler │ ├── CustomBatchMessageHandler.cs │ ├── Examples.BatchMessageHandler.csproj │ ├── Program.cs │ └── appsettings.json ├── Examples.ConsumerHost │ ├── CustomMessageHandler.cs │ ├── Examples.ConsumerHost.csproj │ ├── Program.cs │ └── appsettings.json ├── Examples.Producer │ ├── Examples.Producer.csproj │ ├── Message.cs │ └── Program.cs └── Examples.SslProducer │ ├── Examples.SslProducer.csproj │ ├── Program.cs │ └── appsettings.json ├── icon.png ├── readme.md ├── src └── RabbitMQ.Client.Core.DependencyInjection │ ├── AsyncMessageHandlerDependencyInjectionExtensions.cs │ ├── BasicDeliverEventArgsExtensions.cs │ ├── BatchMessageHandlerDependencyInjectionExtensions.cs │ ├── Configuration │ ├── RabbitMqConnectionOptions.cs │ ├── RabbitMqExchangeOptions.cs │ ├── RabbitMqQueueOptions.cs │ ├── RabbitMqServiceOptions.cs │ ├── RabbitMqSslOption.cs │ └── RabbitMqTcpEndpoint.cs │ ├── Exceptions │ ├── BatchMessageHandlerAlreadyConfiguredException.cs │ ├── BatchMessageHandlerInvalidPropertyValueException.cs │ ├── ConsumingChannelIsNullException.cs │ ├── InitialConnectionException.cs │ ├── ProducingChannelIsNullException.cs │ └── QueueingServiceAlreadyConfiguredException.cs │ ├── Filters │ ├── IBatchMessageHandlingFilter.cs │ ├── IMessageHandlingExceptionFilter.cs │ └── IMessageHandlingFilter.cs │ ├── InternalExtensions │ ├── BaseMessageHandlerDependencyInjectionExtensions.cs │ ├── BaseMessageHandlerDictionaryExtensions.cs │ ├── RabbitMqServiceOptionsDependencyInjectionExtensions.cs │ └── WildcardExtensions.cs │ ├── MessageHandlerDependencyInjectionExtensions.cs │ ├── MessageHandlers │ ├── IAsyncMessageHandler.cs │ ├── IBaseMessageHandler.cs │ └── IMessageHandler.cs │ ├── Models │ ├── BatchConsumerConnectionOptions.cs │ ├── ExchangeServiceDescriptor.cs │ ├── MessageHandlerContainer.cs │ ├── MessageHandlerOrderingContainer.cs │ ├── MessageHandlerOrderingModel.cs │ ├── MessageHandlerRouter.cs │ ├── RabbitMqExchange.cs │ └── TreeNode.cs │ ├── RabbitMQ.Client.Core.DependencyInjection.csproj │ ├── RabbitMqExchangeDependencyInjectionExtensions.cs │ ├── RabbitMqServiceDependencyInjectionExtensions.cs │ └── Services │ ├── BaseBatchMessageHandler.cs │ ├── ChannelDeclarationHostedService.cs │ ├── ChannelDeclarationService.cs │ ├── ConsumingHostedService.cs │ ├── ConsumingService.cs │ ├── Interfaces │ ├── IChannelDeclarationService.cs │ ├── IConsumingService.cs │ ├── IMessageHandlerContainerBuilder.cs │ ├── IMessageHandlingPipelineExecutingService.cs │ ├── IMessageHandlingService.cs │ ├── IProducingService.cs │ ├── IRabbitMqConnectionFactory.cs │ └── IRabbitMqService.cs │ ├── MessageHandlerContainerBuilder.cs │ ├── MessageHandlingPipelineExecutingService.cs │ ├── MessageHandlingService.cs │ ├── ProducingService.cs │ └── RabbitMqConnectionFactory.cs └── tests └── RabbitMQ.Client.Core.DependencyInjection.Tests ├── IntegrationTests ├── RabbitMqConnectionFactoryTests.cs └── RabbitMqServicesTests.cs ├── Models ├── HandleMessageReceivingEventTestData.cs ├── HandleMessageReceivingEventTestDataModel.cs └── MessageHandlerOrderingContainerTestModel.cs ├── RabbitMQ.Client.Core.DependencyInjection.Tests.csproj ├── Stubs ├── IStubCaller.cs ├── StubAsyncMessageHandler.cs ├── StubBaseBatchMessageHandler.cs ├── StubBatchMessageHandlingFilter.cs ├── StubCaller.cs ├── StubExceptionMessageHandler.cs ├── StubMessageHandler.cs ├── StubMessageHandlingExceptionFilter.cs └── StubMessageHandlingFilter.cs ├── UnitTests ├── BaseBatchMessageHandlerTests.cs ├── BatchMessageHandlerDependencyInjectionExtensionsTests.cs ├── ConsumingServiceTests.cs ├── MessageHandlerDependencyInjectionExtensionsTests.cs ├── MessageHandlingPipelineExecutingServiceTests.cs ├── MessageHandlingServiceTests.cs ├── RabbitMqExchangeDependencyInjectionExtensionsTests.cs └── WildcardExtensionsTests.cs └── run-rabbitmq.sh /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | schedule: 9 | - cron: '0 22 * * 3' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | language: ['csharp'] 20 | 21 | steps: 22 | - name: Checkout repository 23 | uses: actions/checkout@v2 24 | with: 25 | fetch-depth: 2 26 | 27 | - run: git checkout HEAD^2 28 | if: ${{ github.event_name == 'pull_request' }} 29 | 30 | - name: Initialize CodeQL 31 | uses: github/codeql-action/init@v1 32 | with: 33 | languages: ${{ matrix.language }} 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v1 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v1 40 | -------------------------------------------------------------------------------- /.github/workflows/github-action.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | workflow: 7 | runs-on: ubuntu-latest 8 | container: mcr.microsoft.com/dotnet/core/runtime:latest 9 | 10 | services: 11 | rabbitmq: 12 | image: rabbitmq:latest 13 | ports: 14 | - 5672:5672 15 | options: --health-cmd "rabbitmqctl node_health_check" --health-interval 10s --health-timeout 5s --health-retries 5 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Setup .NET Core 20 | uses: actions/setup-dotnet@v1 21 | with: 22 | dotnet-version: 5.0.102 23 | - name: Build 24 | run: dotnet build --configuration Release 25 | - name: Run tests 26 | run: dotnet test --configuration Release 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/.project 2 | **/bin/ 3 | **/obj/ 4 | **/.vs/ 5 | **/.vscode/ 6 | **/.idea/ 7 | **/*.xproj.user 8 | **/*.csproj.user -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Vorontsov Anton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /RabbitMQ.Client.Core.DependencyInjection.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28803.156 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ.Client.Core.DependencyInjection", "src\RabbitMQ.Client.Core.DependencyInjection\RabbitMQ.Client.Core.DependencyInjection.csproj", "{0C8EA737-64AC-4C41-8F80-11BFFEE23CE2}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{98236217-C249-4D30-8646-B1FCF4070910}" 9 | ProjectSection(SolutionItems) = preProject 10 | .gitignore = .gitignore 11 | icon.png = icon.png 12 | LICENSE.txt = LICENSE.txt 13 | readme.md = readme.md 14 | EndProjectSection 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{19ADB6D2-AC61-476A-9FF4-F827574658C7}" 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.ConsumerHost", "examples\Examples.ConsumerHost\Examples.ConsumerHost.csproj", "{10C10C9D-F2ED-43C8-B235-780DD75EF8E6}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Producer", "examples\Examples.Producer\Examples.Producer.csproj", "{1F81E848-7781-448F-BBBB-6A1E509B76CC}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{04EFD8F7-5120-4072-9728-64203F6F4873}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{BC4CDBDE-4AEE-44B3-B00B-380EB1AC5E62}" 25 | ProjectSection(SolutionItems) = preProject 26 | docs\advanced-usage.md = docs\advanced-usage.md 27 | docs\changelog.md = docs\changelog.md 28 | docs\exchange-configuration.md = docs\exchange-configuration.md 29 | docs\message-consumption.md = docs\message-consumption.md 30 | docs\message-production.md = docs\message-production.md 31 | docs\rabbit-configuration.md = docs\rabbit-configuration.md 32 | docs\readme.md = docs\readme.md 33 | EndProjectSection 34 | EndProject 35 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{93D59B0E-856C-4260-B50E-57FE5C0F5073}" 36 | ProjectSection(SolutionItems) = preProject 37 | docs\images\delayed-message-model.png = docs\images\delayed-message-model.png 38 | EndProjectSection 39 | EndProject 40 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9D0289B2-C566-46CC-A53A-471BCBA0F277}" 41 | EndProject 42 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RabbitMQ.Client.Core.DependencyInjection.Tests", "tests\RabbitMQ.Client.Core.DependencyInjection.Tests\RabbitMQ.Client.Core.DependencyInjection.Tests.csproj", "{85907F19-00B0-4BA6-9B7C-0452A174903D}" 43 | EndProject 44 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.AdvancedConfiguration", "examples\Examples.AdvancedConfiguration\Examples.AdvancedConfiguration.csproj", "{0C713913-8D54-49E7-AADD-45497216E2EF}" 45 | EndProject 46 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.BatchMessageHandler", "examples\Examples.BatchMessageHandler\Examples.BatchMessageHandler.csproj", "{BC9BE326-AEFE-42F2-B592-80126188514D}" 47 | EndProject 48 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.SslProducer", "examples\Examples.SslProducer\Examples.SslProducer.csproj", "{863CDE58-B16B-4058-8C48-DB78058DDDC9}" 49 | EndProject 50 | Global 51 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 52 | Debug|Any CPU = Debug|Any CPU 53 | Release|Any CPU = Release|Any CPU 54 | EndGlobalSection 55 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 56 | {0C8EA737-64AC-4C41-8F80-11BFFEE23CE2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {0C8EA737-64AC-4C41-8F80-11BFFEE23CE2}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {0C8EA737-64AC-4C41-8F80-11BFFEE23CE2}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {0C8EA737-64AC-4C41-8F80-11BFFEE23CE2}.Release|Any CPU.Build.0 = Release|Any CPU 60 | {10C10C9D-F2ED-43C8-B235-780DD75EF8E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {10C10C9D-F2ED-43C8-B235-780DD75EF8E6}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {10C10C9D-F2ED-43C8-B235-780DD75EF8E6}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {10C10C9D-F2ED-43C8-B235-780DD75EF8E6}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {1F81E848-7781-448F-BBBB-6A1E509B76CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 65 | {1F81E848-7781-448F-BBBB-6A1E509B76CC}.Debug|Any CPU.Build.0 = Debug|Any CPU 66 | {1F81E848-7781-448F-BBBB-6A1E509B76CC}.Release|Any CPU.ActiveCfg = Release|Any CPU 67 | {1F81E848-7781-448F-BBBB-6A1E509B76CC}.Release|Any CPU.Build.0 = Release|Any CPU 68 | {85907F19-00B0-4BA6-9B7C-0452A174903D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {85907F19-00B0-4BA6-9B7C-0452A174903D}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {85907F19-00B0-4BA6-9B7C-0452A174903D}.Release|Any CPU.ActiveCfg = Release|Any CPU 71 | {85907F19-00B0-4BA6-9B7C-0452A174903D}.Release|Any CPU.Build.0 = Release|Any CPU 72 | {0C713913-8D54-49E7-AADD-45497216E2EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 73 | {0C713913-8D54-49E7-AADD-45497216E2EF}.Debug|Any CPU.Build.0 = Debug|Any CPU 74 | {0C713913-8D54-49E7-AADD-45497216E2EF}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {0C713913-8D54-49E7-AADD-45497216E2EF}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {BC9BE326-AEFE-42F2-B592-80126188514D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 77 | {BC9BE326-AEFE-42F2-B592-80126188514D}.Debug|Any CPU.Build.0 = Debug|Any CPU 78 | {BC9BE326-AEFE-42F2-B592-80126188514D}.Release|Any CPU.ActiveCfg = Release|Any CPU 79 | {BC9BE326-AEFE-42F2-B592-80126188514D}.Release|Any CPU.Build.0 = Release|Any CPU 80 | {863CDE58-B16B-4058-8C48-DB78058DDDC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {863CDE58-B16B-4058-8C48-DB78058DDDC9}.Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {863CDE58-B16B-4058-8C48-DB78058DDDC9}.Release|Any CPU.ActiveCfg = Release|Any CPU 83 | {863CDE58-B16B-4058-8C48-DB78058DDDC9}.Release|Any CPU.Build.0 = Release|Any CPU 84 | EndGlobalSection 85 | GlobalSection(SolutionProperties) = preSolution 86 | HideSolutionNode = FALSE 87 | EndGlobalSection 88 | GlobalSection(NestedProjects) = preSolution 89 | {0C8EA737-64AC-4C41-8F80-11BFFEE23CE2} = {19ADB6D2-AC61-476A-9FF4-F827574658C7} 90 | {10C10C9D-F2ED-43C8-B235-780DD75EF8E6} = {04EFD8F7-5120-4072-9728-64203F6F4873} 91 | {1F81E848-7781-448F-BBBB-6A1E509B76CC} = {04EFD8F7-5120-4072-9728-64203F6F4873} 92 | {93D59B0E-856C-4260-B50E-57FE5C0F5073} = {BC4CDBDE-4AEE-44B3-B00B-380EB1AC5E62} 93 | {85907F19-00B0-4BA6-9B7C-0452A174903D} = {9D0289B2-C566-46CC-A53A-471BCBA0F277} 94 | {0C713913-8D54-49E7-AADD-45497216E2EF} = {04EFD8F7-5120-4072-9728-64203F6F4873} 95 | {BC9BE326-AEFE-42F2-B592-80126188514D} = {04EFD8F7-5120-4072-9728-64203F6F4873} 96 | {863CDE58-B16B-4058-8C48-DB78058DDDC9} = {04EFD8F7-5120-4072-9728-64203F6F4873} 97 | EndGlobalSection 98 | GlobalSection(ExtensibilityGlobals) = postSolution 99 | SolutionGuid = {0EBBD182-65B2-47F9-ABBE-64B5B8C9652F} 100 | EndGlobalSection 101 | EndGlobal 102 | -------------------------------------------------------------------------------- /docs/advanced-usage.md: -------------------------------------------------------------------------------- 1 | # Advanced usage 2 | 3 | `IQueueService` is an interface that implements two other interfaces - `IConsumingService` and `IProducingService`. By default, a RabbitMQ Client is registered as `IQueueService` without a logical separation at producing and consuming code. Thus, you can inject only a `IQueueService` instance, and `IConsumingService` or `IProducingService` won't be available. This is not a real deal until you want to control the way a RabbitMQ Client connects to the server. 4 | 5 | An instance of `IQueueService` opens two connections to the RabbitMQ server, one is for message production, and the other one is for message consumption. Normally a RabbitMQ Client is added in a singleton mode, so both connections stay opened while application is running. It is also noticeable that a RabbitMQ Client uses the same credentials for both connections. If you add `IQueueService` in the transient mode (via the `AddRabbitMqClientTransient` extension method) both connections will be opened each time `IQueueService` is being injected somewhere else. This behavior does not fit everybody, so you can change it a little. 6 | 7 | You are allowed to register a RabbitMQ Client as an implementation of two interfaces that have been mentioned before - `IConsumingService` and `IProducingService`. Each interface defines its own connection and its own collection of methods, obviously, for message production and message consumption. You can also use different credentials for different connections, and there is an option `ClientProvidedName` which allows you to create a "named" connection (which will be easier to find in the RabbitMQ management UI). There is also a possibility of registering `IConsumingService` and `IProducingService` in different lifetime modes, in case you want your consumption connection to be persist (singleton `IConsumingService`) and open a connection each time you want to send a message (a transient `IProducingService`). This situation will be covered in code examples below. 8 | 9 | Let' say your application is a web API and you want to use both `IConsumingService` and `IProducingService`. Your `Startup` will look like this. 10 | 11 | ```c# 12 | public class Startup 13 | { 14 | IConfiguration Configuration { get; } 15 | 16 | public Startup(IConfiguration configuration) 17 | { 18 | Configuration = configuration; 19 | } 20 | 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddControllers(); 24 | 25 | // We will use different credentials for connections. 26 | var rabbitMqConsumerSection = Configuration.GetSection("RabbitMqConsumer"); 27 | var rabbitMqProducerSection = Configuration.GetSection("RabbitMqProducer"); 28 | 29 | // And we also configure different exchanges just for a better example. 30 | var producingExchangeSection = Configuration.GetSection("ProducingExchange"); 31 | var consumingExchangeSection = Configuration.GetSection("ConsumingExchange"); 32 | 33 | services.AddRabbitMqConsumingClientSingleton(rabbitMqConsumerSection) 34 | .AddRabbitMqProducingClientSingleton(rabbitMqProducerSection) 35 | .AddProductionExchange("exchange.to.send.messages.only", producingExchangeSection) 36 | .AddConsumptionExchange("consumption.exchange", consumingExchangeSection) 37 | .AddMessageHandlerTransient("routing.key"); 38 | 39 | services.AddHostedService(); 40 | } 41 | 42 | public void Configure(IApplicationBuilder app) 43 | { 44 | app.UseRouting(); 45 | app.UseEndpoints(endpoints => endpoints.MapControllers()); 46 | } 47 | } 48 | ``` 49 | 50 | We have added `IConsumingService` and `IProducingService` via `AddRabbitMqConsumingClientSingleton` and `AddRabbitMqProducingClientSingleton` extension methods. We have also added two exchanges (for different purposes) via `AddProductionExchange` and `AddConsumptionExchange` methods which are covered in previous documentation sections. To start a message consumption we add a custom `IHostedService`, which injects `IConsumingService` and uses its `StartConsuming` method. 51 | 52 | ```c# 53 | public class ConsumingHostedService : IHostedService 54 | { 55 | readonly IConsumingService _consumingService; 56 | 57 | public ConsumingHostedService(IConsumingService consumingService) 58 | { 59 | _consumingService = consumingService; 60 | } 61 | 62 | public Task StartAsync(CancellationToken cancellationToken) 63 | { 64 | _consumingService.StartConsuming(); 65 | return Task.CompletedTask; 66 | } 67 | 68 | public Task StopAsync(CancellationToken cancellationToken) 69 | { 70 | return Task.CompletedTask; 71 | } 72 | } 73 | ``` 74 | 75 | To send messages we can only use `IProducingService`. Let's inject it inside a controller. 76 | 77 | ```c# 78 | [ApiController] 79 | [Route("api/example")] 80 | public class ExampleController : ControllerBase 81 | { 82 | readonly ILogger _logger; 83 | readonly IProducingService _producingService; 84 | 85 | public ExampleController( 86 | IProducingService producingService, 87 | ILogger logger) 88 | { 89 | _producingService = producingService; 90 | _logger = logger; 91 | } 92 | 93 | [HttpGet] 94 | public async Task Get() 95 | { 96 | _logger.LogInformation($"Sending messages with {typeof(IProducingService)}."); 97 | var message = new { message = "text" }; 98 | await _producingService.SendAsync(message, "exchange.to.send.messages.only", "some.routing.key"); 99 | return Ok(message); 100 | } 101 | } 102 | ``` 103 | 104 | And the last thing we have to look at is a configuration file. 105 | ``` 106 | { 107 | "RabbitMqConsumer": { 108 | "ClientProvidedName": "Consumer", 109 | "TcpEndpoints": [ 110 | { 111 | "HostName": "127.0.0.1", 112 | "Port": 5672 113 | } 114 | ], 115 | "Port": "5672", 116 | "UserName": "user-consumer", 117 | "Password": "passwordForConsumer" 118 | }, 119 | "RabbitMqProducer": { 120 | "ClientProvidedName": "Producer", 121 | "TcpEndpoints": [ 122 | { 123 | "HostName": "127.0.0.1", 124 | "Port": 5672 125 | } 126 | ], 127 | "Port": "5672", 128 | "UserName": "user-producer", 129 | "Password": "passwordForProducer" 130 | }, 131 | "ConsumingExchange": { 132 | "Queues": [ 133 | { 134 | "Name": "consuming.queue", 135 | "RoutingKeys": [ "routing.key" ] 136 | } 137 | ] 138 | }, 139 | "ProducingExchange": { 140 | "Queues": [ 141 | { 142 | "Name": "queue.of.producing.exchange", 143 | "RoutingKeys": [ "produce.messages", "produce.events" ] 144 | } 145 | ] 146 | } 147 | } 148 | ``` 149 | 150 | As you can see, we set up a RabbitMQ client, which will create a connection for message production each time we call a `IProducingService`. We have also configured connections with different names and credentials. And the most important part is that we separated `IQueueService` for a logical parts. Be aware that `IQueueService` won't be available for injecting when you configure a RabbitMQ client in a `IConsumingService` plus `IProducingService` way. 151 | 152 | For basic message consumption features see the [Previous page](message-consumption.md) -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this library will be documented in this file. 4 | 5 | ## [4.3.0] - 2020-10-03 6 | 7 | ### Added 8 | 9 | - `BasicDeliverEventArgs` extensions for parsing messages. 10 | 11 | ### Changed 12 | 13 | - **Breaking!** `IMessageHandler`, `IAsyncMessageHandler`, `INonCyclicMessageHandler` and `IAsyncNonCyclicMessageHandler` get messages in `Handle` methods as `BasicDeliverEventArgs` instead of string values. 14 | - **Breaking!** `BatchMessageHandler` has been removed, `BaseBatchMessageHandler` is now one and only base class for handling messages in batches. `HandleMessages` method of `BaseBatchMessageHandler` gets a collection of messages as `BasicDeliverEventArgs` instead of bytes. 15 | 16 | ## [4.2.0] - 2020-10-01 17 | 18 | ### Added 19 | 20 | - Options that can allow configuring behaviour of re-queueing messages. New properties `RequeueTimeoutMilliseconds` and `RequeueAttempts` added to `RabbitMqExchangeOptions`. 21 | - `MessageHandlingPeriod` property for batch message handlers. That feature allows you to execute even unfilled batches each time period expires. 22 | 23 | ### Changed 24 | 25 | - **Breaking!** Now all `Send` or `SendAsync` methods of `IProducingService` (and `IQueueService`) with delay parameter use milliseconds instead of seconds. 26 | 27 | ## [4.1.1] - 2020-05-30 28 | 29 | ### Fixed 30 | 31 | - Fixed batch message handlers DI registering - the case when handlers are in use without RabbitMqClient. 32 | 33 | ## [4.1.0] - 2020-05-17 34 | 35 | ### Added 36 | 37 | - Ssl option while opening a RabbitMq connection with `TcpEndpoints`. 38 | - Example project of a basic ssl usage. 39 | - `StopConsuming` method for `IConsumingService`. 40 | - Retrying mechanism while creating an initial RabbitMq connection. 41 | - Integration and unit testing. 42 | 43 | ### Updated 44 | 45 | - `BaseBatchMessageHandler` and `BatchMessageHandler` protected methods and properties are not public (for testing purposes). 46 | - `RabbitMqConnectionFactory` has been changed from an extension class to the service. 47 | - Updated Microsoft libraries to the newer version (3.1.4). 48 | 49 | ## [4.0.0] - 2020-05-05 50 | 51 | ### Added 52 | 53 | - `BaseBatchMessageHandler` and `BatchMessageHandler` for handling messages in batches via prefetch count feature. 54 | - Example of basic usages of batch message handlers. 55 | 56 | ### Updated 57 | 58 | - Updated RabbitMQ.Client to the newest version 6.0.0. Made some changes according to the breaking changes that come with the newest version of RabbitMQ.Client. 59 | - Moved message handlers to the different namespace `RabbitMQ.Client.Core.DependencyInjection.MessageHandlers`. 60 | - Moved internal DI extensions to the different namespace `RabbitMQ.Client.Core.DependencyInjection.InternalExtensions`. 61 | 62 | ## [3.2.1] - 2020-03-29 63 | 64 | ### Fixed 65 | 66 | - Fixed message acknowledgement when both `IConsumingService` and `IProducingService` in use. 67 | 68 | ### Updated 69 | 70 | - Updated Microsoft libraries to the newer version (3.1.3). 71 | 72 | ## [3.2.0] - 2020-02-14 73 | 74 | ### Added 75 | 76 | - Made a separation of `IQueueService`, which now implements two additional interfaces `IConsumingService` and `IProducingService`. 77 | - Added new DI extension methods for registering independent `IConsumingService` and `IProducingService` services. 78 | 79 | ### Updated 80 | 81 | - Changed to logic of connecting to the RabbitMQ server. For now `IConsumingService` and `IProducingService` have their own connections, so if you use both of them there will be two connections - one for receiving messages and another one for sending. 82 | 83 | ## [3.1.2] - 2020-01-31 84 | 85 | ### Added 86 | 87 | - Ordering for message handlers. 88 | - Separated the DI extensions file for message handlers so there are four of them now (each file covers each message handler type). 89 | - Changed target platform to the .netstandard2.1 90 | 91 | ## [2.2.2] copy of [3.1.1] - 2020-01-18 92 | 93 | **The latest version that supports backward compatibility for .Net Core 2.2** 94 | 95 | ### Added 96 | 97 | - Backwards compability for .Net Core 2.2. 98 | 99 | ## [3.1.1] - 2020-01-18 100 | 101 | ### Added 102 | 103 | - Pattern matching (`WildcardExtensions`) so message handlers can now 104 | - Extension methods which allow user to set the exact exchange from which messages will be processed by message handlers. 105 | - `MessageHandlingService` which is responsible for message processing. 106 | - `WildcardExtensions` and `MessageHandlingService` unit tests. 107 | - `AddRabbitMqClientTransient` extension methods. 108 | 109 | ## [2.2.1] copy of [3.1.0] - 2019-12-06 110 | 111 | ### Added 112 | 113 | - Backwards compability for .Net Core 2.2. 114 | 115 | ## [3.1.0] - 2019-12-06 116 | 117 | ### Added 118 | 119 | - Possibility to connect multiple RabbitMQ hosts and set connection names. 120 | 121 | ### Updated 122 | 123 | - Version of the Net Core platform has been updated to the v3.1. 124 | 125 | ## [2.2.0] copy of [3.0.2] - 2019-11-04 126 | 127 | ### Added 128 | 129 | - Backwards compability for .Net Core 2.2. 130 | 131 | ## [3.0.2] - 2019-11-04 132 | 133 | ### Added 134 | 135 | - **!Breaking change** Boolean parameter `isConsuming` for `AddExchange` method. 136 | - Extension methods for adding different exchanges (`AddProductionExchange` and `AddConsumptionExchange`) that wraps `isConsuming` parameter. 137 | 138 | ### Updated 139 | 140 | - Way of binding exchanges and queues (not `isConsuming` value is being used). 141 | 142 | ### Changed 143 | 144 | - Readme file. 145 | 146 | ## [3.0.1] - 2019-10-12 147 | 148 | ### Updated 149 | 150 | - Updated libraries to the newer versions (Microsoft libraries, RabbitMQ.Client and Newtonsoft.Json). 151 | 152 | ## [3.0.0] - 2019-09-24 153 | 154 | ### Updated 155 | 156 | - Version of the Net Core platform has been updated to the v3.0. 157 | 158 | ## [1.3.2] - 2019-08-27 159 | 160 | ### Added 161 | 162 | - Possibility to configure RabbitMQ connection manually. 163 | 164 | ### Removed 165 | 166 | - Some code redundancy in example projects. 167 | 168 | ## [1.3.1] - 2019-04-24 169 | 170 | ### Added 171 | 172 | - First "stable" library release. 173 | - Example projects that show how to configure RabbitMQ using library for message production and message consumption. -------------------------------------------------------------------------------- /docs/exchange-configuration.md: -------------------------------------------------------------------------------- 1 | # Exchange configuration 2 | 3 | ### Basics 4 | 5 | Client applications work with exchanges and queues which must be "declared" and "bound" to each other in a certain way before they can be used. 6 | Queues and exchanges can also be customized by using additional parameters. This library allows you to do this routine simply calling the `AddExchange` method passing additional parameters to it. 7 | You are allowed to configure multiple exchanges with multiple queues bound to them. 8 | 9 | Your `Startup` code will look like this. 10 | 11 | ```c# 12 | public class Startup 13 | { 14 | public static IConfiguration Configuration; 15 | 16 | public Startup(IConfiguration configuration) 17 | { 18 | Configuration = configuration; 19 | } 20 | 21 | public void ConfigureServices(IServiceCollection services) 22 | { 23 | var clientConfiguration = Configuration.GetSection("RabbitMq"); 24 | var exchangeConfiguration = Configuration.GetSection("RabbitMqExchange"); 25 | services.AddRabbitMqClient(clientConfiguration) 26 | .AddExchange("ExchangeName", isConsuming: true, exchangeConfiguration); 27 | } 28 | 29 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 30 | { 31 | } 32 | } 33 | ``` 34 | 35 | And the `appsettings.json` file will be like this. 36 | 37 | ```json 38 | { 39 | "RabbitMqExchange": { 40 | "Type": "direct", 41 | "Durable": true, 42 | "AutoDelete": false, 43 | "DeadLetterExchange": "default.dlx.exchange", 44 | "RequeueFailedMessages": true, 45 | "RequeueTimeoutMilliseconds": 200, 46 | "RequeueAttempts": 2, 47 | "Arguments": { "key": "value" }, 48 | "Queues": [ 49 | { 50 | "Name": "MyQueueName", 51 | "Durable": true, 52 | "AutoDelete": false, 53 | "Exclusive": false, 54 | "Arguments": { "key": "value" }, 55 | "RoutingKeys": [ "first.routing.key", "second.routing.key" ] 56 | } 57 | ] 58 | } 59 | } 60 | ``` 61 | 62 | The RabbitMQ client configuration section is not specified in this example, for more information see the [documentation](rabbit-configuration.md) file. 63 | 64 | Exchanges can be configured with properties: 65 | - `Type` - an exchange type (direct, topic, fanout). The default value is `"direct"`. 66 | - `Durable` - a durability option. The default value is `true`. 67 | - `AutoDelete` - an option for exchange auto deleting. The default value is `false`. 68 | - `Arguments` - a dictionary of additional arguments. The default value is `null`. 69 | - `RequeueFailedMessages` - an option that specifies behaviour of re-queueing failed messages with certain delay through the dead-letter-exchange. The default value is `true`. The mechanism of sending delayed messages is covered in the [documentation](message-production.md). 70 | - `RequeueTimeoutMilliseconds` - timeout in milliseconds after which the message will be re-queued. The default value is 200. 71 | - `RequeueAttempts` - a number of attempts which queueing service will try to re-queue a message. The default value is 2. 72 | - `DeadLetterExchange` - a value for dead-letter-exchange. The default value for the dead-letter-exchange name is `"default.dlx.exchange"`. 73 | - `Queues` - a collection of queues bound to the exchange. 74 | 75 | Queue options: 76 | - `Name` - a queue name. 77 | - `Durable` - a durability option. The default value is `true`. 78 | - `AutoDelete` - an option for queue auto deleting. The default value is `false`. 79 | - `Exclusive` - an exclusive option. The default value is `false`. 80 | - `Arguments` - a dictionary of additional [arguments](https://www.rabbitmq.com/queues.html#optional-arguments). The default value is `null`. 81 | - `RoutingKeys` - a collection of routing keys that the queue "listens". 82 | 83 | Taking into account all the default values that you can skip, configuration will look like this. 84 | 85 | ```json 86 | { 87 | "RabbitMqExchange": { 88 | "Type": "direct", 89 | "Queues": [ 90 | { 91 | "Name": "MyQueueName", 92 | "RoutingKeys": [ "first.routing.key", "second.routing.key" ] 93 | } 94 | ] 95 | } 96 | } 97 | ``` 98 | 99 | If you want to use routing keys matching with queue names you can skip the `"RoutingKeys"` option and queues will be bound to the exchange by their names. 100 | 101 | ```json 102 | { 103 | "RabbitMqExchange": { 104 | "Type": "direct", 105 | "Queues": [ 106 | { 107 | "Name": "queue.name.as.routing.key" 108 | } 109 | ] 110 | } 111 | } 112 | ``` 113 | 114 | ### Production and consumption exchanges 115 | 116 | There are two custom "types" of exchanges in this library - **production** and **consumption**. **Production** exchanges supposed to be used only in apps that produce messages. **Consumption** exchanges are made for both production and consuming. 117 | Why is it even necessary to have such division? You can control behaviour of set of exchanges in multi-purpose applications that consume as well as consume messages and avoid getting unwanted messages. 118 | 119 | For defining custom "type" of exchange you have to set `isConsuming` parameter accepted by the `AddExchange` method. If the value is `true` then application will be getting (consuming) messages from queues bound to that exchange. If it is `false` exchange will only be used for producing messages. 120 | 121 | ```c# 122 | services.AddRabbitMqClient(clientConfiguration) 123 | .AddExchange("ExchangeName", isConsuming: true, exchangeConfiguration); 124 | ``` 125 | 126 | You can also use `AddConsumptionExchange` or `AddProductionExchange` but under the hood it is the same as using `AddExchange` method with the `isConsuming` parameter. 127 | 128 | ```c# 129 | services.AddRabbitMqClient(clientConfiguration) 130 | .AddConsumptionExchange("ConsumptionExchange", exchangeConfiguration); 131 | 132 | // And the other method. 133 | 134 | services.AddRabbitMqClient(clientConfiguration) 135 | .AddProductionExchange("ProductionExchange", exchangeConfiguration); 136 | ``` 137 | 138 | ### Manual configuring 139 | 140 | You can also configure exchanges manually passing instances of `RabbitMqExchangeOptions` and `RabbitMqQueueOptions` classes to one of the `AddExchange` methods. 141 | 142 | ```c# 143 | var exchangeOptions = new RabbitMqExchangeOptions 144 | { 145 | Type = "topic", 146 | Durable = true, 147 | AutoDelete = true, 148 | Arguments = null, 149 | RequeueFailedMessages = true, 150 | DeadLetterExchange = "default.dlx.exchange", 151 | Queues = new List 152 | { 153 | new RabbitMqQueueOptions 154 | { 155 | Name = "MyQueueName", 156 | Durable = true, 157 | AutoDelete = false, 158 | Exclusive = false, 159 | Arguments = null, 160 | RoutingKeys = new HashSet { "routing.key" } 161 | } 162 | } 163 | }; 164 | services.AddRabbitMqClient(clientConfiguration) 165 | .AddExchange("ExchangeName", isConsuming: true, exchangeOptions); 166 | ``` 167 | 168 | The same configuration will work with `AddConsumptionExchange` or `AddProductionExchange` overloads. 169 | 170 | ```c# 171 | var exchangeOptions = new RabbitMqExchangeOptions 172 | { 173 | Queues = new List 174 | { 175 | new RabbitMqQueueOptions 176 | { 177 | Name = "MyQueueName", 178 | RoutingKeys = new HashSet { "routing.key" } 179 | } 180 | } 181 | }; 182 | services.AddRabbitMqClient(clientConfiguration) 183 | .AddProductionExchange("ProductionExchange", exchangeOptions); 184 | 185 | // Or the other method. 186 | 187 | services.AddRabbitMqClient(clientConfiguration) 188 | .AddConsumptionExchange("ConsumptionExchange", exchangeConfiguration); 189 | ``` 190 | 191 | For the RabbitMQ client configuration see the [Previous page](rabbit-configuration.md) 192 | 193 | For message production features see the [Next page](message-production.md) -------------------------------------------------------------------------------- /docs/images/delayed-message-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ualehosaini/RabbitMQ.Client.Core.DependencyInjection/3d11e1bef76977d1208bf99410a264337c021974/docs/images/delayed-message-model.png -------------------------------------------------------------------------------- /docs/message-production.md: -------------------------------------------------------------------------------- 1 | # Message production 2 | 3 | ### Basics 4 | 5 | `IQueueService` represents a service that handles all the work of sending and receiving messages. You can inject `IQueueService` wherever you want in order to use it. 6 | 7 | It can be a custom service or a controller. 8 | 9 | ```c# 10 | [Route("api/[controller]")] 11 | public class HomeController : Controller 12 | { 13 | readonly IQueueService _queueService; 14 | public HomeController(IQueueService queueService) 15 | { 16 | _queueService = queueService; 17 | } 18 | } 19 | ``` 20 | 21 | Or you can get the instance of `IQueueService` in a console application. 22 | 23 | ```c# 24 | public static class Program 25 | { 26 | public static IConfiguration Configuration { get; set; } 27 | 28 | public static void Main() 29 | { 30 | var builder = new ConfigurationBuilder() 31 | .SetBasePath(Directory.GetCurrentDirectory()) 32 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); 33 | Configuration = builder.Build(); 34 | 35 | var serviceCollection = new ServiceCollection() 36 | .AddRabbitMqClient(Configuration.GetSection("RabbitMq")) 37 | .AddConsumptionExchange("exchange.name", Configuration.GetSection("RabbitMqExchange"); 38 | 39 | var serviceProvider = serviceCollection.BuildServiceProvider(); 40 | var queueService = serviceProvider.GetRequiredService(); 41 | // And you can use the IQueueService as you want. 42 | } 43 | } 44 | ``` 45 | 46 | To publish a message to an exchange, use one of `IQueueService` sending methods. 47 | 48 | You can send objects using `Send` or `SendAsync` methods. Objects will be serialized into json and sent with `IBasicProperties` where content type set as `"application/json"` and `Persistent` set as `true`. 49 | 50 | ```c# 51 | var message = new 52 | { 53 | Id = 1, 54 | Name = "RandomName" 55 | }; 56 | queueService.Send(message, exchangeName: "exchange.name", routingKey: "routing.key"); 57 | // Or asyncronous version 58 | await queueService.SendAsync(message, exchangeName: "exchange.name", routingKey: "routing.key"); 59 | ``` 60 | 61 | With `SendJson` methods you can also send raw json in case you serialized it by yourself. 62 | 63 | ```c# 64 | var message = "{\"id\": 1, \"name\": \"Bob\"}"; 65 | queueService.SendJson(message, exchangeName: "exchange.name", routingKey: "routing.key"); 66 | // Or asyncronous version 67 | await queueService.SendJsonAsync(message, exchangeName: "exchange.name", routingKey: "routing.key"); 68 | ``` 69 | 70 | If you want to send a message in another format different from json you can use `SendString` methods. 71 | In this case `IBasicProperties` will be only with the `Persistent` property set as `true`, so you can send any string you want (e.g. an XML string). 72 | 73 | ```c# 74 | var message = "Hello World!"; 75 | queueService.SendString(message, exchangeName: "exchange.name", routingKey: "routing.key"); 76 | // Or asyncronous version 77 | await queueService.SendStringAsync(message, exchangeName: "exchange.name", routingKey: "routing.key"); 78 | ``` 79 | 80 | And if you want to manage everything by yourself you can use `Send` methods passing message as a byte array and `IBasicProperties` as parameters. 81 | 82 | ```c# 83 | var properties = queueService.Channel.CreateBasicProperties(); 84 | // Set everything you want. 85 | properties.Persistent = true; 86 | 87 | var bytes = new byte[] { 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33 }; 88 | queueService.Send(bytes, properties, exchangeName: "exchange.name", routingKey: "routing.key"); 89 | // Or asyncronous version 90 | await queueService.SendAsync(bytes, properties, exchangeName: "exchange.name", routingKey: "routing.key"); 91 | ``` 92 | 93 | You are also allowed to send messages with delay (in milliseconds). All of previously listed methods have an overload that takes a `delay` parameter. 94 | 95 | ```c# 96 | // Objects 97 | queueService.Send(message, exchangeName: "exchange.name", routingKey: "routing.key", millisecondsDelay: 10); 98 | await queueService.SendAsync(message, exchangeName: "exchange.name", routingKey: "routing.key", millisecondsDelay: 10); 99 | 100 | // Json 101 | queueService.SendJson(message, exchangeName: "exchange.name", routingKey: "routing.key", millisecondsDelay: 10); 102 | await queueService.SendJsonAsync(message, exchangeName: "exchange.name", routingKey: "routing.key", millisecondsDelay: 10); 103 | 104 | // Strings 105 | queueService.SendString(message, exchangeName: "exchange.name", routingKey: "routing.key", millisecondsDelay: 10); 106 | await queueService.SendStringAsync(message, exchangeName: "exchange.name", routingKey: "routing.key", millisecondsDelay: 10); 107 | 108 | // Bytes 109 | queueService.Send(bytes, properties, exchangeName: "exchange.name", routingKey: "routing.key", millisecondsDelay: 10); 110 | await queueService.SendAsync(bytes, properties, exchangeName: "exchange.name", routingKey: "routing.key", millisecondsDelay: 10); 111 | ``` 112 | 113 | ### Mechanism of sending delayed messages 114 | 115 | The implementation of sending deferred (delayed) messages in this project is quite tricky. 116 | The image below shows a model of the whole process of passing the message from the producer to the consumer. 117 | ![Model of sending delayed messages](./images/delayed-message-model.png) 118 | 119 | **Prerequisites.** Let's say that producer want to send a message to the exchange **"Exchange B"** with a routing key **"routing.key"**, and a delay in **30 milliseconds**. 120 | - Message goes to the exchange **"Exchange A"** whose responsibility is to manage delaying (storing) the message. 121 | - After that a queue with a compound name and special arguments will be created. Name consists of three parts: the routing key of the message that has been sent, a word "delayed", and a number of delay milliseconds. 122 | Queue arguments are as follows. 123 | ``` 124 | x-dead-letter-exchange : Exchange В 125 | x-dead-letter-routing-key : routing.key 126 | x-message-ttl : millisecondsDelay 127 | x-expires : millisecondsDelay + 60000 128 | ``` 129 | - A message, which gets in that queue, will have a specified ttl (time to live), and an exchange to which the message will be sent after expiration. 130 | - That new queue bounds to the **Exchange A**. That queue will be automatically deleted if there are no more messages in it within a minute. 131 | - Message goes to that queue, waits until an expiration moment and goes in the **"Exchange B"**, which routes it as it should be. 132 | - Message gets in the destination queue, and the consumer can retrieve that message. 133 | 134 | For the exchange configuration see the [Previous page](exchange-configuration.md) 135 | 136 | For message consumption features see the [Next page](message-consumption.md) -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | These files cover all functionality of the library and if the new feature comes out documentation will have details of that. 4 | Documentation consists of four sections. **RabbitMQ configuration section** covers ways of configuring the connection to the RabbitMQ host or multiple hosts (HA cluster). 5 | **Exchange configuration section** explains the difference between consumption and production exchanges in terms of this library as well as specific nuances of configuring them. 6 | **Message production section** says about features of sending messages and **message consuming section** covers ways of receiving and handling messages. 7 | Last section **Advanced usage** covers specific usage of the RabbitMQ client that can be useful for users who want to set up a tighter behavior control of the RabbitMQ client. 8 | 9 | ## Table of contents 10 | 11 | - [RabbitMQ configuration](rabbit-configuration.md) 12 | - [Exchange configuration](exchange-configuration.md) 13 | - [Message production](message-production.md) 14 | - [Message consumption](message-consumption.md) 15 | - [Advanced usage](advanced-usage.md) -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/Controllers/ExampleController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 5 | 6 | namespace Examples.AdvancedConfiguration.Controllers 7 | { 8 | [ApiController] 9 | [Route("api/example")] 10 | public class ExampleController : ControllerBase 11 | { 12 | private readonly ILogger _logger; 13 | private readonly IProducingService _producingService; 14 | 15 | public ExampleController( 16 | IProducingService producingService, 17 | ILogger logger) 18 | { 19 | _producingService = producingService; 20 | _logger = logger; 21 | } 22 | 23 | [HttpGet] 24 | public async Task Get() 25 | { 26 | _logger.LogInformation($"Sending messages with {typeof(IProducingService)}."); 27 | var message = new { message = "text" }; 28 | await _producingService.SendAsync(message, "consumption.exchange", "routing.key"); 29 | return Ok(message); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/Examples.AdvancedConfiguration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netcoreapp3.1 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/MessageHandlers/CustomAsyncMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 3 | using RabbitMQ.Client.Events; 4 | 5 | namespace Examples.AdvancedConfiguration.MessageHandlers 6 | { 7 | public class CustomAsyncMessageHandler : IAsyncMessageHandler 8 | { 9 | public async Task Handle(BasicDeliverEventArgs eventArgs, string matchingRoute) 10 | { 11 | // The message handler does not do anything. 12 | // It is just an example. 13 | await Task.CompletedTask; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/MessageHandlers/CustomMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 2 | using RabbitMQ.Client.Events; 3 | 4 | namespace Examples.AdvancedConfiguration.MessageHandlers 5 | { 6 | public class CustomMessageHandler : IMessageHandler 7 | { 8 | public void Handle(BasicDeliverEventArgs eventArgs, string matchingRoute) 9 | { 10 | // The message handler does not do anything. 11 | // It is just an example. 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Examples.AdvancedConfiguration 5 | { 6 | public static class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | Host.CreateDefaultBuilder(args) 11 | .ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()) 12 | .Build() 13 | .Run(); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/Services/ConsumingHostedService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Hosting; 4 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 5 | 6 | namespace Examples.AdvancedConfiguration.Services 7 | { 8 | public class ConsumingHostedService : IHostedService 9 | { 10 | private readonly IConsumingService _consumingService; 11 | 12 | public ConsumingHostedService(IConsumingService consumingService) 13 | { 14 | _consumingService = consumingService; 15 | } 16 | 17 | public Task StartAsync(CancellationToken cancellationToken) 18 | { 19 | _consumingService.StartConsuming(); 20 | return Task.CompletedTask; 21 | } 22 | 23 | public Task StopAsync(CancellationToken cancellationToken) 24 | { 25 | return Task.CompletedTask; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/Startup.cs: -------------------------------------------------------------------------------- 1 | using Examples.AdvancedConfiguration.MessageHandlers; 2 | using Examples.AdvancedConfiguration.Services; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using RabbitMQ.Client.Core.DependencyInjection; 7 | 8 | namespace Examples.AdvancedConfiguration 9 | { 10 | public class Startup 11 | { 12 | private IConfiguration Configuration { get; } 13 | 14 | public Startup(IConfiguration configuration) 15 | { 16 | Configuration = configuration; 17 | } 18 | 19 | public void ConfigureServices(IServiceCollection services) 20 | { 21 | services.AddControllers(); 22 | 23 | // Configurations are the same (same user) and same password, but the only difference - names of connections. 24 | var rabbitMqConsumerSection = Configuration.GetSection("RabbitMqConsumer"); 25 | var rabbitMqProducerSection = Configuration.GetSection("RabbitMqProducer"); 26 | 27 | var producingExchangeSection = Configuration.GetSection("ProducingExchange"); 28 | var consumingExchangeSection = Configuration.GetSection("ConsumingExchange"); 29 | 30 | // There is an example of configuring different message handlers with different parameters. 31 | // You can set collection of routing keys or specify the exact exchange that will be listened by given routing keys (or route patterns) by message handlers. 32 | // There are a lot of different extension methods that is better take a closer look to. 33 | services.AddRabbitMqServices(rabbitMqConsumerSection) 34 | .AddRabbitMqProducer(rabbitMqProducerSection) 35 | .AddProductionExchange("exchange.to.send.messages.only", producingExchangeSection) 36 | .AddConsumptionExchange("consumption.exchange", consumingExchangeSection) 37 | .AddMessageHandlerTransient("routing.key") 38 | .AddAsyncMessageHandlerTransient(new[] { "routing.key", "another.routing.key" }); 39 | 40 | services.AddHostedService(); 41 | } 42 | 43 | public void Configure(IApplicationBuilder app) 44 | { 45 | app.UseRouting(); 46 | app.UseEndpoints(endpoints => endpoints.MapControllers()); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information" 5 | } 6 | }, 7 | "RabbitMqConsumer": { 8 | "ClientProvidedName": "Consumer", 9 | "TcpEndpoints": [ 10 | { 11 | "HostName": "127.0.0.1", 12 | "Port": 5672 13 | } 14 | ], 15 | "Port": "5672", 16 | "UserName": "guest", 17 | "Password": "guest" 18 | }, 19 | "RabbitMqProducer": { 20 | "ClientProvidedName": "Producer", 21 | "TcpEndpoints": [ 22 | { 23 | "HostName": "127.0.0.1", 24 | "Port": 5672 25 | } 26 | ], 27 | "Port": "5672", 28 | "UserName": "guest", 29 | "Password": "guest" 30 | }, 31 | "ConsumingExchange": { 32 | "Queues": [ 33 | { 34 | "Name": "consuming.queue", 35 | "RoutingKeys": [ "routing.key" ] 36 | } 37 | ] 38 | }, 39 | "ProducingExchange": { 40 | "Queues": [ 41 | { 42 | "Name": "queue.of.producing.exchange", 43 | "RoutingKeys": [ "produce.messages", "produce.events" ] 44 | } 45 | ] 46 | } 47 | } -------------------------------------------------------------------------------- /examples/Examples.BatchMessageHandler/CustomBatchMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Logging; 5 | using RabbitMQ.Client.Core.DependencyInjection; 6 | using RabbitMQ.Client.Core.DependencyInjection.Filters; 7 | using RabbitMQ.Client.Core.DependencyInjection.Models; 8 | using RabbitMQ.Client.Core.DependencyInjection.Services; 9 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 10 | using RabbitMQ.Client.Events; 11 | 12 | namespace Examples.BatchMessageHandler 13 | { 14 | public class CustomBatchMessageHandler : BaseBatchMessageHandler 15 | { 16 | private readonly ILogger _logger; 17 | 18 | public CustomBatchMessageHandler( 19 | IRabbitMqConnectionFactory rabbitMqConnectionFactory, 20 | IEnumerable batchConsumerConnectionOptions, 21 | IEnumerable batchMessageHandlingFilters, 22 | ILogger logger) 23 | : base(rabbitMqConnectionFactory, batchConsumerConnectionOptions, batchMessageHandlingFilters, logger) 24 | { 25 | _logger = logger; 26 | } 27 | 28 | public override ushort PrefetchCount { get; set; } = 3; 29 | 30 | // You have to be aware that BaseBatchMessageHandler does not declare the specified queue. So if it does not exists an exception will be thrown. 31 | public override string QueueName { get; set; } = "queue.name"; 32 | 33 | public override Task HandleMessages(IEnumerable messages, CancellationToken cancellationToken) 34 | { 35 | _logger.LogInformation("Handling a batch of messages."); 36 | foreach (var message in messages) 37 | { 38 | _logger.LogInformation(message.GetMessage()); 39 | } 40 | return Task.CompletedTask; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /examples/Examples.BatchMessageHandler/Examples.BatchMessageHandler.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/Examples.BatchMessageHandler/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using RabbitMQ.Client.Core.DependencyInjection; 6 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 7 | 8 | namespace Examples.BatchMessageHandler 9 | { 10 | public static class Program 11 | { 12 | public static async Task Main() 13 | { 14 | var builder = new HostBuilder() 15 | .ConfigureAppConfiguration((hostingContext, config) => 16 | { 17 | config.AddJsonFile("appsettings.json", optional: false); 18 | }) 19 | .ConfigureServices((hostContext, services) => 20 | { 21 | // Let's configure two different BatchMessageHandlers with different methods. 22 | // First - configuring an appsettings.json section. 23 | services.AddBatchMessageHandler(hostContext.Configuration.GetSection("RabbitMq")); 24 | 25 | // Second one - passing configuration instance. 26 | var rabbitMqConfiguration = new RabbitMqServiceOptions 27 | { 28 | HostName = "127.0.0.1", 29 | Port = 5672, 30 | UserName = "guest", 31 | Password = "guest" 32 | }; 33 | services.AddBatchMessageHandler(rabbitMqConfiguration); 34 | 35 | // Use either of them. Do not register batch message handlers multiple times in your real project. 36 | }) 37 | .ConfigureLogging((hostingContext, logging) => 38 | { 39 | logging.AddConsole(); 40 | }); 41 | await builder.RunConsoleAsync(); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /examples/Examples.BatchMessageHandler/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RabbitMq": { 3 | "HostName": "127.0.0.1", 4 | "Port": "5672", 5 | "UserName": "guest", 6 | "Password": "guest" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/Examples.ConsumerHost/CustomMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using RabbitMQ.Client.Core.DependencyInjection; 3 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 4 | using RabbitMQ.Client.Events; 5 | 6 | namespace Examples.ConsumerHost 7 | { 8 | public class CustomMessageHandler : IMessageHandler 9 | { 10 | private readonly ILogger _logger; 11 | public CustomMessageHandler(ILogger logger) 12 | { 13 | _logger = logger; 14 | } 15 | 16 | public void Handle(BasicDeliverEventArgs eventArgs, string matchingRoute) 17 | { 18 | _logger.LogInformation($"Handling message {eventArgs.GetMessage()} by routing key {matchingRoute}"); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /examples/Examples.ConsumerHost/Examples.ConsumerHost.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/Examples.ConsumerHost/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Core.DependencyInjection; 6 | 7 | namespace Examples.ConsumerHost 8 | { 9 | public static class Program 10 | { 11 | public static async Task Main() 12 | { 13 | var builder = new HostBuilder() 14 | .ConfigureAppConfiguration((hostingContext, config) => 15 | { 16 | config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); 17 | }) 18 | .ConfigureLogging((hostingContext, logging) => { 19 | logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 20 | logging.AddConsole(); 21 | }) 22 | .ConfigureServices((hostContext, services) => 23 | { 24 | var rabbitMqSection = hostContext.Configuration.GetSection("RabbitMq"); 25 | var exchangeSection = hostContext.Configuration.GetSection("RabbitMqExchange"); 26 | 27 | services.AddRabbitMqServices(rabbitMqSection) 28 | .AddConsumptionExchange("exchange.name", exchangeSection) 29 | .AddMessageHandlerTransient("routing.key"); 30 | }); 31 | await builder.RunConsoleAsync(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /examples/Examples.ConsumerHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information" 5 | } 6 | }, 7 | "RabbitMq": { 8 | "HostName": "127.0.0.1", 9 | "Port": "5672", 10 | "UserName": "guest", 11 | "Password": "guest" 12 | }, 13 | "RabbitMqExchange": { 14 | "Queues": [ 15 | { 16 | "Name": "myqueue", 17 | "RoutingKeys": [ "routing.key" ] 18 | } 19 | ] 20 | } 21 | } -------------------------------------------------------------------------------- /examples/Examples.Producer/Examples.Producer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/Examples.Producer/Message.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Examples.Producer 4 | { 5 | public class Message 6 | { 7 | public string Name { get; set; } 8 | 9 | public bool Flag { get; set; } 10 | 11 | public int Index { get; set; } 12 | 13 | public IEnumerable Numbers { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /examples/Examples.Producer/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Core.DependencyInjection; 6 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 7 | 8 | namespace Examples.Producer 9 | { 10 | public static class Program 11 | { 12 | public static async Task Main() 13 | { 14 | var serviceCollection = new ServiceCollection(); 15 | ConfigureServices(serviceCollection); 16 | 17 | var serviceProvider = serviceCollection.BuildServiceProvider(); 18 | var producingService = serviceProvider.GetRequiredService(); 19 | 20 | for (var i = 0; i < 10; i++) 21 | { 22 | var message = new Message 23 | { 24 | Name = "Custom message", 25 | Flag = true, 26 | Index = i, 27 | Numbers = new[] { 1, 2, 3 } 28 | }; 29 | await producingService.SendAsync(message, "exchange.name", "routing.key"); 30 | } 31 | } 32 | 33 | private static void ConfigureServices(IServiceCollection services) 34 | { 35 | var rabbitMqConfiguration = new RabbitMqServiceOptions 36 | { 37 | HostName = "127.0.0.1", 38 | Port = 5672, 39 | UserName = "guest", 40 | Password = "guest" 41 | }; 42 | var exchangeOptions = new RabbitMqExchangeOptions 43 | { 44 | Queues = new List 45 | { 46 | new RabbitMqQueueOptions 47 | { 48 | Name = "myqueue", 49 | RoutingKeys = new HashSet { "routing.key" } 50 | } 51 | } 52 | }; 53 | services.AddRabbitMqProducer(rabbitMqConfiguration) 54 | .AddProductionExchange("exchange.name", exchangeOptions); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /examples/Examples.SslProducer/Examples.SslProducer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Always 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/Examples.SslProducer/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Net.Security; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using RabbitMQ.Client.Core.DependencyInjection; 8 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 9 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 10 | 11 | namespace Examples.SslProducer 12 | { 13 | public static class Program 14 | { 15 | private static IConfiguration _configuration; 16 | 17 | public static async Task Main() 18 | { 19 | var builder = new ConfigurationBuilder() 20 | .SetBasePath(Directory.GetCurrentDirectory()) 21 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); 22 | _configuration = builder.Build(); 23 | 24 | var serviceCollection = new ServiceCollection(); 25 | ConfigureServices(serviceCollection); 26 | 27 | var serviceProvider = serviceCollection.BuildServiceProvider(); 28 | var producingService = serviceProvider.GetRequiredService(); 29 | 30 | for (var i = 0; i < 10; i++) 31 | { 32 | var message = new 33 | { 34 | Name = "Custom message", 35 | Flag = true, 36 | Index = i, 37 | Numbers = new[] { 1, 2, 3 } 38 | }; 39 | await producingService.SendAsync(message, "exchange.name", "routing.key"); 40 | } 41 | } 42 | 43 | private static void ConfigureServices(IServiceCollection services) 44 | { 45 | // You can either use a json configuration or bind options by yourself. 46 | var rabbitMqConfiguration = _configuration.GetSection("RabbitMq"); 47 | // There are both examples of json and manual configuration. 48 | //var rabbitMqConfiguration = GetClientOptions(); 49 | 50 | var exchangeOptions = GetExchangeOptions(); 51 | 52 | services.AddRabbitMqProducer(rabbitMqConfiguration) 53 | .AddProductionExchange("exchange.name", exchangeOptions); 54 | } 55 | 56 | private static RabbitMqServiceOptions GetClientOptions() => 57 | new RabbitMqServiceOptions 58 | { 59 | UserName = "guest", 60 | Password = "guest", 61 | TcpEndpoints = new List 62 | { 63 | new RabbitMqTcpEndpoint 64 | { 65 | HostName = "127.0.0.1", 66 | Port = 5671, 67 | SslOption = new RabbitMqSslOption 68 | { 69 | Enabled = true, 70 | ServerName = "yourCA", 71 | CertificatePath = "/path/tp/client-key-store.p12", 72 | CertificatePassphrase = "yourPathPhrase", 73 | AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch 74 | } 75 | } 76 | } 77 | }; 78 | 79 | private static RabbitMqExchangeOptions GetExchangeOptions() => 80 | new RabbitMqExchangeOptions 81 | { 82 | Queues = new List 83 | { 84 | new RabbitMqQueueOptions 85 | { 86 | Name = "myqueue", 87 | RoutingKeys = new HashSet { "routing.key" } 88 | } 89 | } 90 | }; 91 | } 92 | } -------------------------------------------------------------------------------- /examples/Examples.SslProducer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RabbitMq": { 3 | "TcpEndpoints": [ 4 | { 5 | "HostName": "127.0.0.1", 6 | "Port": 5671, 7 | "SslOption": { 8 | "Enabled": true, 9 | "ServerName": "yourCA", 10 | "CertificatePath": "/path/tp/client-key-store.p12", 11 | "CertificatePassphrase": "yourPathPhrase", 12 | "AcceptablePolicyErrors": "RemoteCertificateChainErrors, RemoteCertificateNameMismatch" 13 | } 14 | } 15 | ], 16 | "UserName": "guest", 17 | "Password": "guest" 18 | }, 19 | "RabbitMqExchange": { 20 | "Queues": [ 21 | { 22 | "Name": "myqueue", 23 | "RoutingKeys": [ "routing.key" ] 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ualehosaini/RabbitMQ.Client.Core.DependencyInjection/3d11e1bef76977d1208bf99410a264337c021974/icon.png -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/BasicDeliverEventArgsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Newtonsoft.Json; 6 | using RabbitMQ.Client.Events; 7 | 8 | namespace RabbitMQ.Client.Core.DependencyInjection 9 | { 10 | /// 11 | /// BasicDeliverEventArgsExtensions extension that help to work with messages, 12 | /// 13 | public static class BasicDeliverEventArgsExtensions 14 | { 15 | /// 16 | /// Get message from BasicDeliverEventArgs body. 17 | /// 18 | /// Message event args. 19 | /// Message as a string. 20 | public static string GetMessage(this BasicDeliverEventArgs eventArgs) 21 | { 22 | eventArgs.EnsureIsNotNull(); 23 | return Encoding.UTF8.GetString(eventArgs.Body.ToArray()); 24 | } 25 | 26 | /// 27 | /// Get message payload. 28 | /// 29 | /// Message event args. 30 | /// Type of a message body. 31 | /// Object of type . 32 | public static T GetPayload(this BasicDeliverEventArgs eventArgs) 33 | { 34 | eventArgs.EnsureIsNotNull(); 35 | var messageString = Encoding.UTF8.GetString(eventArgs.Body.ToArray()); 36 | return JsonConvert.DeserializeObject(messageString); 37 | } 38 | 39 | /// 40 | /// Get message payload. 41 | /// 42 | /// Message event args. 43 | /// Serializer settings . 44 | /// Type of a message body. 45 | /// Object of type . 46 | public static T GetPayload(this BasicDeliverEventArgs eventArgs, JsonSerializerSettings settings) 47 | { 48 | eventArgs.EnsureIsNotNull(); 49 | var messageString = Encoding.UTF8.GetString(eventArgs.Body.ToArray()); 50 | return JsonConvert.DeserializeObject(messageString, settings); 51 | } 52 | 53 | /// 54 | /// Get message payload. 55 | /// 56 | /// Message event args. 57 | /// A collection of json converters . 58 | /// Type of a message body. 59 | /// Object of type . 60 | public static T GetPayload(this BasicDeliverEventArgs eventArgs, IEnumerable converters) 61 | { 62 | eventArgs.EnsureIsNotNull(); 63 | var messageString = Encoding.UTF8.GetString(eventArgs.Body.ToArray()); 64 | return JsonConvert.DeserializeObject(messageString, converters.ToArray()); 65 | } 66 | 67 | /// 68 | /// Get message payload as an anonymous object. 69 | /// 70 | /// Message event args. 71 | /// An anonymous object base. 72 | /// Type of an anonymous object. 73 | /// Anonymous object. 74 | public static T GetAnonymousPayload(this BasicDeliverEventArgs eventArgs, T anonymousTypeObject) 75 | { 76 | eventArgs.EnsureIsNotNull(); 77 | var messageString = Encoding.UTF8.GetString(eventArgs.Body.ToArray()); 78 | return JsonConvert.DeserializeAnonymousType(messageString, anonymousTypeObject); 79 | } 80 | 81 | /// 82 | /// Get message payload as an anonymous object. 83 | /// 84 | /// Message event args. 85 | /// An anonymous object base. 86 | /// Serializer settings . 87 | /// Type of an anonymous object. 88 | /// Anonymous object. 89 | public static T GetAnonymousPayload(this BasicDeliverEventArgs eventArgs, T anonymousTypeObject, JsonSerializerSettings settings) 90 | { 91 | eventArgs.EnsureIsNotNull(); 92 | var messageString = Encoding.UTF8.GetString(eventArgs.Body.ToArray()); 93 | return JsonConvert.DeserializeAnonymousType(messageString, anonymousTypeObject, settings); 94 | } 95 | 96 | private static BasicDeliverEventArgs EnsureIsNotNull(this BasicDeliverEventArgs eventArgs) 97 | { 98 | if (eventArgs is null) 99 | { 100 | throw new ArgumentNullException(nameof(eventArgs), "BasicDeliverEventArgs have to be not null to parse a message"); 101 | } 102 | 103 | return eventArgs; 104 | } 105 | } 106 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/BatchMessageHandlerDependencyInjectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.DependencyInjection.Extensions; 5 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 6 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 7 | using RabbitMQ.Client.Core.DependencyInjection.InternalExtensions; 8 | using RabbitMQ.Client.Core.DependencyInjection.Models; 9 | using RabbitMQ.Client.Core.DependencyInjection.Services; 10 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 11 | 12 | namespace RabbitMQ.Client.Core.DependencyInjection 13 | { 14 | /// 15 | /// DI extensions for batch message handlers. 16 | /// 17 | public static class BatchMessageHandlerDependencyInjectionExtensions 18 | { 19 | /// 20 | /// Add batch message handler. 21 | /// 22 | /// Batch message handler type. 23 | /// Service collection. 24 | /// RabbitMq configuration section. 25 | /// Service collection. 26 | public static IServiceCollection AddBatchMessageHandler(this IServiceCollection services, IConfiguration configuration) 27 | where TBatchMessageHandler : BaseBatchMessageHandler 28 | { 29 | CheckIfBatchMessageHandlerAlreadyConfigured(services); 30 | services.TryAddSingleton(); 31 | var configurationInstance = RabbitMqServiceOptionsDependencyInjectionExtensions.GetRabbitMqServiceOptionsInstance(configuration); 32 | services.ConfigureBatchConsumerConnectionOptions(configurationInstance); 33 | services.AddHostedService(); 34 | return services; 35 | } 36 | 37 | /// 38 | /// Add batch message handler. 39 | /// 40 | /// Batch message handler type. 41 | /// Service collection. 42 | /// RabbitMq configuration . 43 | /// Service collection. 44 | public static IServiceCollection AddBatchMessageHandler(this IServiceCollection services, RabbitMqServiceOptions configuration) 45 | where TBatchMessageHandler : BaseBatchMessageHandler 46 | { 47 | CheckIfBatchMessageHandlerAlreadyConfigured(services); 48 | services.TryAddSingleton(); 49 | services.ConfigureBatchConsumerConnectionOptions(configuration); 50 | services.AddHostedService(); 51 | return services; 52 | } 53 | 54 | private static IServiceCollection ConfigureBatchConsumerConnectionOptions(this IServiceCollection services, RabbitMqServiceOptions serviceOptions) 55 | where TBatchMessageHandler : BaseBatchMessageHandler 56 | { 57 | var options = new BatchConsumerConnectionOptions 58 | { 59 | Type = typeof(TBatchMessageHandler), 60 | ServiceOptions = serviceOptions 61 | }; 62 | var serviceDescriptor = new ServiceDescriptor(typeof(BatchConsumerConnectionOptions), options); 63 | services.Add(serviceDescriptor); 64 | return services; 65 | } 66 | 67 | private static void CheckIfBatchMessageHandlerAlreadyConfigured(this IServiceCollection services) 68 | { 69 | var descriptor = services.FirstOrDefault(x => x.ImplementationType == typeof(TBatchMessageHandler)); 70 | if (descriptor != null) 71 | { 72 | throw new BatchMessageHandlerAlreadyConfiguredException(typeof(TBatchMessageHandler), $"A batch message handler of type {typeof(TBatchMessageHandler)} has already been configured."); 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Configuration/RabbitMqConnectionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.Client.Core.DependencyInjection.Configuration 2 | { 3 | /// 4 | /// An options model that "contains" sections for producing and consuming connections of a RabbitMQ clients. 5 | /// 6 | public class RabbitMqConnectionOptions 7 | { 8 | /// 9 | /// Producer connection. 10 | /// 11 | public RabbitMqServiceOptions ProducerOptions { get; set; } 12 | 13 | /// 14 | /// Consumer connection. 15 | /// 16 | public RabbitMqServiceOptions ConsumerOptions { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Configuration/RabbitMqExchangeOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Configuration 4 | { 5 | /// 6 | /// Exchange options. 7 | /// 8 | public class RabbitMqExchangeOptions 9 | { 10 | /// 11 | /// Exchange type. 12 | /// 13 | public string Type { get; set; } = "direct"; 14 | 15 | /// 16 | /// Durable option. 17 | /// 18 | public bool Durable { get; set; } = true; 19 | 20 | /// 21 | /// AutoDelete option. 22 | /// 23 | public bool AutoDelete { get; set; } 24 | 25 | /// 26 | /// Default dead-letter-exchange. 27 | /// 28 | public string DeadLetterExchange { get; set; } = "default.dlx.exchange"; 29 | 30 | /// 31 | /// Option to re-queue failed messages. 32 | /// 33 | public bool RequeueFailedMessages { get; set; } = true; 34 | 35 | /// 36 | /// Re-queue message attempts. 37 | /// 38 | public int RequeueAttempts { get; set; } = 2; 39 | 40 | /// 41 | /// Re-queue timeout in milliseconds. 42 | /// 43 | public int RequeueTimeoutMilliseconds { get; set; } = 200; 44 | 45 | /// 46 | /// Additional arguments. 47 | /// 48 | public IDictionary Arguments { get; set; } = new Dictionary(); 49 | 50 | /// 51 | /// Collection of queues bound to the exchange. 52 | /// 53 | public IList Queues { get; set; } = new List(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Configuration/RabbitMqQueueOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Configuration 4 | { 5 | /// 6 | /// Queue options. 7 | /// 8 | public class RabbitMqQueueOptions 9 | { 10 | /// 11 | /// Queue name. 12 | /// 13 | public string Name { get; set; } 14 | 15 | /// 16 | /// Durable option. 17 | /// 18 | public bool Durable { get; set; } = true; 19 | 20 | /// 21 | /// Exclusive option. 22 | /// 23 | public bool Exclusive { get; set; } 24 | 25 | /// 26 | /// AutoDelete option. 27 | /// 28 | public bool AutoDelete { get; set; } 29 | 30 | /// 31 | /// Routing keys collection that queue "listens". 32 | /// 33 | public HashSet RoutingKeys { get; set; } = new HashSet(); 34 | 35 | /// 36 | /// Additional arguments. 37 | /// 38 | public IDictionary Arguments { get; set; } = new Dictionary(); 39 | } 40 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Configuration/RabbitMqServiceOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Configuration 5 | { 6 | /// 7 | /// RabbitMQ configuration model. 8 | /// 9 | public class RabbitMqServiceOptions 10 | { 11 | /// 12 | /// Collection of AMPQ TCP endpoints. 13 | /// It can be used when RabbitMQ HA cluster is running, and you want to connect multiple hosts with different ports. 14 | /// 15 | /// 16 | /// Has the first priority between properties TcpEndpoints, HostNames and HostName. 17 | /// If all properties set, TcpEndpoints will be used. 18 | /// 19 | public IEnumerable TcpEndpoints { get; set; } 20 | 21 | /// 22 | /// Collection of RabbitMQ server host names. 23 | /// It can be used when RabbitMQ HA cluster is running, and you want to connect multiple hosts. 24 | /// If HostNames collection is null or empty then HostName will be used to create connection. 25 | /// Otherwise, HostNames collection will be used and HostName property value will be ignored. 26 | /// 27 | /// 28 | /// Has the second priority between properties TcpEndpoints, HostNames and HostName. 29 | /// If HostNames collection property and HostName property both set then HostNames will be used. 30 | /// 31 | public IEnumerable HostNames { get; set; } 32 | 33 | /// 34 | /// RabbitMQ server. 35 | /// 36 | /// 37 | /// Has the third priority between properties TcpEndpoints, HostNames and HostName. 38 | /// HostName will be used if only TcpEndpoints and HostNames properties are not set. 39 | /// 40 | public string HostName { get; set; } = "127.0.0.1"; 41 | 42 | /// 43 | /// Port. 44 | /// 45 | public int Port { get; set; } = 5672; 46 | 47 | /// 48 | /// UserName that connects to the server. 49 | /// 50 | public string UserName { get; set; } = "guest"; 51 | 52 | /// 53 | /// Password of the chosen user. 54 | /// 55 | public string Password { get; set; } = "guest"; 56 | 57 | /// 58 | /// Application-specific connection name, will be displayed in the management UI if RabbitMQ server supports it. 59 | /// 60 | public string ClientProvidedName { get; set; } 61 | 62 | /// 63 | /// Virtual host. 64 | /// 65 | public string VirtualHost { get; set; } = "/"; 66 | 67 | /// 68 | /// Automatic connection recovery option. 69 | /// 70 | public bool AutomaticRecoveryEnabled { get; set; } = true; 71 | 72 | /// 73 | /// Topology recovery option. 74 | /// 75 | public bool TopologyRecoveryEnabled { get; set; } = true; 76 | 77 | /// 78 | /// Timeout for connection attempts. 79 | /// 80 | public TimeSpan RequestedConnectionTimeout { get; set; } = TimeSpan.FromMilliseconds(60000); 81 | 82 | /// 83 | /// Heartbeat timeout. 84 | /// 85 | public TimeSpan RequestedHeartbeat { get; set; } = TimeSpan.FromSeconds(60); 86 | 87 | /// 88 | /// The number of retries for opening an initial connection. 89 | /// 90 | public int InitialConnectionRetries { get; set; } = 5; 91 | 92 | /// 93 | /// Timeout for initial connection opening retries. 94 | /// 95 | public int InitialConnectionRetryTimeoutMilliseconds { get; set; } = 200; 96 | } 97 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Configuration/RabbitMqSslOption.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Security; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Configuration 4 | { 5 | /// 6 | /// Ssl option model. 7 | /// 8 | public class RabbitMqSslOption 9 | { 10 | /// 11 | /// Canonical server name. 12 | /// 13 | public string ServerName { get; set; } 14 | 15 | /// 16 | /// Path to the certificate. 17 | /// 18 | public string CertificatePath { get; set; } 19 | 20 | /// 21 | /// A pass-phrase for the certificate. 22 | /// 23 | public string CertificatePassphrase { get; set; } 24 | 25 | /// 26 | /// Flag that defines if certificate should be used. 27 | /// 28 | public bool Enabled { get; set; } = true; 29 | 30 | /// 31 | /// Acceptable policy errors. 32 | /// 33 | /// 34 | /// SslPolicyErrors is a flag enum, so you can have multiple set values. 35 | /// 36 | public SslPolicyErrors? AcceptablePolicyErrors { get; set; } 37 | } 38 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Configuration/RabbitMqTcpEndpoint.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.Client.Core.DependencyInjection.Configuration 2 | { 3 | /// 4 | /// Model of AMPQ Tcp endpoint. 5 | /// 6 | public class RabbitMqTcpEndpoint 7 | { 8 | /// 9 | /// RabbitMQ server. 10 | /// 11 | public string HostName { get; set; } 12 | 13 | /// 14 | /// Tcp connection port. 15 | /// 16 | public int Port { get; set; } = 5672; 17 | 18 | /// 19 | /// Ssl option. 20 | /// 21 | /// 22 | /// Should be null if certificate should not be used. 23 | /// 24 | public RabbitMqSslOption SslOption { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/BatchMessageHandlerAlreadyConfiguredException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// An exception that is thrown when batch message handler of some type configured twice. 7 | /// 8 | public class BatchMessageHandlerAlreadyConfiguredException : Exception 9 | { 10 | /// 11 | /// Type of batch message handler. 12 | /// 13 | public Type BatchMessageHandlerType { get; } 14 | 15 | public BatchMessageHandlerAlreadyConfiguredException(Type type, string message) : base(message) 16 | { 17 | BatchMessageHandlerType = type; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/BatchMessageHandlerInvalidPropertyValueException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// An exception that is thrown when batch message handler implementation has invalid value for a property. 7 | /// 8 | public class BatchMessageHandlerInvalidPropertyValueException : Exception 9 | { 10 | /// 11 | /// Property that contains invalid value. 12 | /// 13 | public string PropertyName { get; set; } 14 | 15 | public BatchMessageHandlerInvalidPropertyValueException(string message, string propertyName) : base(message) 16 | { 17 | PropertyName = propertyName; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/ConsumingChannelIsNullException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// An exception that is thrown during the process of starting a consumer when the channel is null. 7 | /// 8 | public class ConsumingChannelIsNullException : Exception 9 | { 10 | public ConsumingChannelIsNullException(string message) : base(message) 11 | { 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/InitialConnectionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// An exception that is thrown when an initial connection could not be established even with retry mechanism. 7 | /// 8 | public class InitialConnectionException : Exception 9 | { 10 | /// 11 | /// The number of retries which has been attempted. 12 | /// 13 | public int NumberOfRetries { get; set; } 14 | 15 | public InitialConnectionException(string message, Exception innerException) : base(message, innerException) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/ProducingChannelIsNullException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// An exception that is thrown during the publication of a message when the channel is null. 7 | /// 8 | public class ProducingChannelIsNullException : Exception 9 | { 10 | public ProducingChannelIsNullException(string message) : base(message) 11 | { 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/QueueingServiceAlreadyConfiguredException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// An exception that is thrown when queuing service of the same type configured twice. 7 | /// 8 | public class QueueingServiceAlreadyConfiguredException : Exception 9 | { 10 | /// 11 | /// Type of queuing service. 12 | /// 13 | public Type QueueingServiceType { get; } 14 | 15 | public QueueingServiceAlreadyConfiguredException(Type type, string message) : base(message) 16 | { 17 | QueueingServiceType = type; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Filters/IBatchMessageHandlingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Events; 6 | 7 | namespace RabbitMQ.Client.Core.DependencyInjection.Filters 8 | { 9 | /// 10 | /// An abstract filter that runs in the pipeline of message handling in batch handler. 11 | /// 12 | public interface IBatchMessageHandlingFilter 13 | { 14 | /// 15 | /// Execute filter logic. 16 | /// 17 | /// Next action. 18 | /// Function that could process received message. 19 | Func, CancellationToken, Task> Execute(Func, CancellationToken, Task> next); 20 | } 21 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Filters/IMessageHandlingExceptionFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 4 | using RabbitMQ.Client.Events; 5 | 6 | namespace RabbitMQ.Client.Core.DependencyInjection.Filters 7 | { 8 | /// 9 | /// An exception filter that runs in the pipeline that processes message handling failure. 10 | /// 11 | public interface IMessageHandlingExceptionFilter 12 | { 13 | /// 14 | /// Execute filter logic. 15 | /// 16 | /// Next action. 17 | /// Function that could process occured exception. 18 | Func Execute(Func next); 19 | } 20 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Filters/IMessageHandlingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 4 | using RabbitMQ.Client.Events; 5 | 6 | namespace RabbitMQ.Client.Core.DependencyInjection.Filters 7 | { 8 | /// 9 | /// An abstract filter that runs in the pipeline of message handling. 10 | /// 11 | public interface IMessageHandlingFilter 12 | { 13 | /// 14 | /// Execute filter logic. 15 | /// 16 | /// Next action. 17 | /// Function that could process received message. 18 | Func Execute(Func next); 19 | } 20 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/InternalExtensions/BaseMessageHandlerDependencyInjectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using RabbitMQ.Client.Core.DependencyInjection.Models; 6 | 7 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions 8 | { 9 | /// 10 | /// Base DI extensions for all types of message handlers. 11 | /// 12 | internal static class BaseMessageHandlerDependencyInjectionExtensions 13 | { 14 | internal static IServiceCollection AddInstanceTransient(this IServiceCollection services, IEnumerable routePatterns, int order) 15 | where TInterface : class 16 | where TImplementation : class, TInterface => 17 | services.AddInstanceTransient(routePatterns, null, order); 18 | 19 | internal static IServiceCollection AddInstanceSingleton(this IServiceCollection services, IEnumerable routePatterns, int order) 20 | where TInterface : class 21 | where TImplementation : class, TInterface => 22 | services.AddInstanceSingleton(routePatterns, null, order); 23 | 24 | internal static IServiceCollection AddInstanceTransient(this IServiceCollection services, IEnumerable routePatterns, string exchange, int order) 25 | where TInterface : class 26 | where TImplementation : class, TInterface 27 | { 28 | var patterns = routePatterns.ToList(); 29 | services.AddTransient(); 30 | var router = new MessageHandlerRouter 31 | { 32 | Type = typeof(TImplementation), 33 | Exchange = exchange, 34 | RoutePatterns = patterns 35 | }; 36 | services.Add(new ServiceDescriptor(typeof(MessageHandlerRouter), router)); 37 | return services.AddMessageHandlerOrderingModel(patterns, exchange, order); 38 | } 39 | 40 | internal static IServiceCollection AddInstanceSingleton(this IServiceCollection services, IEnumerable routePatterns, string exchange, int order) 41 | where TInterface : class 42 | where TImplementation : class, TInterface 43 | { 44 | var patterns = routePatterns.ToList(); 45 | services.AddSingleton(); 46 | var router = new MessageHandlerRouter 47 | { 48 | Type = typeof(TImplementation), 49 | Exchange = exchange, 50 | RoutePatterns = patterns 51 | }; 52 | services.Add(new ServiceDescriptor(typeof(MessageHandlerRouter), router)); 53 | return services.AddMessageHandlerOrderingModel(patterns, exchange, order); 54 | } 55 | 56 | private static IServiceCollection AddMessageHandlerOrderingModel(this IServiceCollection services, IEnumerable routePatterns, string exchange, int order) 57 | where TImplementation : class 58 | { 59 | var patterns = routePatterns.ToList(); 60 | MessageHandlerOrderingModelExists(services, patterns, exchange, order); 61 | var messageHandlerOrderingModel = new MessageHandlerOrderingModel 62 | { 63 | Exchange = exchange, 64 | RoutePatterns = patterns, 65 | Order = order, 66 | MessageHandlerType = typeof(TImplementation) 67 | }; 68 | services.AddSingleton(messageHandlerOrderingModel); 69 | return services; 70 | } 71 | 72 | private static void MessageHandlerOrderingModelExists(IServiceCollection services, IEnumerable routePatterns, string exchange, int order) 73 | { 74 | var patterns = routePatterns.ToList(); 75 | var messageHandlerOrderingModel = services.FirstOrDefault(x => x.ServiceType == typeof(MessageHandlerOrderingModel) 76 | && x.Lifetime == ServiceLifetime.Singleton 77 | && ((MessageHandlerOrderingModel)x.ImplementationInstance).MessageHandlerType == typeof(TImplementation) 78 | && (string.Equals(((MessageHandlerOrderingModel)x.ImplementationInstance).Exchange, exchange, StringComparison.OrdinalIgnoreCase) 79 | || (exchange is null && ((MessageHandlerOrderingModel)x.ImplementationInstance).Exchange is null)) 80 | && ((MessageHandlerOrderingModel)x.ImplementationInstance).Order != order 81 | && patterns.Intersect(((MessageHandlerOrderingModel)x.ImplementationInstance).RoutePatterns).Any()); 82 | if (messageHandlerOrderingModel is null) 83 | { 84 | return; 85 | } 86 | 87 | var intersectRoutePatterns = patterns.Intersect(((MessageHandlerOrderingModel)messageHandlerOrderingModel.ImplementationInstance).RoutePatterns); 88 | throw new ArgumentException($"A message handler {nameof(TImplementation)} for an exchange {exchange} has already been configured for route patterns[{string.Join(", ", intersectRoutePatterns)}] with an order {order}."); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/InternalExtensions/BaseMessageHandlerDictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions 6 | { 7 | /// 8 | /// An extension class which contains methods for easy operating with dictionaries. 9 | /// 10 | internal static class BaseMessageHandlerDictionaryExtensions 11 | { 12 | /// 13 | /// Merge (union) keys and values of two dictionaries of specified type. 14 | /// 15 | /// Source dictionary. 16 | /// Dictionary which are being merged with the source dictionary. 17 | /// 18 | internal static IDictionary> UnionKeysAndValues( 19 | this IDictionary> source, 20 | IDictionary> addition) 21 | { 22 | foreach (var (key, value) in addition) 23 | { 24 | if (source.ContainsKey(key)) 25 | { 26 | source[key] = source[key].Union(value).ToList(); 27 | } 28 | else 29 | { 30 | source.Add(key, value); 31 | } 32 | } 33 | return source; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/InternalExtensions/RabbitMqServiceOptionsDependencyInjectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions 6 | { 7 | /// 8 | /// Base DI extensions for RabbitMQ configuration options. 9 | /// 10 | internal static class RabbitMqServiceOptionsDependencyInjectionExtensions 11 | { 12 | internal static RabbitMqServiceOptions GetRabbitMqServiceOptionsInstance(IConfiguration configuration) 13 | { 14 | var options = new RabbitMqServiceOptions(); 15 | configuration.Bind(options); 16 | return options; 17 | } 18 | 19 | internal static IServiceCollection ConfigureRabbitMqProducingOnlyServiceOptions(this IServiceCollection services, RabbitMqServiceOptions options) => 20 | services.Configure(x => x.ProducerOptions = options); 21 | 22 | internal static IServiceCollection ConfigureRabbitMqConnectionOptions(this IServiceCollection services, RabbitMqServiceOptions options) => 23 | services.Configure(x => { x.ProducerOptions = options; x.ConsumerOptions = options; }); 24 | } 25 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/InternalExtensions/WildcardExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Runtime.CompilerServices; 4 | using RabbitMQ.Client.Core.DependencyInjection.Models; 5 | 6 | [assembly:InternalsVisibleTo("RabbitMQ.Client.Core.DependencyInjection.Tests")] 7 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions 8 | { 9 | /// 10 | /// An extension class that contains functionality of pattern (wildcard) matching. 11 | /// 12 | /// 13 | /// Methods of that class allows finding route patterns by which message handlers are "listening" for messages. 14 | /// Public access modifier set for this class due to unit testing. 15 | /// 16 | internal static class WildcardExtensions 17 | { 18 | private const string Separator = "."; 19 | private const string SingleWordPattern = "*"; 20 | private const string MultipleWordsPattern = "#"; 21 | 22 | /// 23 | /// Construct tree (trie) structure of message handler route patterns. 24 | /// 25 | /// 26 | /// Collection of message handler route patterns, which are used by them for a message "listening". 27 | /// 28 | /// 29 | /// Collection of tree nodes . 30 | /// Depending on routing key bindings that collection can be flat or treelike. 31 | /// 32 | public static IEnumerable ConstructRoutesTree(IEnumerable routePatterns) 33 | { 34 | var tree = new List(); 35 | 36 | foreach (var binding in routePatterns) 37 | { 38 | var keyParts = binding.Split(Separator); 39 | TreeNode parentTreeNode = null; 40 | var currentTreeNode = tree; 41 | for (var index = 0; index < keyParts.Length; index++) 42 | { 43 | var part = keyParts[index]; 44 | 45 | var existingNode = index == keyParts.Length - 1 46 | ? currentTreeNode.FirstOrDefault(x => x.KeyPartition == part && x.IsLastNode) 47 | : currentTreeNode.FirstOrDefault(x => x.KeyPartition == part && !x.IsLastNode); 48 | 49 | if (existingNode is null) 50 | { 51 | var node = new TreeNode 52 | { 53 | Parent = parentTreeNode, 54 | KeyPartition = part, 55 | IsLastNode = index == keyParts.Length - 1 56 | }; 57 | currentTreeNode.Add(node); 58 | currentTreeNode = node.Nodes; 59 | parentTreeNode = node; 60 | } 61 | else 62 | { 63 | currentTreeNode = existingNode.Nodes; 64 | parentTreeNode = existingNode; 65 | } 66 | } 67 | } 68 | 69 | return tree; 70 | } 71 | 72 | /// 73 | /// Get route patterns that match the given routing key. 74 | /// 75 | /// Collection (tree, trie) of nodes. 76 | /// Array of routing key parts split by dots. 77 | /// Collection of route patterns that correspond to the given routing key. 78 | public static IEnumerable GetMatchingRoutePatterns(IEnumerable tree, string[] routingKeyParts) 79 | { 80 | return GetMatchingRoutePatterns(tree, routingKeyParts, depth: 0); 81 | } 82 | 83 | private static IEnumerable GetMatchingRoutePatterns(IEnumerable tree, IReadOnlyList routingKeyParts, int depth) 84 | { 85 | foreach (var node in tree) 86 | { 87 | var matchingPart = routingKeyParts[depth]; 88 | if (node.KeyPartition == MultipleWordsPattern) 89 | { 90 | if (!node.Nodes.Any()) 91 | { 92 | yield return CollectRoutingKeyInReverseOrder(node); 93 | } 94 | else 95 | { 96 | var tails = CollectRoutingKeyTails(routingKeyParts, depth); 97 | foreach (var tail in tails) 98 | { 99 | var routes = GetMatchingRoutePatterns(node.Nodes, tail, depth: 0); 100 | foreach (var route in routes) 101 | { 102 | yield return route; 103 | } 104 | } 105 | } 106 | } 107 | else if(node.KeyPartition == SingleWordPattern || node.KeyPartition == matchingPart) 108 | { 109 | if (routingKeyParts.Count == depth + 1 && !node.Nodes.Any()) 110 | { 111 | yield return CollectRoutingKeyInReverseOrder(node); 112 | } 113 | else if (routingKeyParts.Count != depth + 1 && node.Nodes.Any()) 114 | { 115 | var routes = GetMatchingRoutePatterns(node.Nodes, routingKeyParts, depth + 1).ToList(); 116 | foreach (var route in routes) 117 | { 118 | yield return route; 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | private static IEnumerable CollectRoutingKeyTails(IReadOnlyCollection routingKeyParts, int depthStart) 126 | { 127 | for (var index = depthStart; index < routingKeyParts.Count; index++) 128 | { 129 | yield return routingKeyParts.Skip(index).ToArray(); 130 | } 131 | } 132 | 133 | private static string CollectRoutingKeyInReverseOrder(TreeNode node, string routingKey = "") 134 | { 135 | routingKey = string.IsNullOrEmpty(routingKey) ? node.KeyPartition : $"{node.KeyPartition}.{routingKey}"; 136 | return node.Parent != null ? CollectRoutingKeyInReverseOrder(node.Parent, routingKey) : routingKey; 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/MessageHandlers/IAsyncMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using RabbitMQ.Client.Events; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.MessageHandlers 5 | { 6 | /// 7 | /// Interface of asynchronous message handler. 8 | /// 9 | public interface IAsyncMessageHandler : IBaseMessageHandler 10 | { 11 | /// 12 | /// Handle message from a queue. 13 | /// 14 | /// Message event args. 15 | /// Matching routing key. 16 | Task Handle(BasicDeliverEventArgs eventArgs, string matchingRoute); 17 | } 18 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/MessageHandlers/IBaseMessageHandler.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.Client.Core.DependencyInjection.MessageHandlers 2 | { 3 | /// 4 | /// A base interface that unites all message handlers. 5 | /// 6 | public interface IBaseMessageHandler 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/MessageHandlers/IMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Events; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.MessageHandlers 4 | { 5 | /// 6 | /// Interface of message handler. 7 | /// 8 | public interface IMessageHandler : IBaseMessageHandler 9 | { 10 | /// 11 | /// Handle message from a queue. 12 | /// 13 | /// Message event args. 14 | /// Matching routing key. 15 | void Handle(BasicDeliverEventArgs eventArgs, string matchingRoute); 16 | } 17 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/BatchConsumerConnectionOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 5 | { 6 | /// 7 | /// A configuration model that contains client connection options for each batch message handler. 8 | /// 9 | public class BatchConsumerConnectionOptions 10 | { 11 | /// 12 | /// Type of batch message handler. 13 | /// 14 | public Type Type { get; set; } 15 | 16 | /// 17 | /// Consumer client connection options. 18 | /// 19 | public RabbitMqServiceOptions ServiceOptions { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/ExchangeServiceDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 5 | { 6 | /// 7 | /// A service extension for registration exchange singleton "services". 8 | /// 9 | internal class ExchangeServiceDescriptor : ServiceDescriptor 10 | { 11 | /// 12 | /// Name of the exchange that has been registered. 13 | /// 14 | public string ExchangeName { get; set; } 15 | 16 | public ExchangeServiceDescriptor(Type serviceType, object instance) 17 | : base(serviceType, instance) 18 | { 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/MessageHandlerContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 5 | { 6 | /// 7 | /// The service model that represents a container that contains a route patterns tree connected with an exchange and all types of message handlers. 8 | /// If route patterns configured without an exchange they get in the "general" container. 9 | /// 10 | public class MessageHandlerContainer 11 | { 12 | /// 13 | /// An exchange. 14 | /// 15 | /// 16 | /// Could be null. 17 | /// 18 | public string Exchange { get; set; } 19 | 20 | /// 21 | /// Route patterns tree (trie) structure. 22 | /// 23 | public IEnumerable Tree { get; set; } 24 | 25 | /// 26 | /// Flag is the container general. 27 | /// 28 | // TODO: validate that everything is okay with general exchanges. 29 | public bool IsGeneral => string.IsNullOrEmpty(Exchange); 30 | 31 | /// 32 | /// Dictionary of route patterns and message handlers connected by them. 33 | /// 34 | public IDictionary> MessageHandlers { get; set; } 35 | 36 | /// 37 | /// The collection of models that contain information about an order in which message handlers will be called. 38 | /// 39 | public IEnumerable MessageHandlerOrderingModels { get; set; } 40 | } 41 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/MessageHandlerOrderingContainer.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 4 | { 5 | /// 6 | /// An internal model that contains information about message handler, its order and route by which it was matched with current message's routing key. 7 | /// 8 | internal class MessageHandlerOrderingContainer 9 | { 10 | /// 11 | /// The instance of message handler. 12 | /// 13 | public IBaseMessageHandler MessageHandler { get; set; } 14 | 15 | /// 16 | /// Order. 17 | /// 18 | public int? Order { get; set; } 19 | 20 | /// 21 | /// Route that matches a routing key of received message. 22 | /// 23 | public string MatchingRoute { get; set; } 24 | } 25 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/MessageHandlerOrderingModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 5 | { 6 | /// 7 | /// A model that contains information about message handler (by type) and its connection with exchange and routing keys as well as an order value. 8 | /// That model being registered in DI for each message handler. 9 | /// 10 | public class MessageHandlerOrderingModel 11 | { 12 | /// 13 | /// A type of the registered message handler. 14 | /// 15 | public Type MessageHandlerType { get; set; } 16 | 17 | /// 18 | /// An exchange which message handler bound to. 19 | /// 20 | public string Exchange { get; set; } 21 | 22 | /// 23 | /// A collection of route patterns which message handler "listens". 24 | /// 25 | public IEnumerable RoutePatterns { get; set; } 26 | 27 | /// 28 | /// The value of order. 29 | /// 30 | public int Order { get; set; } 31 | } 32 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/MessageHandlerRouter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 5 | { 6 | /// 7 | /// Message handler router model. 8 | /// 9 | public class MessageHandlerRouter 10 | { 11 | /// 12 | /// Message Handler Type 13 | /// 14 | public Type Type { get; set; } 15 | /// 16 | /// Collection of route patterns (routing keys) that handler will be "listening". 17 | /// 18 | public List RoutePatterns { get; set; } = new List(); 19 | 20 | /// 21 | /// An exchange which is being listened by the message handler by specified route patterns. 22 | /// 23 | /// 24 | /// Exchange can be null, and in that scenario message handler will be general 25 | /// (it will listen all messages regardless of an exchange). 26 | /// 27 | public string Exchange { get; set; } 28 | 29 | /// 30 | /// Flag is the message handler general. 31 | /// 32 | public bool IsGeneral => string.IsNullOrEmpty(Exchange); 33 | } 34 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/RabbitMqExchange.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 4 | { 5 | /// 6 | /// Exchange model. 7 | /// 8 | public class RabbitMqExchange 9 | { 10 | /// 11 | /// The unique name of the exchange. 12 | /// 13 | public string Name { get; set; } 14 | 15 | /// 16 | /// Flag determining whether the exchange made for message consumption. 17 | /// If false then an exchange made only for publishing. 18 | /// 19 | public bool IsConsuming { get; set; } 20 | 21 | /// 22 | /// Exchange options. 23 | /// 24 | public RabbitMqExchangeOptions Options { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/TreeNode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 4 | { 5 | /// 6 | /// A model that represents nodes supposed for building a tree (trie) structure for a pattern (wildcard) matching. 7 | /// 8 | public class TreeNode 9 | { 10 | /// 11 | /// Part of the routing key. 12 | /// 13 | /// 14 | /// Routing key split into parts by the dot. 15 | /// 16 | public string KeyPartition { get; set; } 17 | 18 | /// 19 | /// Parent node. 20 | /// 21 | public TreeNode Parent { get; set; } 22 | 23 | /// 24 | /// Child nodes. 25 | /// 26 | public List Nodes { get; } = new List(); 27 | 28 | /// 29 | /// Flag is the node "last" - a single word that comes without any other parts. 30 | /// 31 | public bool IsLastNode { get; set; } 32 | } 33 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/RabbitMQ.Client.Core.DependencyInjection.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.1 4 | latest 5 | 5.0.0 6 | RabbitMQ 7 | https://github.com/AntonyVorontsov/RabbitMQ.Client.Core.DependencyInjection 8 | GIT 9 | https://github.com/AntonyVorontsov/RabbitMQ.Client.Core.DependencyInjection/blob/master/readme.md 10 | 11 | A RabbitMQ.Client wrapper that provides easy, managed, injectable (via standard dependency injection) message queue consume and publish operations. 12 | true 13 | icon.png 14 | LICENSE.txt 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/RabbitMqExchangeDependencyInjectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 4 | using System; 5 | using System.Linq; 6 | using RabbitMQ.Client.Core.DependencyInjection.Models; 7 | 8 | namespace RabbitMQ.Client.Core.DependencyInjection 9 | { 10 | /// 11 | /// DI extensions for RabbitMQ exchange. 12 | /// 13 | public static class RabbitMqExchangeDependencyInjectionExtensions 14 | { 15 | /// 16 | /// Add a consumption exchange as singleton. 17 | /// Consumption exchange can be used for producing messages as well as for consuming. 18 | /// 19 | /// Service collection. 20 | /// Exchange name. 21 | /// Exchange configuration section. 22 | /// Service collection. 23 | public static IServiceCollection AddConsumptionExchange(this IServiceCollection services, string exchangeName, IConfiguration configuration) => 24 | services.AddExchange(exchangeName, isConsuming: true, configuration); 25 | 26 | /// 27 | /// Add a production exchange as singleton. 28 | /// Production exchange made only for producing messages into queues and cannot consume at all. 29 | /// 30 | /// Service collection. 31 | /// Exchange name. 32 | /// Exchange configuration section. 33 | /// Service collection. 34 | public static IServiceCollection AddProductionExchange(this IServiceCollection services, string exchangeName, IConfiguration configuration) => 35 | services.AddExchange(exchangeName, isConsuming: false, configuration); 36 | 37 | /// 38 | /// Add a consumption exchange as singleton. 39 | /// Consumption exchange can be used for producing messages as well as for consuming. 40 | /// 41 | /// Service collection. 42 | /// Exchange name. 43 | /// Exchange configuration . 44 | /// Service collection. 45 | public static IServiceCollection AddConsumptionExchange(this IServiceCollection services, string exchangeName, RabbitMqExchangeOptions options) => 46 | services.AddExchange(exchangeName, isConsuming: true, options); 47 | 48 | /// 49 | /// Add a production exchange as singleton. 50 | /// Production exchange made only for producing messages into queues and cannot consume at all. 51 | /// 52 | /// Service collection. 53 | /// Exchange name. 54 | /// Exchange configuration . 55 | /// Service collection. 56 | public static IServiceCollection AddProductionExchange(this IServiceCollection services, string exchangeName, RabbitMqExchangeOptions options) => 57 | services.AddExchange(exchangeName, isConsuming: false, options); 58 | 59 | /// 60 | /// Add an exchange as a singleton. 61 | /// 62 | /// Service collection. 63 | /// Exchange name. 64 | /// Exchange configuration section. 65 | /// Flag whether an exchange made for consumption. 66 | /// Service collection. 67 | public static IServiceCollection AddExchange(this IServiceCollection services, string exchangeName, bool isConsuming, IConfiguration configuration) 68 | { 69 | CheckExchangeExists(services, exchangeName); 70 | 71 | var options = new RabbitMqExchangeOptions(); 72 | configuration.Bind(options); 73 | return services.AddExchange(exchangeName, isConsuming, options); 74 | } 75 | 76 | /// 77 | /// Add an exchange as a singleton. 78 | /// 79 | /// Service collection. 80 | /// Exchange name. 81 | /// Exchange configuration . 82 | /// Flag whether an exchange made for consumption. 83 | /// Service collection. 84 | public static IServiceCollection AddExchange(this IServiceCollection services, string exchangeName, bool isConsuming, RabbitMqExchangeOptions options) 85 | { 86 | CheckExchangeExists(services, exchangeName); 87 | 88 | var exchangeOptions = options ?? new RabbitMqExchangeOptions(); 89 | var exchange = new RabbitMqExchange 90 | { 91 | Name = exchangeName, 92 | IsConsuming = isConsuming, 93 | Options = exchangeOptions 94 | }; 95 | var service = new ExchangeServiceDescriptor(typeof(RabbitMqExchange), exchange) 96 | { 97 | ExchangeName = exchangeName 98 | }; 99 | services.Add(service); 100 | return services; 101 | } 102 | 103 | private static void CheckExchangeExists(IServiceCollection services, string exchangeName) 104 | { 105 | var exchangeExists = services.Any(x => x.ServiceType == typeof(RabbitMqExchange) 106 | && x.Lifetime == ServiceLifetime.Singleton 107 | && string.Equals(((ExchangeServiceDescriptor)x).ExchangeName, exchangeName, StringComparison.OrdinalIgnoreCase)); 108 | if (exchangeExists) 109 | { 110 | throw new ArgumentException($"Exchange {exchangeName} has been added already!"); 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/RabbitMqServiceDependencyInjectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 5 | using RabbitMQ.Client.Core.DependencyInjection.InternalExtensions; 6 | using RabbitMQ.Client.Core.DependencyInjection.Services; 7 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection 10 | { 11 | /// 12 | /// DI extensions for RabbitMQ services (RabbitMQ connection). 13 | /// 14 | public static class RabbitMqServiceDependencyInjectionExtensions 15 | { 16 | /// 17 | /// Add fully-functional RabbitMQ services and required infrastructure. 18 | /// RabbitMQ services consists of two components: consumer and producer . 19 | /// 20 | /// Service collection. 21 | /// RabbitMq configuration section. 22 | /// Service collection. 23 | public static IServiceCollection AddRabbitMqServices(this IServiceCollection services, IConfiguration configuration) 24 | { 25 | services.AddRabbitMqInfrastructure(); 26 | services.ConfigureRabbitMqConnectionOptions(RabbitMqServiceOptionsDependencyInjectionExtensions.GetRabbitMqServiceOptionsInstance(configuration)); 27 | services.AddRabbitMqServices(); 28 | services.AddConsumptionStarter(); 29 | return services; 30 | } 31 | 32 | /// 33 | /// Add fully-functional RabbitMQ services and required infrastructure. 34 | /// RabbitMQ services consists of two components: consumer and producer . 35 | /// 36 | /// Service collection. 37 | /// RabbitMq configuration . 38 | /// Service collection. 39 | public static IServiceCollection AddRabbitMqServices(this IServiceCollection services, RabbitMqServiceOptions configuration) 40 | { 41 | services.AddRabbitMqInfrastructure(); 42 | services.ConfigureRabbitMqConnectionOptions(configuration); 43 | services.AddRabbitMqServices(); 44 | services.AddConsumptionStarter(); 45 | return services; 46 | } 47 | 48 | /// 49 | /// Add a singleton producing RabbitMQ service and required infrastructure. 50 | /// 51 | /// Service collection. 52 | /// RabbitMq configuration section. 53 | /// Service collection. 54 | public static IServiceCollection AddRabbitMqProducer(this IServiceCollection services, IConfiguration configuration) 55 | { 56 | services.AddRabbitMqInfrastructure(); 57 | services.ConfigureRabbitMqProducingOnlyServiceOptions(RabbitMqServiceOptionsDependencyInjectionExtensions.GetRabbitMqServiceOptionsInstance(configuration)); 58 | services.AddRabbitMqServices(); 59 | return services; 60 | } 61 | 62 | /// 63 | /// Add a singleton producing RabbitMQ service and required infrastructure. 64 | /// 65 | /// Service collection. 66 | /// RabbitMq configuration . 67 | /// Service collection. 68 | public static IServiceCollection AddRabbitMqProducer(this IServiceCollection services, RabbitMqServiceOptions configuration) 69 | { 70 | services.AddRabbitMqInfrastructure(); 71 | services.ConfigureRabbitMqProducingOnlyServiceOptions(configuration); 72 | services.AddRabbitMqServices(); 73 | return services; 74 | } 75 | 76 | private static IServiceCollection AddRabbitMqServices(this IServiceCollection services) 77 | { 78 | services.TryAddSingleton(); 79 | services.TryAddSingleton(); 80 | return services; 81 | } 82 | 83 | private static IServiceCollection AddConsumptionStarter(this IServiceCollection services) 84 | { 85 | services.AddHostedService(); 86 | return services; 87 | } 88 | 89 | private static IServiceCollection AddRabbitMqInfrastructure(this IServiceCollection services) 90 | { 91 | services.AddOptions(); 92 | services.AddLogging(); 93 | services.TryAddSingleton(); 94 | services.TryAddSingleton(); 95 | services.TryAddSingleton(); 96 | services.TryAddSingleton(); 97 | services.TryAddSingleton(); 98 | services.AddHostedService(); 99 | return services; 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/ChannelDeclarationHostedService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Hosting; 4 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 5 | 6 | namespace RabbitMQ.Client.Core.DependencyInjection.Services 7 | { 8 | /// 9 | /// Hosted service that is responsible for creating connections and channels for both producing and consuming services. 10 | /// It does its thing by using IChannelDeclarationService . 11 | /// 12 | public class ChannelDeclarationHostedService : IHostedService 13 | { 14 | private readonly IChannelDeclarationService _channelDeclarationService; 15 | 16 | public ChannelDeclarationHostedService(IChannelDeclarationService channelDeclarationService) 17 | { 18 | _channelDeclarationService = channelDeclarationService; 19 | } 20 | 21 | public Task StartAsync(CancellationToken cancellationToken) 22 | { 23 | _channelDeclarationService.SetConnectionInfrastructureForRabbitMqServices(); 24 | return Task.CompletedTask; 25 | } 26 | 27 | public Task StopAsync(CancellationToken cancellationToken) 28 | { 29 | return Task.CompletedTask; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/ConsumingHostedService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Hosting; 4 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 5 | 6 | namespace RabbitMQ.Client.Core.DependencyInjection.Services 7 | { 8 | /// 9 | /// Hosted service that is responsible for starting the consumer. 10 | /// 11 | public class ConsumingHostedService : IHostedService 12 | { 13 | private readonly IConsumingService _consumingService; 14 | 15 | public ConsumingHostedService(IConsumingService consumingService) 16 | { 17 | _consumingService = consumingService; 18 | } 19 | 20 | public Task StartAsync(CancellationToken cancellationToken) 21 | { 22 | _consumingService.StartConsuming(); 23 | return Task.CompletedTask; 24 | } 25 | 26 | public Task StopAsync(CancellationToken cancellationToken) 27 | { 28 | return Task.CompletedTask; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/ConsumingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading.Tasks; 6 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 7 | using RabbitMQ.Client.Core.DependencyInjection.Models; 8 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 9 | using RabbitMQ.Client.Events; 10 | 11 | namespace RabbitMQ.Client.Core.DependencyInjection.Services 12 | { 13 | /// 14 | public class ConsumingService : IConsumingService, IDisposable 15 | { 16 | /// 17 | public IConnection Connection { get; private set; } 18 | 19 | /// 20 | public IModel Channel { get; private set; } 21 | 22 | /// 23 | public AsyncEventingBasicConsumer Consumer { get; private set; } 24 | 25 | private bool _consumingStarted; 26 | 27 | private readonly IMessageHandlingPipelineExecutingService _messageHandlingPipelineExecutingService; 28 | private readonly IEnumerable _exchanges; 29 | 30 | private IEnumerable _consumerTags = new List(); 31 | 32 | public ConsumingService( 33 | IMessageHandlingPipelineExecutingService messageHandlingPipelineExecutingService, 34 | IEnumerable exchanges) 35 | { 36 | _messageHandlingPipelineExecutingService = messageHandlingPipelineExecutingService; 37 | _exchanges = exchanges; 38 | } 39 | 40 | /// 41 | public void Dispose() 42 | { 43 | if (Channel?.IsOpen == true) 44 | { 45 | Channel.Close((int)HttpStatusCode.OK, "Channel closed"); 46 | } 47 | 48 | if (Connection?.IsOpen == true) 49 | { 50 | Connection.Close(); 51 | } 52 | 53 | Channel?.Dispose(); 54 | Connection?.Dispose(); 55 | } 56 | 57 | /// 58 | public void UseConnection(IConnection connection) 59 | { 60 | Connection = connection; 61 | } 62 | 63 | /// 64 | public void UseChannel(IModel channel) 65 | { 66 | Channel = channel; 67 | } 68 | 69 | /// 70 | public void UseConsumer(AsyncEventingBasicConsumer consumer) 71 | { 72 | Consumer = consumer; 73 | } 74 | 75 | /// 76 | public void StartConsuming() 77 | { 78 | if (Channel is null) 79 | { 80 | throw new ConsumingChannelIsNullException($"Consuming channel is null. Configure {nameof(IConsumingService)} or full functional {nameof(IConsumingService)} for consuming messages."); 81 | } 82 | 83 | if (_consumingStarted) 84 | { 85 | return; 86 | } 87 | 88 | Consumer.Received += ConsumerOnReceived; 89 | _consumingStarted = true; 90 | 91 | var consumptionExchanges = _exchanges.Where(x => x.IsConsuming); 92 | _consumerTags = consumptionExchanges.SelectMany( 93 | exchange => exchange.Options.Queues.Select( 94 | queue => Channel.BasicConsume(queue: queue.Name, autoAck: false, consumer: Consumer))) 95 | .Distinct() 96 | .ToList(); 97 | } 98 | 99 | /// 100 | public void StopConsuming() 101 | { 102 | if (Channel is null) 103 | { 104 | throw new ConsumingChannelIsNullException($"Consuming channel is null. Configure {nameof(IConsumingService)} or full functional {nameof(IConsumingService)} for consuming messages."); 105 | } 106 | 107 | if (!_consumingStarted) 108 | { 109 | return; 110 | } 111 | 112 | Consumer.Received -= ConsumerOnReceived; 113 | _consumingStarted = false; 114 | foreach (var tag in _consumerTags) 115 | { 116 | Channel.BasicCancel(tag); 117 | } 118 | } 119 | 120 | private Task ConsumerOnReceived(object sender, BasicDeliverEventArgs eventArgs) => _messageHandlingPipelineExecutingService.Execute(eventArgs, this); 121 | } 122 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IChannelDeclarationService.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 2 | { 3 | /// 4 | /// Service that is responsible for declaring queues and exchanges. 5 | /// 6 | public interface IChannelDeclarationService 7 | { 8 | /// 9 | /// Create connection and declare everything for both consuming and producing services. 10 | /// 11 | void SetConnectionInfrastructureForRabbitMqServices(); 12 | } 13 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IConsumingService.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Events; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 4 | { 5 | /// 6 | /// Custom RabbitMQ consuming service interface. 7 | /// 8 | public interface IConsumingService : IRabbitMqService 9 | { 10 | /// 11 | /// RabbitMQ consuming connection. 12 | /// 13 | IConnection Connection { get; } 14 | 15 | /// 16 | /// RabbitMQ consuming channel. 17 | /// 18 | IModel Channel { get; } 19 | 20 | /// 21 | /// Asynchronous consumer. 22 | /// 23 | AsyncEventingBasicConsumer Consumer { get; } 24 | 25 | /// 26 | /// Start consuming (getting messages). 27 | /// 28 | void StartConsuming(); 29 | 30 | /// 31 | /// Stop consuming (getting messages). 32 | /// 33 | void StopConsuming(); 34 | 35 | /// 36 | /// Specify a consumer instance that will be used by the service. 37 | /// 38 | /// 39 | void UseConsumer(AsyncEventingBasicConsumer consumer); 40 | } 41 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IMessageHandlerContainerBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using RabbitMQ.Client.Core.DependencyInjection.Models; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 5 | { 6 | /// 7 | /// Interface of the service that build message handler containers collection. 8 | /// Those containers contain information about message handlers (all types) bound to the exchange. 9 | /// Container could be "general" if message handler has not been bound to the exchange (so it will "listen" regardless of the exchange). 10 | /// 11 | public interface IMessageHandlerContainerBuilder 12 | { 13 | /// 14 | /// Build message handler containers collection. 15 | /// 16 | /// Collection of message handler containers . 17 | IEnumerable BuildCollection(); 18 | } 19 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IMessageHandlingPipelineExecutingService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using RabbitMQ.Client.Events; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 5 | { 6 | /// 7 | /// Service that is responsible for handling received message in the pipeline of filters. 8 | /// 9 | public interface IMessageHandlingPipelineExecutingService 10 | { 11 | /// 12 | /// Execute message handling pipeline. 13 | /// 14 | /// Received message. 15 | /// Consuming service. 16 | Task Execute(BasicDeliverEventArgs eventArgs, IConsumingService consumingService); 17 | } 18 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IMessageHandlingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using RabbitMQ.Client.Events; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 6 | { 7 | /// 8 | /// Service that contains logic of handling message receiving (consumption) events and passing those messages to the message handlers. 9 | /// 10 | public interface IMessageHandlingService 11 | { 12 | /// 13 | /// Handle message receiving event. 14 | /// 15 | /// Arguments of message receiving event. 16 | /// An instance of consuming service . 17 | Task HandleMessageReceivingEvent(BasicDeliverEventArgs eventArgs, IConsumingService consumingService); 18 | 19 | /// 20 | /// Handle message processing failure. 21 | /// 22 | /// An occured exception. 23 | /// Arguments of message receiving event. 24 | /// An instance of consuming service . 25 | Task HandleMessageProcessingFailure(Exception exception, BasicDeliverEventArgs eventArgs, IConsumingService consumingService); 26 | } 27 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IProducingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 5 | { 6 | /// 7 | /// Custom RabbitMQ producing service interface. 8 | /// 9 | public interface IProducingService : IRabbitMqService 10 | { 11 | /// 12 | /// RabbitMQ producing connection. 13 | /// 14 | IConnection Connection { get; } 15 | 16 | /// 17 | /// RabbitMQ producing channel. 18 | /// 19 | IModel Channel { get; } 20 | 21 | /// 22 | /// Send a message. 23 | /// 24 | /// Model class. 25 | /// Object message. 26 | /// Exchange name. 27 | /// Routing key. 28 | void Send(T @object, string exchangeName, string routingKey) where T : class; 29 | 30 | /// 31 | /// Send a delayed message. 32 | /// 33 | /// Model class. 34 | /// Object message. 35 | /// Exchange name. 36 | /// Routing key. 37 | /// Delay time in milliseconds. 38 | void Send(T @object, string exchangeName, string routingKey, int millisecondsDelay) where T : class; 39 | 40 | /// 41 | /// Send a message. 42 | /// 43 | /// Json message. 44 | /// Exchange name. 45 | /// Routing key. 46 | void SendJson(string json, string exchangeName, string routingKey); 47 | 48 | /// 49 | /// Send a delayed message. 50 | /// 51 | /// 52 | /// 53 | /// 54 | /// Delay time in milliseconds. 55 | void SendJson(string json, string exchangeName, string routingKey, int millisecondsDelay); 56 | 57 | /// 58 | /// Send a message. 59 | /// 60 | /// Message. 61 | /// Exchange name. 62 | /// Routing key. 63 | void SendString(string message, string exchangeName, string routingKey); 64 | 65 | /// 66 | /// Send a delayed message. 67 | /// 68 | /// Message. 69 | /// Exchange name. 70 | /// Routing key. 71 | /// Delay time in milliseconds. 72 | void SendString(string message, string exchangeName, string routingKey, int millisecondsDelay); 73 | 74 | /// 75 | /// Send a message. 76 | /// 77 | /// Byte array message. 78 | /// Message properties. 79 | /// Exchange name. 80 | /// Routing key. 81 | void Send(ReadOnlyMemory bytes, IBasicProperties properties, string exchangeName, string routingKey); 82 | 83 | /// 84 | /// Send a delayed message. 85 | /// 86 | /// Byte array message. 87 | /// Message properties. 88 | /// Exchange name. 89 | /// Routing key. 90 | /// Delay time in milliseconds. 91 | void Send(ReadOnlyMemory bytes, IBasicProperties properties, string exchangeName, string routingKey, int millisecondsDelay); 92 | 93 | /// 94 | /// Send a message asynchronously. 95 | /// 96 | /// Model class. 97 | /// Object message. 98 | /// Exchange name. 99 | /// Routing key. 100 | Task SendAsync(T @object, string exchangeName, string routingKey) where T : class; 101 | 102 | /// 103 | /// Send a delayed message asynchronously. 104 | /// 105 | /// Model class. 106 | /// Object message. 107 | /// Exchange name. 108 | /// Routing key. 109 | /// Delay time in milliseconds. 110 | Task SendAsync(T @object, string exchangeName, string routingKey, int millisecondsDelay) where T : class; 111 | 112 | /// 113 | /// Send a message asynchronously. 114 | /// 115 | /// Json message. 116 | /// Exchange name. 117 | /// Routing key. 118 | Task SendJsonAsync(string json, string exchangeName, string routingKey); 119 | 120 | /// 121 | /// Send a delayed message asynchronously. 122 | /// 123 | /// Json message. 124 | /// Exchange name. 125 | /// Routing key. 126 | /// Delay time in milliseconds. 127 | Task SendJsonAsync(string json, string exchangeName, string routingKey, int millisecondsDelay); 128 | 129 | /// 130 | /// Send a message asynchronously. 131 | /// 132 | /// Message. 133 | /// Exchange name. 134 | /// Routing key. 135 | Task SendStringAsync(string message, string exchangeName, string routingKey); 136 | 137 | /// 138 | /// Send a delayed message asynchronously. 139 | /// 140 | /// Message. 141 | /// Exchange name. 142 | /// Routing key. 143 | /// Delay time in milliseconds. 144 | Task SendStringAsync(string message, string exchangeName, string routingKey, int millisecondsDelay); 145 | 146 | /// 147 | /// Send a message asynchronously. 148 | /// 149 | /// Byte array message. 150 | /// Message properties. 151 | /// Exchange name. 152 | /// Routing key. 153 | Task SendAsync(ReadOnlyMemory bytes, IBasicProperties properties, string exchangeName, string routingKey); 154 | 155 | /// 156 | /// Send a delayed message asynchronously. 157 | /// 158 | /// Byte array message. 159 | /// Message properties. 160 | /// Exchange name. 161 | /// Routing key. 162 | /// Delay time in milliseconds. 163 | Task SendAsync(ReadOnlyMemory bytes, IBasicProperties properties, string exchangeName, string routingKey, int millisecondsDelay); 164 | } 165 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IRabbitMqConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 2 | using RabbitMQ.Client.Events; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 5 | { 6 | /// 7 | /// Interface of the service that is responsible for creating RabbitMQ connections depending on options . 8 | /// 9 | public interface IRabbitMqConnectionFactory 10 | { 11 | /// 12 | /// Create a RabbitMQ connection. 13 | /// 14 | /// An instance of options . 15 | /// An instance of connection . 16 | /// If options parameter is null the method return null too. 17 | IConnection CreateRabbitMqConnection(RabbitMqServiceOptions options); 18 | 19 | /// 20 | /// Create a consumer depending on the connection channel. 21 | /// 22 | /// Connection channel. 23 | /// A consumer instance . 24 | AsyncEventingBasicConsumer CreateConsumer(IModel channel); 25 | } 26 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IRabbitMqService.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 2 | { 3 | /// 4 | /// Custom RabbitMQ service interface. 5 | /// 6 | public interface IRabbitMqService 7 | { 8 | /// 9 | /// Specify the connection that will be used by the service. 10 | /// 11 | /// Connection. 12 | void UseConnection(IConnection connection); 13 | 14 | /// 15 | /// Specify the channel that will be used by the service. 16 | /// 17 | /// channel. 18 | void UseChannel(IModel channel); 19 | } 20 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/MessageHandlerContainerBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using RabbitMQ.Client.Core.DependencyInjection.InternalExtensions; 5 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 6 | using RabbitMQ.Client.Core.DependencyInjection.Models; 7 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection.Services 10 | { 11 | /// 12 | public class MessageHandlerContainerBuilder : IMessageHandlerContainerBuilder 13 | { 14 | private readonly IEnumerable _routers; 15 | private readonly IEnumerable _orderingModels; 16 | private readonly IEnumerable _messageHandlers; 17 | private readonly IEnumerable _asyncMessageHandlers; 18 | 19 | public MessageHandlerContainerBuilder( 20 | IEnumerable routers, 21 | IEnumerable orderingModels, 22 | IEnumerable messageHandlers, 23 | IEnumerable asyncMessageHandlers) 24 | { 25 | _routers = routers; 26 | _orderingModels = orderingModels; 27 | _messageHandlers = messageHandlers; 28 | _asyncMessageHandlers = asyncMessageHandlers; 29 | } 30 | 31 | /// 32 | public IEnumerable BuildCollection() 33 | { 34 | var containers = new List(); 35 | var generalRouters = _routers.Where(x => x.IsGeneral).ToList(); 36 | if (generalRouters.Any()) 37 | { 38 | var container = CreateContainer(null, generalRouters); 39 | containers.Add(container); 40 | } 41 | 42 | var exchanges = _routers.Where(x => !x.IsGeneral).Select(x => x.Exchange).Distinct().ToList(); 43 | foreach (var exchange in exchanges) 44 | { 45 | var exchangeRouters = _routers.Where(x => x.Exchange == exchange).ToList(); 46 | var container = CreateContainer(exchange, exchangeRouters); 47 | containers.Add(container); 48 | } 49 | return containers; 50 | } 51 | 52 | private MessageHandlerContainer CreateContainer(string exchange, IList selectedRouters) 53 | { 54 | var routersDictionary = TransformMessageHandlerRoutersToDictionary(selectedRouters); 55 | var boundMessageHandlers = _messageHandlers.Where(x => routersDictionary.Keys.Contains(x.GetType())).ToList(); 56 | var boundAsyncMessageHandlers = _asyncMessageHandlers.Where(x => routersDictionary.Keys.Contains(x.GetType())).ToList(); 57 | var routePatterns = selectedRouters.SelectMany(x => x.RoutePatterns).Distinct().ToList(); 58 | var messageHandlers = TransformMessageHandlersCollectionsToDictionary( 59 | boundMessageHandlers, 60 | boundAsyncMessageHandlers, 61 | routersDictionary); 62 | var orderingModels = GetMessageHandlerOrderingModels( 63 | exchange, 64 | boundMessageHandlers, 65 | boundAsyncMessageHandlers, 66 | _orderingModels); 67 | return new MessageHandlerContainer 68 | { 69 | Exchange = exchange, 70 | Tree = WildcardExtensions.ConstructRoutesTree(routePatterns), 71 | MessageHandlers = messageHandlers, 72 | MessageHandlerOrderingModels = orderingModels 73 | }; 74 | } 75 | 76 | private static IDictionary> TransformMessageHandlerRoutersToDictionary(IEnumerable routers) 77 | { 78 | var dictionary = new Dictionary>(); 79 | foreach (var router in routers) 80 | { 81 | if (dictionary.ContainsKey(router.Type)) 82 | { 83 | dictionary[router.Type] = dictionary[router.Type].Union(router.RoutePatterns).ToList(); 84 | } 85 | else 86 | { 87 | dictionary.Add(router.Type, router.RoutePatterns); 88 | } 89 | } 90 | return dictionary; 91 | } 92 | 93 | private static IEnumerable GetMessageHandlerOrderingModels( 94 | string exchange, 95 | IEnumerable messageHandlers, 96 | IEnumerable asyncMessageHandlers, 97 | IEnumerable orderingModels) 98 | { 99 | var messageHandlersCollection = new List(messageHandlers); 100 | messageHandlersCollection.AddRange(asyncMessageHandlers); 101 | 102 | var messageHandlerTypes = messageHandlersCollection.Select(x => x.GetType()).ToList(); 103 | return orderingModels.Where(x => messageHandlerTypes.Contains(x.MessageHandlerType) && x.Exchange == exchange); 104 | } 105 | 106 | private static IDictionary> TransformMessageHandlersCollectionsToDictionary( 107 | IEnumerable messageHandlers, 108 | IEnumerable asyncMessageHandlers, 109 | IDictionary> routersDictionary) 110 | { 111 | var transformedMessageHandlers = TransformMessageHandlersCollectionToDictionary(messageHandlers, routersDictionary); 112 | var transformedAsyncMessageHandlers = TransformMessageHandlersCollectionToDictionary(asyncMessageHandlers, routersDictionary); 113 | return transformedMessageHandlers.UnionKeysAndValues(transformedAsyncMessageHandlers); 114 | } 115 | 116 | private static IDictionary> TransformMessageHandlersCollectionToDictionary( 117 | IEnumerable messageHandlers, 118 | IDictionary> routersDictionary) 119 | where T : class, IBaseMessageHandler 120 | { 121 | var dictionary = new Dictionary>(); 122 | foreach (var handler in messageHandlers) 123 | { 124 | var type = handler.GetType(); 125 | foreach (var routingKey in routersDictionary[type]) 126 | { 127 | if (dictionary.ContainsKey(routingKey)) 128 | { 129 | if (!dictionary[routingKey].Any(x => x.GetType() == handler.GetType())) 130 | { 131 | dictionary[routingKey].Add(handler); 132 | } 133 | } 134 | else 135 | { 136 | dictionary.Add(routingKey, new List { handler }); 137 | } 138 | } 139 | } 140 | return dictionary; 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/MessageHandlingPipelineExecutingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Core.DependencyInjection.Filters; 6 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 7 | using RabbitMQ.Client.Events; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection.Services 10 | { 11 | /// 12 | public class MessageHandlingPipelineExecutingService : IMessageHandlingPipelineExecutingService 13 | { 14 | private readonly IMessageHandlingService _messageHandlingService; 15 | private readonly IEnumerable _handlingFilters; 16 | private readonly IEnumerable _exceptionFilters; 17 | 18 | public MessageHandlingPipelineExecutingService( 19 | IMessageHandlingService messageHandlingService, 20 | IEnumerable handlingFilters, 21 | IEnumerable exceptionFilters) 22 | { 23 | _messageHandlingService = messageHandlingService; 24 | _handlingFilters = handlingFilters ?? Enumerable.Empty(); 25 | _exceptionFilters = exceptionFilters ?? Enumerable.Empty(); 26 | } 27 | 28 | /// 29 | public async Task Execute(BasicDeliverEventArgs eventArgs, IConsumingService consumingService) 30 | { 31 | try 32 | { 33 | await ExecutePipeline(eventArgs, consumingService).ConfigureAwait(false); 34 | } 35 | catch (Exception exception) 36 | { 37 | await ExecuteFailurePipeline(exception, eventArgs, consumingService).ConfigureAwait(false); 38 | } 39 | } 40 | 41 | private async Task ExecutePipeline(BasicDeliverEventArgs eventArgs, IConsumingService consumingService) 42 | { 43 | Func handle = _messageHandlingService.HandleMessageReceivingEvent; 44 | foreach (var filter in _handlingFilters.Reverse()) 45 | { 46 | handle = filter.Execute(handle); 47 | } 48 | 49 | await handle(eventArgs, consumingService).ConfigureAwait(false); 50 | } 51 | 52 | private async Task ExecuteFailurePipeline(Exception exception, BasicDeliverEventArgs eventArgs, IConsumingService consumingService) 53 | { 54 | Func handle = _messageHandlingService.HandleMessageProcessingFailure; 55 | foreach (var filter in _exceptionFilters.Reverse()) 56 | { 57 | handle = filter.Execute(handle); 58 | } 59 | 60 | await handle(exception, eventArgs, consumingService).ConfigureAwait(false); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/RabbitMqConnectionFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 6 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 7 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 8 | using RabbitMQ.Client.Events; 9 | using RabbitMQ.Client.Exceptions; 10 | 11 | namespace RabbitMQ.Client.Core.DependencyInjection.Services 12 | { 13 | /// 14 | public class RabbitMqConnectionFactory : IRabbitMqConnectionFactory 15 | { 16 | /// 17 | public IConnection CreateRabbitMqConnection(RabbitMqServiceOptions options) 18 | { 19 | if (options is null) 20 | { 21 | return null; 22 | } 23 | 24 | var factory = new ConnectionFactory 25 | { 26 | Port = options.Port, 27 | UserName = options.UserName, 28 | Password = options.Password, 29 | VirtualHost = options.VirtualHost, 30 | AutomaticRecoveryEnabled = options.AutomaticRecoveryEnabled, 31 | TopologyRecoveryEnabled = options.TopologyRecoveryEnabled, 32 | RequestedConnectionTimeout = options.RequestedConnectionTimeout, 33 | RequestedHeartbeat = options.RequestedHeartbeat, 34 | DispatchConsumersAsync = true 35 | }; 36 | 37 | if (options.TcpEndpoints?.Any() == true) 38 | { 39 | return CreateConnectionWithTcpEndpoints(options, factory); 40 | } 41 | 42 | return string.IsNullOrEmpty(options.ClientProvidedName) 43 | ? CreateConnection(options, factory) 44 | : CreateNamedConnection(options, factory); 45 | } 46 | 47 | /// 48 | public AsyncEventingBasicConsumer CreateConsumer(IModel channel) => new AsyncEventingBasicConsumer(channel); 49 | 50 | private static IConnection CreateConnectionWithTcpEndpoints(RabbitMqServiceOptions options, ConnectionFactory factory) 51 | { 52 | var clientEndpoints = new List(); 53 | foreach (var endpoint in options.TcpEndpoints) 54 | { 55 | var sslOption = endpoint.SslOption; 56 | if (sslOption != null) 57 | { 58 | var convertedOption = new SslOption(sslOption.ServerName, sslOption.CertificatePath, sslOption.Enabled); 59 | if (!string.IsNullOrEmpty(sslOption.CertificatePassphrase)) 60 | { 61 | convertedOption.CertPassphrase = sslOption.CertificatePassphrase; 62 | } 63 | 64 | if (sslOption.AcceptablePolicyErrors != null) 65 | { 66 | convertedOption.AcceptablePolicyErrors = sslOption.AcceptablePolicyErrors.Value; 67 | } 68 | 69 | clientEndpoints.Add(new AmqpTcpEndpoint(endpoint.HostName, endpoint.Port, convertedOption)); 70 | } 71 | else 72 | { 73 | clientEndpoints.Add(new AmqpTcpEndpoint(endpoint.HostName, endpoint.Port)); 74 | } 75 | } 76 | return TryToCreateConnection(() => factory.CreateConnection(clientEndpoints), options.InitialConnectionRetries, options.InitialConnectionRetryTimeoutMilliseconds); 77 | } 78 | 79 | private static IConnection CreateNamedConnection(RabbitMqServiceOptions options, ConnectionFactory factory) 80 | { 81 | if (options.HostNames?.Any() == true) 82 | { 83 | return TryToCreateConnection(() => factory.CreateConnection(options.HostNames.ToList(), options.ClientProvidedName), options.InitialConnectionRetries, options.InitialConnectionRetryTimeoutMilliseconds); 84 | } 85 | 86 | factory.HostName = options.HostName; 87 | return TryToCreateConnection(() => factory.CreateConnection(options.ClientProvidedName), options.InitialConnectionRetries, options.InitialConnectionRetryTimeoutMilliseconds); 88 | } 89 | 90 | private static IConnection CreateConnection(RabbitMqServiceOptions options, ConnectionFactory factory) 91 | { 92 | if (options.HostNames?.Any() == true) 93 | { 94 | return TryToCreateConnection(() => factory.CreateConnection(options.HostNames.ToList()), options.InitialConnectionRetries, options.InitialConnectionRetryTimeoutMilliseconds); 95 | } 96 | 97 | factory.HostName = options.HostName; 98 | return TryToCreateConnection(factory.CreateConnection, options.InitialConnectionRetries, options.InitialConnectionRetryTimeoutMilliseconds); 99 | } 100 | 101 | private static IConnection TryToCreateConnection(Func connectionFunction, int numberOfRetries, int timeoutMilliseconds) 102 | { 103 | ValidateArguments(numberOfRetries, timeoutMilliseconds); 104 | 105 | var attempts = 0; 106 | BrokerUnreachableException latestException = null; 107 | while (attempts < numberOfRetries) 108 | { 109 | try 110 | { 111 | if (attempts > 0) 112 | { 113 | Thread.Sleep(timeoutMilliseconds); 114 | } 115 | 116 | return connectionFunction(); 117 | } 118 | catch (BrokerUnreachableException exception) 119 | { 120 | attempts++; 121 | latestException = exception; 122 | } 123 | } 124 | 125 | throw new InitialConnectionException($"Could not establish an initial connection in {numberOfRetries} retries", latestException) 126 | { 127 | NumberOfRetries = attempts 128 | }; 129 | } 130 | 131 | private static void ValidateArguments(int numberOfRetries, int timeoutMilliseconds) 132 | { 133 | if (numberOfRetries < 1) 134 | { 135 | throw new ArgumentException("Number of retries should be a positive number.", nameof(numberOfRetries)); 136 | } 137 | 138 | if (timeoutMilliseconds < 1) 139 | { 140 | throw new ArgumentException("Initial reconnection timeout should be a positive number.", nameof(timeoutMilliseconds)); 141 | } 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/IntegrationTests/RabbitMqConnectionFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 3 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 4 | using RabbitMQ.Client.Core.DependencyInjection.Services; 5 | using Xunit; 6 | 7 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.IntegrationTests 8 | { 9 | public class RabbitMqConnectionFactoryTests 10 | { 11 | [Theory] 12 | [InlineData(1)] 13 | [InlineData(5)] 14 | [InlineData(10)] 15 | public void ShouldProperlyRetryCreatingInitialConnection(int retries) 16 | { 17 | var connectionOptions = new RabbitMqServiceOptions 18 | { 19 | HostName = "anotherHost", 20 | InitialConnectionRetries = retries, 21 | InitialConnectionRetryTimeoutMilliseconds = 20 22 | }; 23 | ExecuteUnsuccessfulConnectionCreationAndAssertResults(connectionOptions); 24 | } 25 | 26 | [Theory] 27 | [InlineData(1)] 28 | [InlineData(5)] 29 | [InlineData(10)] 30 | public void ShouldProperlyRetryCreatingInitialConnectionWithConnectionName(int retries) 31 | { 32 | var connectionOptions = new RabbitMqServiceOptions 33 | { 34 | HostName = "anotherHost", 35 | ClientProvidedName = "connectionName", 36 | InitialConnectionRetries = retries, 37 | InitialConnectionRetryTimeoutMilliseconds = 20 38 | }; 39 | ExecuteUnsuccessfulConnectionCreationAndAssertResults(connectionOptions); 40 | } 41 | 42 | [Theory] 43 | [InlineData(1)] 44 | [InlineData(5)] 45 | [InlineData(10)] 46 | public void ShouldProperlyRetryCreatingInitialConnectionWithTcpEndpoints(int retries) 47 | { 48 | var connectionOptions = new RabbitMqServiceOptions 49 | { 50 | TcpEndpoints = new List 51 | { 52 | new() 53 | { 54 | HostName = "anotherHost" 55 | } 56 | }, 57 | InitialConnectionRetries = retries, 58 | InitialConnectionRetryTimeoutMilliseconds = 20 59 | }; 60 | ExecuteUnsuccessfulConnectionCreationAndAssertResults(connectionOptions); 61 | } 62 | 63 | [Theory] 64 | [InlineData(1)] 65 | [InlineData(5)] 66 | [InlineData(10)] 67 | public void ShouldProperlyRetryCreatingInitialConnectionWithHostNames(int retries) 68 | { 69 | var connectionOptions = new RabbitMqServiceOptions 70 | { 71 | HostNames = new List { "anotherHost" }, 72 | InitialConnectionRetries = retries, 73 | InitialConnectionRetryTimeoutMilliseconds = 20 74 | }; 75 | ExecuteUnsuccessfulConnectionCreationAndAssertResults(connectionOptions); 76 | } 77 | 78 | [Theory] 79 | [InlineData(1)] 80 | [InlineData(5)] 81 | [InlineData(10)] 82 | public void ShouldProperlyRetryCreatingInitialConnectionWithHostNamesAndNamedConnection(int retries) 83 | { 84 | var connectionOptions = new RabbitMqServiceOptions 85 | { 86 | HostNames = new List { "anotherHost" }, 87 | ClientProvidedName = "connectionName", 88 | InitialConnectionRetries = retries, 89 | InitialConnectionRetryTimeoutMilliseconds = 20 90 | }; 91 | ExecuteUnsuccessfulConnectionCreationAndAssertResults(connectionOptions); 92 | } 93 | 94 | [Fact] 95 | public void ShouldProperlyCreateInitialConnection() 96 | { 97 | var connectionOptions = new RabbitMqServiceOptions 98 | { 99 | HostName = "rabbitmq", 100 | InitialConnectionRetries = 1, 101 | InitialConnectionRetryTimeoutMilliseconds = 20 102 | }; 103 | ExecuteSuccessfulConnectionCreationAndAssertResults(connectionOptions); 104 | } 105 | 106 | [Fact] 107 | public void ShouldProperlyCreateInitialConnectionWithConnectionName() 108 | { 109 | var connectionOptions = new RabbitMqServiceOptions 110 | { 111 | HostName = "rabbitmq", 112 | ClientProvidedName = "connectionName", 113 | InitialConnectionRetries = 3, 114 | InitialConnectionRetryTimeoutMilliseconds = 20 115 | }; 116 | ExecuteSuccessfulConnectionCreationAndAssertResults(connectionOptions); 117 | } 118 | 119 | [Fact] 120 | public void ShouldProperlyCreateInitialConnectionWithTcpEndpoints() 121 | { 122 | var connectionOptions = new RabbitMqServiceOptions 123 | { 124 | TcpEndpoints = new List 125 | { 126 | new() 127 | { 128 | HostName = "rabbitmq" 129 | } 130 | }, 131 | InitialConnectionRetries = 3, 132 | InitialConnectionRetryTimeoutMilliseconds = 20 133 | }; 134 | ExecuteSuccessfulConnectionCreationAndAssertResults(connectionOptions); 135 | } 136 | 137 | [Fact] 138 | public void ShouldProperlyCreateInitialConnectionWithHostNames() 139 | { 140 | var connectionOptions = new RabbitMqServiceOptions 141 | { 142 | HostNames = new List { "rabbitmq" }, 143 | InitialConnectionRetries = 3, 144 | InitialConnectionRetryTimeoutMilliseconds = 20 145 | }; 146 | ExecuteSuccessfulConnectionCreationAndAssertResults(connectionOptions); 147 | } 148 | 149 | [Fact] 150 | public void ShouldProperlyCreateInitialConnectionWithHostNamesAndNamedConnection() 151 | { 152 | var connectionOptions = new RabbitMqServiceOptions 153 | { 154 | HostNames = new List { "rabbitmq" }, 155 | ClientProvidedName = "connectionName", 156 | InitialConnectionRetries = 3, 157 | InitialConnectionRetryTimeoutMilliseconds = 20 158 | }; 159 | ExecuteSuccessfulConnectionCreationAndAssertResults(connectionOptions); 160 | } 161 | 162 | private static void ExecuteUnsuccessfulConnectionCreationAndAssertResults(RabbitMqServiceOptions connectionOptions) 163 | { 164 | var connectionFactory = new RabbitMqConnectionFactory(); 165 | var exception = Assert.Throws(() => connectionFactory.CreateRabbitMqConnection(connectionOptions)); 166 | Assert.Equal(connectionOptions.InitialConnectionRetries, exception.NumberOfRetries); 167 | } 168 | 169 | private static void ExecuteSuccessfulConnectionCreationAndAssertResults(RabbitMqServiceOptions connectionOptions) 170 | { 171 | var connectionFactory = new RabbitMqConnectionFactory(); 172 | using var connection = connectionFactory.CreateRabbitMqConnection(connectionOptions); 173 | Assert.True(connection.IsOpen); 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/IntegrationTests/RabbitMqServicesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Moq; 8 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 9 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 10 | using RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs; 11 | using Xunit; 12 | 13 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.IntegrationTests 14 | { 15 | [SuppressMessage("ReSharper", "AccessToDisposedClosure")] 16 | public class RabbitMqServicesTests 17 | { 18 | private readonly TimeSpan _globalTestsTimeout = TimeSpan.FromSeconds(60); 19 | 20 | private const string DefaultExchangeName = "exchange.name"; 21 | private const string FirstRoutingKey = "first.routing.key"; 22 | private const string SecondRoutingKey = "second.routing.key"; 23 | private const int RequeueAttempts = 4; 24 | 25 | [Fact] 26 | public async Task ShouldProperlyPublishAndConsumeMessages() 27 | { 28 | var callerMock = new Mock(); 29 | var serviceCollection = new ServiceCollection(); 30 | serviceCollection 31 | .AddSingleton(callerMock.Object) 32 | .AddRabbitMqServices(GetClientOptions()) 33 | .AddConsumptionExchange(DefaultExchangeName, GetExchangeOptions()) 34 | .AddMessageHandlerTransient(FirstRoutingKey) 35 | .AddAsyncMessageHandlerTransient(SecondRoutingKey); 36 | 37 | await using var serviceProvider = serviceCollection.BuildServiceProvider(); 38 | var consumingService = serviceProvider.GetRequiredService(); 39 | var producingService = serviceProvider.GetRequiredService(); 40 | var channelDeclarationService = serviceProvider.GetRequiredService(); 41 | 42 | channelDeclarationService.SetConnectionInfrastructureForRabbitMqServices(); 43 | consumingService.StartConsuming(); 44 | using var resetEvent = new AutoResetEvent(false); 45 | consumingService.Consumer.Received += (_, _) => 46 | { 47 | resetEvent.Set(); 48 | return Task.CompletedTask; 49 | }; 50 | 51 | await producingService.SendAsync(new { Message = "message" }, DefaultExchangeName, FirstRoutingKey); 52 | resetEvent.WaitOne(_globalTestsTimeout); 53 | callerMock.Verify(x => x.Call(It.IsAny()), Times.Once); 54 | 55 | await producingService.SendAsync(new { Message = "message" }, DefaultExchangeName, SecondRoutingKey); 56 | resetEvent.WaitOne(_globalTestsTimeout); 57 | callerMock.Verify(x => x.CallAsync(It.IsAny()), Times.Once); 58 | } 59 | 60 | [Fact] 61 | public async Task ShouldProperlyRequeueMessages() 62 | { 63 | var callerMock = new Mock(); 64 | var serviceCollection = new ServiceCollection(); 65 | serviceCollection 66 | .AddSingleton(callerMock.Object) 67 | .AddRabbitMqServices(GetClientOptions()) 68 | .AddConsumptionExchange(DefaultExchangeName, GetExchangeOptions()) 69 | .AddMessageHandlerTransient(FirstRoutingKey); 70 | 71 | await using var serviceProvider = serviceCollection.BuildServiceProvider(); 72 | var consumingService = serviceProvider.GetRequiredService(); 73 | var producingService = serviceProvider.GetRequiredService(); 74 | var channelDeclarationService = serviceProvider.GetRequiredService(); 75 | 76 | channelDeclarationService.SetConnectionInfrastructureForRabbitMqServices(); 77 | consumingService.StartConsuming(); 78 | using var resetEvent = new AutoResetEvent(false); 79 | consumingService.Consumer.Received += (_, _) => 80 | { 81 | resetEvent.Set(); 82 | return Task.CompletedTask; 83 | }; 84 | 85 | await producingService.SendAsync(new { Message = "message" }, DefaultExchangeName, FirstRoutingKey); 86 | 87 | for (var i = 1; i <= RequeueAttempts + 1; i++) 88 | { 89 | resetEvent.WaitOne(_globalTestsTimeout); 90 | } 91 | callerMock.Verify(x => x.Call(It.IsAny()), Times.Exactly(RequeueAttempts + 1)); 92 | } 93 | 94 | private static RabbitMqServiceOptions GetClientOptions() => 95 | new() 96 | { 97 | HostName = "rabbitmq", 98 | Port = 5672, 99 | UserName = "guest", 100 | Password = "guest", 101 | VirtualHost = "/" 102 | }; 103 | 104 | private static RabbitMqExchangeOptions GetExchangeOptions() => 105 | new() 106 | { 107 | Type = "direct", 108 | DeadLetterExchange = "exchange.dlx", 109 | RequeueAttempts = RequeueAttempts, 110 | RequeueTimeoutMilliseconds = 50, 111 | Queues = new List 112 | { 113 | new() 114 | { 115 | Name = "test.queue", 116 | RoutingKeys = new HashSet { FirstRoutingKey, SecondRoutingKey } 117 | } 118 | } 119 | }; 120 | } 121 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Models/HandleMessageReceivingEventTestDataModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Models 4 | { 5 | public record HandleMessageReceivingEventTestDataModel 6 | { 7 | public string MessageRoutingKey { get; init; } 8 | 9 | public string MessageExchange { get; init; } 10 | 11 | public string MessageHandlerExchange { get; init; } 12 | 13 | public List MessageHandlerPatterns { get; init; } 14 | 15 | public bool MessageHandlerShouldTrigger { get; init; } 16 | 17 | public int? MessageHandlerOrder { get; init; } 18 | 19 | public string AsyncMessageHandlerExchange { get; init; } 20 | 21 | public List AsyncMessageHandlerPatterns { get; init; } 22 | 23 | public bool AsyncMessageHandlerShouldTrigger { get; init; } 24 | 25 | public int? AsyncMessageHandlerOrder { get; init; } 26 | } 27 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Models/MessageHandlerOrderingContainerTestModel.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Models 4 | { 5 | internal record MessageHandlerOrderingContainerTestModel 6 | { 7 | public IBaseMessageHandler MessageHandler { get; init; } 8 | 9 | public bool ShouldTrigger { get; init; } 10 | 11 | public int? OrderValue { get; init; } 12 | 13 | public int? CallOrder { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/RabbitMQ.Client.Core.DependencyInjection.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net5.0 4 | latest 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/IStubCaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 5 | { 6 | public interface IStubCaller 7 | { 8 | void EmptyCall(); 9 | 10 | void Call(ReadOnlyMemory message); 11 | 12 | void Call(string message); 13 | 14 | Task CallAsync(string message); 15 | } 16 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubAsyncMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 3 | using RabbitMQ.Client.Events; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 6 | { 7 | public class StubAsyncMessageHandler : IAsyncMessageHandler 8 | { 9 | private readonly IStubCaller _caller; 10 | 11 | public StubAsyncMessageHandler(IStubCaller caller) 12 | { 13 | _caller = caller; 14 | } 15 | 16 | public async Task Handle(BasicDeliverEventArgs eventArgs, string matchingRoute) 17 | { 18 | await _caller.CallAsync($"{eventArgs.GetMessage()}:{matchingRoute}"); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubBaseBatchMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Logging; 6 | using RabbitMQ.Client.Core.DependencyInjection.Filters; 7 | using RabbitMQ.Client.Core.DependencyInjection.Models; 8 | using RabbitMQ.Client.Core.DependencyInjection.Services; 9 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 10 | using RabbitMQ.Client.Events; 11 | 12 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 13 | { 14 | public class StubBaseBatchMessageHandler : BaseBatchMessageHandler 15 | { 16 | private readonly IStubCaller _caller; 17 | 18 | public StubBaseBatchMessageHandler( 19 | IStubCaller caller, 20 | IRabbitMqConnectionFactory rabbitMqConnectionFactory, 21 | IEnumerable batchConsumerConnectionOptions, 22 | IEnumerable batchMessageHandlingFilters, 23 | ILogger logger) 24 | : base(rabbitMqConnectionFactory, batchConsumerConnectionOptions, batchMessageHandlingFilters, logger) 25 | { 26 | _caller = caller; 27 | } 28 | 29 | public override ushort PrefetchCount { get; set; } 30 | 31 | public override string QueueName { get; set; } 32 | 33 | public override TimeSpan? MessageHandlingPeriod { get; set; } 34 | 35 | public override Task HandleMessages(IEnumerable messages, CancellationToken cancellationToken) 36 | { 37 | foreach (var message in messages) 38 | { 39 | _caller.Call(message.Body); 40 | } 41 | _caller.EmptyCall(); 42 | return Task.CompletedTask; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubBatchMessageHandlingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using RabbitMQ.Client.Core.DependencyInjection.Filters; 7 | using RabbitMQ.Client.Events; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 10 | { 11 | public class StubBatchMessageHandlingFilter : IBatchMessageHandlingFilter 12 | { 13 | public int MessageHandlerNumber { get; } 14 | 15 | private readonly Dictionary _handlerOrderMap; 16 | 17 | public StubBatchMessageHandlingFilter(int messageHandlerNumber, Dictionary handlerOrderMap) 18 | { 19 | if (!handlerOrderMap.ContainsKey(messageHandlerNumber)) 20 | { 21 | handlerOrderMap.Add(messageHandlerNumber, 0); 22 | } 23 | 24 | _handlerOrderMap = handlerOrderMap; 25 | MessageHandlerNumber = messageHandlerNumber; 26 | } 27 | 28 | public Func, CancellationToken, Task> Execute(Func, CancellationToken, Task> next) 29 | { 30 | var order = _handlerOrderMap.Values.Max(); 31 | _handlerOrderMap[MessageHandlerNumber] = order + 1; 32 | return next; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubCaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 6 | { 7 | public class StubCallerDecorator : IStubCaller 8 | { 9 | private readonly IStubCaller Caller; 10 | 11 | public StubCallerDecorator(IStubCaller caller) 12 | { 13 | Caller = caller; 14 | } 15 | 16 | public EventWaitHandle WaitHandle { get; set; } 17 | 18 | public void EmptyCall() 19 | { 20 | Caller.EmptyCall(); 21 | WaitHandle.Set(); 22 | } 23 | 24 | public void Call(ReadOnlyMemory message) 25 | { 26 | Caller.Call(message); 27 | } 28 | 29 | public void Call(string message) 30 | { 31 | Caller.Call(message); 32 | } 33 | 34 | public Task CallAsync(string message) 35 | { 36 | return Caller.CallAsync(message); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubExceptionMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 3 | using RabbitMQ.Client.Events; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 6 | { 7 | public class StubExceptionMessageHandler : IMessageHandler 8 | { 9 | private readonly IStubCaller _caller; 10 | 11 | public StubExceptionMessageHandler(IStubCaller caller) 12 | { 13 | _caller = caller; 14 | } 15 | 16 | public void Handle(BasicDeliverEventArgs eventArgs, string matchingRoute) 17 | { 18 | _caller.Call($"{eventArgs.GetMessage()}:{matchingRoute}"); 19 | throw new Exception(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 2 | using RabbitMQ.Client.Events; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 5 | { 6 | public class StubMessageHandler : IMessageHandler 7 | { 8 | private readonly IStubCaller _caller; 9 | 10 | public StubMessageHandler(IStubCaller caller) 11 | { 12 | _caller = caller; 13 | } 14 | 15 | public void Handle(BasicDeliverEventArgs eventArgs, string matchingRoute) 16 | { 17 | _caller.Call($"{eventArgs.GetMessage()}:{matchingRoute}"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubMessageHandlingExceptionFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Core.DependencyInjection.Filters; 6 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 7 | using RabbitMQ.Client.Events; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 10 | { 11 | public class StubMessageHandlingExceptionFilter : IMessageHandlingExceptionFilter 12 | { 13 | public int FilterNumber { get; } 14 | 15 | private readonly Dictionary _filterOrderMap; 16 | 17 | public StubMessageHandlingExceptionFilter(int messageHandlerNumber, Dictionary filterOrderMap) 18 | { 19 | if (!filterOrderMap.ContainsKey(messageHandlerNumber)) 20 | { 21 | filterOrderMap.Add(messageHandlerNumber, 0); 22 | } 23 | 24 | _filterOrderMap = filterOrderMap; 25 | FilterNumber = messageHandlerNumber; 26 | } 27 | 28 | public Func Execute(Func next) 29 | { 30 | var order = _filterOrderMap.Values.Max(); 31 | _filterOrderMap[FilterNumber] = order + 1; 32 | return next; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubMessageHandlingFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Core.DependencyInjection.Filters; 6 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 7 | using RabbitMQ.Client.Events; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 10 | { 11 | public class StubMessageHandlingFilter : IMessageHandlingFilter 12 | { 13 | public int MessageHandlerNumber { get; } 14 | 15 | private readonly Dictionary _handlerOrderMap; 16 | 17 | public StubMessageHandlingFilter(int messageHandlerNumber, Dictionary handlerOrderMap) 18 | { 19 | if (!handlerOrderMap.ContainsKey(messageHandlerNumber)) 20 | { 21 | handlerOrderMap.Add(messageHandlerNumber, 0); 22 | } 23 | 24 | _handlerOrderMap = handlerOrderMap; 25 | MessageHandlerNumber = messageHandlerNumber; 26 | } 27 | 28 | public Func Execute(Func next) 29 | { 30 | var order = _handlerOrderMap.Values.Max(); 31 | _handlerOrderMap[MessageHandlerNumber] = order + 1; 32 | return next; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/BatchMessageHandlerDependencyInjectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Moq; 5 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 6 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 7 | using RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs; 8 | using Xunit; 9 | 10 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 11 | { 12 | public class BatchMessageHandlerDependencyInjectionExtensionsTests 13 | { 14 | [Fact] 15 | public async Task ShouldProperlyThrowExceptionWhenRegisteringSameBatchMessageHandlerTwiceWithConfiguration() 16 | { 17 | await Assert.ThrowsAsync(() => 18 | { 19 | var configurationMock = new Mock(); 20 | new ServiceCollection() 21 | .AddBatchMessageHandler(configurationMock.Object) 22 | .AddBatchMessageHandler(configurationMock.Object); 23 | return Task.CompletedTask; 24 | }); 25 | } 26 | 27 | [Fact] 28 | public async Task ShouldProperlyThrowExceptionWhenRegisteringSameBatchMessageHandlerTwiceWithOptions() 29 | { 30 | await Assert.ThrowsAsync(() => 31 | { 32 | new ServiceCollection() 33 | .AddBatchMessageHandler(new RabbitMqServiceOptions()) 34 | .AddBatchMessageHandler(new RabbitMqServiceOptions()); 35 | return Task.CompletedTask; 36 | }); 37 | } 38 | 39 | [Fact] 40 | public async Task ShouldProperlyThrowExceptionWhenRegisteringSameBatchMessageHandlerTwiceWithConfigurationAndOptions() 41 | { 42 | await Assert.ThrowsAsync(() => 43 | { 44 | var configurationMock = new Mock(); 45 | new ServiceCollection() 46 | .AddBatchMessageHandler(configurationMock.Object) 47 | .AddBatchMessageHandler(new RabbitMqServiceOptions()); 48 | return Task.CompletedTask; 49 | }); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/ConsumingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Moq; 5 | using RabbitMQ.Client.Core.DependencyInjection.Models; 6 | using RabbitMQ.Client.Core.DependencyInjection.Services; 7 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 8 | using RabbitMQ.Client.Events; 9 | using Xunit; 10 | 11 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 12 | { 13 | public class ConsumingServiceTests 14 | { 15 | [Theory] 16 | [InlineData(1)] 17 | [InlineData(5)] 18 | [InlineData(10)] 19 | [InlineData(15)] 20 | [InlineData(20)] 21 | [InlineData(25)] 22 | public async Task ShouldProperlyConsumeMessages(int numberOfMessages) 23 | { 24 | var channelMock = new Mock(); 25 | var connectionMock = new Mock(); 26 | var consumer = new AsyncEventingBasicConsumer(channelMock.Object); 27 | 28 | var messageHandlingPipelineExecutingServiceMock = new Mock(); 29 | var consumingService = CreateConsumingService(messageHandlingPipelineExecutingServiceMock.Object); 30 | 31 | consumingService.UseConnection(connectionMock.Object); 32 | consumingService.UseChannel(channelMock.Object); 33 | consumingService.UseConsumer(consumer); 34 | 35 | await consumer.HandleBasicDeliver( 36 | "1", 37 | 0, 38 | false, 39 | "exchange", 40 | "routing,key", 41 | null, 42 | new ReadOnlyMemory()); 43 | messageHandlingPipelineExecutingServiceMock.Verify(x => x.Execute(It.IsAny(), It.IsAny()), Times.Never); 44 | 45 | consumingService.StartConsuming(); 46 | 47 | for (var i = 1; i <= numberOfMessages; i++) 48 | { 49 | await consumer.HandleBasicDeliver( 50 | "1", 51 | (ulong)numberOfMessages, 52 | false, 53 | "exchange", 54 | "routing,key", 55 | null, 56 | new ReadOnlyMemory()); 57 | } 58 | 59 | messageHandlingPipelineExecutingServiceMock.Verify(x => x.Execute(It.IsAny(), It.IsAny()), Times.Exactly(numberOfMessages)); 60 | } 61 | 62 | [Theory] 63 | [InlineData(1)] 64 | [InlineData(5)] 65 | [InlineData(10)] 66 | [InlineData(15)] 67 | [InlineData(20)] 68 | [InlineData(25)] 69 | public async Task ShouldProperlyStopConsumingMessages(int numberOfMessages) 70 | { 71 | var channelMock = new Mock(); 72 | var connectionMock = new Mock(); 73 | var consumer = new AsyncEventingBasicConsumer(channelMock.Object); 74 | 75 | var messageHandlingPipelineExecutingServiceMock = new Mock(); 76 | var consumingService = CreateConsumingService(messageHandlingPipelineExecutingServiceMock.Object); 77 | 78 | consumingService.UseConnection(connectionMock.Object); 79 | consumingService.UseChannel(channelMock.Object); 80 | consumingService.UseConsumer(consumer); 81 | 82 | consumingService.StartConsuming(); 83 | for (var i = 1; i <= numberOfMessages; i++) 84 | { 85 | await consumer.HandleBasicDeliver( 86 | "1", 87 | (ulong)numberOfMessages, 88 | false, 89 | "exchange", 90 | "routing,key", 91 | null, 92 | new ReadOnlyMemory()); 93 | } 94 | 95 | messageHandlingPipelineExecutingServiceMock.Verify(x => x.Execute(It.IsAny(), It.IsAny()), Times.Exactly(numberOfMessages)); 96 | 97 | consumingService.StopConsuming(); 98 | await consumer.HandleBasicDeliver( 99 | "1", 100 | 0, 101 | false, 102 | "exchange", 103 | "routing,key", 104 | null, 105 | new ReadOnlyMemory()); 106 | 107 | messageHandlingPipelineExecutingServiceMock.Verify(x => x.Execute(It.IsAny(), It.IsAny()), Times.Exactly(numberOfMessages)); 108 | } 109 | 110 | private static IConsumingService CreateConsumingService(IMessageHandlingPipelineExecutingService messageHandlingPipelineExecutingService) => 111 | new ConsumingService(messageHandlingPipelineExecutingService, new List()); 112 | } 113 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/MessageHandlerDependencyInjectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs; 5 | using Xunit; 6 | 7 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 8 | { 9 | public class MessageHandlerDependencyInjectionExtensionsTests 10 | { 11 | [Fact] 12 | public async Task ShouldProperlyThrowExceptionWhenRegisteringSameMessageHandlerTwiceForOneRoutingKeyWithDifferentOrder() 13 | { 14 | await Assert.ThrowsAsync(() => 15 | { 16 | new ServiceCollection() 17 | .AddMessageHandlerTransient("routing.key", 0) 18 | .AddMessageHandlerTransient("routing.key", 1); 19 | return Task.CompletedTask; 20 | }); 21 | } 22 | 23 | [Fact] 24 | public async Task ShouldProperlyThrowExceptionWhenRegisteringSameAsyncMessageHandlerTwiceForOneRoutingKeyWithDifferentOrder() 25 | { 26 | await Assert.ThrowsAsync(() => 27 | { 28 | new ServiceCollection() 29 | .AddAsyncMessageHandlerTransient("routing.key", 0) 30 | .AddAsyncMessageHandlerTransient("routing.key", 1); 31 | return Task.CompletedTask; 32 | }); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/MessageHandlingPipelineExecutingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Moq; 6 | using RabbitMQ.Client.Core.DependencyInjection.Filters; 7 | using RabbitMQ.Client.Core.DependencyInjection.Services; 8 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 9 | using RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs; 10 | using RabbitMQ.Client.Events; 11 | using Xunit; 12 | 13 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 14 | { 15 | public class MessageHandlingPipelineExecutingServiceTests 16 | { 17 | [Fact] 18 | public async Task ShouldProperlyExecutePipelineWithNoFilters() 19 | { 20 | var argsMock = new Mock(); 21 | var consumingServiceMock = new Mock(); 22 | 23 | var messageHandlingServiceMock = new Mock(); 24 | 25 | var service = CreateService( 26 | messageHandlingServiceMock.Object, 27 | Enumerable.Empty(), 28 | Enumerable.Empty()); 29 | 30 | await service.Execute(argsMock.Object, consumingServiceMock.Object); 31 | 32 | messageHandlingServiceMock.Verify(x => x.HandleMessageReceivingEvent(argsMock.Object, consumingServiceMock.Object), Times.Once); 33 | } 34 | 35 | [Fact] 36 | public async Task ShouldProperlyExecutePipelineInReverseOrder() 37 | { 38 | var argsMock = new Mock(); 39 | var consumingServiceMock = new Mock(); 40 | 41 | var messageHandlingServiceMock = new Mock(); 42 | 43 | var handlerOrderMap = new Dictionary(); 44 | var firstFilter = new StubMessageHandlingFilter(1, handlerOrderMap); 45 | var secondFilter = new StubMessageHandlingFilter(2, handlerOrderMap); 46 | var thirdFilter = new StubMessageHandlingFilter(3, handlerOrderMap); 47 | 48 | var handlingFilters = new List 49 | { 50 | firstFilter, 51 | secondFilter, 52 | thirdFilter 53 | }; 54 | 55 | var service = CreateService( 56 | messageHandlingServiceMock.Object, 57 | handlingFilters, 58 | Enumerable.Empty()); 59 | 60 | await service.Execute(argsMock.Object, consumingServiceMock.Object); 61 | 62 | messageHandlingServiceMock.Verify(x => x.HandleMessageReceivingEvent(argsMock.Object, consumingServiceMock.Object), Times.Once); 63 | Assert.Equal(1, handlerOrderMap[thirdFilter.MessageHandlerNumber]); 64 | Assert.Equal(2, handlerOrderMap[secondFilter.MessageHandlerNumber]); 65 | Assert.Equal(3, handlerOrderMap[firstFilter.MessageHandlerNumber]); 66 | } 67 | 68 | [Fact] 69 | public async Task ShouldProperlyExecuteFailurePipelineInReverseOrderWhenMessageHandlingServiceThrowsException() 70 | { 71 | var argsMock = new Mock(); 72 | var consumingServiceMock = new Mock(); 73 | 74 | var exception = new Exception(); 75 | var messageHandlingServiceMock = new Mock(); 76 | messageHandlingServiceMock.Setup(x => x.HandleMessageReceivingEvent(argsMock.Object, consumingServiceMock.Object)) 77 | .ThrowsAsync(exception); 78 | 79 | var filterOrderMap = new Dictionary(); 80 | var firstFilter = new StubMessageHandlingExceptionFilter(1, filterOrderMap); 81 | var secondFilter = new StubMessageHandlingExceptionFilter(2, filterOrderMap); 82 | var thirdFilter = new StubMessageHandlingExceptionFilter(3, filterOrderMap); 83 | var exceptionFilters = new List 84 | { 85 | firstFilter, 86 | secondFilter, 87 | thirdFilter 88 | }; 89 | 90 | var service = CreateService( 91 | messageHandlingServiceMock.Object, 92 | Enumerable.Empty(), 93 | exceptionFilters); 94 | 95 | await service.Execute(argsMock.Object, consumingServiceMock.Object); 96 | 97 | messageHandlingServiceMock.Verify(x => x.HandleMessageProcessingFailure(exception, argsMock.Object, consumingServiceMock.Object), Times.Once); 98 | Assert.Equal(1, filterOrderMap[thirdFilter.FilterNumber]); 99 | Assert.Equal(2, filterOrderMap[secondFilter.FilterNumber]); 100 | Assert.Equal(3, filterOrderMap[firstFilter.FilterNumber]); 101 | } 102 | 103 | [Fact] 104 | public async Task ShouldProperlyExecuteFailurePipelineInReverseOrderWhenMessageHandlingFilterThrowsException() 105 | { 106 | var argsMock = new Mock(); 107 | var consumingServiceMock = new Mock(); 108 | 109 | var messageHandlingServiceMock = new Mock(); 110 | 111 | var exception = new Exception(); 112 | var handlingFilter = new Mock(); 113 | handlingFilter.Setup(x => x.Execute(It.IsAny>())) 114 | .Throws(exception); 115 | var handlingFilters = new List 116 | { 117 | handlingFilter.Object 118 | }; 119 | 120 | var filterOrderMap = new Dictionary(); 121 | var firstFilter = new StubMessageHandlingExceptionFilter(1, filterOrderMap); 122 | var secondFilter = new StubMessageHandlingExceptionFilter(2, filterOrderMap); 123 | var thirdFilter = new StubMessageHandlingExceptionFilter(3, filterOrderMap); 124 | var exceptionFilters = new List 125 | { 126 | firstFilter, 127 | secondFilter, 128 | thirdFilter 129 | }; 130 | 131 | var service = CreateService( 132 | messageHandlingServiceMock.Object, 133 | handlingFilters, 134 | exceptionFilters); 135 | 136 | await service.Execute(argsMock.Object, consumingServiceMock.Object); 137 | 138 | messageHandlingServiceMock.Verify(x => x.HandleMessageProcessingFailure(exception, argsMock.Object, consumingServiceMock.Object), Times.Once); 139 | Assert.Equal(1, filterOrderMap[thirdFilter.FilterNumber]); 140 | Assert.Equal(2, filterOrderMap[secondFilter.FilterNumber]); 141 | Assert.Equal(3, filterOrderMap[firstFilter.FilterNumber]); 142 | } 143 | 144 | private static IMessageHandlingPipelineExecutingService CreateService( 145 | IMessageHandlingService messageHandlingService, 146 | IEnumerable handlingFilters, 147 | IEnumerable exceptionFilters) 148 | { 149 | return new MessageHandlingPipelineExecutingService( 150 | messageHandlingService, 151 | handlingFilters, 152 | exceptionFilters); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/RabbitMqExchangeDependencyInjectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Moq; 6 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 7 | using Xunit; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 10 | { 11 | public class RabbitMqExchangeDependencyInjectionExtensionsTests 12 | { 13 | [Fact] 14 | public async Task ShouldProperlyThrowExceptionWhenRegisteringSameExchangeWithSameNameAndOptionsTwice() 15 | { 16 | await Assert.ThrowsAsync(() => 17 | { 18 | new ServiceCollection() 19 | .AddExchange("exchange.name", true, new RabbitMqExchangeOptions()) 20 | .AddExchange("exchange.name", false, new RabbitMqExchangeOptions()); 21 | return Task.CompletedTask; 22 | }); 23 | } 24 | 25 | [Fact] 26 | public async Task ShouldProperlyThrowExceptionWhenRegisteringSameExchangeWithSameNameAndConfigurationTwice() 27 | { 28 | await Assert.ThrowsAsync(() => 29 | { 30 | var configurationMock = new Mock(); 31 | new ServiceCollection() 32 | .AddExchange("exchange.name", true, configurationMock.Object) 33 | .AddExchange("exchange.name", false, configurationMock.Object); 34 | return Task.CompletedTask; 35 | }); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/WildcardExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using RabbitMQ.Client.Core.DependencyInjection.InternalExtensions; 4 | using RabbitMQ.Client.Core.DependencyInjection.Models; 5 | using Xunit; 6 | 7 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 8 | { 9 | public class WildcardExtensionsTests 10 | { 11 | private readonly string[] _routes; 12 | 13 | public WildcardExtensionsTests() 14 | { 15 | _routes = new[] { 16 | "#", 17 | "#.delete", 18 | "#.create", 19 | "#.update", 20 | "create.*", 21 | "create.#", 22 | "*.update", 23 | "*.create.*", 24 | "*.*.*", 25 | "*.*.create", 26 | }; 27 | } 28 | 29 | [Fact] 30 | public void ShouldProperlyConstructTree() 31 | { 32 | var tree = WildcardExtensions.ConstructRoutesTree(_routes).ToList(); 33 | 34 | var countNodes = CountNodes(tree); 35 | Assert.Equal(15, countNodes); 36 | 37 | Assert.Equal(4, tree.Count()); 38 | Assert.Contains(tree, x => x.IsLastNode && x.KeyPartition == "#"); 39 | Assert.Contains(tree, x => !x.IsLastNode && x.KeyPartition == "#"); 40 | Assert.Contains(tree, x => x.KeyPartition == "*"); 41 | Assert.Contains(tree, x => x.KeyPartition == "create"); 42 | 43 | var sharpNodes = tree.FirstOrDefault(x => !x.IsLastNode && x.KeyPartition == "#")?.Nodes; 44 | Assert.NotNull(sharpNodes); 45 | Assert.Equal(3, sharpNodes.Count); 46 | Assert.Contains(sharpNodes, x => x.KeyPartition == "delete"); 47 | Assert.Contains(sharpNodes, x => x.KeyPartition == "create"); 48 | Assert.Contains(sharpNodes, x => x.KeyPartition == "update"); 49 | 50 | var createNodes = tree.FirstOrDefault(x => x.KeyPartition == "create")?.Nodes; 51 | Assert.NotNull(createNodes); 52 | Assert.Equal(2, createNodes.Count); 53 | Assert.Contains(createNodes, x => x.KeyPartition == "*"); 54 | Assert.Contains(createNodes, x => x.KeyPartition == "#"); 55 | 56 | var asteriskNodes = tree.FirstOrDefault(x => x.KeyPartition == "*")?.Nodes; 57 | Assert.NotNull(asteriskNodes); 58 | Assert.Equal(3, asteriskNodes.Count); 59 | Assert.Contains(asteriskNodes, x => x.KeyPartition == "*"); 60 | Assert.Contains(asteriskNodes, x => x.KeyPartition == "create"); 61 | Assert.Contains(asteriskNodes, x => x.KeyPartition == "update"); 62 | 63 | var doubleAsteriskNodes = asteriskNodes.FirstOrDefault(x => x.KeyPartition == "*")?.Nodes; 64 | Assert.NotNull(doubleAsteriskNodes); 65 | Assert.Equal(2, doubleAsteriskNodes.Count); 66 | Assert.Contains(doubleAsteriskNodes, x => x.KeyPartition == "*"); 67 | Assert.Contains(doubleAsteriskNodes, x => x.KeyPartition == "create"); 68 | 69 | var asteriskCreateNodes = asteriskNodes.FirstOrDefault(x => x.KeyPartition == "create")?.Nodes; 70 | Assert.NotNull(asteriskCreateNodes); 71 | Assert.Single(asteriskCreateNodes); 72 | Assert.Contains(asteriskCreateNodes, x => x.KeyPartition == "*"); 73 | } 74 | 75 | [Theory] 76 | [InlineData("create.connection", new[] { "#", "create.*", "create.#" })] 77 | [InlineData("create.stable.connection", new[] { "#", "create.#", "*.*.*" })] 78 | [InlineData("connection.create.stable", new[] { "#", "*.create.*", "*.*.*" })] 79 | [InlineData("file.delete", new[] { "#", "#.delete" })] 80 | [InlineData("file.info.delete", new[] { "#", "#.delete", "*.*.*" })] 81 | [InlineData("file.update", new[] { "#", "#.update", "*.update" })] 82 | [InlineData("file.update.author", new[] { "#", "*.*.*" })] 83 | [InlineData("file.update.author.credentials", new[] { "#" })] 84 | [InlineData("report.create", new[] { "#", "#.create" })] 85 | [InlineData("final.report.create", new[] { "#", "#.create", "*.*.*", "*.*.create" })] 86 | public void ShouldProperlyGetMatchingRoutes(string routingKey, IEnumerable routes) 87 | { 88 | var tree = WildcardExtensions.ConstructRoutesTree(_routes); 89 | 90 | var routingKeyParts = routingKey.Split("."); 91 | var matchingRoutes = WildcardExtensions.GetMatchingRoutePatterns(tree, routingKeyParts).ToList(); 92 | 93 | var listOfRoutes = routes.ToList(); 94 | Assert.Equal(listOfRoutes.Count, matchingRoutes.Count); 95 | foreach (var route in listOfRoutes) 96 | { 97 | Assert.Contains(matchingRoutes, x => x == route); 98 | } 99 | } 100 | 101 | private static int CountNodes(IList nodes) 102 | { 103 | var count = nodes.Count(); 104 | foreach (var node in nodes) 105 | { 106 | if (node.Nodes.Any()) 107 | { 108 | count += CountNodes(node.Nodes); 109 | } 110 | } 111 | 112 | return count; 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/run-rabbitmq.sh: -------------------------------------------------------------------------------- 1 | docker run -d --hostname my-rabbit --name some-rabbit -p 4369:4369 -p 5671:5671 -p 5672:5672 -p 15672:15672 rabbitmq 2 | docker exec some-rabbit rabbitmq-plugins enable rabbitmq_management --------------------------------------------------------------------------------