├── .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 │ ├── DbContexts │ │ └── ApplicationDbContext.cs │ ├── Examples.AdvancedConfiguration.csproj │ ├── MessageHandlers │ │ ├── CustomAsyncMessageHandler.cs │ │ └── CustomMessageHandler.cs │ ├── Program.cs │ ├── Services │ │ ├── ConsumingWithProviderBatchMessageHandler.cs │ │ └── ConsumingWithScopeBatchMessageHandler.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 │ ├── BehaviourConfiguration.cs │ ├── RabbitMqConnectionOptions.cs │ ├── RabbitMqExchangeOptions.cs │ ├── RabbitMqQueueOptions.cs │ ├── RabbitMqServiceOptions.cs │ ├── RabbitMqSslOption.cs │ └── RabbitMqTcpEndpoint.cs │ ├── ErrorProcessingServiceDependencyInjectionExtensions.cs │ ├── Exceptions │ ├── BatchMessageHandlerAlreadyConfiguredException.cs │ ├── BatchMessageHandlerInvalidPropertyValueException.cs │ ├── ChannelIsNullException.cs │ ├── ConnectionIsNullException.cs │ ├── ConsumerIsNullException.cs │ ├── ExchangelIsNullException.cs │ ├── InitialConnectionException.cs │ ├── InvalidExchangeTypeException.cs │ ├── MessageHasAlreadyBeenAcknowledgedException.cs │ └── ProducingChannelIsNullException.cs │ ├── InternalExtensions │ ├── BaseMessageHandlerDependencyInjectionExtensions.cs │ ├── BaseMessageHandlerDictionaryExtensions.cs │ ├── MessageHandlerContainerExtensions.cs │ ├── RabbitMqServiceOptionsDependencyInjectionExtensions.cs │ ├── Validation │ │ ├── AsyncEventingBasicConsumerValidationExtensions.cs │ │ ├── ConnectionValidationExtensions.cs │ │ ├── ModelValidationExtensions.cs │ │ └── RabbitMqExchangeValidationExtensions.cs │ └── WildcardExtensions.cs │ ├── MessageHandlerDependencyInjectionExtensions.cs │ ├── MessageHandlers │ ├── IAsyncMessageHandler.cs │ ├── IBaseMessageHandler.cs │ └── IMessageHandler.cs │ ├── MessageHandlingMiddlewareDependencyInjectionExtensions.cs │ ├── Middlewares │ ├── IBatchMessageHandlingMiddleware.cs │ └── IMessageHandlingMiddleware.cs │ ├── Models │ ├── BatchConsumerConnectionOptions.cs │ ├── ClientExchangeType.cs │ ├── DeadLetterExchange.cs │ ├── ExchangeServiceDescriptor.cs │ ├── MessageHandlerContainer.cs │ ├── MessageHandlerOrderingContainer.cs │ ├── MessageHandlerOrderingModel.cs │ ├── MessageHandlerRouter.cs │ ├── MessageHandlingContext.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 │ ├── ErrorProcessingService.cs │ ├── Interfaces │ │ ├── IChannelDeclarationService.cs │ │ ├── IConsumingService.cs │ │ ├── IErrorProcessingService.cs │ │ ├── ILoggingService.cs │ │ ├── IMessageHandlerContainerBuilder.cs │ │ ├── IMessageHandlingPipelineExecutingService.cs │ │ ├── IMessageHandlingService.cs │ │ ├── IProducingService.cs │ │ ├── IRabbitMqConnectionFactory.cs │ │ └── IRabbitMqService.cs │ ├── LoggingService.cs │ ├── MessageHandlerContainerBuilder.cs │ ├── MessageHandlingPipelineExecutingService.cs │ ├── MessageHandlingService.cs │ ├── ProducingService.cs │ └── RabbitMqConnectionFactory.cs │ └── Specifications │ ├── DuplicatedMessageHandlerDeclarationSpecification.cs │ ├── DuplicatedRabbitMqExchangeDeclarationSpecification.cs │ ├── Specification.cs │ ├── ValidDeadLetterExchangeTypeSpecification.cs │ └── ValidExchangeTypeSpecification.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 ├── StubBatchMessageHandlingMiddleware.cs ├── StubCaller.cs ├── StubErrorProcessingService.cs ├── StubExceptionMessageHandler.cs ├── StubMessageHandler.cs └── StubMessageHandlingMiddleware.cs ├── UnitTests ├── BaseBatchMessageHandlerTests.cs ├── BatchMessageHandlerDependencyInjectionExtensionsTests.cs ├── ConsumingServiceTests.cs ├── ErrorProcessingServiceDependencyInjectionExtensionsTests.cs ├── MessageHandlerDependencyInjectionExtensionsTests.cs ├── MessageHandlingPipelineExecutingServiceTests.cs ├── MessageHandlingServiceTests.cs ├── ProducingServiceTests.cs ├── RabbitMqExchangeDependencyInjectionExtensionsTests.cs ├── SpecificationTests.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/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/images/delayed-message-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonyvorontsov/RabbitMQ.Client.Core.DependencyInjection/27c5835b98857542a8886133acd325adb7b5446d/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/DbContexts/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace Examples.AdvancedConfiguration.DbContexts 4 | { 5 | public class ApplicationDbContext : DbContext 6 | { 7 | public ApplicationDbContext(DbContextOptions options) 8 | : base(options) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/Examples.AdvancedConfiguration.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net5.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/MessageHandlers/CustomAsyncMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 3 | using RabbitMQ.Client.Core.DependencyInjection.Models; 4 | 5 | namespace Examples.AdvancedConfiguration.MessageHandlers 6 | { 7 | public class CustomAsyncMessageHandler : IAsyncMessageHandler 8 | { 9 | public async Task Handle(MessageHandlingContext context, 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.Core.DependencyInjection.Models; 3 | 4 | namespace Examples.AdvancedConfiguration.MessageHandlers 5 | { 6 | public class CustomMessageHandler : IMessageHandler 7 | { 8 | public void Handle(MessageHandlingContext context, 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/ConsumingWithProviderBatchMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Examples.AdvancedConfiguration.DbContexts; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using RabbitMQ.Client.Core.DependencyInjection.Middlewares; 8 | using RabbitMQ.Client.Core.DependencyInjection.Models; 9 | using RabbitMQ.Client.Core.DependencyInjection.Services; 10 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 11 | using RabbitMQ.Client.Events; 12 | 13 | namespace Examples.AdvancedConfiguration.Services 14 | { 15 | public class ConsumingWithProviderBatchMessageHandler : BaseBatchMessageHandler 16 | { 17 | private readonly ApplicationDbContext _dbContext; 18 | 19 | public ConsumingWithProviderBatchMessageHandler( 20 | IRabbitMqConnectionFactory rabbitMqConnectionFactory, 21 | IEnumerable batchConsumerConnectionOptions, 22 | IEnumerable batchMessageHandlingMiddlewares, 23 | ILoggingService loggingService, 24 | IServiceProvider provider) 25 | : base(rabbitMqConnectionFactory, batchConsumerConnectionOptions, batchMessageHandlingMiddlewares, loggingService) 26 | { 27 | _dbContext = provider.GetRequiredService(); 28 | } 29 | 30 | public override ushort PrefetchCount { get; set; } = 15; 31 | 32 | public override string QueueName { get; set; } = "your.queue.name"; 33 | 34 | public override async Task HandleMessages(IEnumerable messages, CancellationToken cancellationToken) 35 | { 36 | // Handle messages. 37 | await _dbContext.SaveChangesAsync(cancellationToken); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/Services/ConsumingWithScopeBatchMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Examples.AdvancedConfiguration.DbContexts; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using RabbitMQ.Client.Core.DependencyInjection.Middlewares; 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.AdvancedConfiguration.Services 13 | { 14 | public class ConsumingWithScopeBatchMessageHandler : BaseBatchMessageHandler 15 | { 16 | private readonly IServiceScopeFactory _scopeFactory; 17 | 18 | public ConsumingWithScopeBatchMessageHandler( 19 | IRabbitMqConnectionFactory rabbitMqConnectionFactory, 20 | IEnumerable batchConsumerConnectionOptions, 21 | IEnumerable batchMessageHandlingMiddlewares, 22 | ILoggingService loggingService, 23 | IServiceScopeFactory scopeFactory) 24 | : base(rabbitMqConnectionFactory, batchConsumerConnectionOptions, batchMessageHandlingMiddlewares, loggingService) 25 | { 26 | _scopeFactory = scopeFactory; 27 | } 28 | 29 | public override ushort PrefetchCount { get; set; } = 15; 30 | 31 | public override string QueueName { get; set; } = "your.queue.name"; 32 | 33 | public override async Task HandleMessages(IEnumerable messages, CancellationToken cancellationToken) 34 | { 35 | using var scope = _scopeFactory.CreateScope(); 36 | var dbContext = scope.ServiceProvider.GetRequiredService(); 37 | // Handler a batch of messages with db context. 38 | await dbContext.SaveChangesAsync(cancellationToken); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /examples/Examples.AdvancedConfiguration/Startup.cs: -------------------------------------------------------------------------------- 1 | using Examples.AdvancedConfiguration.DbContexts; 2 | using Examples.AdvancedConfiguration.MessageHandlers; 3 | using Examples.AdvancedConfiguration.Services; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using RabbitMQ.Client.Core.DependencyInjection; 8 | 9 | namespace Examples.AdvancedConfiguration 10 | { 11 | public class Startup 12 | { 13 | private IConfiguration Configuration { get; } 14 | 15 | public Startup(IConfiguration configuration) 16 | { 17 | Configuration = configuration; 18 | } 19 | 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddControllers(); 23 | services.AddDbContext(); 24 | 25 | // Configurations are the same (same user) and same password, but the only difference - names of connections. 26 | var rabbitMqConsumerSection = Configuration.GetSection("RabbitMqConsumer"); 27 | var rabbitMqProducerSection = Configuration.GetSection("RabbitMqProducer"); 28 | 29 | var producingExchangeSection = Configuration.GetSection("ProducingExchange"); 30 | var consumingExchangeSection = Configuration.GetSection("ConsumingExchange"); 31 | 32 | // There is an example of configuring different message handlers with different parameters. 33 | // 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. 34 | // There are a lot of different extension methods that is better take a closer look to. 35 | services.AddRabbitMqServices(rabbitMqConsumerSection) 36 | .AddRabbitMqProducer(rabbitMqProducerSection) 37 | .AddProductionExchange("exchange.to.send.messages.only", producingExchangeSection) 38 | .AddConsumptionExchange("consumption.exchange", consumingExchangeSection) 39 | .AddMessageHandlerTransient("routing.key") 40 | .AddAsyncMessageHandlerTransient(new[] { "routing.key", "another.routing.key" }) 41 | .AddBatchMessageHandler(consumingExchangeSection) 42 | .AddBatchMessageHandler(consumingExchangeSection); 43 | } 44 | 45 | public void Configure(IApplicationBuilder app) 46 | { 47 | app.UseRouting(); 48 | app.UseEndpoints(endpoints => endpoints.MapControllers()); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /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.Middlewares; 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 batchMessageHandlingMiddlewares, 22 | ILoggingService loggingService, 23 | ILogger logger) 24 | : base(rabbitMqConnectionFactory, batchConsumerConnectionOptions, batchMessageHandlingMiddlewares, loggingService) 25 | { 26 | _logger = logger; 27 | } 28 | 29 | public override ushort PrefetchCount { get; set; } = 3; 30 | 31 | // You have to be aware that BaseBatchMessageHandler does not declare the specified queue. So if it does not exists an exception will be thrown. 32 | public override string QueueName { get; set; } = "queue.name"; 33 | 34 | public override Task HandleMessages(IEnumerable messages, CancellationToken cancellationToken) 35 | { 36 | _logger.LogInformation("Handling a batch of messages."); 37 | foreach (var message in messages) 38 | { 39 | _logger.LogInformation(message.GetMessage()); 40 | } 41 | return Task.CompletedTask; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /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.Core.DependencyInjection.Models; 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(MessageHandlingContext context, string matchingRoute) 17 | { 18 | _logger.LogInformation($"Handling message {context.Message.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/antonyvorontsov/RabbitMQ.Client.Core.DependencyInjection/27c5835b98857542a8886133acd325adb7b5446d/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(typeof(TBatchMessageHandler), serviceOptions); 58 | var serviceDescriptor = new ServiceDescriptor(typeof(BatchConsumerConnectionOptions), options); 59 | services.Add(serviceDescriptor); 60 | return services; 61 | } 62 | 63 | private static void CheckIfBatchMessageHandlerAlreadyConfigured(this IServiceCollection services) 64 | { 65 | var descriptor = services.FirstOrDefault(x => x.ImplementationType == typeof(TBatchMessageHandler)); 66 | if (descriptor != null) 67 | { 68 | throw new BatchMessageHandlerAlreadyConfiguredException(typeof(TBatchMessageHandler), $"A batch message handler of type {typeof(TBatchMessageHandler)} has already been configured."); 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Configuration/BehaviourConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.Client.Core.DependencyInjection.Configuration 2 | { 3 | /// 4 | /// Custom configuration that are responsible for service behaviours. 5 | /// 6 | public class BehaviourConfiguration 7 | { 8 | /// 9 | /// Flag if logging has to be disabled. 10 | /// 11 | /// 12 | /// It disables only information/warning/debug logs unlike errors. Errors are still logging properly. 13 | /// 14 | public bool DisableInternalLogging { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /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 | /// Dead-letter-exchange type. 32 | /// 33 | public string DeadLetterExchangeType { get; set; } = "direct"; 34 | 35 | /// 36 | /// Option to re-queue failed messages. 37 | /// 38 | public bool RequeueFailedMessages { get; set; } = true; 39 | 40 | /// 41 | /// Re-queue message attempts. 42 | /// 43 | public int RequeueAttempts { get; set; } = 2; 44 | 45 | /// 46 | /// Re-queue timeout in milliseconds. 47 | /// 48 | public int RequeueTimeoutMilliseconds { get; set; } = 200; 49 | 50 | /// 51 | /// Additional arguments. 52 | /// 53 | public IDictionary Arguments { get; set; } = new Dictionary(); 54 | 55 | /// 56 | /// Collection of queues bound to the exchange. 57 | /// 58 | public IList Queues { get; set; } = new List(); 59 | 60 | /// 61 | /// Do not auto-ack a received message after processing. 62 | /// 63 | public bool DisableAutoAck { get; set; } = false; 64 | } 65 | } -------------------------------------------------------------------------------- /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; } = string.Empty; 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 IList TcpEndpoints { get; set; } = new List(); 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 IList HostNames { get; set; } = new List(); 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; } = string.Empty; 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; } = string.Empty; 14 | 15 | /// 16 | /// Path to the certificate. 17 | /// 18 | public string CertificatePath { get; set; } = string.Empty; 19 | 20 | /// 21 | /// A pass-phrase for the certificate. 22 | /// 23 | public string CertificatePassphrase { get; set; } = string.Empty; 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; } = string.Empty; 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/ErrorProcessingServiceDependencyInjectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection 6 | { 7 | /// 8 | /// DI extensions for custom error processing service implementations. 9 | /// 10 | public static class ErrorProcessingServiceDependencyInjectionExtensions 11 | { 12 | /// 13 | /// Add custom error processing service. 14 | /// 15 | /// Service collection. 16 | /// Type of a custom error processing service. 17 | /// Service collection. 18 | public static IServiceCollection AddCustomMessageHandlingErrorProcessingService(this IServiceCollection services) 19 | where T : class, IErrorProcessingService 20 | { 21 | var descriptor = services.FirstOrDefault(x => x.ServiceType == typeof(IErrorProcessingService)); 22 | if (descriptor is not null) 23 | { 24 | services.Remove(descriptor); 25 | } 26 | 27 | return services.AddSingleton(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/BatchMessageHandlerAlreadyConfiguredException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// This exception 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 | /// This exception 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; } 14 | 15 | public BatchMessageHandlerInvalidPropertyValueException(string message, string propertyName) : base(message) 16 | { 17 | PropertyName = propertyName; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/ChannelIsNullException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// This exception is thrown when channel is null. 7 | /// 8 | public class ChannelIsNullException : Exception 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/ConnectionIsNullException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// This exception is thrown when connection is null (thus connection factory could not create a connection). 7 | /// 8 | public class ConnectionIsNullException : Exception 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/ConsumerIsNullException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// This exception is thrown when consumer instance is null so there is no option of consuming messages. 7 | /// 8 | public class ConsumerIsNullException : Exception 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/ExchangelIsNullException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// This exception is thrown when an exchange could not be found. 7 | /// 8 | public class ExchangeIsNullException : Exception 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/InitialConnectionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// This exception 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/InvalidExchangeTypeException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 5 | { 6 | /// 7 | /// This exception if throw when an exchange is being declared with a wrong type. 8 | /// 9 | public class InvalidExchangeTypeException : Exception 10 | { 11 | public InvalidExchangeTypeException(string exchangeName, string exchangeType) 12 | { 13 | ExchangeName = exchangeName; 14 | ExchangeType = exchangeType; 15 | AllowedExchangeTypes = Client.ExchangeType.All(); 16 | } 17 | 18 | /// 19 | /// Wrong exchange type. 20 | /// 21 | public string ExchangeType { get; } 22 | 23 | /// 24 | /// Name of the exchange that is being declared wrong. 25 | /// 26 | public string ExchangeName { get; } 27 | 28 | /// 29 | /// Allowed exchange types. 30 | /// 31 | public ICollection AllowedExchangeTypes { get; } 32 | } 33 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/MessageHasAlreadyBeenAcknowledgedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// This exception is thrown when the message has been acknowledged already. 7 | /// 8 | public class MessageHasAlreadyBeenAcknowledgedException : Exception 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Exceptions/ProducingChannelIsNullException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Exceptions 4 | { 5 | /// 6 | /// This exception that 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/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 | using RabbitMQ.Client.Core.DependencyInjection.Specifications; 7 | 8 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions 9 | { 10 | /// 11 | /// Base DI extensions for all types of message handlers. 12 | /// 13 | internal static class BaseMessageHandlerDependencyInjectionExtensions 14 | { 15 | internal static IServiceCollection AddInstanceTransient(this IServiceCollection services, IEnumerable routePatterns, int order) 16 | where TInterface : class 17 | where TImplementation : class, TInterface => 18 | services.AddInstanceTransient(routePatterns, null, order); 19 | 20 | internal static IServiceCollection AddInstanceSingleton(this IServiceCollection services, IEnumerable routePatterns, int order) 21 | where TInterface : class 22 | where TImplementation : class, TInterface => 23 | services.AddInstanceSingleton(routePatterns, null, order); 24 | 25 | internal static IServiceCollection AddInstanceTransient(this IServiceCollection services, IEnumerable routePatterns, string? exchange, int order) 26 | where TInterface : class 27 | where TImplementation : class, TInterface 28 | { 29 | var patterns = routePatterns.ToList(); 30 | services.AddTransient(); 31 | var router = new MessageHandlerRouter(typeof(TImplementation), exchange, patterns); 32 | services.Add(new ServiceDescriptor(typeof(MessageHandlerRouter), router)); 33 | return services.AddMessageHandlerOrderingModel(patterns, exchange, order); 34 | } 35 | 36 | internal static IServiceCollection AddInstanceSingleton(this IServiceCollection services, IEnumerable routePatterns, string? exchange, int order) 37 | where TInterface : class 38 | where TImplementation : class, TInterface 39 | { 40 | var patterns = routePatterns.ToList(); 41 | services.AddSingleton(); 42 | var router = new MessageHandlerRouter(typeof(TImplementation), exchange, patterns); 43 | services.Add(new ServiceDescriptor(typeof(MessageHandlerRouter), router)); 44 | return services.AddMessageHandlerOrderingModel(patterns, exchange, order); 45 | } 46 | 47 | private static IServiceCollection AddMessageHandlerOrderingModel(this IServiceCollection services, IEnumerable routePatterns, string? exchange, int order) 48 | where TImplementation : class 49 | { 50 | var patterns = routePatterns.ToList(); 51 | MessageHandlerOrderingModelExists(services, patterns, exchange, order); 52 | var messageHandlerOrderingModel = new MessageHandlerOrderingModel(typeof(TImplementation), exchange, patterns, order); 53 | services.AddSingleton(messageHandlerOrderingModel); 54 | return services; 55 | } 56 | 57 | private static void MessageHandlerOrderingModelExists(IServiceCollection services, IReadOnlyCollection routePatterns, string? exchange, int order) 58 | { 59 | var specification = new DuplicatedMessageHandlerDeclarationSpecification(typeof(TImplementation), routePatterns, exchange, order); 60 | var messageHandlerOrderingModel = services.FirstOrDefault(x => specification.IsSatisfiedBy(x)); 61 | if (messageHandlerOrderingModel is null) 62 | { 63 | return; 64 | } 65 | 66 | var intersectRoutePatterns = routePatterns.Intersect(((MessageHandlerOrderingModel)messageHandlerOrderingModel.ImplementationInstance).RoutePatterns); 67 | 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}."); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /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/MessageHandlerContainerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 3 | using RabbitMQ.Client.Core.DependencyInjection.Models; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions 6 | { 7 | /// 8 | /// Internal extensions for easier work with MessageHandlerContainer model. 9 | /// 10 | internal static class MessageHandlerContainerExtensions 11 | { 12 | internal static int? GetOrderForHandler(this MessageHandlerContainer container, IBaseMessageHandler handler) 13 | { 14 | return container.MessageHandlerOrderingModels.FirstOrDefault(x => x.MessageHandlerType == handler.GetType())?.Order; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /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/Validation/AsyncEventingBasicConsumerValidationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 3 | using RabbitMQ.Client.Events; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions.Validation 6 | { 7 | internal static class AsyncEventingBasicConsumerValidationExtensions 8 | { 9 | internal static AsyncEventingBasicConsumer EnsureIsNotNull([NotNull]this AsyncEventingBasicConsumer? consumer) 10 | { 11 | if (consumer is null) 12 | { 13 | throw new ConsumerIsNullException(); 14 | } 15 | 16 | return consumer; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/InternalExtensions/Validation/ConnectionValidationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions.Validation 5 | { 6 | internal static class ConnectionValidationExtensions 7 | { 8 | internal static IConnection EnsureIsNotNull([NotNull] this IConnection? connection) 9 | { 10 | if (connection is null) 11 | { 12 | throw new ConnectionIsNullException(); 13 | } 14 | 15 | return connection; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/InternalExtensions/Validation/ModelValidationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions.Validation 5 | { 6 | internal static class ModelValidationExtensions 7 | { 8 | internal static IModel EnsureIsNotNull([NotNull]this IModel? channel) 9 | { 10 | if (channel is null) 11 | { 12 | throw new ChannelIsNullException(); 13 | } 14 | 15 | return channel; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/InternalExtensions/Validation/RabbitMqExchangeValidationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 3 | using RabbitMQ.Client.Core.DependencyInjection.Models; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.InternalExtensions.Validation 6 | { 7 | internal static class RabbitMqExchangeValidationExtensions 8 | { 9 | internal static RabbitMqExchange EnsureIsNotNull([NotNull]this RabbitMqExchange? exchange) 10 | { 11 | if (exchange is null) 12 | { 13 | throw new ExchangeIsNullException(); 14 | } 15 | 16 | return exchange; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /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.Core.DependencyInjection.Models; 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 handling context. 15 | /// Matching routing key. 16 | Task Handle(MessageHandlingContext context, 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.Core.DependencyInjection.Models; 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 handling context. 14 | /// Matching routing key. 15 | void Handle(MessageHandlingContext context, string matchingRoute); 16 | } 17 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/MessageHandlingMiddlewareDependencyInjectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using RabbitMQ.Client.Core.DependencyInjection.Middlewares; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection 5 | { 6 | /// 7 | /// DI extensions for message handling middlewares that combine into single pipeline. 8 | /// 9 | public static class MessageHandlingMiddlewareDependencyInjectionExtensions 10 | { 11 | /// 12 | /// Add a middleware component that will be used in the message handling pipeline in a transient mode (by default). 13 | /// 14 | /// Service collection. 15 | /// Middleware type (has to implement ). 16 | /// Service collection. 17 | public static IServiceCollection AddMessageHandlingMiddleware(this IServiceCollection services) 18 | where T : class, IMessageHandlingMiddleware => 19 | services.AddMessageHandlingMiddlewareTransient(); 20 | 21 | /// 22 | /// Add a middleware component that will be used in the message handling pipeline in a singleton mode. 23 | /// 24 | /// Service collection. 25 | /// Middleware type (has to implement ). 26 | /// Service collection. 27 | public static IServiceCollection AddMessageHandlingMiddlewareSingleton(this IServiceCollection services) 28 | where T : class, IMessageHandlingMiddleware => 29 | services.AddSingleton(); 30 | 31 | /// 32 | /// Add a middleware component that will be used in the message handling pipeline in a transient mode. 33 | /// 34 | /// Service collection. 35 | /// Middleware type (has to implement ). 36 | /// Service collection. 37 | public static IServiceCollection AddMessageHandlingMiddlewareTransient(this IServiceCollection services) 38 | where T : class, IMessageHandlingMiddleware => services.AddTransient(); 39 | 40 | /// 41 | /// Add a middleware component that will be used in the batch message handling pipeline in a transient mode (by default). 42 | /// 43 | /// Service collection. 44 | /// Middleware type (has to implement ). 45 | /// Service collection. 46 | public static IServiceCollection AddBatchMessageHandlingMiddleware(this IServiceCollection services) 47 | where T : class, IBatchMessageHandlingMiddleware => 48 | services.AddBatchMessageHandlingMiddlewareTransient(); 49 | 50 | /// 51 | /// Add a middleware component that will be used in the batch message handling pipeline in a singleton mode. 52 | /// 53 | /// Service collection. 54 | /// Middleware type (has to implement ). 55 | /// Service collection. 56 | public static IServiceCollection AddBatchMessageHandlingMiddlewareSingleton(this IServiceCollection services) 57 | where T : class, IBatchMessageHandlingMiddleware => 58 | services.AddSingleton(); 59 | 60 | /// 61 | /// Add a middleware component that will be used in the message handling pipeline in a singleton mode. 62 | /// 63 | /// Service collection. 64 | /// Middleware type (has to implement ). 65 | /// Service collection. 66 | public static IServiceCollection AddBatchMessageHandlingMiddlewareTransient(this IServiceCollection services) 67 | where T : class, IBatchMessageHandlingMiddleware => services.AddTransient(); 68 | } 69 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Middlewares/IBatchMessageHandlingMiddleware.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.Middlewares 8 | { 9 | /// 10 | /// Middleware that is used in message handling pipeline for batch message handlers. 11 | /// 12 | public interface IBatchMessageHandlingMiddleware 13 | { 14 | /// 15 | /// Handle a batch of messages. 16 | /// 17 | /// A collection of messages. 18 | /// Next action (middleware) in the constructed pipeline. 19 | /// Cancellation token. 20 | Task Handle(IEnumerable messages, Func next, CancellationToken cancellationToken); 21 | } 22 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Middlewares/IMessageHandlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using RabbitMQ.Client.Core.DependencyInjection.Models; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Middlewares 6 | { 7 | /// 8 | /// Middleware that is used in message handling pipeline for the message handling service. 9 | /// 10 | public interface IMessageHandlingMiddleware 11 | { 12 | /// 13 | /// Middleware action for message processing. 14 | /// 15 | /// Message handling context. 16 | /// Next action (middleware) in the constructed pipeline. 17 | Task Handle(MessageHandlingContext context, Func next); 18 | 19 | /// 20 | /// Middleware action for exception handling. 21 | /// 22 | /// Message handling context. 23 | /// An exception occured while trying to process a consumed message. 24 | /// Next action (middleware) in the constructed pipeline. 25 | Task HandleError(MessageHandlingContext context, Exception exception, Func next); 26 | } 27 | } -------------------------------------------------------------------------------- /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 | public BatchConsumerConnectionOptions(Type type, RabbitMqServiceOptions options) 12 | { 13 | Type = type; 14 | ServiceOptions = options; 15 | } 16 | 17 | /// 18 | /// Type of batch message handler. 19 | /// 20 | public Type Type { get; } 21 | 22 | /// 23 | /// Consumer client connection options. 24 | /// 25 | public RabbitMqServiceOptions ServiceOptions { get; } 26 | } 27 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/ClientExchangeType.cs: -------------------------------------------------------------------------------- 1 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 2 | { 3 | /// 4 | /// Custom exchange type that defines which functionality is allowed for an exchange. 5 | /// 6 | public enum ClientExchangeType 7 | { 8 | /// 9 | /// Exchange is only for consumption. 10 | /// 11 | Consumption, 12 | 13 | /// 14 | /// Exchange is only for production. 15 | /// 16 | Production, 17 | 18 | /// 19 | /// Exchange is for both consumption and production. 20 | /// 21 | Universal 22 | } 23 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/DeadLetterExchange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 5 | { 6 | /// 7 | /// Internal model that represents a dead letter exchange. 8 | /// 9 | internal sealed class DeadLetterExchange 10 | { 11 | internal DeadLetterExchange(string name, string type) 12 | { 13 | Name = name; 14 | Type = type; 15 | } 16 | 17 | /// 18 | /// Exchange name. 19 | /// 20 | internal string Name { get; } 21 | 22 | /// 23 | /// Exchange type. 24 | /// 25 | internal string Type { get; } 26 | } 27 | 28 | /// 29 | /// Default equality comparer for . 30 | /// 31 | internal sealed class DeadLetterExchangeEqualityComparer : IEqualityComparer 32 | { 33 | public bool Equals(DeadLetterExchange? x, DeadLetterExchange? y) 34 | { 35 | if (ReferenceEquals(x, y)) 36 | { 37 | return true; 38 | } 39 | 40 | if (ReferenceEquals(x, null)) 41 | { 42 | return false; 43 | } 44 | 45 | if (ReferenceEquals(y, null)) 46 | { 47 | return false; 48 | } 49 | 50 | if (x.GetType() != y.GetType()) 51 | { 52 | return false; 53 | } 54 | 55 | return string.Equals(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) && string.Equals(x.Type, y.Type, StringComparison.OrdinalIgnoreCase); 56 | } 57 | 58 | public int GetHashCode(DeadLetterExchange obj) 59 | { 60 | return HashCode.Combine(obj.Name, obj.Type); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /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; } 15 | 16 | /// 17 | /// Type of the exchange. 18 | /// 19 | public ClientExchangeType ClientExchangeType { get; } 20 | 21 | public ExchangeServiceDescriptor(Type serviceType, object instance, string exchangeName, ClientExchangeType clientExchangeType) 22 | : base(serviceType, instance) 23 | { 24 | ExchangeName = exchangeName; 25 | ClientExchangeType = clientExchangeType; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /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 | public MessageHandlerContainer(string? exchange, IEnumerable tree, IDictionary> messageHandlers, IEnumerable messageHandlerOrderingModels) 13 | { 14 | Exchange = exchange; 15 | Tree = tree; 16 | MessageHandlers = messageHandlers; 17 | MessageHandlerOrderingModels = messageHandlerOrderingModels; 18 | } 19 | 20 | /// 21 | /// An exchange. 22 | /// 23 | /// 24 | /// Could be null. 25 | /// 26 | public string? Exchange { get; } 27 | 28 | /// 29 | /// Route patterns tree (trie) structure. 30 | /// 31 | public IEnumerable Tree { get; } 32 | 33 | /// 34 | /// Flag is the container general. 35 | /// 36 | // TODO: validate that everything is okay with general exchanges. 37 | public bool IsGeneral => string.IsNullOrEmpty(Exchange); 38 | 39 | /// 40 | /// Dictionary of route patterns and message handlers connected by them. 41 | /// 42 | public IDictionary> MessageHandlers { get; } 43 | 44 | /// 45 | /// The collection of models that contain information about an order in which message handlers will be called. 46 | /// 47 | public IEnumerable MessageHandlerOrderingModels { get; } 48 | } 49 | } -------------------------------------------------------------------------------- /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 | internal MessageHandlerOrderingContainer( 11 | IBaseMessageHandler handler, 12 | string matchingRoute, 13 | int? order) 14 | { 15 | MessageHandler = handler; 16 | MatchingRoute = matchingRoute; 17 | Order = order; 18 | } 19 | 20 | /// 21 | /// The instance of message handler. 22 | /// 23 | public IBaseMessageHandler MessageHandler { get; } 24 | 25 | /// 26 | /// Order. 27 | /// 28 | public int? Order { get; } 29 | 30 | /// 31 | /// Route that matches a routing key of received message. 32 | /// 33 | public string MatchingRoute { get; } 34 | } 35 | } -------------------------------------------------------------------------------- /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 | public MessageHandlerOrderingModel(Type messageHandlerType, string? exchange, IEnumerable routePatterns, int order) 13 | { 14 | MessageHandlerType = messageHandlerType; 15 | Exchange = exchange; 16 | RoutePatterns = routePatterns; 17 | Order = order; 18 | } 19 | 20 | /// 21 | /// A type of the registered message handler. 22 | /// 23 | public Type MessageHandlerType { get; } 24 | 25 | /// 26 | /// An exchange which message handler bound to. 27 | /// 28 | public string? Exchange { get; } 29 | 30 | /// 31 | /// A collection of route patterns which message handler "listens". 32 | /// 33 | public IEnumerable RoutePatterns { get; } 34 | 35 | /// 36 | /// The value of order. 37 | /// 38 | public int Order { get; } 39 | } 40 | } -------------------------------------------------------------------------------- /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 | public MessageHandlerRouter(Type type, string? exchange, IList routePatterns) 12 | { 13 | Type = type; 14 | Exchange = exchange; 15 | RoutePatterns = routePatterns; 16 | } 17 | 18 | /// 19 | /// Message Handler Type 20 | /// 21 | public Type Type { get; } 22 | 23 | /// 24 | /// An exchange which is being listened by the message handler by specified route patterns. 25 | /// 26 | /// 27 | /// Exchange can be null, and in that scenario message handler will be general 28 | /// (it will listen all messages regardless of an exchange). 29 | /// 30 | public string? Exchange { get; } 31 | 32 | /// 33 | /// Collection of route patterns (routing keys) that handler will be "listening". 34 | /// 35 | public IList RoutePatterns { get; } 36 | 37 | /// 38 | /// Flag is the message handler general. 39 | /// 40 | public bool IsGeneral => string.IsNullOrEmpty(Exchange); 41 | } 42 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/MessageHandlingContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 3 | using RabbitMQ.Client.Events; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 6 | { 7 | public class MessageHandlingContext 8 | { 9 | private readonly Action _ackAction; 10 | private bool _alreadyAcknowledged; 11 | 12 | public MessageHandlingContext(BasicDeliverEventArgs message, Action ackAction, bool disableAutoAck) 13 | { 14 | Message = message; 15 | _ackAction = ackAction; 16 | AutoAckEnabled = !disableAutoAck; 17 | } 18 | 19 | public BasicDeliverEventArgs Message { get; } 20 | 21 | public bool AutoAckEnabled { get; } 22 | 23 | public void AcknowledgeMessage() 24 | { 25 | if (_alreadyAcknowledged) 26 | { 27 | throw new MessageHasAlreadyBeenAcknowledgedException(); 28 | } 29 | 30 | _ackAction(Message); 31 | _alreadyAcknowledged = true; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Models/RabbitMqExchange.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Models 6 | { 7 | /// 8 | /// Exchange model. 9 | /// 10 | public class RabbitMqExchange 11 | { 12 | private readonly IReadOnlyCollection _exchangeTypesAllowedForConsuming = new[] 13 | { 14 | ClientExchangeType.Consumption, 15 | ClientExchangeType.Universal 16 | }; 17 | 18 | private readonly IReadOnlyCollection _exchangeTypesAllowedForProducing = new[] 19 | { 20 | ClientExchangeType.Production, 21 | ClientExchangeType.Universal 22 | }; 23 | 24 | public RabbitMqExchange(string name, ClientExchangeType clientExchangeType, RabbitMqExchangeOptions options) 25 | { 26 | Name = name; 27 | ClientExchangeType = clientExchangeType; 28 | Options = options; 29 | } 30 | 31 | /// 32 | /// The unique name of the exchange. 33 | /// 34 | public string Name { get; } 35 | 36 | /// 37 | /// Custom client exchange type. 38 | /// 39 | public ClientExchangeType ClientExchangeType { get; } 40 | 41 | /// 42 | /// Flag determining whether the exchange made for message consumption. 43 | /// If false then an exchange made only for publishing. 44 | /// 45 | public bool IsConsuming => _exchangeTypesAllowedForConsuming.Contains(ClientExchangeType); 46 | 47 | /// 48 | /// Flag determining whether the exchange made for message production. 49 | /// If false then an exchange made only for consumption. 50 | /// 51 | public bool IsProducing => _exchangeTypesAllowedForProducing.Contains(ClientExchangeType); 52 | 53 | /// 54 | /// Exchange options. 55 | /// 56 | public RabbitMqExchangeOptions Options { get; } 57 | } 58 | } -------------------------------------------------------------------------------- /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; } = string.Empty; 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 | enable 6 | nullable 7 | 5.0.0-alpha 8 | RabbitMQ 9 | https://github.com/AntonyVorontsov/RabbitMQ.Client.Core.DependencyInjection 10 | GIT 11 | https://github.com/AntonyVorontsov/RabbitMQ.Client.Core.DependencyInjection/blob/master/readme.md 12 | 13 | A RabbitMQ.Client wrapper that provides easy, managed, injectable (via standard dependency injection) message queue consume and publish operations. 14 | true 15 | icon.png 16 | LICENSE.txt 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /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.InternalExtensions.Validation; 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 | Channel.EnsureIsNotNull(); 79 | Consumer.EnsureIsNotNull(); 80 | 81 | if (_consumingStarted) 82 | { 83 | return; 84 | } 85 | 86 | Consumer.Received += ConsumerOnReceived; 87 | _consumingStarted = true; 88 | 89 | var consumptionExchanges = _exchanges.Where(x => x.IsConsuming); 90 | _consumerTags = consumptionExchanges.SelectMany( 91 | exchange => exchange.Options.Queues.Select( 92 | queue => Channel.BasicConsume(queue: queue.Name, autoAck: false, consumer: Consumer))) 93 | .Distinct() 94 | .ToList(); 95 | } 96 | 97 | /// 98 | public void StopConsuming() 99 | { 100 | Channel.EnsureIsNotNull(); 101 | Consumer.EnsureIsNotNull(); 102 | 103 | if (!_consumingStarted) 104 | { 105 | return; 106 | } 107 | 108 | Consumer.Received -= ConsumerOnReceived; 109 | _consumingStarted = false; 110 | foreach (var tag in _consumerTags) 111 | { 112 | Channel.BasicCancel(tag); 113 | } 114 | } 115 | 116 | private void AckAction(BasicDeliverEventArgs eventArgs) => Channel.EnsureIsNotNull().BasicAck(eventArgs.DeliveryTag, false); 117 | 118 | private async Task ConsumerOnReceived(object sender, BasicDeliverEventArgs eventArgs) 119 | { 120 | var exchangeOptions = _exchanges.FirstOrDefault(x => string.Equals(x.Name, eventArgs.Exchange)).EnsureIsNotNull().Options; 121 | var context = new MessageHandlingContext(eventArgs, AckAction, exchangeOptions.DisableAutoAck); 122 | await _messageHandlingPipelineExecutingService.Execute(context); 123 | } 124 | } 125 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/ErrorProcessingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Core.DependencyInjection.Models; 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 ErrorProcessingService : IErrorProcessingService 13 | { 14 | private readonly IProducingService _producingService; 15 | private readonly IEnumerable _exchanges; 16 | private readonly ILoggingService _loggingService; 17 | 18 | public ErrorProcessingService( 19 | IProducingService producingService, 20 | IEnumerable exchanges, 21 | ILoggingService loggingService) 22 | { 23 | _producingService = producingService; 24 | _exchanges = exchanges; 25 | _loggingService = loggingService; 26 | } 27 | 28 | /// 29 | public virtual async Task HandleMessageProcessingFailure(MessageHandlingContext context, Exception exception) 30 | { 31 | var eventArgs = context.Message; 32 | if (context.AutoAckEnabled) 33 | { 34 | context.AcknowledgeMessage(); 35 | } 36 | 37 | _loggingService.LogError(exception, $"An error occurred while processing received message with the delivery tag {eventArgs.DeliveryTag}."); 38 | await HandleFailedMessageProcessing(eventArgs).ConfigureAwait(false); 39 | } 40 | 41 | protected async Task HandleFailedMessageProcessing(BasicDeliverEventArgs eventArgs) 42 | { 43 | var exchange = _exchanges.FirstOrDefault(x => x.Name == eventArgs.Exchange); 44 | if (exchange is null) 45 | { 46 | _loggingService.LogWarning($"Could not detect an exchange \"{eventArgs.Exchange}\" to determine the necessity of resending the failed message. The message won't be re-queued"); 47 | return; 48 | } 49 | 50 | if (!exchange.Options.RequeueFailedMessages) 51 | { 52 | _loggingService.LogWarning($"RequeueFailedMessages option for an exchange \"{eventArgs.Exchange}\" is disabled. The message won't be re-queued"); 53 | return; 54 | } 55 | 56 | if (string.IsNullOrEmpty(exchange.Options.DeadLetterExchange)) 57 | { 58 | _loggingService.LogWarning($"DeadLetterExchange has not been configured for an exchange \"{eventArgs.Exchange}\". The message won't be re-queued"); 59 | return; 60 | } 61 | 62 | if (exchange.Options.RequeueTimeoutMilliseconds < 1) 63 | { 64 | _loggingService.LogWarning($"The value RequeueTimeoutMilliseconds for an exchange \"{eventArgs.Exchange}\" less than 1 millisecond. Configuration is invalid. The message won't be re-queued"); 65 | return; 66 | } 67 | 68 | if (exchange.Options.RequeueAttempts < 1) 69 | { 70 | _loggingService.LogWarning($"The value RequeueAttempts for an exchange \"{eventArgs.Exchange}\" less than 1. Configuration is invalid. The message won't be re-queued"); 71 | return; 72 | } 73 | 74 | if (eventArgs.BasicProperties.Headers is null) 75 | { 76 | eventArgs.BasicProperties.Headers = new Dictionary(); 77 | } 78 | 79 | if (!eventArgs.BasicProperties.Headers.ContainsKey("re-queue-attempts")) 80 | { 81 | eventArgs.BasicProperties.Headers.Add("re-queue-attempts", 1); 82 | await RequeueMessage(eventArgs, exchange.Options.RequeueTimeoutMilliseconds); 83 | return; 84 | } 85 | 86 | var currentAttempt = (int)eventArgs.BasicProperties.Headers["re-queue-attempts"]; 87 | if (currentAttempt < exchange.Options.RequeueAttempts) 88 | { 89 | eventArgs.BasicProperties.Headers["re-queue-attempts"] = currentAttempt + 1; 90 | await RequeueMessage(eventArgs, exchange.Options.RequeueTimeoutMilliseconds); 91 | } 92 | else 93 | { 94 | _loggingService.LogInformation("The failed message would not be re-queued. Attempts limit exceeded"); 95 | } 96 | } 97 | 98 | protected async Task RequeueMessage(BasicDeliverEventArgs eventArgs, int timeoutMilliseconds) 99 | { 100 | await _producingService.SendAsync(eventArgs.Body, eventArgs.BasicProperties, eventArgs.Exchange, eventArgs.RoutingKey, timeoutMilliseconds); 101 | _loggingService.LogInformation("The failed message has been re-queued"); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /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/IErrorProcessingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using RabbitMQ.Client.Core.DependencyInjection.Models; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 6 | { 7 | /// 8 | /// Service that contains logic of handling errors that occured in the pipeline of processing consumed messages. 9 | /// 10 | public interface IErrorProcessingService 11 | { 12 | /// 13 | /// Handle message processing failure. 14 | /// 15 | /// Model that contains consumed message alongside with additional actions . 16 | /// An occured exception. 17 | Task HandleMessageProcessingFailure(MessageHandlingContext context, Exception exception); 18 | } 19 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/ILoggingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 4 | { 5 | /// 6 | /// Custom wrapper of default Microsoft logger. 7 | /// 8 | /// 9 | /// This service is made for potential logging disabling when it is not necessary. 10 | /// 11 | public interface ILoggingService 12 | { 13 | /// 14 | /// Log occured error. 15 | /// 16 | void LogError(Exception exception, string message); 17 | 18 | /// 19 | /// Log warning. 20 | /// 21 | void LogWarning(string message); 22 | 23 | /// 24 | /// Log information. 25 | /// 26 | void LogInformation(string message); 27 | 28 | /// 29 | /// Log debug. 30 | /// 31 | void LogDebug(string message); 32 | } 33 | } -------------------------------------------------------------------------------- /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.Core.DependencyInjection.Models; 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 | /// Model that contains consumed message alongside with additional actions . 15 | Task Execute(MessageHandlingContext context); 16 | } 17 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/Interfaces/IMessageHandlingService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using RabbitMQ.Client.Core.DependencyInjection.Models; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces 5 | { 6 | /// 7 | /// Service that contains logic of handling message receiving (consumption) events and passing those messages to the message handlers. 8 | /// 9 | public interface IMessageHandlingService 10 | { 11 | /// 12 | /// Handle message receiving event. 13 | /// 14 | /// Model that contains consumed message alongside with additional actions . 15 | Task HandleMessageReceivingEvent(MessageHandlingContext context); 16 | } 17 | } -------------------------------------------------------------------------------- /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/LoggingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.Extensions.Options; 4 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 5 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 6 | 7 | namespace RabbitMQ.Client.Core.DependencyInjection.Services 8 | { 9 | /// 10 | public class LoggingService : ILoggingService 11 | { 12 | private readonly ILogger _logger; 13 | private readonly bool disableLogging; 14 | 15 | public LoggingService( 16 | ILogger logger, 17 | IOptions options) 18 | { 19 | _logger = logger; 20 | disableLogging = options.Value.DisableInternalLogging; 21 | } 22 | 23 | /// 24 | public void LogError(Exception exception, string message) 25 | { 26 | // We do not disable logging for errors. Warning or debug messages are okay to skip, but skipping errors is risky. 27 | _logger.LogError(new EventId(), exception, message); 28 | } 29 | 30 | /// 31 | public void LogWarning(string message) 32 | { 33 | if (disableLogging) 34 | { 35 | return; 36 | } 37 | 38 | _logger.LogWarning(message); 39 | } 40 | 41 | /// 42 | public void LogInformation(string message) 43 | { 44 | if (disableLogging) 45 | { 46 | return; 47 | } 48 | 49 | _logger.LogInformation(message); 50 | } 51 | 52 | /// 53 | public void LogDebug(string message) 54 | { 55 | if (disableLogging) 56 | { 57 | return; 58 | } 59 | 60 | _logger.LogDebug(message); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /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.Middlewares; 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 MessageHandlingPipelineExecutingService : IMessageHandlingPipelineExecutingService 13 | { 14 | private readonly IMessageHandlingService _messageHandlingService; 15 | private readonly IErrorProcessingService _errorProcessingService; 16 | private readonly IEnumerable _messageHandlingMiddlewares; 17 | 18 | public MessageHandlingPipelineExecutingService( 19 | IMessageHandlingService messageHandlingService, 20 | IErrorProcessingService errorProcessingService, 21 | IEnumerable messageHandlingMiddlewares) 22 | { 23 | _messageHandlingService = messageHandlingService; 24 | _errorProcessingService = errorProcessingService; 25 | _messageHandlingMiddlewares = messageHandlingMiddlewares; 26 | } 27 | 28 | /// 29 | public async Task Execute(MessageHandlingContext context) 30 | { 31 | try 32 | { 33 | await ExecutePipeline(context).ConfigureAwait(false); 34 | } 35 | catch (Exception exception) 36 | { 37 | await ExecuteFailurePipeline(context, exception).ConfigureAwait(false); 38 | } 39 | } 40 | 41 | private async Task ExecutePipeline(MessageHandlingContext context) 42 | { 43 | if (!_messageHandlingMiddlewares.Any()) 44 | { 45 | await _messageHandlingService.HandleMessageReceivingEvent(context); 46 | return; 47 | } 48 | 49 | Func handleFunction = async () => await _messageHandlingService.HandleMessageReceivingEvent(context); 50 | foreach (var middleware in _messageHandlingMiddlewares) 51 | { 52 | var previousHandleFunction = handleFunction; 53 | handleFunction = async () => await middleware.Handle(context, previousHandleFunction); 54 | } 55 | 56 | await handleFunction().ConfigureAwait(false); 57 | } 58 | 59 | private async Task ExecuteFailurePipeline(MessageHandlingContext context, Exception exception) 60 | { 61 | if (!_messageHandlingMiddlewares.Any()) 62 | { 63 | await _errorProcessingService.HandleMessageProcessingFailure(context, exception); 64 | return; 65 | } 66 | 67 | Func handleFunction = async () => await _errorProcessingService.HandleMessageProcessingFailure(context, exception); 68 | foreach (var middleware in _messageHandlingMiddlewares) 69 | { 70 | var previousHandleFunction = handleFunction; 71 | handleFunction = async () => await middleware.HandleError(context, exception, previousHandleFunction); 72 | } 73 | 74 | await handleFunction().ConfigureAwait(false); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Services/MessageHandlingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Core.DependencyInjection.InternalExtensions; 6 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 7 | using RabbitMQ.Client.Core.DependencyInjection.Models; 8 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 9 | 10 | namespace RabbitMQ.Client.Core.DependencyInjection.Services 11 | { 12 | /// 13 | public class MessageHandlingService : IMessageHandlingService 14 | { 15 | private readonly IEnumerable _messageHandlerContainers; 16 | private readonly ILoggingService _loggingService; 17 | 18 | public MessageHandlingService( 19 | IMessageHandlerContainerBuilder messageHandlerContainerBuilder, 20 | ILoggingService loggingService) 21 | { 22 | _messageHandlerContainers = messageHandlerContainerBuilder.BuildCollection(); 23 | _loggingService = loggingService; 24 | } 25 | 26 | /// 27 | public async Task HandleMessageReceivingEvent(MessageHandlingContext context) 28 | { 29 | var eventArgs = context.Message; 30 | _loggingService.LogInformation($"A new message received with deliveryTag {eventArgs.DeliveryTag}."); 31 | var matchingRoutes = GetMatchingRoutePatterns(eventArgs.Exchange, eventArgs.RoutingKey); 32 | await ProcessMessageEvent(context, matchingRoutes).ConfigureAwait(false); 33 | if (context.AutoAckEnabled) 34 | { 35 | context.AcknowledgeMessage(); 36 | } 37 | _loggingService.LogInformation($"Message processing finished successfully. Acknowledge has been sent with deliveryTag {eventArgs.DeliveryTag}."); 38 | } 39 | 40 | private IEnumerable GetMatchingRoutePatterns(string exchange, string routingKey) 41 | { 42 | var tree = _messageHandlerContainers.FirstOrDefault(x => x.Exchange == exchange)?.Tree ?? 43 | _messageHandlerContainers.FirstOrDefault(x => x.IsGeneral)?.Tree; 44 | if (tree is null) 45 | { 46 | return Enumerable.Empty(); 47 | } 48 | 49 | var routingKeyParts = routingKey.Split("."); 50 | return WildcardExtensions.GetMatchingRoutePatterns(tree, routingKeyParts).ToList(); 51 | } 52 | 53 | private async Task ProcessMessageEvent(MessageHandlingContext context, IEnumerable matchingRoutes) 54 | { 55 | var container = _messageHandlerContainers.FirstOrDefault(x => x.Exchange == context.Message.Exchange) ?? 56 | _messageHandlerContainers.FirstOrDefault(x => x.IsGeneral); 57 | if (container is null) 58 | { 59 | return; 60 | } 61 | 62 | var messageHandlerOrderingContainers = new List(); 63 | foreach (var matchingRoute in matchingRoutes) 64 | { 65 | if (!container.MessageHandlers.ContainsKey(matchingRoute)) 66 | { 67 | continue; 68 | } 69 | 70 | var orderingContainers = container.MessageHandlers[matchingRoute] 71 | .Select(handler => new MessageHandlerOrderingContainer(handler, matchingRoute, container.GetOrderForHandler(handler))); 72 | messageHandlerOrderingContainers.AddRange(orderingContainers); 73 | } 74 | 75 | var executedHandlers = new List(); 76 | var orderedContainers = messageHandlerOrderingContainers.OrderByDescending(x => x.Order) 77 | .ThenByDescending(x => x.MessageHandler.GetHashCode()) 78 | .ToList(); 79 | foreach (var orderedContainer in orderedContainers) 80 | { 81 | var handlerType = orderedContainer.MessageHandler.GetType(); 82 | if (executedHandlers.Contains(handlerType)) 83 | { 84 | continue; 85 | } 86 | 87 | switch (orderedContainer.MessageHandler) 88 | { 89 | case IMessageHandler messageHandler: 90 | RunMessageHandler(messageHandler, context, orderedContainer.MatchingRoute); 91 | break; 92 | case IAsyncMessageHandler asyncMessageHandler: 93 | await RunAsyncMessageHandler(asyncMessageHandler, context, orderedContainer.MatchingRoute).ConfigureAwait(false); 94 | break; 95 | default: 96 | throw new NotSupportedException($"The type {orderedContainer.MessageHandler.GetType()} of message handler is not supported."); 97 | } 98 | 99 | executedHandlers.Add(handlerType); 100 | } 101 | } 102 | 103 | private void RunMessageHandler(IMessageHandler handler, MessageHandlingContext context, string matchingRoute) 104 | { 105 | ValidateMessageHandler(handler); 106 | _loggingService.LogDebug($"Starting processing the message by message handler {handler.GetType().Name}"); 107 | handler.Handle(context, matchingRoute); 108 | _loggingService.LogDebug($"The message has been processed by message handler {handler.GetType().Name}"); 109 | } 110 | 111 | private async Task RunAsyncMessageHandler(IAsyncMessageHandler handler, MessageHandlingContext context, string matchingRoute) 112 | { 113 | ValidateMessageHandler(handler); 114 | _loggingService.LogDebug($"Starting processing the message by async message handler {handler.GetType().Name}"); 115 | await handler.Handle(context, matchingRoute); 116 | _loggingService.LogDebug($"The message has been processed by async message handler {handler.GetType().Name}"); 117 | } 118 | 119 | private static void ValidateMessageHandler(T messageHandler) 120 | { 121 | if (messageHandler is null) 122 | { 123 | throw new ArgumentNullException(nameof(messageHandler), "Message handler is null."); 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /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()) 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()) 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()) 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 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Specifications/DuplicatedMessageHandlerDeclarationSpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using RabbitMQ.Client.Core.DependencyInjection.Models; 7 | 8 | namespace RabbitMQ.Client.Core.DependencyInjection.Specifications 9 | { 10 | /// 11 | /// Specification that represents duplication of same message handlers with different orders. 12 | /// 13 | internal class DuplicatedMessageHandlerDeclarationSpecification : Specification 14 | { 15 | private readonly Type _implementationType; 16 | private readonly IReadOnlyCollection _routePatterns; 17 | private readonly string? _exchange; 18 | private readonly int _order; 19 | 20 | internal DuplicatedMessageHandlerDeclarationSpecification( 21 | Type implementationType, 22 | IReadOnlyCollection routePatterns, 23 | string? exchange, 24 | int order) 25 | { 26 | _implementationType = implementationType; 27 | _routePatterns = routePatterns; 28 | _exchange = exchange; 29 | _order = order; 30 | } 31 | 32 | protected override Expression> ToExpression() 33 | { 34 | return x => x.ServiceType == typeof(MessageHandlerOrderingModel) && 35 | x.Lifetime == ServiceLifetime.Singleton && 36 | ((MessageHandlerOrderingModel)x.ImplementationInstance).MessageHandlerType == _implementationType && 37 | (string.Equals(((MessageHandlerOrderingModel)x.ImplementationInstance).Exchange, _exchange, StringComparison.OrdinalIgnoreCase) || 38 | (_exchange != null && ((MessageHandlerOrderingModel)x.ImplementationInstance).Exchange != null)) && 39 | (((MessageHandlerOrderingModel)x.ImplementationInstance)!).Order != _order && 40 | _routePatterns.Intersect(((MessageHandlerOrderingModel)x.ImplementationInstance).RoutePatterns).Any(); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Specifications/DuplicatedRabbitMqExchangeDeclarationSpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using RabbitMQ.Client.Core.DependencyInjection.Models; 5 | 6 | namespace RabbitMQ.Client.Core.DependencyInjection.Specifications 7 | { 8 | /// 9 | /// Specification that represents rabbitMq exchange duplications. 10 | /// 11 | internal class DuplicatedRabbitMqExchangeDeclarationSpecification : Specification 12 | { 13 | private readonly string _exchangeName; 14 | private readonly ClientExchangeType _exchangeType; 15 | 16 | internal DuplicatedRabbitMqExchangeDeclarationSpecification(string exchangeName, ClientExchangeType exchangeType) 17 | { 18 | _exchangeName = exchangeName; 19 | _exchangeType = exchangeType; 20 | } 21 | 22 | protected override Expression> ToExpression() 23 | { 24 | return x => x.ServiceType == typeof(RabbitMqExchange) && 25 | x.Lifetime == ServiceLifetime.Singleton && 26 | string.Equals(((ExchangeServiceDescriptor)x).ExchangeName, _exchangeName, StringComparison.OrdinalIgnoreCase) && 27 | (_exchangeType == ((ExchangeServiceDescriptor)x).ClientExchangeType || 28 | ((ExchangeServiceDescriptor)x).ClientExchangeType == ClientExchangeType.Universal); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Specifications/Specification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace RabbitMQ.Client.Core.DependencyInjection.Specifications 5 | { 6 | internal abstract class Specification 7 | { 8 | protected abstract Expression> ToExpression(); 9 | 10 | internal bool IsSatisfiedBy(T entity) 11 | { 12 | Func predicate = ToExpression().Compile(); 13 | return predicate(entity); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Specifications/ValidDeadLetterExchangeTypeSpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Runtime.CompilerServices; 4 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 5 | 6 | [assembly:InternalsVisibleTo("RabbitMQ.Client.Core.DependencyInjection.Tests")] 7 | namespace RabbitMQ.Client.Core.DependencyInjection.Specifications 8 | { 9 | /// 10 | /// Specification that validates dead letter exchange types. 11 | /// 12 | /// 13 | /// Dead letter exchange type is being validated only of RequeueFailedMessages is enabled. 14 | /// 15 | internal class ValidDeadLetterExchangeTypeSpecification : Specification 16 | { 17 | protected override Expression> ToExpression() 18 | { 19 | var allowedExchangeTypes = ExchangeType.All()!; 20 | // Do not validate dead letter exchange type if actual DeadLetterExchange is empty. 21 | return options => string.IsNullOrEmpty(options.DeadLetterExchange) || allowedExchangeTypes.Contains(options.DeadLetterExchangeType); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/RabbitMQ.Client.Core.DependencyInjection/Specifications/ValidExchangeTypeSpecification.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Runtime.CompilerServices; 4 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 5 | 6 | [assembly:InternalsVisibleTo("RabbitMQ.Client.Core.DependencyInjection.Tests")] 7 | namespace RabbitMQ.Client.Core.DependencyInjection.Specifications 8 | { 9 | /// 10 | /// Specification that validates exchange types. 11 | /// 12 | internal class ValidExchangeTypeSpecification : Specification 13 | { 14 | protected override Expression> ToExpression() 15 | { 16 | var allowedExchangeTypes = ExchangeType.All()!; 17 | return options => allowedExchangeTypes.Contains(options.Type); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /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.InternalExtensions.Validation; 10 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 11 | using RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs; 12 | using Xunit; 13 | 14 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.IntegrationTests 15 | { 16 | [SuppressMessage("ReSharper", "AccessToDisposedClosure")] 17 | public class RabbitMqServicesTests 18 | { 19 | private readonly TimeSpan _globalTestsTimeout = TimeSpan.FromSeconds(60); 20 | 21 | private const string DefaultExchangeName = "exchange.name"; 22 | private const string FirstRoutingKey = "first.routing.key"; 23 | private const string SecondRoutingKey = "second.routing.key"; 24 | private const int RequeueAttempts = 4; 25 | 26 | [Fact] 27 | public async Task ShouldProperlyPublishAndConsumeMessages() 28 | { 29 | var callerMock = new Mock(); 30 | var serviceCollection = new ServiceCollection(); 31 | serviceCollection 32 | .AddSingleton(callerMock.Object) 33 | .AddRabbitMqServices(GetClientOptions()) 34 | .AddExchange(DefaultExchangeName, GetExchangeOptions()) 35 | .AddMessageHandlerTransient(FirstRoutingKey) 36 | .AddAsyncMessageHandlerTransient(SecondRoutingKey); 37 | 38 | await using var serviceProvider = serviceCollection.BuildServiceProvider(); 39 | var consumingService = serviceProvider.GetRequiredService(); 40 | var producingService = serviceProvider.GetRequiredService(); 41 | var channelDeclarationService = serviceProvider.GetRequiredService(); 42 | 43 | channelDeclarationService.SetConnectionInfrastructureForRabbitMqServices(); 44 | consumingService.StartConsuming(); 45 | using var resetEvent = new AutoResetEvent(false); 46 | consumingService.Consumer.EnsureIsNotNull().Received += (_, _) => 47 | { 48 | resetEvent.Set(); 49 | return Task.CompletedTask; 50 | }; 51 | 52 | await producingService.SendAsync(new { Message = "message" }, DefaultExchangeName, FirstRoutingKey); 53 | resetEvent.WaitOne(_globalTestsTimeout); 54 | callerMock.Verify(x => x.Call(It.IsAny()), Times.Once); 55 | 56 | await producingService.SendAsync(new { Message = "message" }, DefaultExchangeName, SecondRoutingKey); 57 | resetEvent.WaitOne(_globalTestsTimeout); 58 | callerMock.Verify(x => x.CallAsync(It.IsAny()), Times.Once); 59 | } 60 | 61 | [Fact] 62 | public async Task ShouldProperlyRequeueMessages() 63 | { 64 | var callerMock = new Mock(); 65 | var serviceCollection = new ServiceCollection(); 66 | serviceCollection 67 | .AddSingleton(callerMock.Object) 68 | .AddRabbitMqServices(GetClientOptions()) 69 | .AddExchange(DefaultExchangeName, GetExchangeOptions()) 70 | .AddMessageHandlerTransient(FirstRoutingKey); 71 | 72 | await using var serviceProvider = serviceCollection.BuildServiceProvider(); 73 | var consumingService = serviceProvider.GetRequiredService(); 74 | var producingService = serviceProvider.GetRequiredService(); 75 | var channelDeclarationService = serviceProvider.GetRequiredService(); 76 | 77 | channelDeclarationService.SetConnectionInfrastructureForRabbitMqServices(); 78 | consumingService.StartConsuming(); 79 | using var resetEvent = new AutoResetEvent(false); 80 | consumingService.Consumer.EnsureIsNotNull().Received += (_, _) => 81 | { 82 | resetEvent.Set(); 83 | return Task.CompletedTask; 84 | }; 85 | 86 | await producingService.SendAsync(new { Message = "message" }, DefaultExchangeName, FirstRoutingKey); 87 | 88 | for (var i = 1; i <= RequeueAttempts + 1; i++) 89 | { 90 | resetEvent.WaitOne(_globalTestsTimeout); 91 | } 92 | callerMock.Verify(x => x.Call(It.IsAny()), Times.Exactly(RequeueAttempts + 1)); 93 | } 94 | 95 | private static RabbitMqServiceOptions GetClientOptions() => 96 | new() 97 | { 98 | HostName = "rabbitmq", 99 | Port = 5672, 100 | UserName = "guest", 101 | Password = "guest", 102 | VirtualHost = "/" 103 | }; 104 | 105 | private static RabbitMqExchangeOptions GetExchangeOptions() => 106 | new() 107 | { 108 | Type = "direct", 109 | DeadLetterExchange = "exchange.dlx", 110 | RequeueAttempts = RequeueAttempts, 111 | RequeueTimeoutMilliseconds = 50, 112 | Queues = new List 113 | { 114 | new() 115 | { 116 | Name = "test.queue", 117 | RoutingKeys = new HashSet { FirstRoutingKey, SecondRoutingKey } 118 | } 119 | } 120 | }; 121 | } 122 | } -------------------------------------------------------------------------------- /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.Core.DependencyInjection.Models; 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(MessageHandlingContext context, string matchingRoute) 17 | { 18 | await _caller.CallAsync($"{context.Message.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 RabbitMQ.Client.Core.DependencyInjection.Middlewares; 6 | using RabbitMQ.Client.Core.DependencyInjection.Models; 7 | using RabbitMQ.Client.Core.DependencyInjection.Services; 8 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 9 | using RabbitMQ.Client.Events; 10 | 11 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 12 | { 13 | public class StubBaseBatchMessageHandler : BaseBatchMessageHandler 14 | { 15 | private readonly IStubCaller _caller; 16 | 17 | public StubBaseBatchMessageHandler( 18 | IStubCaller caller, 19 | IRabbitMqConnectionFactory rabbitMqConnectionFactory, 20 | IEnumerable batchConsumerConnectionOptions, 21 | IEnumerable batchMessageHandlingMiddlewares, 22 | ILoggingService loggingService) 23 | : base(rabbitMqConnectionFactory, batchConsumerConnectionOptions, batchMessageHandlingMiddlewares, loggingService) 24 | { 25 | _caller = caller; 26 | } 27 | 28 | public override ushort PrefetchCount { get; set; } 29 | 30 | public override string QueueName { get; set; } 31 | 32 | public override TimeSpan? MessageHandlingPeriod { get; set; } 33 | 34 | public override Task HandleMessages(IEnumerable messages, CancellationToken cancellationToken) 35 | { 36 | foreach (var message in messages) 37 | { 38 | _caller.Call(message.Body); 39 | } 40 | _caller.EmptyCall(); 41 | return Task.CompletedTask; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubBatchMessageHandlingMiddleware.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.Middlewares; 7 | using RabbitMQ.Client.Events; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 10 | { 11 | public class StubBatchMessageHandlingMiddleware : IBatchMessageHandlingMiddleware 12 | { 13 | public int Number { get; } 14 | 15 | private readonly Dictionary _orderingMap; 16 | 17 | public StubBatchMessageHandlingMiddleware(int messageHandlerNumber, Dictionary orderingMap) 18 | { 19 | if (!orderingMap.ContainsKey(messageHandlerNumber)) 20 | { 21 | orderingMap.Add(messageHandlerNumber, 0); 22 | } 23 | 24 | _orderingMap = orderingMap; 25 | Number = messageHandlerNumber; 26 | } 27 | 28 | public async Task Handle(IEnumerable messages, Func next, CancellationToken cancellationToken) 29 | { 30 | var order = _orderingMap.Values.Max(); 31 | _orderingMap[Number] = order + 1; 32 | await 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/StubErrorProcessingService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using RabbitMQ.Client.Core.DependencyInjection.Models; 4 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 5 | 6 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 7 | { 8 | public class StubErrorProcessingService : IErrorProcessingService 9 | { 10 | public Task HandleMessageProcessingFailure(MessageHandlingContext context, Exception exception) 11 | { 12 | return Task.CompletedTask; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubExceptionMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RabbitMQ.Client.Core.DependencyInjection.MessageHandlers; 3 | using RabbitMQ.Client.Core.DependencyInjection.Models; 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(MessageHandlingContext context, string matchingRoute) 17 | { 18 | _caller.Call($"{context.Message.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.Core.DependencyInjection.Models; 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(MessageHandlingContext context, string matchingRoute) 16 | { 17 | _caller.Call($"{context.Message.GetMessage()}:{matchingRoute}"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/Stubs/StubMessageHandlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using RabbitMQ.Client.Core.DependencyInjection.Middlewares; 6 | using RabbitMQ.Client.Core.DependencyInjection.Models; 7 | 8 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs 9 | { 10 | public class StubMessageHandlingMiddleware : IMessageHandlingMiddleware 11 | { 12 | public int Number { get; } 13 | 14 | private readonly Dictionary _orderingMap; 15 | private readonly Dictionary _errorOrderingMap; 16 | 17 | public StubMessageHandlingMiddleware(int messageHandlerNumber, Dictionary orderingMap, Dictionary errorOrderingMap) 18 | { 19 | if (!orderingMap.ContainsKey(messageHandlerNumber)) 20 | { 21 | orderingMap.Add(messageHandlerNumber, 0); 22 | } 23 | 24 | if (!errorOrderingMap.ContainsKey(messageHandlerNumber)) 25 | { 26 | errorOrderingMap.Add(messageHandlerNumber, 0); 27 | } 28 | 29 | _orderingMap = orderingMap; 30 | _errorOrderingMap = errorOrderingMap; 31 | 32 | Number = messageHandlerNumber; 33 | } 34 | 35 | public async Task Handle(MessageHandlingContext context, Func next) 36 | { 37 | var order = _orderingMap.Values.Max(); 38 | _orderingMap[Number] = order + 1; 39 | await next(); 40 | } 41 | 42 | public async Task HandleError(MessageHandlingContext context, Exception exception, Func next) 43 | { 44 | var order = _errorOrderingMap.Values.Max(); 45 | _errorOrderingMap[Number] = order + 1; 46 | await next(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /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/ErrorProcessingServiceDependencyInjectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.DependencyInjection.Extensions; 4 | using RabbitMQ.Client.Core.DependencyInjection.Services; 5 | using RabbitMQ.Client.Core.DependencyInjection.Services.Interfaces; 6 | using RabbitMQ.Client.Core.DependencyInjection.Tests.Stubs; 7 | using Xunit; 8 | 9 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 10 | { 11 | public class ErrorProcessingServiceDependencyInjectionExtensionsTests 12 | { 13 | [Fact] 14 | public void ShouldProperlyRegisterCustomErrorProcessingService() 15 | { 16 | var collection = new ServiceCollection(); 17 | collection.TryAddSingleton(); 18 | collection.AddCustomMessageHandlingErrorProcessingService(); 19 | 20 | var errorProcessingServices = collection.Where(x => x.ServiceType == typeof(IErrorProcessingService)).ToList(); 21 | Assert.Single(errorProcessingServices); 22 | var errorProcessingServiceDescriptor = errorProcessingServices.FirstOrDefault(); 23 | Assert.NotNull(errorProcessingServiceDescriptor); 24 | Assert.Equal(typeof(StubErrorProcessingService), errorProcessingServiceDescriptor.ImplementationType); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /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.Middlewares; 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.Core.DependencyInjection.Tests.Stubs; 11 | using RabbitMQ.Client.Events; 12 | using Xunit; 13 | 14 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 15 | { 16 | public class MessageHandlingPipelineExecutingServiceTests 17 | { 18 | [Fact] 19 | public async Task ShouldProperlyExecutePipelineWithNoAdditionalMiddlewares() 20 | { 21 | var argsMock = new Mock(); 22 | var messageHandlingServiceMock = new Mock(); 23 | var errorProcessingServiceMock = new Mock(); 24 | 25 | var service = CreateService( 26 | messageHandlingServiceMock.Object, 27 | errorProcessingServiceMock.Object, 28 | Enumerable.Empty()); 29 | 30 | var context = new MessageHandlingContext(argsMock.Object, AckAction, false); 31 | await service.Execute(context); 32 | messageHandlingServiceMock.Verify(x => x.HandleMessageReceivingEvent(It.IsAny()), Times.Once); 33 | } 34 | 35 | [Fact] 36 | public async Task ShouldProperlyExecutePipeline() 37 | { 38 | var argsMock = new Mock(); 39 | var messageHandlingServiceMock = new Mock(); 40 | var errorProcessingServiceMock = new Mock(); 41 | 42 | var middlewareOrderingMap = new Dictionary(); 43 | var firstMiddleware = new StubMessageHandlingMiddleware(1, middlewareOrderingMap, new Dictionary()); 44 | var secondMiddleware = new StubMessageHandlingMiddleware(2, middlewareOrderingMap, new Dictionary()); 45 | var thirdMiddleware = new StubMessageHandlingMiddleware(3, middlewareOrderingMap, new Dictionary()); 46 | var middlewares = new List 47 | { 48 | firstMiddleware, 49 | secondMiddleware, 50 | thirdMiddleware 51 | }; 52 | 53 | var service = CreateService( 54 | messageHandlingServiceMock.Object, 55 | errorProcessingServiceMock.Object, 56 | middlewares); 57 | var context = new MessageHandlingContext(argsMock.Object, AckAction, false); 58 | await service.Execute(context); 59 | 60 | messageHandlingServiceMock.Verify(x => x.HandleMessageReceivingEvent(It.IsAny()), Times.Once); 61 | Assert.Equal(1, middlewareOrderingMap[thirdMiddleware.Number]); 62 | Assert.Equal(2, middlewareOrderingMap[secondMiddleware.Number]); 63 | Assert.Equal(3, middlewareOrderingMap[firstMiddleware.Number]); 64 | } 65 | 66 | [Fact] 67 | public async Task ShouldProperlyExecuteFailurePipelineWhenMessageHandlingServiceThrowsException() 68 | { 69 | var argsMock = new Mock(); 70 | var exception = new Exception(); 71 | var messageHandlingServiceMock = new Mock(); 72 | messageHandlingServiceMock.Setup(x => x.HandleMessageReceivingEvent(It.IsAny())) 73 | .ThrowsAsync(exception); 74 | var errorProcessingServiceMock = new Mock(); 75 | 76 | var middlewareOrderingMap = new Dictionary(); 77 | var firstMiddleware = new StubMessageHandlingMiddleware(1, new Dictionary(), middlewareOrderingMap); 78 | var secondMiddleware = new StubMessageHandlingMiddleware(2, new Dictionary(), middlewareOrderingMap); 79 | var thirdMiddleware = new StubMessageHandlingMiddleware(3, new Dictionary(), middlewareOrderingMap); 80 | var middlewares = new List 81 | { 82 | firstMiddleware, 83 | secondMiddleware, 84 | thirdMiddleware 85 | }; 86 | 87 | var service = CreateService( 88 | messageHandlingServiceMock.Object, 89 | errorProcessingServiceMock.Object, 90 | middlewares); 91 | var context = new MessageHandlingContext(argsMock.Object, AckAction, false); 92 | await service.Execute(context); 93 | 94 | errorProcessingServiceMock.Verify(x => x.HandleMessageProcessingFailure(It.IsAny(), exception), Times.Once); 95 | Assert.Equal(1, middlewareOrderingMap[thirdMiddleware.Number]); 96 | Assert.Equal(2, middlewareOrderingMap[secondMiddleware.Number]); 97 | Assert.Equal(3, middlewareOrderingMap[firstMiddleware.Number]); 98 | } 99 | 100 | private static IMessageHandlingPipelineExecutingService CreateService( 101 | IMessageHandlingService messageHandlingService, 102 | IErrorProcessingService errorProcessingService, 103 | IEnumerable middlewares) => 104 | new MessageHandlingPipelineExecutingService(messageHandlingService, errorProcessingService, middlewares); 105 | 106 | private static void AckAction(BasicDeliverEventArgs message) { } 107 | } 108 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/ProducingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 4 | using RabbitMQ.Client.Core.DependencyInjection.Models; 5 | using RabbitMQ.Client.Core.DependencyInjection.Services; 6 | using Xunit; 7 | 8 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 9 | { 10 | public class ProducingServiceTests 11 | { 12 | [Fact] 13 | public void ShouldProperlyThrowExceptionWhenThereAreNoProductionExchanges() 14 | { 15 | var consumptionExchange = new RabbitMqExchange("exchange", ClientExchangeType.Consumption, new RabbitMqExchangeOptions()); 16 | var service = CreateService(new[] { consumptionExchange }); 17 | Assert.Throws(() => service.ValidateArguments("another.exchange", "routing.key")); 18 | } 19 | 20 | [Fact] 21 | public void ShouldProperlyThrowExceptionWhenThereIsAnExchangeWithSameNameButWithConsumptionType() 22 | { 23 | const string exchangeName = "exchange"; 24 | var consumptionExchange = new RabbitMqExchange(exchangeName, ClientExchangeType.Consumption, new RabbitMqExchangeOptions()); 25 | var service = CreateService(new[] { consumptionExchange }); 26 | Assert.Throws(() => service.ValidateArguments(exchangeName, "routing.key")); 27 | } 28 | 29 | [Fact] 30 | public void ShouldProperlyValidateWhenThereIsProductionExchange() 31 | { 32 | const string exchangeName = "exchange"; 33 | var consumptionExchange = new RabbitMqExchange(exchangeName, ClientExchangeType.Production, new RabbitMqExchangeOptions()); 34 | var service = CreateService(new[] { consumptionExchange }); 35 | service.ValidateArguments(exchangeName, "routing.key"); 36 | } 37 | 38 | [Fact] 39 | public void ShouldProperlyValidateWhenThereIsUiversalExchange() 40 | { 41 | const string exchangeName = "exchange"; 42 | var consumptionExchange = new RabbitMqExchange(exchangeName, ClientExchangeType.Universal, new RabbitMqExchangeOptions()); 43 | var service = CreateService(new[] { consumptionExchange }); 44 | service.ValidateArguments(exchangeName, "routing.key"); 45 | } 46 | 47 | private ProducingService CreateService(IEnumerable exchanges) 48 | { 49 | return new ProducingService(exchanges); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/RabbitMqExchangeDependencyInjectionExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Moq; 7 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 8 | using RabbitMQ.Client.Core.DependencyInjection.Exceptions; 9 | using RabbitMQ.Client.Core.DependencyInjection.Models; 10 | using Xunit; 11 | 12 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 13 | { 14 | [SuppressMessage("ReSharper", "RedundantArgumentDefaultValue")] 15 | public class RabbitMqExchangeDependencyInjectionExtensionsTests 16 | { 17 | [Fact] 18 | public void ShouldProperlyThrowExceptionWhenRegisteringSameExchangeOfSameTypeTwice() 19 | { 20 | Assert.Throws( 21 | () => 22 | { 23 | new ServiceCollection() 24 | .AddExchange("exchange.name", new RabbitMqExchangeOptions(), ClientExchangeType.Consumption) 25 | .AddExchange("exchange.name", new RabbitMqExchangeOptions(), ClientExchangeType.Consumption); 26 | }); 27 | } 28 | 29 | [Fact] 30 | public void ShouldProperlyNotThrowExceptionWhenRegisteringSameExchangeWithDifferentTypeTwice() 31 | { 32 | new ServiceCollection() 33 | .AddConsumptionExchange("exchange.name", new RabbitMqExchangeOptions()) 34 | .AddProductionExchange("exchange.name", new RabbitMqExchangeOptions()); 35 | } 36 | 37 | [Fact] 38 | public async Task ShouldProperlyThrowExceptionWhenRegisteringSameExchangeWithSameNameAndConfigurationTwice() 39 | { 40 | await Assert.ThrowsAsync(() => 41 | { 42 | var configurationMock = new Mock(); 43 | new ServiceCollection() 44 | .AddExchange("exchange.name", configurationMock.Object, ClientExchangeType.Consumption) 45 | .AddExchange("exchange.name", configurationMock.Object, ClientExchangeType.Consumption); 46 | return Task.CompletedTask; 47 | }); 48 | } 49 | 50 | [Fact] 51 | public void ShouldProperlyNotThrowExceptionWhenRegisteringSameExchangeWithDifferentTypeButSameConfigurationTwice() 52 | { 53 | var configurationMock = new Mock(); 54 | new ServiceCollection() 55 | .AddConsumptionExchange("exchange.name", configurationMock.Object) 56 | .AddProductionExchange("exchange.name", configurationMock.Object); 57 | } 58 | 59 | [Fact] 60 | public void ShouldProperlyThrowExceptionWhenRegisteringConsumptionExchangeWhenUniversalExchangeWithSameNameAlreadyAdded() 61 | { 62 | Assert.Throws( 63 | () => 64 | { 65 | new ServiceCollection() 66 | .AddExchange("exchange.name", new RabbitMqExchangeOptions(), ClientExchangeType.Universal) 67 | .AddExchange("exchange.name", new RabbitMqExchangeOptions(), ClientExchangeType.Consumption); 68 | }); 69 | } 70 | 71 | [Fact] 72 | public void ShouldProperlyThrowExceptionWhenRegisteringProductionExchangeWhenUniversalExchangeWithSameNameAlreadyAdded() 73 | { 74 | Assert.Throws( 75 | () => 76 | { 77 | new ServiceCollection() 78 | .AddExchange("exchange.name", new RabbitMqExchangeOptions(), ClientExchangeType.Universal) 79 | .AddExchange("exchange.name", new RabbitMqExchangeOptions(), ClientExchangeType.Production); 80 | }); 81 | } 82 | 83 | [Fact] 84 | public void ShouldProperlyThrowExceptionWhenRegisteringExchangeWithWrongType() 85 | { 86 | Assert.Throws( 87 | () => 88 | { 89 | new ServiceCollection() 90 | .AddExchange("exchange.name", new RabbitMqExchangeOptions { Type = "wrong.type" }, ClientExchangeType.Universal); 91 | }); 92 | } 93 | 94 | [Fact] 95 | public void ShouldProperlyThrowExceptionWhenRegisteringDeadLetterExchangeWithWrongType() 96 | { 97 | Assert.Throws( 98 | () => 99 | { 100 | new ServiceCollection() 101 | .AddExchange("exchange.name", new RabbitMqExchangeOptions { RequeueFailedMessages = true, DeadLetterExchangeType = "wrong.type" }, ClientExchangeType.Universal); 102 | }); 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /tests/RabbitMQ.Client.Core.DependencyInjection.Tests/UnitTests/SpecificationTests.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Core.DependencyInjection.Configuration; 2 | using RabbitMQ.Client.Core.DependencyInjection.Specifications; 3 | using Xunit; 4 | 5 | namespace RabbitMQ.Client.Core.DependencyInjection.Tests.UnitTests 6 | { 7 | public class SpecificationTests 8 | { 9 | [Theory] 10 | [InlineData(ExchangeType.Direct, true)] 11 | [InlineData(ExchangeType.Topic, true)] 12 | [InlineData(ExchangeType.Fanout, true)] 13 | [InlineData(ExchangeType.Headers, true)] 14 | [InlineData("wrong.type", false)] 15 | public void ShouldProperlyValidateExchangeType(string type, bool expectedResult) 16 | { 17 | var specification = new ValidExchangeTypeSpecification(); 18 | var options = new RabbitMqExchangeOptions 19 | { 20 | Type = type 21 | }; 22 | var result = specification.IsSatisfiedBy(options); 23 | Assert.Equal(expectedResult, result); 24 | } 25 | 26 | [Theory] 27 | [InlineData("exchange", ExchangeType.Direct, true)] 28 | [InlineData("exchange", ExchangeType.Topic, true)] 29 | [InlineData("exchange", ExchangeType.Fanout, true)] 30 | [InlineData("exchange", ExchangeType.Headers, true)] 31 | [InlineData("exchange", "wrong.type", false)] 32 | [InlineData(null, "wrong.type", true)] 33 | [InlineData("", "wrong.type", true)] 34 | [InlineData("", ExchangeType.Direct, true)] 35 | [InlineData("", ExchangeType.Topic, true)] 36 | [InlineData("", ExchangeType.Fanout, true)] 37 | [InlineData("", ExchangeType.Headers, true)] 38 | public void ShouldProperlyValidateDeadLetterExchangeTypeWhenResendingFailedMessagesIsEnabled(string exchangeName, string type, bool expectedResult) 39 | { 40 | var specification = new ValidDeadLetterExchangeTypeSpecification(); 41 | var options = new RabbitMqExchangeOptions 42 | { 43 | DeadLetterExchange = exchangeName, 44 | DeadLetterExchangeType = type 45 | }; 46 | var result = specification.IsSatisfiedBy(options); 47 | Assert.Equal(expectedResult, result); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /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 --------------------------------------------------------------------------------