├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build-and-publish.yml │ ├── build-and-test.yml │ └── snyk-test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Examples ├── Example.Application.Testing │ ├── Example.Application.Testing.csproj │ ├── ExampleService.cs │ ├── FakeReceiverMessage.cs │ ├── FizzBuzzEngine.cs │ ├── IDatabase.cs │ ├── README.md │ └── Tests │ │ ├── ExampleServiceTests.cs │ │ └── FizzBuzzEngineTests.cs ├── Example.Common.Tests │ ├── CommandSendingServiceTests.cs │ ├── DataSendingServiceTests.cs │ ├── Example.Common.Tests.csproj │ ├── FakeReceiverMessage.cs │ ├── ReceivingServiceTests.cs │ ├── SendingServiceTests.cs │ └── SingleMessageServiceTests.cs ├── Example.Common │ ├── Casing.cs │ ├── CommandSendingService.cs │ ├── DataSendingService.cs │ ├── Example.Common.csproj │ ├── ReceivingService.cs │ ├── SendingService.cs │ └── SingleMessageService.cs ├── Example.Messaging.CloudEvents │ ├── Example.Messaging.CloudEvents.csproj │ ├── ExampleOptions.cs │ ├── ExampleService.cs │ ├── Program.cs │ └── appsettings.json ├── Example.Messaging.Http │ ├── Example.Messaging.Http.csproj │ ├── Program.cs │ └── appsettings.json ├── Example.Messaging.Kafka │ ├── Example.Messaging.Kafka.csproj │ ├── Program.cs │ └── appsettings.json ├── Example.Messaging.NamedPipes │ ├── Example.Messaging.NamedPipes.csproj │ ├── Program.cs │ └── appsettings.json ├── Example.Messaging.RabbitMQ │ ├── Example.Messaging.RabbitMQ.csproj │ ├── Program.cs │ └── appsettings.json ├── Example.Messaging.SNS │ ├── Example.Messaging.SNS.csproj │ └── Program.cs └── Example.Messaging.SQS │ ├── Example.Messaging.SQS.csproj │ ├── Program.cs │ └── appsettings.json ├── LICENSE.md ├── README.md ├── RockLib.Messaging.All.sln ├── RockLib.Messaging.CloudEvents ├── .editorconfig ├── CHANGELOG.md ├── CloudEvent.cs ├── CloudEventExtensions.CopyConstructor.cs ├── CloudEventExtensions.MessageConstructor.cs ├── CloudEventExtensions.ValidateMethod.cs ├── CloudEventExtensions.cs ├── CloudEventValidationException.cs ├── CorrelatedEvent.cs ├── Correlating │ └── CorrelatingExtensions.cs ├── DataSerialization.cs ├── Directory.Build.props ├── HttpUtils.cs ├── IProtocolBinding.cs ├── PartitionedEvent.cs ├── Partitioning │ └── PartitioningExtensions.cs ├── ProtocolBindings.cs ├── RockLib.Messaging.CloudEvents.csproj ├── RockLib.Messaging.CloudEvents.sln ├── SequenceTypes.cs ├── Sequencing │ └── SequencingExtensions.cs └── SequentialEvent.cs ├── RockLib.Messaging.Http ├── .editorconfig ├── CHANGELOG.md ├── DefaultHttpResponseGenerator.cs ├── Directory.Build.props ├── HttpClientSender.cs ├── HttpListenerReceiver.cs ├── HttpListenerReceiverMessage.cs ├── HttpResponse.cs ├── HttpUtils.cs ├── IHttpResponseGenerator.cs ├── RequiredHttpRequestHeaders.cs ├── RockLib.Messaging.Http.csproj └── RockLib.Messaging.Http.sln ├── RockLib.Messaging.Kafka ├── .editorconfig ├── CHANGELOG.md ├── Constants.cs ├── DependencyInjection │ ├── KafkaExtensions.cs │ ├── KafkaReceiverOptions.cs │ └── KafkaSenderOptions.cs ├── Directory.Build.props ├── Exceptions │ └── InvalidMessageException.cs ├── KafkaReceiver.cs ├── KafkaReceiverMessage.cs ├── KafkaSender.cs ├── MessageExtensions.cs ├── RockLib.Messaging.Kafka.csproj ├── RockLib.Messaging.Kafka.sln └── StatisticsExtensions.cs ├── RockLib.Messaging.NamedPipes ├── .editorconfig ├── CHANGELOG.md ├── DependencyInjection │ ├── NamedPipeExtensions.cs │ └── NamedPipeOptions.cs ├── Directory.Build.props ├── NamedPipeMessage.cs ├── NamedPipeMessageSerializer.cs ├── NamedPipeReceiver.cs ├── NamedPipeReceiverMessage.cs ├── NamedPipeSender.cs ├── RockLib.Messaging.NamedPipes.csproj └── RockLib.Messaging.NamedPipes.sln ├── RockLib.Messaging.RabbitMQ ├── .editorconfig ├── CHANGELOG.md ├── Directory.Build.props ├── RabbitReceiver.cs ├── RabbitReceiverMessage.cs ├── RabbitSender.cs ├── RockLib.Messaging.RabbitMQ.csproj └── RockLib.Messaging.RabbitMQ.sln ├── RockLib.Messaging.SNS ├── .editorconfig ├── CHANGELOG.md ├── DependencyInjection │ ├── SNSExtensions.cs │ └── SNSSenderOptions.cs ├── Directory.Build.props ├── RockLib.Messaging.SNS.csproj ├── RockLib.Messaging.SNS.sln └── SNSSender.cs ├── RockLib.Messaging.SQS ├── .editorconfig ├── CHANGELOG.md ├── DependencyInjection │ ├── SQSExtensions.cs │ ├── SQSReceiverOptions.cs │ └── SQSSenderOptions.cs ├── Directory.Build.props ├── InternalsVisibleTo.cs ├── RockLib.Messaging.SQS.csproj ├── RockLib.Messaging.SQS.sln ├── SQSReceiver.cs ├── SQSReceiverMessage.cs └── SQSSender.cs ├── RockLib.Messaging ├── .editorconfig ├── AssemblyAttributes.cs ├── CHANGELOG.md ├── DependencyInjection │ ├── DecorationDelegates.cs │ ├── ForwardingExtensions.cs │ ├── ForwardingReceiverOptions.cs │ ├── IForwardingReceiverOptions.cs │ ├── IReceiverBuilder.cs │ ├── ISenderBuilder.cs │ ├── ITransactionalSenderBuilder.cs │ ├── LookupDelegates.cs │ ├── MessagingExtensions.cs │ ├── ReceiverBuilder.cs │ ├── RegistrationDelegates.cs │ ├── ReloadingReceiver.cs │ ├── ReloadingSender.cs │ ├── SenderBuilder.cs │ ├── TransactionalSenderBuilder.cs │ └── ValidationExtensions.cs ├── Directory.Build.props ├── DisconnectedEventArgs.cs ├── ErrorEventArgs.cs ├── ForwardingMessageHandler.cs ├── ForwardingOutcome.cs ├── ForwardingReceiver.cs ├── ForwardingReceiverMessage.cs ├── GZipCompressor.cs ├── GZipDecompressor.cs ├── HeaderDictionary.cs ├── HeaderNames.cs ├── IMessageHandler.cs ├── IReceiver.cs ├── IReceiverMessage.cs ├── ISender.cs ├── ISenderTransaction.cs ├── ITransactionalSender.cs ├── InternalVisibleTo.cs ├── MessagingScenarioFactory.cs ├── NullableAttributes.cs ├── OnMessageReceivedDelegate.cs ├── Receiver.cs ├── ReceiverExtensions.cs ├── ReceiverMessage.cs ├── ReceiverMessageExtensions.cs ├── RockLib.Messaging.csproj ├── RockLib.Messaging.sln ├── SenderExtensions.cs ├── SenderMessage.cs ├── ValidatingSender.cs ├── ValidatingSenderTransaction.cs └── ValidatingTransactionalSender.cs ├── Tests ├── RockLib.Messaging.CloudEvents.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── CloudEventExtensionsTests.cs │ ├── CloudEventTests.cs │ ├── CorrelatedEventTests.cs │ ├── CorrelatingExtensionsTests.cs │ ├── Directory.Build.props │ ├── FakeReceiverMessage.cs │ ├── MockExtensions.cs │ ├── PartitionedEventTests.cs │ ├── PartitioningExtensionsTests.cs │ ├── ProtocolBindingsTests.cs │ ├── RockLib.Messaging.CloudEvents.Tests.csproj │ ├── SequencingExtensionsTests.cs │ └── SequentialEventTests.cs ├── RockLib.Messaging.Http.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── Directory.Build.props │ ├── HttpTests.cs │ ├── Properties │ │ └── launchSettings.json │ └── RockLib.Messaging.Http.Tests.csproj ├── RockLib.Messaging.Kafka.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── DependencyInjectionTests.cs │ ├── Directory.Build.props │ ├── KafkaReceiverTests.cs │ ├── KafkaSenderTests.cs │ ├── RockLib.Messaging.Kafka.Tests.csproj │ └── StatisticsExtensionsTests.cs ├── RockLib.Messaging.NamedPipes.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── DependencyInjectionTests.cs │ ├── Directory.Build.props │ ├── NamedPipesTests.cs │ └── RockLib.Messaging.NamedPipes.Tests.csproj ├── RockLib.Messaging.RabbitMQ.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── Directory.Build.props │ ├── RabbitReceiverTests.cs │ ├── RabbitSenderTests.cs │ └── RockLib.Messaging.RabbitMQ.Tests.csproj ├── RockLib.Messaging.SNS.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── DependencyInjectionTests.cs │ ├── Directory.Build.props │ ├── RockLib.Messaging.SNS.Tests.csproj │ └── SNSTests.cs ├── RockLib.Messaging.SQS.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── DependencyInjectionTests.cs │ ├── Directory.Build.props │ ├── RockLib.Messaging.SQS.Tests.csproj │ └── SQSTests.cs └── RockLib.Messaging.Tests │ ├── .editorconfig │ ├── AssemblySettings.cs │ ├── CustomConfigFiles │ ├── MultipleReceivers_appsettings.json │ ├── MultipleSenders_appsettings.json │ ├── SingleReceiver_appsettings.json │ └── SingleSender_appsettings.json │ ├── DependencyInjectionTests.cs │ ├── Directory.Build.props │ ├── EncodingTests.cs │ ├── FakeMessageHandler.cs │ ├── FakeReceiver.cs │ ├── FakeReceiverMessage.cs │ ├── FakeSender.cs │ ├── ForwardingMessageHandlerTests.cs │ ├── ForwardingReceiverMessageTests.cs │ ├── ForwardingReceiverTests.cs │ ├── MessagingExtensionsTests.cs │ ├── MessagingScenarioFactoryTests.cs │ ├── ReceiverExtensionsTests.cs │ ├── ReloadingReceiverTests.cs │ ├── ReloadingSenderTests.cs │ ├── RockLib.Messaging.Tests.csproj │ ├── SenderMessageTests.cs │ ├── TestReceiverMessageTests.cs │ ├── ValidatingSenderTests.cs │ ├── ValidatingSenderTransactionTests.cs │ ├── ValidatingTransactionalSenderTests.cs │ └── appsettings.json ├── docs ├── CloudEvents.md ├── Compressed.md ├── DIContainer.md ├── ForwardingReceiver.md ├── GettingStarted.md ├── Http.md ├── Kafka.md ├── NamedPipes.md ├── RabbitMQ.md ├── ReceivingMessages.md ├── SNS.md ├── SQS.md ├── SendingMessages.md └── TransactionalSend.md └── icon.png /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 12 | 13 | ## Type of change: 14 | 15 | 1. Non-functional change (e.g. documentation changes, removing unused `using` directives, renaming local variables, etc) 16 | 2. Bug fix (non-breaking change that fixes an issue) 17 | 3. New feature (non-breaking change that adds functionality) 18 | 4. Breaking change (fix or feature that could cause existing functionality to not work as expected) 19 | 20 | ## Checklist: 21 | 22 | - Have you reviewed your own code? Do you understand every change? 23 | - Are you following the [contributing guidelines](../blob/main/CONTRIBUTING.md)? 24 | - Have you added tests that prove your fix is effective or that this feature works? 25 | - New and existing unit tests pass locally with these changes? 26 | - Have you made corresponding changes to the documentation? 27 | - Will this change require an update to an example project? (if so, create an issue and link to it) 28 | 29 | --- 30 | 31 | _[Reviewer guidelines](../blob/main/CONTRIBUTING.md#reviewing-changes)_ 32 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run when a release is published. 8 | release: 9 | types: [ released, prereleased ] 10 | 11 | #################################################################################################### 12 | ## WORKFLOW JOBS 13 | #################################################################################################### 14 | jobs: 15 | # Calls the shared build-and-publish workflow. 16 | call_build_and_publish: 17 | name: Call build-and-publish workflow 18 | uses: RockLib/RockLib.Workflows/.github/workflows/build-and-publish.yml@main 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Run Unit Test 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run on pull requests to the main branch. 8 | pull_request: 9 | branches: [ main ] 10 | 11 | #################################################################################################### 12 | ## WORKFLOW JOBS 13 | #################################################################################################### 14 | jobs: 15 | # Calls the shared unit-test workflow. 16 | call_unit_test: 17 | name: Call unit-test workflow 18 | uses: RockLib/RockLib.Workflows/.github/workflows/unit-test.yml@main 19 | secrets: inherit 20 | -------------------------------------------------------------------------------- /.github/workflows/snyk-test.yml: -------------------------------------------------------------------------------- 1 | name: Run Snyk Test 2 | 3 | #################################################################################################### 4 | ## WORKFLOW TRIGGER 5 | #################################################################################################### 6 | on: 7 | # Workflow will run after unit-test is completed. 8 | workflow_run: 9 | workflows: [ Run Unit Test ] 10 | types: [ completed ] 11 | 12 | #################################################################################################### 13 | ## WORKFLOW JOBS 14 | #################################################################################################### 15 | jobs: 16 | # Calls the shared snyk-test workflow. 17 | call_snyk_test: 18 | name: Call snyk-test workflow 19 | uses: RockLib/RockLib.Workflows/.github/workflows/snyk-test.yml@main 20 | secrets: inherit 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Secret stuff 2 | build/apikey.txt 3 | 4 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 5 | [Bb]in/ 6 | [Oo]bj/ 7 | docs/output 8 | 9 | # mstest test results 10 | TestResults 11 | 12 | ## Ignore Visual Studio temporary files, build results, and 13 | ## files generated by popular Visual Studio add-ons. 14 | 15 | # User-specific files 16 | *.suo 17 | *.user 18 | *.sln.docstates 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Rr]elease/ 23 | x64/ 24 | *_i.c 25 | *_p.c 26 | *.ilk 27 | *.meta 28 | *.obj 29 | *.pch 30 | *.pdb 31 | *.pgc 32 | *.pgd 33 | *.rsp 34 | *.sbr 35 | *.tlb 36 | *.tli 37 | *.tlh 38 | *.tmp 39 | *.log 40 | *.vspscc 41 | *.vssscc 42 | .builds 43 | 44 | # Visual C++ cache files 45 | ipch/ 46 | *.aps 47 | *.ncb 48 | *.opensdf 49 | *.sdf 50 | 51 | # Visual Studio profiler 52 | *.psess 53 | *.vsp 54 | *.vspx 55 | 56 | # Guidance Automation Toolkit 57 | *.gpState 58 | 59 | # ReSharper is a .NET coding add-in 60 | _ReSharper* 61 | 62 | # NCrunch 63 | *.ncrunch* 64 | .*crunch*.local.xml 65 | 66 | # Installshield output folder 67 | [Ee]xpress 68 | 69 | # DocProject is a documentation generator add-in 70 | DocProject/buildhelp/ 71 | DocProject/Help/*.HxT 72 | DocProject/Help/*.HxC 73 | DocProject/Help/*.hhc 74 | DocProject/Help/*.hhk 75 | DocProject/Help/*.hhp 76 | DocProject/Help/Html2 77 | DocProject/Help/html 78 | 79 | # Click-Once directory 80 | publish 81 | 82 | # Publish Web Output 83 | *.Publish.xml 84 | 85 | # NuGet Packages Directory 86 | packages 87 | 88 | # Windows Azure Build Output 89 | csx 90 | *.build.csdef 91 | 92 | # Windows Store app package directory 93 | AppPackages/ 94 | 95 | # Others 96 | [Bb]in 97 | [Oo]bj 98 | sql 99 | TestResults 100 | [Tt]est[Rr]esult* 101 | *.Cache 102 | ClientBin 103 | [Ss]tyle[Cc]op.* 104 | ~$* 105 | *.dbmdl 106 | Generated_Code #added for RIA/Silverlight projects 107 | 108 | # Backup & report files from converting an old project file to a newer 109 | # Visual Studio version. Backup files are not needed, because we have git ;-) 110 | _UpgradeReport_Files/ 111 | Backup*/ 112 | UpgradeLog*.XML 113 | 114 | # Nuget 115 | *.nupkg 116 | .vs/ 117 | -------------------------------------------------------------------------------- /Examples/Example.Application.Testing/Example.Application.Testing.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | false 5 | Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Examples/Example.Application.Testing/ExampleService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using RockLib.Messaging; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Example 8 | { 9 | public class ExampleService : IHostedService 10 | { 11 | public ExampleService(IReceiver receiver, IDatabase database) 12 | { 13 | Receiver = receiver ?? throw new ArgumentNullException(nameof(receiver)); 14 | Receiver.Error += (obj, args) => 15 | { 16 | // Do something when the receiver encounters an error 17 | throw new Exception($"Error in Receiver: {args.Exception.Message}"); 18 | }; 19 | 20 | Database = database ?? throw new ArgumentNullException(nameof(database)); 21 | } 22 | 23 | public IReceiver Receiver { get; } 24 | 25 | public IDatabase Database { get; } 26 | 27 | public Task StartAsync(CancellationToken cancellationToken) 28 | { 29 | Receiver.Start(OnMessageReceived); 30 | return Task.CompletedTask; 31 | } 32 | 33 | public Task StopAsync(CancellationToken cancellationToken) 34 | { 35 | Receiver.Dispose(); 36 | return Task.CompletedTask; 37 | } 38 | 39 | private async Task OnMessageReceived(IReceiverMessage message) 40 | { 41 | if (message.Headers.TryGetValue("operation", out string operation)) 42 | { 43 | if (operation == "create") 44 | { 45 | await Database.CreateAsync(message.StringPayload); 46 | await message.AcknowledgeAsync(); 47 | } 48 | else if (operation == "update") 49 | { 50 | await Database.UpdateAsync(message.StringPayload); 51 | await message.AcknowledgeAsync(); 52 | } 53 | else if (operation == "delete") 54 | { 55 | await Database.DeleteAsync(message.StringPayload); 56 | await message.AcknowledgeAsync(); 57 | } 58 | else 59 | { 60 | // TODO: Send error log - invalid message 61 | await message.RejectAsync(); 62 | } 63 | } 64 | else 65 | { 66 | // TODO: Send error log - invalid message 67 | await message.RejectAsync(); 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Examples/Example.Application.Testing/FizzBuzzEngine.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Messaging; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace Example 6 | { 7 | public class FizzBuzzEngine 8 | { 9 | public FizzBuzzEngine(ISender sender) => 10 | Sender = sender ?? throw new ArgumentNullException(nameof(sender)); 11 | 12 | public ISender Sender { get; } 13 | 14 | public async Task SendFizzBuzzMessage(long value) 15 | { 16 | if (value < 1) 17 | throw new ArgumentOutOfRangeException(nameof(value), "Must be greater than zero."); 18 | 19 | if (value % 15 == 0) 20 | await Sender.SendAsync("fizz-buzz"); 21 | else if (value % 3 == 0) 22 | await Sender.SendAsync("fizz"); 23 | else if (value % 5 == 0) 24 | await Sender.SendAsync("buzz"); 25 | else 26 | await Sender.SendAsync(value.ToString()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Examples/Example.Application.Testing/IDatabase.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Example 4 | { 5 | public interface IDatabase 6 | { 7 | Task CreateAsync(string payload); 8 | 9 | Task UpdateAsync(string payload); 10 | 11 | Task DeleteAsync(string payload); 12 | } 13 | } -------------------------------------------------------------------------------- /Examples/Example.Application.Testing/README.md: -------------------------------------------------------------------------------- 1 | # Testing components that use RockLib.Messaging 2 | 3 | This project is intended to demonstrate how to test classes that have a dependency on an `ISender` or `IReceiver`. 4 | 5 | --- 6 | 7 | The [`FizzBuzzEngine`](FizzBuzzEngine.cs) class is an implementation of [FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz#Programming) that sends the result to an `ISender`. [To test it](Tests/FizzBuzzEngineTests.cs), we pass a mock `ISender` to the constructor, then verify that the correct message for the given value was sent to it. 8 | 9 | --- 10 | 11 | This [`ExampleService`](ExampleService.cs) class receives messages from an `IReceiver` and, depending on the value of a message's `Operation` header, creates, updates, or deletes the message's payload using an `IDatabase` interface. [For testing](Tests/ExampleServiceTests.cs), we pass a mock `IReceiver` and a mock `IDatabase` to the constructor. Then we simulate receiving a message by starting the receiver and passing a fake receiver message to the `OnMessageReceivedAsync` method of the receiver's `MessageHandler`. We then verify that the fake receiver message was handled correctly and that the correct method was called on the `IDatabase`. 12 | -------------------------------------------------------------------------------- /Examples/Example.Common.Tests/CommandSendingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using RockLib.Dynamic; 4 | using RockLib.Messaging; 5 | using System; 6 | using Xunit; 7 | 8 | namespace Example.Common.Tests 9 | { 10 | public class CommandSendingServiceTests 11 | { 12 | [Fact(DisplayName = "Constructor sets properties")] 13 | public void ConstructorHappyPath() 14 | { 15 | // Arrange 16 | var sender = new Mock().Object; 17 | 18 | // Act 19 | var sendingService = new CommandSendingService(sender); 20 | 21 | // Assert 22 | sendingService.Sender.Should().BeSameAs(sender); 23 | } 24 | 25 | [Fact(DisplayName = "Constructor throws when sender parameter is null")] 26 | public void ConstructorSadPath() 27 | { 28 | // Arrange 29 | Action act = () => new CommandSendingService(null); 30 | 31 | // Act/Assert 32 | act.Should().ThrowExactly().WithMessage("*sender*"); 33 | } 34 | 35 | [Fact(DisplayName = "Prompt returns correct message")] 36 | public void Prompt() 37 | { 38 | // Arrange 39 | var sender = new Mock().Object; 40 | 41 | var sendingService = new CommandSendingService(sender).Unlock(); 42 | 43 | // Act 44 | string prompt = sendingService.Prompt; 45 | 46 | // Assert 47 | prompt.Should().Be("Enter command to send. (Legal values are: Default, UPPER, lower, SpOnGeBoB)"); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Examples/Example.Common.Tests/DataSendingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using RockLib.Dynamic; 4 | using RockLib.Messaging; 5 | using System; 6 | using Xunit; 7 | 8 | namespace Example.Common.Tests 9 | { 10 | public class DataSendingServiceTests 11 | { 12 | [Fact(DisplayName = "Constructor sets properties")] 13 | public void ConstructorHappyPath() 14 | { 15 | // Arrange 16 | var sender = new Mock().Object; 17 | 18 | // Act 19 | var sendingService = new DataSendingService(sender); 20 | 21 | // Assert 22 | sendingService.Sender.Should().BeSameAs(sender); 23 | } 24 | 25 | [Fact(DisplayName = "Constructor throws when sender parameter is null")] 26 | public void ConstructorSadPath() 27 | { 28 | // Arrange 29 | Action act = () => new DataSendingService(null); 30 | 31 | // Act/Assert 32 | act.Should().ThrowExactly().WithMessage("*sender*"); 33 | } 34 | 35 | [Fact(DisplayName = "Prompt returns correct message")] 36 | public void Prompt() 37 | { 38 | // Arrange 39 | var sender = new Mock().Object; 40 | 41 | var sendingService = new DataSendingService(sender).Unlock(); 42 | 43 | // Act 44 | string prompt = sendingService.Prompt; 45 | 46 | // Assert 47 | prompt.Should().Be("Enter data to send."); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Examples/Example.Common.Tests/Example.Common.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | false 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/Example.Common.Tests/SendingServiceTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using Moq.Protected; 4 | using RockLib.Dynamic; 5 | using RockLib.Messaging; 6 | using System; 7 | using System.Threading; 8 | using Xunit; 9 | 10 | namespace Example.Common.Tests 11 | { 12 | public class SendingServiceTests 13 | { 14 | [Fact(DisplayName = "Constructor sets properties")] 15 | public void ConstructorHappyPath() 16 | { 17 | // Arrange 18 | var sender = new Mock().Object; 19 | 20 | // Act 21 | var sendingService = new ConcreteSendingService(sender); 22 | 23 | // Assert 24 | sendingService.Sender.Should().BeSameAs(sender); 25 | } 26 | 27 | [Fact(DisplayName = "Constructor throws when sender parameter is null")] 28 | public void ConstructorSadPath() 29 | { 30 | // Arrange 31 | Action act = () => new ConcreteSendingService(null); 32 | 33 | // Act/Assert 34 | act.Should().ThrowExactly().WithMessage("*sender*"); 35 | } 36 | 37 | [Fact(DisplayName = "ReadAndSendMessage reads a line and sends it")] 38 | public void ReadAndSendMessage() 39 | { 40 | // Arrange 41 | var mockSender = new Mock(); 42 | 43 | var mockSendingService = new Mock(mockSender.Object); 44 | 45 | mockSendingService.Protected().As() 46 | .Setup(m => m.ReadLine()).Returns("test-message"); 47 | 48 | var sendingService = mockSendingService.Object.Unlock(); 49 | 50 | // Act 51 | sendingService.ReadAndSendMessage(); 52 | 53 | // Assert 54 | mockSendingService.Protected().As() 55 | .Verify(m => m.ReadLine(), Times.Once()); 56 | 57 | mockSender.Verify(m => m.SendAsync(It.Is(m => m.StringPayload == "test-message"), It.IsAny()), 58 | Times.Once()); 59 | } 60 | 61 | private class ConcreteSendingService : SendingService 62 | { 63 | public ConcreteSendingService(ISender sender) 64 | : base(sender) 65 | { 66 | } 67 | 68 | protected override string Prompt => null; 69 | } 70 | 71 | private interface IProtected 72 | { 73 | string ReadLine(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Examples/Example.Common.Tests/SingleMessageServiceTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using Moq.Protected; 4 | using RockLib.Dynamic; 5 | using RockLib.Messaging; 6 | using System; 7 | using System.Threading; 8 | using Xunit; 9 | namespace Example.Common.Tests 10 | { 11 | public class SingleMessageServiceTests 12 | { 13 | [Fact(DisplayName = "Constructor sets properties")] 14 | public void ConstructorHappyPath() 15 | { 16 | // Arrange 17 | var sender = new Mock().Object; 18 | var receiver = new Mock().Object; 19 | 20 | // Act 21 | var service = new SingleMessageService(sender, receiver); 22 | 23 | // Assert 24 | service.Sender.Should().BeSameAs(sender); 25 | service.Receiver.Should().BeSameAs(receiver); 26 | } 27 | 28 | [Fact(DisplayName = "Constructor throws when sender parameter is null")] 29 | public void ConstructorSadPath1() 30 | { 31 | // Arrange 32 | var receiver = new Mock().Object; 33 | 34 | Action act = () => new SingleMessageService(null, receiver); 35 | 36 | // Act/Assert 37 | act.Should().ThrowExactly().WithMessage("*sender*"); 38 | } 39 | 40 | [Fact(DisplayName = "Constructor throws when receiver parameter is null")] 41 | public void ConstructorSadPath2() 42 | { 43 | // Arrange 44 | var sender = new Mock().Object; 45 | 46 | Action act = () => new SingleMessageService(sender, null); 47 | 48 | // Act/Assert 49 | act.Should().ThrowExactly().WithMessage("*receiver*"); 50 | } 51 | 52 | [Fact(DisplayName = "WaitAndSendMessage waits then sends a message with the current date/time")] 53 | public void WaitAndSendMessage() 54 | { 55 | // Arrange 56 | var mockSender = new Mock(); 57 | var receiver = new Mock().Object; 58 | 59 | var mockService = new Mock(mockSender.Object, receiver); 60 | 61 | var now = DateTime.Now; 62 | 63 | mockService.Protected().As() 64 | .Setup(m => m.Now).Returns(now); 65 | 66 | var service = mockService.Object.Unlock(); 67 | 68 | // Act 69 | service.WaitAndSendMessage(); 70 | 71 | // Assert 72 | mockService.Protected().As() 73 | .Verify(m => m.Wait(), Times.Once()); 74 | 75 | mockSender.Verify(m => m.SendAsync(It.Is(m => m.StringPayload == $"[{now:G}] Example message"), It.IsAny()), 76 | Times.Once()); 77 | } 78 | 79 | private interface IProtected 80 | { 81 | void Wait(); 82 | DateTime Now { get; } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Examples/Example.Common/Casing.cs: -------------------------------------------------------------------------------- 1 | namespace Example.Common 2 | { 3 | public enum Casing 4 | { 5 | Default, 6 | UPPER, 7 | lower, 8 | SpOnGeBoB 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Examples/Example.Common/CommandSendingService.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Messaging; 2 | using System; 3 | 4 | namespace Example.Common 5 | { 6 | public class CommandSendingService : SendingService 7 | { 8 | public CommandSendingService(ISender sender) 9 | : base(sender) 10 | { 11 | } 12 | 13 | protected override string Prompt => $"Enter command to send. (Legal values are: {string.Join(", ", Enum.GetNames(typeof(Casing)))})"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Examples/Example.Common/DataSendingService.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Messaging; 2 | 3 | namespace Example.Common 4 | { 5 | public class DataSendingService : SendingService 6 | { 7 | public DataSendingService(ISender sender) 8 | : base(sender) 9 | { 10 | } 11 | 12 | protected override string Prompt => "Enter data to send."; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Examples/Example.Common/Example.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Examples/Example.Common/SendingService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using RockLib.Messaging; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Example.Common 8 | { 9 | public abstract class SendingService : IHostedService 10 | { 11 | private readonly Thread _senderThread; 12 | 13 | protected SendingService(ISender sender) 14 | { 15 | Sender = sender ?? throw new ArgumentNullException(nameof(sender)); 16 | _senderThread = new Thread(SendMessages) { IsBackground = true }; 17 | } 18 | 19 | public ISender Sender { get; } 20 | 21 | protected abstract string Prompt { get; } 22 | 23 | public Task StartAsync(CancellationToken cancellationToken) 24 | { 25 | _senderThread.Start(); 26 | return Task.CompletedTask; 27 | } 28 | 29 | public Task StopAsync(CancellationToken cancellationToken) 30 | { 31 | Sender.Dispose(); 32 | return Task.CompletedTask; 33 | } 34 | 35 | private void SendMessages() 36 | { 37 | // Wait a bit to let the startup logs get written to console. Otherwise, logs 38 | // get written after the prompt and it's a weird experience for the user. 39 | Thread.Sleep(500); 40 | 41 | Console.WriteLine(Prompt); 42 | while (true) 43 | { 44 | Console.Write(">"); 45 | ReadAndSendMessage(); 46 | } 47 | } 48 | 49 | private async Task ReadAndSendMessage() 50 | { 51 | string message = ReadLine(); 52 | if (message is object) 53 | await Sender.SendAsync(message); 54 | } 55 | 56 | protected virtual string ReadLine() => Console.ReadLine(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Examples/Example.Common/SingleMessageService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using RockLib.Messaging; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Example.Common 8 | { 9 | public class SingleMessageService : IHostedService 10 | { 11 | public SingleMessageService(ISender sender, IReceiver receiver) 12 | { 13 | Sender = sender ?? throw new ArgumentNullException(nameof(sender)); 14 | Receiver = receiver ?? throw new ArgumentNullException(nameof(receiver)); 15 | } 16 | 17 | public ISender Sender { get; } 18 | 19 | public IReceiver Receiver { get; } 20 | 21 | public Task StartAsync(CancellationToken cancellationToken) 22 | { 23 | Receiver.Start(OnMessageReceived); 24 | ThreadPool.QueueUserWorkItem(async _ => await WaitAndSendMessage()); 25 | 26 | return Task.CompletedTask; 27 | } 28 | 29 | public Task StopAsync(CancellationToken cancellationToken) 30 | { 31 | return Task.CompletedTask; 32 | } 33 | 34 | private Task OnMessageReceived(IReceiverMessage message) 35 | { 36 | Console.WriteLine($"Received message: '{message.StringPayload}'"); 37 | return message.AcknowledgeAsync(); 38 | } 39 | 40 | private async Task WaitAndSendMessage() 41 | { 42 | Wait(); 43 | await Sender.SendAsync($"[{Now:G}] Example message"); 44 | } 45 | 46 | protected virtual void Wait() => Thread.Sleep(1000); 47 | 48 | protected virtual DateTime Now => DateTime.Now; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.CloudEvents/Example.Messaging.CloudEvents.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.CloudEvents/ExampleOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Example.Messaging.CloudEvents 2 | { 3 | public class ExampleOptions 4 | { 5 | public string Source { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.CloudEvents/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using RockLib.Messaging.DependencyInjection; 4 | using System.Threading.Tasks; 5 | 6 | namespace Example.Messaging.CloudEvents 7 | { 8 | class Program 9 | { 10 | static Task Main(string[] args) 11 | { 12 | return CreateHostBuilder(args).RunConsoleAsync(options => options.SuppressStatusMessages = true); 13 | } 14 | 15 | static IHostBuilder CreateHostBuilder(string[] args) 16 | { 17 | IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args); 18 | return hostBuilder.ConfigureServices((context, services) => 19 | { 20 | services.AddNamedPipeSender("user-pipe"); 21 | services.AddNamedPipeReceiver("user-pipe"); 22 | 23 | services.AddNamedPipeSender("worker-pipe-1"); 24 | services.AddNamedPipeReceiver("worker-pipe-1"); 25 | 26 | services.AddNamedPipeSender("worker-pipe-2"); 27 | services.AddNamedPipeReceiver("worker-pipe-2"); 28 | 29 | services.Configure(context.Configuration.GetSection("examplesettings")); 30 | services.AddHostedService(); 31 | }); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.CloudEvents/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "examplesettings": { 3 | "source": "example.org/abc/123" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.Http/Example.Messaging.Http.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Always 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.Http/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "senders": { 4 | "type": "RockLib.Messaging.Http.HttpClientSender, RockLib.Messaging.Http", 5 | "value": { 6 | "name": "Sender1", 7 | "url": "http://localhost:5000/api/commands" 8 | } 9 | }, 10 | "receivers": { 11 | "type": "RockLib.Messaging.Http.HttpListenerReceiver, RockLib.Messaging.Http", 12 | "value": { 13 | "name": "Receiver1", 14 | "url": "http://localhost:5000/api/commands" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Examples/Example.Messaging.Kafka/Example.Messaging.Kafka.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | Always 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.Kafka/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "senders": { 4 | "type": "RockLib.Messaging.Kafka.KafkaSender, RockLib.Messaging.Kafka", 5 | "value": { 6 | "name": "Sender1", 7 | "topic": "test", 8 | "bootstrapServers": "localhost:9092", 9 | "statisticsIntervalMs": 1000 10 | } 11 | }, 12 | "receivers": { 13 | "type": "RockLib.Messaging.Kafka.KafkaReceiver, RockLib.Messaging.Kafka", 14 | "value": { 15 | "name": "Receiver1", 16 | "topic": "test", 17 | "groupId": "test-consumer-group", 18 | "bootstrapServers": "localhost:9092", 19 | "statisticsIntervalMs": 1000 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /Examples/Example.Messaging.NamedPipes/Example.Messaging.NamedPipes.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.NamedPipes/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "DataSettings:PipeName": "data_pipe", 3 | "CommandSettings:PipeName": "command_pipe", 4 | 5 | "RockLib.Messaging": { 6 | "Senders": { 7 | "Type": "RockLib.Messaging.NamedPipes.NamedPipeSender, RockLib.Messaging.NamedPipes", 8 | "Value": { 9 | "Name": "SingleMessageSender", 10 | "PipeName": "example_pipe" 11 | } 12 | }, 13 | "Receivers": { 14 | "Type": "RockLib.Messaging.NamedPipes.NamedPipeReceiver, RockLib.Messaging.NamedPipes", 15 | "Value": { 16 | "Name": "SingleMessageReceiver", 17 | "PipeName": "example_pipe" 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Examples/Example.Messaging.RabbitMQ/Example.Messaging.RabbitMQ.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | PreserveNewest 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.RabbitMQ/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "senders": { 4 | "type": "RockLib.Messaging.RabbitMQ.RabbitSender, RockLib.Messaging.RabbitMQ", 5 | "value": { 6 | "name": "Sender1", 7 | "connection": { "hostName": "localhost" }, 8 | "routingKey": "task_queue" 9 | } 10 | }, 11 | "receivers": { 12 | "type": "RockLib.Messaging.RabbitMQ.RabbitReceiver, RockLib.Messaging.RabbitMQ", 13 | "value": { 14 | "name": "Receiver1", 15 | "connection": { "hostName": "localhost" }, 16 | "queue": "task_queue", 17 | "prefetchCount": 1 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Examples/Example.Messaging.SNS/Example.Messaging.SNS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.SQS/Example.Messaging.SQS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net8.0 5 | 6 | 7 | 8 | PreserveNewest 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Examples/Example.Messaging.SQS/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "DataSettings:QueueUrl": "https://sqs.{region}.amazonaws.com/{account-id}/{data-queue-name}", 3 | "CommandSettings:QueueUrl": "https://sqs.{region}.amazonaws.com/{account-id}/{command-queue-name}", 4 | 5 | "RockLib.Messaging": { 6 | "Senders": { 7 | "Type": "RockLib.Messaging.SQS.SQSSender, RockLib.Messaging.SQS", 8 | "Value": { 9 | "Name": "SingleMessageSender", 10 | "QueueUrl": "https://sqs.{region}.amazonaws.com/{account-id}/{example-queue-name}" 11 | } 12 | }, 13 | "Receivers": { 14 | "Type": "RockLib.Messaging.SQS.SQSReceiver, RockLib.Messaging.SQS", 15 | "Value": { 16 | "Name": "SingleMessageReceiver", 17 | "QueueUrl": "https://sqs.{region}.amazonaws.com/{account-id}/{example-queue-name}" 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2021 Rocket Mortgage 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RockLib.Messaging 2 | 3 | *A simple API for sending and receiving messages.* 4 | 5 | ## Packages 6 | 7 | ### RockLib.Messaging 8 | 9 | > [!WARNING] 10 | > The 5.0.0 release of this library will be the final version with upgrades and changes. Bug fixes will continue to be released as needed. 11 | 12 | ### RockLib.Messaging.NamedPipes 13 | 14 | > [!WARNING] 15 | > The 5.0.0 release of this library will be the final version with upgrades and changes. Bug fixes will continue to be released as needed. 16 | 17 | ### RockLib.Messaging.SQS 18 | 19 | ### RockLib.Messaging.SNS 20 | 21 | ### RockLib.Messaging.Http 22 | 23 | > [!WARNING] 24 | > The 4.0.0 release of this library will be the final version with upgrades and changes. Bug fixes will continue to be released as needed. 25 | 26 | ### RockLib.Messaging.Kafka 27 | 28 | > [!WARNING] 29 | > The 4.0.0 release of this library will be the final version with upgrades and changes. Bug fixes will continue to be released as needed. 30 | 31 | ### RockLib.Messaging.RabbitMQ 32 | 33 | > [!WARNING] 34 | > The 3.0.0 release of this library will be the final version with upgrades and changes. Bug fixes will continue to be released as needed. 35 | 36 | ### RockLib.Messaging.CloudEvents 37 | 38 | > [!WARNING] 39 | > The 4.0.0 release of this library will be the final version with upgrades and changes. Bug fixes will continue to be released as needed. 40 | 41 | ----- 42 | 43 | - [Getting started](docs/GettingStarted.md) 44 | - How to: 45 | - [Send messages](docs/SendingMessages.md) 46 | - [Receive messages](docs/ReceivingMessages.md) 47 | - [Configure and use RockLib.Messaging.NamedPipes](docs/NamedPipes.md) 48 | - [Configure and use RockLib.Messaging.SQS](docs/SQS.md) 49 | - [Configure and use RockLib.Messaging.SNS](docs/SNS.md) 50 | - [Configure and use RockLib.Messaging.Http](docs/Http.md) 51 | - [Configure and use RockLib.Messaging.Kafka](docs/Kafka.md) 52 | - [Configure and use RockLib.Messaging.RabbitMQ](docs/RabbitMQ.md) 53 | - [Configure and use ForwardingReceiver](docs/ForwardingReceiver.md) 54 | - Enables the "fault queue" pattern. 55 | - [Use RockLib.Messaging with a DI/IOC container](docs/DIContainer.md) 56 | - [Send and receive compressed messages](docs/Compressed.md) 57 | - [Send messages transactionally](docs/TransactionalSend.md) 58 | - [Send and receive message as CloudEvents](docs/CloudEvents.md) 🆕 59 | - [Test components that use RockLib.Messaging](https://github.com/RockLib/RockLib.Messaging/tree/main/Examples/Example.Application.Testing) 🆕 60 | - API Reference: 61 | - [RockLib.Messaging](https://www.nuget.org/packages/RockLib.Messaging) 62 | - [RockLib.Messaging.NamedPipes](https://www.nuget.org/packages/RockLib.Messaging.NamedPipes) 63 | - [RockLib.Messaging.SQS](https://www.nuget.org/packages/RockLib.Messaging.SQS) 64 | - [RockLib.Messaging.SNS](https://www.nuget.org/packages/RockLib.Messaging.SNS) 65 | - [RockLib.Messaging.Http](https://www.nuget.org/packages/RockLib.Messaging.Http) 66 | - [RockLib.Messaging.Kafka](https://www.nuget.org/packages/RockLib.Messaging.Kafka) 67 | - [RockLib.Messaging.RabbitMQ](https://www.nuget.org/packages/RockLib.Messaging.RabbitMQ) 68 | - [RockLib.Messaging.CloudEvents](https://www.nuget.org/packages/RockLib.Messaging.CloudEvents) 69 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/CloudEventExtensions.CopyConstructor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace RockLib.Messaging.CloudEvents 5 | { 6 | partial class CloudEventExtensions 7 | { 8 | private sealed class CopyConstructor 9 | { 10 | private Func _invokeConstructor; 11 | 12 | private CopyConstructor(ConstructorInfo constructor) 13 | { 14 | // The initial function uses regular reflection. 15 | _invokeConstructor = cloudEvent => 16 | (CloudEvent)constructor.Invoke(new object[] { cloudEvent }); 17 | } 18 | 19 | public static CopyConstructor? Create(Type type) 20 | { 21 | var constructor = GetConstructor(type); 22 | 23 | if (constructor is null) 24 | { 25 | return null; 26 | } 27 | 28 | return new CopyConstructor(constructor); 29 | } 30 | 31 | public CloudEvent Invoke(CloudEvent cloudEvent) => 32 | _invokeConstructor(cloudEvent); 33 | 34 | private static ConstructorInfo? GetConstructor(Type type) => 35 | type.GetConstructor(new[] { type }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/CloudEventExtensions.MessageConstructor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace RockLib.Messaging.CloudEvents 5 | { 6 | partial class CloudEventExtensions 7 | { 8 | private sealed class MessageConstructor 9 | { 10 | private static readonly Type[] _constructorParameters = new[] { typeof(IReceiverMessage), typeof(IProtocolBinding) }; 11 | 12 | private Func _invokeConstructor; 13 | 14 | private MessageConstructor(ConstructorInfo constructor) 15 | { 16 | // The initial function uses regular reflection. 17 | _invokeConstructor = (receiverMessage, protocolBinding) => 18 | constructor.Invoke(new object[] { receiverMessage, protocolBinding! }); 19 | } 20 | 21 | public static MessageConstructor? Create(Type type) 22 | { 23 | var constructor = GetConstructor(type); 24 | 25 | if (constructor is null) 26 | { 27 | return null; 28 | } 29 | 30 | return new MessageConstructor(constructor); 31 | } 32 | 33 | public static bool Exists(Type type) => 34 | GetConstructor(type) is not null; 35 | 36 | public object Invoke(IReceiverMessage receiverMessage, IProtocolBinding? protocolBinding) => 37 | _invokeConstructor(receiverMessage, protocolBinding); 38 | 39 | private static ConstructorInfo? GetConstructor(Type type) => 40 | type.GetConstructor(_constructorParameters); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/CloudEventExtensions.ValidateMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace RockLib.Messaging.CloudEvents 5 | { 6 | partial class CloudEventExtensions 7 | { 8 | private sealed class ValidateMethod 9 | { 10 | private const BindingFlags _publicStaticFlags = BindingFlags.Public | BindingFlags.Static; 11 | private static readonly Type[] _validateMethodParameters = new[] { typeof(SenderMessage), typeof(IProtocolBinding) }; 12 | 13 | private Action _invokeValidateMethod; 14 | 15 | private ValidateMethod(MethodInfo validateMethod) 16 | { 17 | // The initial function uses regular reflection. 18 | _invokeValidateMethod = (senderMessage, protocolBinding) => 19 | validateMethod.Invoke(null, new object[] { senderMessage, protocolBinding }); 20 | } 21 | 22 | public static ValidateMethod? Create(Type type) 23 | { 24 | var validateMethod = GetValidateMethod(type); 25 | 26 | if (validateMethod is null) 27 | { 28 | return null; 29 | } 30 | 31 | return new ValidateMethod(validateMethod); 32 | } 33 | 34 | public void Invoke(SenderMessage senderMessage, IProtocolBinding protocolBinding) => 35 | _invokeValidateMethod(senderMessage, protocolBinding); 36 | 37 | private static MethodInfo? GetValidateMethod(Type type) => 38 | type.GetMethod(nameof(CloudEvent.Validate), _publicStaticFlags, null, _validateMethodParameters, null); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/CloudEventValidationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.CloudEvents 4 | { 5 | /// 6 | /// The exception that is thrown when validation for a CloudEvent fails. 7 | /// 8 | [Serializable] 9 | public class CloudEventValidationException : Exception 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | public CloudEventValidationException() 15 | { 16 | } 17 | 18 | /// 19 | /// Initializes a new instance of the class with a 20 | /// specified error message. 21 | /// 22 | /// The message that describes the error. 23 | public CloudEventValidationException(string message) : base(message) 24 | { 25 | } 26 | 27 | /// 28 | /// Initializes a new instance of the class with a specified 29 | /// error message and a reference to the inner exception that is the cause of this exception. 30 | /// 31 | /// The error message that explains the reason for the exception. 32 | /// 33 | /// The exception that is the cause of the current exception, or a null reference if no inner 34 | /// exception is specified. 35 | public CloudEventValidationException(string message, Exception innerException) : base(message, innerException) 36 | { 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/Correlating/CorrelatingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static RockLib.Messaging.CloudEvents.CorrelatedEvent; 3 | 4 | namespace RockLib.Messaging.CloudEvents.Correlating 5 | { 6 | /// 7 | /// Extension methods for getting and setting the Correlation ID of an event. 8 | /// 9 | public static class CorrelatingExtensions 10 | { 11 | /// 12 | /// Gets the Correlation ID of the event. 13 | /// 14 | /// The cloud event. 15 | /// The Correlation ID of the event. 16 | /// 17 | /// If is . 18 | /// 19 | public static string GetCorrelationId(this CloudEvent cloudEvent) 20 | { 21 | #if NET6_0_OR_GREATER 22 | ArgumentNullException.ThrowIfNull(cloudEvent); 23 | #else 24 | if (cloudEvent is null) { throw new ArgumentNullException(nameof(cloudEvent)); } 25 | #endif 26 | 27 | if (cloudEvent.Attributes.TryGetValue(CorrelationIdAttribute, out var value) && 28 | value is string correlationId) 29 | { 30 | return correlationId; 31 | } 32 | 33 | correlationId = NewCorrelationId(); 34 | cloudEvent.Attributes[CorrelationIdAttribute] = correlationId; 35 | return correlationId; 36 | } 37 | 38 | /// 39 | /// Sets the Correlation ID of the event. 40 | /// 41 | /// The cloud event. 42 | /// The Correlation ID of the event. 43 | /// 44 | /// If or is . 46 | /// 47 | public static void SetCorrelationId(this CloudEvent cloudEvent, string correlationId) 48 | { 49 | #if NET6_0_OR_GREATER 50 | ArgumentNullException.ThrowIfNull(cloudEvent); 51 | #else 52 | if (cloudEvent is null) { throw new ArgumentNullException(nameof(cloudEvent)); } 53 | #endif 54 | 55 | if (string.IsNullOrEmpty(correlationId)) 56 | { 57 | throw new ArgumentNullException(nameof(correlationId)); 58 | } 59 | 60 | cloudEvent.Attributes[CorrelationIdAttribute] = correlationId; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/DataSerialization.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.CloudEvents 2 | { 3 | /// 4 | /// Defines types of serialization to use when getting or setting the data of a as a generic type. 6 | /// 7 | public enum DataSerialization 8 | { 9 | /// 10 | /// JSON serialization. 11 | /// 12 | Json, 13 | 14 | /// 15 | /// XML serialization. 16 | /// 17 | Xml 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/IProtocolBinding.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.CloudEvents 2 | { 3 | /// 4 | /// Defines how to bind a CloudEvent attribute name to a or 5 | /// header name. 6 | /// 7 | public interface IProtocolBinding 8 | { 9 | /// 10 | /// Gets the header name for a given the specified CloudEvent 11 | /// attribute name. 12 | /// 13 | /// The CloudEvent attribute name. 14 | /// The header name for a . 15 | string GetHeaderName(string attributeName); 16 | 17 | /// 18 | /// Gets the attribute name for a given the specified header name. 20 | /// 21 | /// The header name. 22 | /// 23 | /// When this method returns, a value indicating whether 24 | /// represents a CloudEvent attribute according to this protocol binding. A value of indicates that represents some 26 | /// non-CloudEvent attribute. 27 | /// 28 | /// The CloudEvent attribute name. 29 | string GetAttributeName(string headerName, out bool isCloudEventAttribute); 30 | 31 | /// 32 | /// Binds the attributes of the to the headers of the . Called after has been 34 | /// mapped to . 35 | /// 36 | /// The source . 37 | /// The target . 38 | void Bind(CloudEvent fromCloudEvent, SenderMessage toSenderMessage); 39 | 40 | /// 41 | /// Binds the headers of the to the attributes of the . Called after all of the headers of the have been added to , 44 | /// or . 45 | /// 46 | /// The source . 47 | /// The target . 48 | void Bind(IReceiverMessage fromReceiverMessage, CloudEvent toCloudEvent); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/RockLib.Messaging.CloudEvents.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | Send and receive messages using the CloudEvents format. 5 | True 6 | true 7 | True 8 | icon.png 9 | RockLib.Messaging.CloudEvents 10 | LICENSE.md 11 | https://github.com/RockLib/RockLib.Messaging 12 | A changelog is available at https://github.com/RockLib/RockLib.Messaging/blob/main/RockLib.Messaging.CloudEvents/CHANGELOG.md. 13 | false 14 | rocklib messaging cloudevents 15 | 4.0.0 16 | True 17 | 4.0.0 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/RockLib.Messaging.CloudEvents.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29926.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.CloudEvents", "RockLib.Messaging.CloudEvents.csproj", "{A40564DD-9C71-444D-B8E9-2098A6AA3445}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.CloudEvents.Tests", "..\Tests\RockLib.Messaging.CloudEvents.Tests\RockLib.Messaging.CloudEvents.Tests.csproj", "{57E86379-F6E9-4F83-93E5-F4CF68B51049}" 9 | EndProject 10 | Global 11 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 12 | RockLib.Messaging.HttpUtils\RockLib.Messaging.HttpUtils.projitems*{a40564dd-9c71-444d-b8e9-2098a6aa3445}*SharedItemsImports = 5 13 | EndGlobalSection 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {A40564DD-9C71-444D-B8E9-2098A6AA3445}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {A40564DD-9C71-444D-B8E9-2098A6AA3445}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {A40564DD-9C71-444D-B8E9-2098A6AA3445}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {A40564DD-9C71-444D-B8E9-2098A6AA3445}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {57E86379-F6E9-4F83-93E5-F4CF68B51049}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {57E86379-F6E9-4F83-93E5-F4CF68B51049}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {57E86379-F6E9-4F83-93E5-F4CF68B51049}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {57E86379-F6E9-4F83-93E5-F4CF68B51049}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(SolutionProperties) = preSolution 29 | HideSolutionNode = FALSE 30 | EndGlobalSection 31 | GlobalSection(ExtensibilityGlobals) = postSolution 32 | SolutionGuid = {3FEE2AF4-936B-474F-A3C0-7118F5655DCC} 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /RockLib.Messaging.CloudEvents/SequenceTypes.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.CloudEvents 2 | { 3 | /// 4 | /// Defines values for the attribute. 5 | /// 6 | public static class SequenceTypes 7 | { 8 | /// 9 | /// If the attribute is set to "Integer", 10 | /// the attribute has the following semantics: 11 | /// 12 | /// The values of sequence are string-encoded signed 32-bit Integers. 13 | /// The sequence MUST start with a value of 1 and increase by 1 for each subsequent value 14 | /// (i.e. be contiguous and monotonically increasing). 15 | /// The sequence wraps around from 2,147,483,647 (2^31 - 1) to -2,147,483,648 (-2^31). 16 | /// 17 | /// 18 | #pragma warning disable CA1720 // Identifiers should not contain type names 19 | public const string Integer = "Integer"; 20 | #pragma warning restore CA1720 // Identifiers should not contain type names 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RockLib.Messaging.Http/DefaultHttpResponseGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.Http 2 | { 3 | /// 4 | /// A simple implementation of . 5 | /// 6 | public class DefaultHttpResponseGenerator : IHttpResponseGenerator 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | /// 12 | /// The status code to be returned to the client when a message is acknowledged. 13 | /// 14 | /// 15 | /// The status code to be returned to the client when a message is rolled back. 16 | /// 17 | /// 18 | /// The status code to be returned to the client when a message is acknowledged. 19 | /// 20 | public DefaultHttpResponseGenerator(int acknowledgeStatusCode, int rollbackStatusCode, int rejectStatusCode) 21 | { 22 | AcknowledgeResponse = new HttpResponse(acknowledgeStatusCode); 23 | RollbackResponse = new HttpResponse(rollbackStatusCode); 24 | RejectResponse = new HttpResponse(rejectStatusCode); 25 | } 26 | 27 | /// 28 | /// Gets the response to be returned from calls to . 29 | /// 30 | #pragma warning disable CA1721 // Property names should not match get methods 31 | public HttpResponse AcknowledgeResponse { get; } 32 | #pragma warning restore CA1721 // Property names should not match get methods 33 | 34 | /// 35 | /// Gets the response to be returned from calls to . 36 | /// 37 | #pragma warning disable CA1721 // Property names should not match get methods 38 | public HttpResponse RollbackResponse { get; } 39 | #pragma warning restore CA1721 // Property names should not match get methods 40 | 41 | /// 42 | /// Gets the response to be returned from calls to . 43 | /// 44 | #pragma warning disable CA1721 // Property names should not match get methods 45 | public HttpResponse RejectResponse { get; } 46 | #pragma warning restore CA1721 // Property names should not match get methods 47 | 48 | /// 49 | /// Returns . 50 | /// 51 | public HttpResponse GetAcknowledgeResponse(HttpListenerReceiverMessage message) => AcknowledgeResponse; 52 | 53 | /// 54 | /// Returns . 55 | /// 56 | public HttpResponse GetRollbackResponse(HttpListenerReceiverMessage message) => RollbackResponse; 57 | 58 | /// 59 | /// Returns . 60 | /// 61 | public HttpResponse GetRejectResponse(HttpListenerReceiverMessage message) => RejectResponse; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /RockLib.Messaging.Http/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Messaging.Http/HttpResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RockLib.Messaging.Http 5 | { 6 | /// 7 | /// Defines an http response. 8 | /// 9 | public class HttpResponse 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The http status code of the response. 15 | /// The http status description of the response. 16 | /// The string content of the response. 17 | public HttpResponse(int statusCode, string? content = null, string? statusDescription = null) 18 | : this(statusCode, statusDescription, (object)content!) 19 | { 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// The http status code of the response. 26 | /// The http status description of the response. 27 | /// The binary content of the response. 28 | public HttpResponse(int statusCode, byte[] content, string? statusDescription = null) 29 | : this(statusCode, statusDescription, content) 30 | { 31 | } 32 | 33 | private HttpResponse(int statusCode, string? statusDescription, object? content) 34 | { 35 | if (statusCode < 100 || statusCode > 999) 36 | { 37 | throw new ArgumentException("statusCode cannot be less than 100 or greater than 999.", nameof(statusCode)); 38 | } 39 | StatusCode = statusCode; 40 | StatusDescription = statusDescription; 41 | Content = content; 42 | } 43 | 44 | /// 45 | /// Gets the status code of the response. 46 | /// 47 | public int StatusCode { get; } 48 | 49 | /// 50 | /// Gets the status description of the response. 51 | /// 52 | public string? StatusDescription { get; } 53 | 54 | /// 55 | /// Gets the content of the response. 56 | /// 57 | public object? Content { get; } 58 | 59 | /// 60 | /// Gets a dictionary representing the headers of the response. 61 | /// 62 | public IDictionary Headers { get; } = new Dictionary(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /RockLib.Messaging.Http/IHttpResponseGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.Http 2 | { 3 | /// 4 | /// Defines an object that generates http responses for a specified message. 5 | /// 6 | public interface IHttpResponseGenerator 7 | { 8 | /// 9 | /// Get an http response for an http request when the user has acknowledged 10 | /// it. 11 | /// 12 | /// The message that is being acknowledged. 13 | HttpResponse GetAcknowledgeResponse(HttpListenerReceiverMessage message); 14 | 15 | /// 16 | /// Get an http response for an http request when the user has rolled it 17 | /// back. 18 | /// 19 | /// The message that is being rolled back. 20 | HttpResponse GetRollbackResponse(HttpListenerReceiverMessage message); 21 | 22 | /// 23 | /// Get an http response for an http request when the user has rejected it. 24 | /// 25 | /// The message that is being rejected. 26 | HttpResponse GetRejectResponse(HttpListenerReceiverMessage message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RockLib.Messaging.Http/RockLib.Messaging.Http.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | RockLib 4 | True 5 | Embedded 6 | An implementation of RockLib.Messaging that sends and receives messages via HTTP. 7 | true 8 | True 9 | RockLib.Messaging.Http 10 | 4.0.0 11 | false 12 | A changelog is available at https://github.com/RockLib/RockLib.Messaging/blob/main/RockLib.Messaging.Http/CHANGELOG.md. 13 | https://github.com/RockLib/RockLib.Messaging 14 | LICENSE.md 15 | icon.png 16 | rocklib messaging http 17 | True 18 | 4.0.0 19 | 20 | 21 | true 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /RockLib.Messaging.Http/RockLib.Messaging.Http.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32228.430 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.Http", "RockLib.Messaging.Http.csproj", "{C74A231E-DDE2-413F-BBF2-673AE2DB9B51}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.Http.Tests", "..\Tests\RockLib.Messaging.Http.Tests\RockLib.Messaging.Http.Tests.csproj", "{BA8E64C8-2F6F-468D-91CD-225D0357A43E}" 9 | EndProject 10 | Global 11 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 12 | ..\RockLib.Messaging.HttpUtils\RockLib.Messaging.HttpUtils.projitems*{c74a231e-dde2-413f-bbf2-673ae2db9b51}*SharedItemsImports = 5 13 | EndGlobalSection 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {C74A231E-DDE2-413F-BBF2-673AE2DB9B51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {C74A231E-DDE2-413F-BBF2-673AE2DB9B51}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {C74A231E-DDE2-413F-BBF2-673AE2DB9B51}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {C74A231E-DDE2-413F-BBF2-673AE2DB9B51}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {BA8E64C8-2F6F-468D-91CD-225D0357A43E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {BA8E64C8-2F6F-468D-91CD-225D0357A43E}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {BA8E64C8-2F6F-468D-91CD-225D0357A43E}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {BA8E64C8-2F6F-468D-91CD-225D0357A43E}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(SolutionProperties) = preSolution 29 | HideSolutionNode = FALSE 30 | EndGlobalSection 31 | GlobalSection(ExtensibilityGlobals) = postSolution 32 | SolutionGuid = {DC62819D-E06C-4684-A84D-7F7DA3833A3B} 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /RockLib.Messaging.Kafka/Constants.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | 3 | namespace RockLib.Messaging.Kafka 4 | { 5 | /// 6 | /// Defines constants for the RockLib.Messaging.Kafka package. 7 | /// 8 | public static class Constants 9 | { 10 | /// 11 | /// The default timeout value. 12 | /// 13 | public const int DefaultTimeout = 10_000; 14 | 15 | /// 16 | /// The name of the header where a Kafka message's Key is stored. 17 | /// If a has a header with this name and a sends it, then the Key of the outgoing Kafka is set to that header's value. 20 | /// When a receives a 21 | /// with a non-empty Key, then that Key is found in the 22 | /// header with this name. 23 | /// 24 | public const string KafkaKeyHeader = "Kafka.Key"; 25 | 26 | /// 27 | /// The name of the header containing the schema ID. 28 | /// 29 | public const string KafkaSchemaIdHeader = "Kafka.SchemaId"; 30 | 31 | /// 32 | /// Byte value to indicate the raw payload contains a schema ID. When the first byte of the payload is the 33 | /// the subsequent 4 bytes compose the schema ID value according to the Confluent 34 | /// wire format 35 | /// 36 | internal const byte SchemaIdLeadingByte = 0; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /RockLib.Messaging.Kafka/DependencyInjection/KafkaReceiverOptions.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | 3 | namespace RockLib.Messaging.Kafka.DependencyInjection 4 | { 5 | /// 6 | /// Defines the settings for creating instances of . 7 | /// 8 | public class KafkaReceiverOptions : ConsumerConfig 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public KafkaReceiverOptions() 14 | : base() 15 | { 16 | EnableAutoOffsetStore = false; 17 | AutoOffsetReset = Confluent.Kafka.AutoOffsetReset.Latest; 18 | } 19 | 20 | /// 21 | /// Gets or sets the topic to subscribe to. 22 | /// 23 | public string? Topic { get; set; } 24 | 25 | /// 26 | /// Gets or sets whether the kafka receiver expects schema information in the payload 27 | /// 28 | public bool SchemaIdRequired { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /RockLib.Messaging.Kafka/DependencyInjection/KafkaSenderOptions.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | 3 | namespace RockLib.Messaging.Kafka.DependencyInjection 4 | { 5 | /// 6 | /// Defines the settings for creating instances of . 7 | /// 8 | public class KafkaSenderOptions : ProducerConfig 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | public KafkaSenderOptions() 14 | : base() 15 | { 16 | MessageTimeoutMs = Constants.DefaultTimeout; 17 | } 18 | 19 | /// 20 | /// Gets or sets the topic to subscribe to. 21 | /// 22 | public string? Topic { get; set; } 23 | 24 | /// 25 | /// Gets or sets the schema ID to validate messages against 26 | /// 27 | public int SchemaId { get; set; } 28 | } 29 | } -------------------------------------------------------------------------------- /RockLib.Messaging.Kafka/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Messaging.Kafka/Exceptions/InvalidMessageException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.Kafka.Exceptions 4 | { 5 | /// 6 | /// Represents an error that occured when validating a message 7 | /// 8 | [Serializable] 9 | public class InvalidMessageException : Exception 10 | { 11 | /// 12 | /// Construct a new instance of with a specified message 13 | /// 14 | /// Message describing the error 15 | public InvalidMessageException(string message) : base(message) 16 | { 17 | } 18 | 19 | /// 20 | /// Construct a new instance of 21 | /// 22 | public InvalidMessageException() 23 | { 24 | } 25 | 26 | /// 27 | /// Construct a new instance of with a specified message 28 | /// and inner exception 29 | /// 30 | /// Message describing the error 31 | /// The inner exception 32 | public InvalidMessageException(string message, Exception innerException) : base(message, innerException) 33 | { 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /RockLib.Messaging.Kafka/MessageExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using static RockLib.Messaging.Kafka.Constants; 3 | 4 | namespace RockLib.Messaging.Kafka 5 | { 6 | /// 7 | /// Extension methods for and 8 | /// related to Kafka. 9 | /// 10 | public static class MessageExtensions 11 | { 12 | /// 13 | /// Gets the Key of the Kafka message, as stored in the header 14 | /// of the . 15 | /// 16 | /// The . 17 | /// The Key of the Kafka message. 18 | public static string? GetKafkaKey(this IReceiverMessage receiverMessage) 19 | { 20 | #if NET6_0_OR_GREATER 21 | ArgumentNullException.ThrowIfNull(receiverMessage); 22 | #else 23 | if (receiverMessage is null) { throw new ArgumentNullException(nameof(receiverMessage)); } 24 | #endif 25 | 26 | if (receiverMessage.Headers.TryGetValue(KafkaKeyHeader, out string? kafkaKey)) 27 | { 28 | return kafkaKey; 29 | } 30 | 31 | return null; 32 | } 33 | 34 | /// 35 | /// Sets the Key of the Kafka message, as stored in the header 36 | /// of the . 37 | /// 38 | /// The . 39 | /// The Key of the Kafka message. If , the 40 | /// Kafka Key header of the message is removed. 41 | /// event is removed. 42 | public static void SetKafkaKey(this SenderMessage senderMessage, string kafkaKey) 43 | { 44 | #if NET6_0_OR_GREATER 45 | ArgumentNullException.ThrowIfNull(senderMessage); 46 | #else 47 | if (senderMessage is null) { throw new ArgumentNullException(nameof(senderMessage)); } 48 | #endif 49 | 50 | if (!string.IsNullOrEmpty(kafkaKey)) 51 | { 52 | senderMessage.Headers[KafkaKeyHeader] = kafkaKey; 53 | } 54 | else 55 | { 56 | senderMessage.Headers.Remove(KafkaKeyHeader); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RockLib.Messaging.Kafka/RockLib.Messaging.Kafka.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | Implementation of RockLib.Messaging API that sends and receives messages using Kafka. 5 | True 6 | true 7 | True 8 | icon.png 9 | RockLib.Messaging.Kafka 10 | LICENSE.md 11 | https://github.com/RockLib/RockLib.Messaging 12 | A changelog is available at https://github.com/RockLib/RockLib.Messaging/blob/main/RockLib.Messaging.Kafka/CHANGELOG.md. 13 | false 14 | rocklib messaging kafka 15 | 4.0.0 16 | True 17 | 4.0.0 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /RockLib.Messaging.Kafka/RockLib.Messaging.Kafka.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29926.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.Kafka", "RockLib.Messaging.Kafka.csproj", "{2D35EA9A-C9C3-4915-894F-815D9CE5700A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.Kafka.Tests", "..\Tests\RockLib.Messaging.Kafka.Tests\RockLib.Messaging.Kafka.Tests.csproj", "{BDA37A2A-CA76-4FF1-B08F-2783C637B492}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {2D35EA9A-C9C3-4915-894F-815D9CE5700A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {2D35EA9A-C9C3-4915-894F-815D9CE5700A}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {2D35EA9A-C9C3-4915-894F-815D9CE5700A}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {2D35EA9A-C9C3-4915-894F-815D9CE5700A}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {BDA37A2A-CA76-4FF1-B08F-2783C637B492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {BDA37A2A-CA76-4FF1-B08F-2783C637B492}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {BDA37A2A-CA76-4FF1-B08F-2783C637B492}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {BDA37A2A-CA76-4FF1-B08F-2783C637B492}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3FEE2AF4-936B-474F-A3C0-7118F5655DCC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Messaging.NamedPipes/DependencyInjection/NamedPipeOptions.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Messaging.NamedPipes; 2 | 3 | namespace RockLib.Messaging.DependencyInjection 4 | { 5 | /// 6 | /// Defines the settings for creating instances of and 7 | /// . 8 | /// 9 | public class NamedPipeOptions 10 | { 11 | /// 12 | /// Gets or sets the pipe name. If null, the name of the sender or receiver is used as 13 | /// the pipe name. 14 | /// 15 | public string? PipeName { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /RockLib.Messaging.NamedPipes/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Messaging.NamedPipes/NamedPipeMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RockLib.Messaging.NamedPipes 4 | { 5 | internal sealed class NamedPipeMessage 6 | { 7 | public string? StringValue { get; set; } 8 | public Dictionary? Headers { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /RockLib.Messaging.NamedPipes/NamedPipeReceiverMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace RockLib.Messaging.NamedPipes 7 | { 8 | /// 9 | /// An implementation of IReceiverMessage for use by the 10 | /// class. 11 | /// 12 | public class NamedPipeReceiverMessage : ReceiverMessage 13 | { 14 | private readonly NamedPipeMessage _namedPipeMessage; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The message that was sent. 20 | internal NamedPipeReceiverMessage(NamedPipeMessage namedPipeMessage) 21 | : base(() => namedPipeMessage.StringValue!) 22 | { 23 | _namedPipeMessage = namedPipeMessage; 24 | } 25 | 26 | /// 27 | protected override Task AcknowledgeMessageAsync(CancellationToken cancellationToken) => Task.CompletedTask; 28 | 29 | /// 30 | protected override Task RollbackMessageAsync(CancellationToken cancellationToken) => Task.CompletedTask; 31 | 32 | /// 33 | protected override Task RejectMessageAsync(CancellationToken cancellationToken) => Task.CompletedTask; 34 | 35 | /// 36 | protected override void InitializeHeaders(IDictionary headers) 37 | { 38 | #if NET6_0_OR_GREATER 39 | ArgumentNullException.ThrowIfNull(headers); 40 | #else 41 | if (headers is null) { throw new ArgumentNullException(nameof(headers)); } 42 | #endif 43 | 44 | if (_namedPipeMessage.Headers is not null) 45 | { 46 | foreach (var header in _namedPipeMessage.Headers) 47 | { 48 | headers.Add(header.Key, header.Value); 49 | } 50 | } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /RockLib.Messaging.NamedPipes/RockLib.Messaging.NamedPipes.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | Implementation of RockLib.Messaging API that sends and receives messages to a local named pipe. 5 | True 6 | true 7 | True 8 | icon.png 9 | RockLib.Messaging.NamedPipes 10 | LICENSE.md 11 | https://github.com/RockLib/RockLib.Messaging 12 | A changelog is available at https://github.com/RockLib/RockLib.Messaging/blob/main/RockLib.Messaging.NamedPipes/CHANGELOG.md. 13 | false 14 | rocklib messaging 15 | 5.0.0 16 | True 17 | 5.0.0 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /RockLib.Messaging.NamedPipes/RockLib.Messaging.NamedPipes.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.NamedPipes", "RockLib.Messaging.NamedPipes.csproj", "{C1B97CC2-45AC-4B73-8172-C484FD6871A9}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.NamedPipes.Tests", "..\Tests\RockLib.Messaging.NamedPipes.Tests\RockLib.Messaging.NamedPipes.Tests.csproj", "{968246FA-5614-4969-97CF-C3E312DE4D28}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C1B97CC2-45AC-4B73-8172-C484FD6871A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {C1B97CC2-45AC-4B73-8172-C484FD6871A9}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {C1B97CC2-45AC-4B73-8172-C484FD6871A9}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {C1B97CC2-45AC-4B73-8172-C484FD6871A9}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {968246FA-5614-4969-97CF-C3E312DE4D28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {968246FA-5614-4969-97CF-C3E312DE4D28}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {968246FA-5614-4969-97CF-C3E312DE4D28}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {968246FA-5614-4969-97CF-C3E312DE4D28}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3FEE2AF4-936B-474F-A3C0-7118F5655DCC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Messaging.RabbitMQ/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Messaging.RabbitMQ/RabbitReceiverMessage.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client.Events; 2 | using RabbitMQ.Client; 3 | using System.Collections.Generic; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using System.Text; 7 | using System; 8 | 9 | namespace RockLib.Messaging.RabbitMQ 10 | { 11 | /// 12 | /// An implementation of IReceiverMessage for use by the 13 | /// class. 14 | /// 15 | public sealed class RabbitReceiverMessage : ReceiverMessage 16 | { 17 | private readonly BasicDeliverEventArgs _args; 18 | private readonly IModel _channel; 19 | private readonly bool _autoAck; 20 | 21 | internal RabbitReceiverMessage(BasicDeliverEventArgs args, IModel channel, bool autoAck) 22 | : base(() => args.Body.ToArray()) 23 | { 24 | _args = args; 25 | _channel = channel; 26 | _autoAck = autoAck; 27 | } 28 | 29 | /// 30 | protected override void InitializeHeaders(IDictionary headers) 31 | { 32 | #if NET6_0_OR_GREATER 33 | ArgumentNullException.ThrowIfNull(headers); 34 | #else 35 | if (headers is null) { throw new ArgumentNullException(nameof(headers)); } 36 | #endif 37 | 38 | if (_args.BasicProperties?.Headers == null) 39 | { 40 | return; 41 | } 42 | 43 | foreach (var header in _args.BasicProperties.Headers) 44 | { 45 | if (header.Value is byte[] binary) 46 | { 47 | headers.Add(header.Key, Encoding.UTF8.GetString(binary)); 48 | } 49 | else 50 | { 51 | headers.Add(header); 52 | } 53 | } 54 | } 55 | 56 | /// 57 | protected override Task AcknowledgeMessageAsync(CancellationToken cancellationToken) 58 | { 59 | if (!_autoAck) 60 | { 61 | _channel.BasicAck(_args.DeliveryTag, false); 62 | } 63 | return Task.CompletedTask; 64 | } 65 | 66 | /// 67 | protected override Task RollbackMessageAsync(CancellationToken cancellationToken) 68 | { 69 | if (!_autoAck) 70 | { 71 | _channel.BasicNack(_args.DeliveryTag, false, true); 72 | } 73 | return Task.CompletedTask; 74 | } 75 | 76 | /// 77 | protected override Task RejectMessageAsync(CancellationToken cancellationToken) 78 | { 79 | if (!_autoAck) 80 | { 81 | _channel.BasicNack(_args.DeliveryTag, false, false); 82 | } 83 | return Task.CompletedTask; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /RockLib.Messaging.RabbitMQ/RockLib.Messaging.RabbitMQ.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | Implementation of RockLib.Messaging API that sends and receives messages using RabbitMQ. 5 | True 6 | true 7 | True 8 | icon.png 9 | RockLib.Messaging.RabbitMQ 10 | LICENSE.md 11 | https://github.com/RockLib/RockLib.Messaging 12 | A changelog is available at https://github.com/RockLib/RockLib.Messaging/blob/main/RockLib.Messaging.RabbitMQ/CHANGELOG.md. 13 | false 14 | rocklib messaging rabbitmq 15 | 3.0.0 16 | True 17 | 3.0.0 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /RockLib.Messaging.RabbitMQ/RockLib.Messaging.RabbitMQ.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.RabbitMQ", "RockLib.Messaging.RabbitMQ.csproj", "{2D35EA9A-C9C3-4915-894F-815D9CE5700A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {2D35EA9A-C9C3-4915-894F-815D9CE5700A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {2D35EA9A-C9C3-4915-894F-815D9CE5700A}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {2D35EA9A-C9C3-4915-894F-815D9CE5700A}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {2D35EA9A-C9C3-4915-894F-815D9CE5700A}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3FEE2AF4-936B-474F-A3C0-7118F5655DCC} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /RockLib.Messaging.SNS/DependencyInjection/SNSExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using RockLib.Messaging.SNS; 3 | using System; 4 | 5 | namespace RockLib.Messaging.DependencyInjection 6 | { 7 | /// 8 | /// Extension methods for registering SNS senders. 9 | /// 10 | public static class SNSExtensions 11 | { 12 | /// 13 | /// Adds an to the service collection. 14 | /// 15 | /// The . 16 | /// The name of the sender. 17 | /// A callback for configuring the . 18 | /// 19 | /// Whether to create an SNS sender that automatically reloads itself when its 20 | /// configuration or options change. 21 | /// 22 | /// A builder allowing the sender to be decorated. 23 | public static ISenderBuilder AddSNSSender(this IServiceCollection services, string name, 24 | Action? configureOptions = null, bool reloadOnChange = true) 25 | { 26 | return services.AddSender(name, CreateSNSSender, configureOptions, reloadOnChange); 27 | 28 | ISender CreateSNSSender(SNSSenderOptions options, IServiceProvider serviceProvider) => 29 | new SNSSender(name, options.TopicArn, options.Region); 30 | } 31 | 32 | /// 33 | /// Adds an to the service collection. 34 | /// 35 | /// The . 36 | /// The name of the sender. 37 | /// A callback for configuring the . 38 | /// A builder allowing the sender to be decorated. 39 | public static ISenderBuilder AddSNSSender(this IServiceCollection services, string name, 40 | Action configureOptions) => 41 | services.AddSNSSender(name, configureOptions, true); 42 | } 43 | } -------------------------------------------------------------------------------- /RockLib.Messaging.SNS/DependencyInjection/SNSSenderOptions.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Messaging.SNS; 2 | using System; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace RockLib.Messaging.DependencyInjection 6 | { 7 | /// 8 | /// Defines the settings for creating instances of . 9 | /// 10 | public class SNSSenderOptions 11 | { 12 | private string? _topicArn; 13 | 14 | /// 15 | /// Gets or sets the arn of the SNS topic. 16 | /// 17 | public string TopicArn 18 | { 19 | [return: MaybeNull] get => _topicArn!; 20 | set => _topicArn = value ?? throw new ArgumentNullException(nameof(value)); 21 | } 22 | 23 | /// 24 | /// Gets or sets the region of the SNS topic. 25 | /// 26 | public string? Region { get; set; } 27 | } 28 | } -------------------------------------------------------------------------------- /RockLib.Messaging.SNS/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Messaging.SNS/RockLib.Messaging.SNS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | Implementation of RockLib.Messaging API that sends messages using Amazon SNS. 5 | True 6 | true 7 | True 8 | icon.png 9 | RockLib.Messaging.SNS 10 | LICENSE.md 11 | https://github.com/RockLib/RockLib.Messaging 12 | A changelog is available at https://github.com/RockLib/RockLib.Messaging/blob/main/RockLib.Messaging.SNS/CHANGELOG.md. 13 | false 14 | rocklib messaging sns 15 | 4.0.0 16 | True 17 | 4.0.0 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /RockLib.Messaging.SNS/RockLib.Messaging.SNS.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.SNS", "RockLib.Messaging.SNS.csproj", "{F5558582-F71D-40E3-B47F-66E1F025527E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.SNS.Tests", "..\Tests\RockLib.Messaging.SNS.Tests\RockLib.Messaging.SNS.Tests.csproj", "{616A4D6D-44E7-46BB-A080-DECC15D53448}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {F5558582-F71D-40E3-B47F-66E1F025527E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {F5558582-F71D-40E3-B47F-66E1F025527E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {F5558582-F71D-40E3-B47F-66E1F025527E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {F5558582-F71D-40E3-B47F-66E1F025527E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {616A4D6D-44E7-46BB-A080-DECC15D53448}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {616A4D6D-44E7-46BB-A080-DECC15D53448}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {616A4D6D-44E7-46BB-A080-DECC15D53448}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {616A4D6D-44E7-46BB-A080-DECC15D53448}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3FEE2AF4-936B-474F-A3C0-7118F5655DCC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Messaging.SQS/DependencyInjection/SQSSenderOptions.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SQS; 2 | using RockLib.Messaging.SQS; 3 | using System; 4 | 5 | namespace RockLib.Messaging.DependencyInjection 6 | { 7 | /// 8 | /// Defines the settings for creating instances of . 9 | /// 10 | public class SQSSenderOptions 11 | { 12 | private Uri? _queueUrl; 13 | 14 | /// 15 | /// Gets or sets the url of the SQS queue. 16 | /// 17 | public Uri? QueueUrl 18 | { 19 | get => _queueUrl; 20 | set => _queueUrl = value ?? throw new ArgumentNullException(nameof(value)); 21 | } 22 | 23 | /// 24 | /// Gets or sets the object that communicates with SQS. 25 | /// 26 | public IAmazonSQS? SqsClient { get; set; } 27 | 28 | /// 29 | /// Gets or sets the region of the SQS client. 30 | /// 31 | public string? Region { get; set; } 32 | 33 | /// 34 | /// Gets or sets the tag that specifies that a message belongs to a specific message 35 | /// group. Messages that belong to the same message group are processed in a FIFO manner 36 | /// (however, messages in different message groups might be processed out of order). To 37 | /// interleave multiple ordered streams within a single queue, use MessageGroupId values 38 | /// (for example, session data for multiple users). In this scenario, multiple consumers 39 | /// can process the queue, but the session data of each user is processed in a FIFO 40 | /// fashion. 41 | /// This parameter applies only to FIFO (first-in-first-out) queues. 42 | /// 43 | public string? MessageGroupId { get; set; } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /RockLib.Messaging.SQS/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Messaging.SQS/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("RockLib.Messaging.SQS.Tests")] -------------------------------------------------------------------------------- /RockLib.Messaging.SQS/RockLib.Messaging.SQS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | Implementation of RockLib.Messaging API that sends and receives messages using Amazon SQS. 5 | True 6 | true 7 | True 8 | icon.png 9 | RockLib.Messaging.SQS 10 | LICENSE.md 11 | https://github.com/RockLib/RockLib.Messaging 12 | 5.0.0 13 | A changelog is available at https://github.com/RockLib/RockLib.Messaging/blob/main/RockLib.Messaging.SQS/CHANGELOG.md. 14 | True 15 | false 16 | rocklib messaging sqs 17 | 5.0.0 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /RockLib.Messaging.SQS/RockLib.Messaging.SQS.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.SQS", "RockLib.Messaging.SQS.csproj", "{C4385BD1-2B62-4232-ADC2-896BDBE34142}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.SQS.Tests", "..\Tests\RockLib.Messaging.SQS.Tests\RockLib.Messaging.SQS.Tests.csproj", "{202FD7C2-2E35-4AA2-81A6-FCA895852CF9}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C4385BD1-2B62-4232-ADC2-896BDBE34142}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {C4385BD1-2B62-4232-ADC2-896BDBE34142}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {C4385BD1-2B62-4232-ADC2-896BDBE34142}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {C4385BD1-2B62-4232-ADC2-896BDBE34142}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {202FD7C2-2E35-4AA2-81A6-FCA895852CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {202FD7C2-2E35-4AA2-81A6-FCA895852CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {202FD7C2-2E35-4AA2-81A6-FCA895852CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {202FD7C2-2E35-4AA2-81A6-FCA895852CF9}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3FEE2AF4-936B-474F-A3C0-7118F5655DCC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Messaging/AssemblyAttributes.cs: -------------------------------------------------------------------------------- 1 | using RockLib.Configuration.ObjectFactory; 2 | using RockLib.Messaging; 3 | using System.Collections.Generic; 4 | 5 | [assembly: ConfigSection("RockLib.Messaging:Senders", typeof(List))] 6 | [assembly: ConfigSection("RockLib.Messaging:Receivers", typeof(List))] 7 | -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/DecorationDelegates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.DependencyInjection 4 | { 5 | /// 6 | /// Represents a method that creates an decorator. 7 | /// 8 | /// The that is being decorated. 9 | /// 10 | /// The that resolves dependencies necessary for the creation of the 11 | /// decorator. 12 | /// 13 | /// An decorator. 14 | public delegate ISender SenderDecoration(ISender sender, IServiceProvider serviceProvider); 15 | 16 | /// 17 | /// Represents a method that creates an decorator. 18 | /// 19 | /// The that is being decorated. 20 | /// 21 | /// The that resolves dependencies necessary for the creation of the 22 | /// decorator. 23 | /// 24 | /// An decorator. 25 | public delegate ITransactionalSender TransactionalSenderDecoration(ITransactionalSender sender, IServiceProvider serviceProvider); 26 | 27 | /// 28 | /// Represents a method that creates an decorator. 29 | /// 30 | /// The that is being decorated. 31 | /// 32 | /// The that resolves dependencies necessary for the creation of the 33 | /// decorator. 34 | /// 35 | /// An decorator. 36 | public delegate IReceiver ReceiverDecoration(IReceiver receiver, IServiceProvider serviceProvider); 37 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/ForwardingExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Options; 3 | using System; 4 | 5 | namespace RockLib.Messaging.DependencyInjection 6 | { 7 | /// 8 | /// Extension methods for registering a forwarding receiver. 9 | /// 10 | public static class ForwardingExtensions 11 | { 12 | /// 13 | /// Adds a registration to the builder. 14 | /// 15 | /// The . 16 | /// A callback for configuring the . 17 | /// The same . 18 | public static IReceiverBuilder AddForwardingReceiver(this IReceiverBuilder builder, 19 | Action? configureOptions = null) 20 | { 21 | #if NET6_0_OR_GREATER 22 | ArgumentNullException.ThrowIfNull(builder); 23 | #else 24 | if (builder is null) { throw new ArgumentNullException(nameof(builder)); } 25 | #endif 26 | builder.AddDecorator((receiver, serviceProvider) => 27 | { 28 | var optionsMonitor = serviceProvider.GetService>(); 29 | var options = optionsMonitor?.Get(receiver.Name) ?? new ForwardingReceiverOptions(); 30 | configureOptions?.Invoke(options); 31 | 32 | return new ForwardingReceiver(receiver.Name, receiver, 33 | options.GetAcknowledgeForwarder(serviceProvider), options.AcknowledgeOutcome, 34 | options.GetRollbackForwarder(serviceProvider), options.RollbackOutcome, 35 | options.GetRejectForwarder(serviceProvider), options.RejectOutcome); 36 | }); 37 | 38 | return builder; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/IForwardingReceiverOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.DependencyInjection 4 | { 5 | /// 6 | /// Defines the editable settings for creating an instance of . 7 | /// 8 | public interface IForwardingReceiverOptions 9 | { 10 | /// 11 | /// Gets or sets the name of the receiver - as obtained from an 12 | /// - that is used as the forwarder for messages that are acknowledged. 13 | /// 14 | string? AcknowledgeForwarderName { get; set; } 15 | 16 | /// 17 | /// Gets or sets the outcome for received messages that have been forwarded to to the 18 | /// acknowledge forwarder. 19 | /// 20 | ForwardingOutcome AcknowledgeOutcome { get; set; } 21 | 22 | /// 23 | /// Gets or sets the name of the receiver - as obtained from an 24 | /// - that is used as the forwarder for messages that are rolled back. 25 | /// 26 | string? RollbackForwarderName { get; set; } 27 | 28 | /// 29 | /// Gets or sets the outcome for received messages that have been forwarded to to the 30 | /// rollback forwarder. 31 | /// 32 | ForwardingOutcome RollbackOutcome { get; set; } 33 | 34 | /// 35 | /// Gets or sets the name of the receiver - as obtained from an 36 | /// - that is used as the forwarder for messages that are rejected. 37 | /// 38 | string? RejectForwarderName { get; set; } 39 | 40 | /// 41 | /// Gets or sets the outcome for received messages that have been forwarded to to the 42 | /// reject forwarder. 43 | /// 44 | ForwardingOutcome RejectOutcome { get; set; } 45 | } 46 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/IReceiverBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.DependencyInjection 2 | { 3 | /// 4 | /// A builder used to decorate an . 5 | /// 6 | public interface IReceiverBuilder 7 | { 8 | /// 9 | /// Adds a decoration delegate to the builder. 10 | /// 11 | /// The decoration delegate. 12 | IReceiverBuilder AddDecorator(ReceiverDecoration decoration); 13 | } 14 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/ISenderBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.DependencyInjection 2 | { 3 | /// 4 | /// A builder used to decorate an . 5 | /// 6 | public interface ISenderBuilder 7 | { 8 | /// 9 | /// Adds a decoration delegate to the builder. 10 | /// 11 | /// The decoration delegate. 12 | ISenderBuilder AddDecorator(SenderDecoration decoration); 13 | } 14 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/ITransactionalSenderBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.DependencyInjection 2 | { 3 | /// 4 | /// A builder used to decorate an . 5 | /// 6 | public interface ITransactionalSenderBuilder 7 | { 8 | /// 9 | /// Adds a decoration delegate to the builder. 10 | /// 11 | /// The decoration delegate. 12 | ITransactionalSenderBuilder AddDecorator(TransactionalSenderDecoration decoration); 13 | } 14 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/LookupDelegates.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging.DependencyInjection 2 | { 3 | /// 4 | /// Represents a method that retrieves an by its name. 5 | /// 6 | /// The name of the to retrieve. 7 | /// The matching . 8 | public delegate ISender SenderLookup(string name); 9 | 10 | /// 11 | /// Represents a method that retrieves an by its name. 12 | /// 13 | /// The name of the to retrieve. 14 | /// The matching . 15 | public delegate ITransactionalSender TransactionalSenderLookup(string name); 16 | 17 | /// 18 | /// Represents a method that retrieves an by its name. 19 | /// 20 | /// The name of the to retrieve. 21 | /// The matching . 22 | public delegate IReceiver ReceiverLookup(string name); 23 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/ReceiverBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.DependencyInjection 4 | { 5 | /// 6 | /// The default implementation of the interface. 7 | /// 8 | public class ReceiverBuilder : IReceiverBuilder 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// 14 | /// The registration delegate that is responsible for creating the . 15 | /// 16 | public ReceiverBuilder(ReceiverRegistration registration) => 17 | Registration = registration ?? throw new ArgumentNullException(nameof(registration)); 18 | 19 | /// 20 | /// Gets the registration delegate that is responsible for creating the . 21 | /// 22 | public ReceiverRegistration Registration { get; private set; } 23 | 24 | /// 25 | /// Adds a decoration delegate to the builder. 26 | /// 27 | /// The decoration delegate. 28 | public IReceiverBuilder AddDecorator(ReceiverDecoration decoration) 29 | { 30 | #if NET6_0_OR_GREATER 31 | ArgumentNullException.ThrowIfNull(decoration); 32 | #else 33 | if (decoration is null) { throw new ArgumentNullException(nameof(decoration)); } 34 | #endif 35 | 36 | var registration = Registration; 37 | Registration = serviceProvider => 38 | decoration.Invoke(registration.Invoke(serviceProvider), serviceProvider); 39 | return this; 40 | } 41 | 42 | /// 43 | /// Creates an instance of using the . 44 | /// 45 | /// 46 | /// The that retrieves the services required to create the . 47 | /// 48 | /// An instance of . 49 | public IReceiver Build(IServiceProvider serviceProvider) => Registration(serviceProvider); 50 | } 51 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/RegistrationDelegates.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.DependencyInjection 4 | { 5 | /// 6 | /// Represents a method that creates an . 7 | /// 8 | /// 9 | /// The that resolves dependencies necessary for the creation of the 10 | /// . 11 | /// 12 | /// An . 13 | public delegate ISender SenderRegistration(IServiceProvider serviceProvider); 14 | 15 | /// 16 | /// Represents a method that creates an . 17 | /// 18 | /// 19 | /// The that resolves dependencies necessary for the creation of the 20 | /// . 21 | /// 22 | /// An . 23 | public delegate ITransactionalSender TransactionalSenderRegistration(IServiceProvider serviceProvider); 24 | 25 | /// 26 | /// Represents a method that creates an . 27 | /// 28 | /// 29 | /// The that resolves dependencies necessary for the creation of the 30 | /// . 31 | /// 32 | /// An . 33 | public delegate IReceiver ReceiverRegistration(IServiceProvider serviceProvider); 34 | } 35 | -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/ReloadingSender.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace RockLib.Messaging.DependencyInjection 7 | { 8 | internal class ReloadingSender : ISender 9 | where TSenderOptions : class, new() 10 | { 11 | public ReloadingSender(string name, Func createSender, 12 | IOptionsMonitor optionsMonitor, Action? configureOptions) 13 | { 14 | Name = name; 15 | CreateSender = createSender; 16 | ConfigureOptions = configureOptions; 17 | 18 | var options = optionsMonitor.GetOptions(Name, configureOptions); 19 | Sender = CreateSender.Invoke(options); 20 | ChangeListener = optionsMonitor.OnChange(OptionsMonitorChanged)!; 21 | } 22 | 23 | public string Name { get; } 24 | 25 | public ISender Sender { get; private set; } 26 | 27 | public Func CreateSender { get; } 28 | 29 | public Action? ConfigureOptions { get; } 30 | 31 | public IDisposable ChangeListener { get; } 32 | 33 | public Task SendAsync(SenderMessage message, CancellationToken cancellationToken) => 34 | Sender.SendAsync(message, cancellationToken); 35 | 36 | public void Dispose() 37 | { 38 | ChangeListener.Dispose(); 39 | Sender.Dispose(); 40 | } 41 | 42 | private void OptionsMonitorChanged(TSenderOptions options, string? name) 43 | { 44 | if (string.Equals(Name, name, StringComparison.OrdinalIgnoreCase)) 45 | { 46 | ConfigureOptions?.Invoke(options); 47 | 48 | var oldSender = Sender; 49 | var newSender = CreateSender.Invoke(options); 50 | 51 | Sender = newSender; 52 | oldSender.Dispose(); 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/SenderBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.DependencyInjection 4 | { 5 | /// 6 | /// The default implementation of the interface. 7 | /// 8 | public class SenderBuilder : ISenderBuilder 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// 14 | /// The registration delegate that is responsible for creating the . 15 | /// 16 | public SenderBuilder(SenderRegistration registration) => 17 | Registration = registration ?? throw new ArgumentNullException(nameof(registration)); 18 | 19 | /// 20 | /// Gets the registration delegate that is responsible for creating the . 21 | /// 22 | public SenderRegistration Registration { get; private set; } 23 | 24 | /// 25 | /// Adds a decoration delegate to the builder. 26 | /// 27 | /// The decoration delegate. 28 | public ISenderBuilder AddDecorator(SenderDecoration decoration) 29 | { 30 | #if NET6_0_OR_GREATER 31 | ArgumentNullException.ThrowIfNull(decoration); 32 | #else 33 | if (decoration is null) { throw new ArgumentNullException(nameof(decoration)); } 34 | #endif 35 | 36 | var registration = Registration; 37 | Registration = serviceProvider => 38 | decoration.Invoke(registration.Invoke(serviceProvider), serviceProvider); 39 | return this; 40 | } 41 | 42 | /// 43 | /// Creates an instance of using the . 44 | /// 45 | /// 46 | /// The that retrieves the services required to create the . 47 | /// 48 | /// An instance of . 49 | public ISender Build(IServiceProvider serviceProvider) => Registration(serviceProvider); 50 | } 51 | } -------------------------------------------------------------------------------- /RockLib.Messaging/DependencyInjection/TransactionalSenderBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.DependencyInjection 4 | { 5 | /// 6 | /// The default implementation of the interface. 7 | /// 8 | public class TransactionalSenderBuilder : ITransactionalSenderBuilder 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// 14 | /// The registration delegate that is responsible for creating the . 15 | /// 16 | public TransactionalSenderBuilder(TransactionalSenderRegistration registration) => 17 | Registration = registration ?? throw new ArgumentNullException(nameof(registration)); 18 | 19 | /// 20 | /// Gets the registration delegate that is responsible for creating the . 21 | /// 22 | public TransactionalSenderRegistration Registration { get; private set; } 23 | 24 | /// 25 | /// Adds a decoration delegate to the builder. 26 | /// 27 | /// The decoration delegate. 28 | public ITransactionalSenderBuilder AddDecorator(TransactionalSenderDecoration decoration) 29 | { 30 | #if NET6_0_OR_GREATER 31 | ArgumentNullException.ThrowIfNull(decoration); 32 | #else 33 | if (decoration is null) { throw new ArgumentNullException(nameof(decoration)); } 34 | #endif 35 | 36 | var registration = Registration; 37 | Registration = serviceProvider => 38 | decoration.Invoke(registration.Invoke(serviceProvider), serviceProvider); 39 | return this; 40 | } 41 | 42 | /// 43 | /// Creates an instance of using the . 44 | /// 45 | /// 46 | /// The that retrieves the services required to create the . 47 | /// 48 | /// An instance of . 49 | public ITransactionalSender Build(IServiceProvider serviceProvider) => Registration(serviceProvider); 50 | } 51 | } -------------------------------------------------------------------------------- /RockLib.Messaging/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /RockLib.Messaging/DisconnectedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging 4 | { 5 | /// 6 | /// Provides data for the event. 7 | /// 8 | public class DisconnectedEventArgs : EventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The error message that describes the reason for the disconnection. 14 | public DisconnectedEventArgs(string errorMessage) => ErrorMessage = errorMessage; 15 | 16 | /// 17 | /// Gets the error message that describes the reason for the disconnection. 18 | /// 19 | public string ErrorMessage { get; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /RockLib.Messaging/ErrorEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging 4 | { 5 | /// 6 | /// Provides data for error events such as . 7 | /// 8 | public class ErrorEventArgs : EventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// A message the describes the error. 14 | /// The exception responsible for the error. 15 | public ErrorEventArgs(string message, Exception exception) 16 | { 17 | Message = message; 18 | Exception = exception; 19 | } 20 | 21 | /// 22 | /// Gets the message that describes the error. 23 | /// 24 | public string Message { get; } 25 | 26 | /// 27 | /// Gets the exception responsible for the error. 28 | /// 29 | public Exception Exception { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /RockLib.Messaging/ForwardingMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace RockLib.Messaging 4 | { 5 | /// 6 | /// A decorator for the interface that forwards 7 | /// received messages to configured instances when 8 | /// messages are acknowledged, rolled back, or rejected. 9 | /// 10 | public class ForwardingMessageHandler : IMessageHandler 11 | { 12 | internal ForwardingMessageHandler(ForwardingReceiver forwardingReceiver, IMessageHandler messageHandler) 13 | { 14 | ForwardingReceiver = forwardingReceiver; 15 | MessageHandler = messageHandler; 16 | } 17 | 18 | /// 19 | /// Gets the whose properties determine 20 | /// what happens to messages when they are acknowledged, rolled back, or 21 | /// rejected. The that this decorates is the actual 22 | /// source of messages. 23 | /// 24 | public ForwardingReceiver ForwardingReceiver { get; } 25 | 26 | /// 27 | /// Gets the actual that gets notified when 28 | /// a message has been received. 29 | /// 30 | public IMessageHandler MessageHandler { get; } 31 | 32 | /// 33 | /// Handles a received message. 34 | /// 35 | /// When invoked, this method invokes the 36 | /// method of the property. It passes the 37 | /// property as the receiver argument and 38 | /// a new decorator as the message 39 | /// argument. 40 | /// 41 | /// 42 | /// The instance of that received the message. 43 | /// The message that was received. 44 | public Task OnMessageReceivedAsync(IReceiver receiver, IReceiverMessage message) 45 | { 46 | using var forwardMessage = new ForwardingReceiverMessage(ForwardingReceiver, message); 47 | return MessageHandler?.OnMessageReceivedAsync(ForwardingReceiver, forwardMessage) ?? Task.CompletedTask; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RockLib.Messaging/ForwardingOutcome.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging 2 | { 3 | /// 4 | /// Defines the outcomes for a forwarded message. 5 | /// 6 | public enum ForwardingOutcome 7 | { 8 | /// 9 | /// A forwarded message should have its method called. 10 | /// 11 | Acknowledge, 12 | 13 | /// 14 | /// A forwarded message should have its method called. 15 | /// 16 | Rollback, 17 | 18 | /// 19 | /// A forwarded message should have its method called. 20 | /// 21 | Reject 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RockLib.Messaging/GZipCompressor.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | using System.IO; 3 | 4 | namespace RockLib.Messaging; 5 | 6 | // Lifted from: 7 | // https://github.com/RockLib/RockLib.Compression/blob/main/RockLib.Compression/GZipCompressor.cs 8 | // https://github.com/RockLib/RockLib.Compression/blob/main/RockLib.Compression/CompressionExtensions.cs 9 | internal static class GZipCompressor 10 | { 11 | internal static byte[] Compress(byte[] data) 12 | { 13 | using var inputStream = new MemoryStream(data); 14 | using var outputStream = new MemoryStream(); 15 | using (var gzStream = new GZipStream(outputStream, CompressionMode.Compress, true)) 16 | { 17 | inputStream.CopyTo(gzStream); 18 | } 19 | 20 | return outputStream.ToArray(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /RockLib.Messaging/GZipDecompressor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | 5 | namespace RockLib.Messaging; 6 | 7 | // Lifted from: 8 | // https://github.com/RockLib/RockLib.Compression/blob/main/RockLib.Compression/GZipDecompressor.cs 9 | // https://github.com/RockLib/RockLib.Compression/blob/main/RockLib.Compression/CompressionExtensions.cs 10 | internal static class GZipDecompressor 11 | { 12 | internal static byte[] Decompress(byte[] data) 13 | { 14 | using var inputStream = new MemoryStream(data); 15 | using var outputStream = new MemoryStream(); 16 | using (var gzStream = new GZipStream(inputStream, CompressionMode.Decompress, true)) 17 | { 18 | gzStream.CopyTo(outputStream); 19 | } 20 | 21 | return outputStream.ToArray(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RockLib.Messaging/HeaderNames.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging 2 | { 3 | /// 4 | /// Provides the names of standard headers. 5 | /// 6 | public static class HeaderNames 7 | { 8 | /// 9 | /// The name of the header used to identify a message. 10 | /// 11 | public const string MessageId = "core_internal_id"; 12 | 13 | /// 14 | /// The name of the header used to mark a message as having its payload compressed. 15 | /// 16 | public const string IsCompressedPayload = "core_compressed_payload"; 17 | 18 | /// 19 | /// The name of the header used to indicate which messaging system generated the message. 20 | /// 21 | public const string OriginatingSystem = "core_originating_system"; 22 | 23 | /// 24 | /// The name of the header used to indicate that a message was originally constructed with 25 | /// a binary message. 26 | /// 27 | public const string IsBinaryPayload = "core_binary_payload"; 28 | } 29 | } -------------------------------------------------------------------------------- /RockLib.Messaging/IMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace RockLib.Messaging 4 | { 5 | /// 6 | /// Defines an object that handles the messages received by an instance 7 | /// of . 8 | /// 9 | public interface IMessageHandler 10 | { 11 | /// 12 | /// Handles a received message. 13 | /// 14 | /// The instance of that received the message. 15 | /// The message that was received. 16 | Task OnMessageReceivedAsync(IReceiver receiver, IReceiverMessage message); 17 | } 18 | } -------------------------------------------------------------------------------- /RockLib.Messaging/IReceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging 4 | { 5 | /// 6 | /// Defines an interface for receiving messages. To start receiving messages, 7 | /// set the value of the property. 8 | /// 9 | public interface IReceiver : IDisposable 10 | { 11 | /// 12 | /// Gets the name of this instance of . 13 | /// 14 | string Name { get; } 15 | 16 | /// 17 | /// Gets or sets the message handler for this receiver. When set, the receiver is started 18 | /// and will invoke the value's method 19 | /// when messages are received. 20 | /// 21 | /// 22 | /// Implementions of this interface should not allow this property to be set to null or 23 | /// to be set more than once. 24 | /// 25 | IMessageHandler MessageHandler { get; set; } 26 | 27 | /// 28 | /// Occurs when a connection is established. 29 | /// 30 | event EventHandler Connected; 31 | 32 | /// 33 | /// Occurs when a connection is lost. 34 | /// 35 | event EventHandler Disconnected; 36 | 37 | /// 38 | /// Occurs when an error happens. 39 | /// 40 | #pragma warning disable CA1716 // Identifiers should not match keywords 41 | event EventHandler Error; 42 | #pragma warning restore CA1716 // Identifiers should not match keywords 43 | } 44 | } -------------------------------------------------------------------------------- /RockLib.Messaging/ISender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace RockLib.Messaging 6 | { 7 | /// 8 | /// Defines an interface for sending messages. 9 | /// 10 | public interface ISender : IDisposable 11 | { 12 | /// 13 | /// Gets the name of this instance of . 14 | /// 15 | string Name { get; } 16 | 17 | /// 18 | /// Asynchronously sends the specified message. 19 | /// 20 | /// The message to send. 21 | /// The token to monitor for cancellation requests. 22 | Task SendAsync(SenderMessage message, CancellationToken cancellationToken); 23 | } 24 | } -------------------------------------------------------------------------------- /RockLib.Messaging/ISenderTransaction.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging 2 | { 3 | /// 4 | /// Defines an interface for a messaging transaction. 5 | /// 6 | public interface ISenderTransaction 7 | { 8 | /// 9 | /// Adds the specified message to the transaction. 10 | /// 11 | /// The message to add. 12 | void Add(SenderMessage message); 13 | 14 | /// 15 | /// Commits any messages that were added to the transaction. 16 | /// 17 | void Commit(); 18 | 19 | /// 20 | /// Rolls back any messages that were added to the transaction. 21 | /// 22 | void Rollback(); 23 | } 24 | } -------------------------------------------------------------------------------- /RockLib.Messaging/ITransactionalSender.cs: -------------------------------------------------------------------------------- 1 | namespace RockLib.Messaging 2 | { 3 | /// 4 | /// Defines an interface for sending messages transactionally. 5 | /// 6 | public interface ITransactionalSender : ISender 7 | { 8 | /// 9 | /// Starts a message-sending transaction. 10 | /// 11 | /// An object representing the new transaction. 12 | ISenderTransaction BeginTransaction(); 13 | } 14 | } -------------------------------------------------------------------------------- /RockLib.Messaging/InternalVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("RockLib.Messaging.Tests")] -------------------------------------------------------------------------------- /RockLib.Messaging/NullableAttributes.cs: -------------------------------------------------------------------------------- 1 | #if NET48 2 | // This code is copied from https://source.dot.net/#System.Private.CoreLib/NullableAttributes.cs 3 | // and is only used for .NET 4.8 4 | using System; 5 | 6 | namespace System.Diagnostics.CodeAnalysis 7 | { 8 | /// 9 | /// Specifies that an output may be null even if the corresponding type disallows it. 10 | /// 11 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] 12 | public sealed class MaybeNullAttribute 13 | : Attribute { } 14 | 15 | /// 16 | /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. 17 | /// 18 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] 19 | public sealed class MaybeNullWhenAttribute : Attribute 20 | { 21 | /// Initializes the attribute with the specified return value condition. 22 | /// 23 | /// The return value condition. If the method returns this value, the associated parameter may be null. 24 | /// 25 | public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; 26 | 27 | /// Gets the return value condition. 28 | public bool ReturnValue { get; } 29 | } 30 | } 31 | #endif -------------------------------------------------------------------------------- /RockLib.Messaging/OnMessageReceivedDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading.Tasks; 4 | 5 | namespace RockLib.Messaging 6 | { 7 | /// 8 | /// Defines a synchronous function that handles a received message. 9 | /// 10 | /// The instance of that received the message. 11 | /// The message that was received. 12 | [Obsolete("Use OnMessageReceivedAsyncDelegate instead.")] 13 | [EditorBrowsable(EditorBrowsableState.Never)] 14 | #pragma warning disable CA1711 // Identifiers should not have incorrect suffix 15 | public delegate void OnMessageReceivedDelegate(IReceiver receiver, IReceiverMessage message); 16 | #pragma warning restore CA1711 // Identifiers should not have incorrect suffix 17 | 18 | /// 19 | /// Defines an asynchronous function that handles a received message. 20 | /// 21 | /// The instance of that received the message. 22 | /// The message that was received. 23 | #pragma warning disable CA1711 // Identifiers should not have incorrect suffix 24 | public delegate Task OnMessageReceivedAsyncDelegate(IReceiver receiver, IReceiverMessage message); 25 | #pragma warning restore CA1711 // Identifiers should not have incorrect suffix 26 | } 27 | -------------------------------------------------------------------------------- /RockLib.Messaging/RockLib.Messaging.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Embedded 4 | A simple API for sending and receiving messages. 5 | True 6 | True 7 | icon.png 8 | RockLib.Messaging 9 | LICENSE.md 10 | https://github.com/RockLib/RockLib.Messaging 11 | false 12 | A changelog is available at https://github.com/RockLib/RockLib.Messaging/blob/main/RockLib.Messaging/CHANGELOG.md. 13 | rocklib messaging 14 | 5.0.0 15 | True 16 | 5.0.0 17 | 18 | 19 | bin\$(Configuration)\$(TargetFramework)\$(PackageId).xml 20 | 21 | 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /RockLib.Messaging/RockLib.Messaging.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging", "RockLib.Messaging.csproj", "{E1860FD3-54D8-4EDB-82E0-E61ED8725F20}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RockLib.Messaging.Tests", "..\Tests\RockLib.Messaging.Tests\RockLib.Messaging.Tests.csproj", "{6B81F84C-A768-43DC-8F2E-F6F0EE9A9F32}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {E1860FD3-54D8-4EDB-82E0-E61ED8725F20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {E1860FD3-54D8-4EDB-82E0-E61ED8725F20}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {E1860FD3-54D8-4EDB-82E0-E61ED8725F20}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {E1860FD3-54D8-4EDB-82E0-E61ED8725F20}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {6B81F84C-A768-43DC-8F2E-F6F0EE9A9F32}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {6B81F84C-A768-43DC-8F2E-F6F0EE9A9F32}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {6B81F84C-A768-43DC-8F2E-F6F0EE9A9F32}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {6B81F84C-A768-43DC-8F2E-F6F0EE9A9F32}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {3FEE2AF4-936B-474F-A3C0-7118F5655DCC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /RockLib.Messaging/ValidatingSenderTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging 4 | { 5 | /// 6 | /// A decorator for the interface that invokes a validate delegate 7 | /// before each message is added to the transaction. 8 | /// 9 | public class ValidatingSenderTransaction : ISenderTransaction 10 | { 11 | internal ValidatingSenderTransaction(ISenderTransaction transaction, Action validate) 12 | { 13 | Transaction = transaction; 14 | Validate = validate; 15 | } 16 | 17 | /// 18 | /// Gets the actual . 19 | /// 20 | public ISenderTransaction Transaction { get; } 21 | 22 | /// 23 | /// Gets the delegate that will be invoked before each message added to a transaction. 24 | /// 25 | public Action Validate { get; } 26 | 27 | /// 28 | /// Validates, then adds the specified message to the transaction. 29 | /// 30 | /// The message to add. 31 | public void Add(SenderMessage message) 32 | { 33 | Validate(message); 34 | Transaction.Add(message); 35 | } 36 | 37 | /// 38 | /// Commits any messages that were added to the transaction. 39 | /// 40 | public void Commit() => Transaction.Commit(); 41 | 42 | /// 43 | /// Rolls back any messages that were added to the transaction. 44 | /// 45 | public void Rollback() => Transaction.Rollback(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RockLib.Messaging/ValidatingTransactionalSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging 4 | { 5 | /// 6 | /// A decorator for the interface that invokes a validate delegate 7 | /// before each message is sent or added to a transaction. 8 | /// 9 | public class ValidatingTransactionalSender : ValidatingSender, ITransactionalSender 10 | { 11 | /// 12 | /// Initializes a new instances of the class. 13 | /// 14 | /// The name of the validating sender. 15 | /// 16 | /// The that actually sends messages and begins transactions. 17 | /// 18 | /// 19 | /// A delegate that will be invoked before each message is sent or added to a transaction. The delegate 20 | /// should throw an exception if header values are invalid or if required headers are missing. The delegate 21 | /// may also add missing headers that can be calculated at runtime. 22 | /// 23 | public ValidatingTransactionalSender(string name, ITransactionalSender transactionalSender, Action validate) 24 | : base(name, transactionalSender, validate) 25 | { 26 | TransactionalSender = transactionalSender; 27 | } 28 | 29 | /// 30 | /// Gets the that actually sends messages and begins transactions. 31 | /// 32 | public ITransactionalSender TransactionalSender { get; } 33 | 34 | /// 35 | /// Starts a message-sending transaction that validates messages as they are added. 36 | /// 37 | /// An object representing the new validating transaction. 38 | public ISenderTransaction BeginTransaction() => 39 | new ValidatingSenderTransaction(TransactionalSender.BeginTransaction(), Validate); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.CloudEvents.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.CloudEvents.Tests/CorrelatingExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using RockLib.Messaging.CloudEvents.Correlating; 3 | using System; 4 | using Xunit; 5 | 6 | namespace RockLib.Messaging.CloudEvents.Tests 7 | { 8 | public class CorrelatingExtensionsTests 9 | { 10 | [Fact(DisplayName = "GetCorrelationId extension method returns the 'correlationid' attribute")] 11 | public void GetCorrelationIdExtensionMethodHappyPath1() 12 | { 13 | var cloudEvent = new CloudEvent 14 | { 15 | Attributes = { [CorrelatedEvent.CorrelationIdAttribute] = "MyCorrelationId" } 16 | }; 17 | 18 | var correlationId = cloudEvent.GetCorrelationId(); 19 | 20 | correlationId.Should().Be("MyCorrelationId"); 21 | } 22 | 23 | [Fact(DisplayName = "GetCorrelationId extension method adds the 'correlationid' attribute if missing")] 24 | public void GetCorrelationIdExtensionMethodHappyPath2() 25 | { 26 | var cloudEvent = new CloudEvent(); 27 | 28 | cloudEvent.Attributes.Should().BeEmpty(); 29 | 30 | cloudEvent.GetCorrelationId(); 31 | 32 | cloudEvent.Attributes.Should().ContainKey(CorrelatedEvent.CorrelationIdAttribute) 33 | .WhoseValue.Should().NotBeNull(); 34 | } 35 | 36 | [Fact(DisplayName = "GetCorrelationId extension method throws if cloudEvent parameter is null")] 37 | public void GetCorrelationIdExtensionMethodSadPath() 38 | { 39 | CloudEvent cloudEvent = null!; 40 | 41 | Action act = () => cloudEvent.GetCorrelationId(); 42 | 43 | act.Should().ThrowExactly().WithMessage("*cloudEvent*"); 44 | } 45 | 46 | [Fact(DisplayName = "SetCorrelationId extension method sets the 'correlationid' attribute")] 47 | public void SetCorrelationIdExtensionMethodHappyPath() 48 | { 49 | var cloudEvent = new CloudEvent(); 50 | 51 | cloudEvent.SetCorrelationId("MyCorrelationId"); 52 | 53 | cloudEvent.Attributes.Should().ContainKey(CorrelatedEvent.CorrelationIdAttribute) 54 | .WhoseValue.Should().Be("MyCorrelationId"); 55 | } 56 | 57 | [Fact(DisplayName = "SetCorrelationId extension method throws if cloudEvent is null")] 58 | public void SetCorrelationIdExtensionMethodSadPath1() 59 | { 60 | CloudEvent cloudEvent = null!; 61 | 62 | Action act = () => cloudEvent.SetCorrelationId("MyCorrelationId"); 63 | 64 | act.Should().ThrowExactly().WithMessage("*cloudEvent*"); 65 | } 66 | 67 | [Theory(DisplayName = "SetCorrelationId extension method throws if correlationId is null or empty")] 68 | [InlineData(null)] 69 | [InlineData("")] 70 | public void SetCorrelationIdExtensionMethodSadPath2(string? correlationId) 71 | { 72 | var cloudEvent = new CloudEvent(); 73 | 74 | Action act = () => cloudEvent.SetCorrelationId(correlationId!); 75 | 76 | act.Should().ThrowExactly().WithMessage("*correlationId*"); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.CloudEvents.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.CloudEvents.Tests/MockExtensions.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using System; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace RockLib.Messaging.CloudEvents.Tests 6 | { 7 | public static class MockExtensions 8 | { 9 | public static Mock SetupTestProtocolBinding(this Mock mockProtocolBinding) 10 | { 11 | #if NET6_0_OR_GREATER 12 | ArgumentNullException.ThrowIfNull(mockProtocolBinding); 13 | #else 14 | if (mockProtocolBinding is null) { throw new ArgumentNullException(nameof(mockProtocolBinding)); } 15 | #endif 16 | 17 | mockProtocolBinding.Setup(m => m.GetHeaderName(It.IsAny())).Returns(header => "test-" + header); 18 | 19 | mockProtocolBinding.Setup(m => m.GetAttributeName(It.IsAny(), out It.Ref.IsAny)) 20 | .Callback(new GetAttributeNameCallback(TestGetAttributeNameCallback)) 21 | .Returns(new GetAttributeName(TestGetAttributeName)); 22 | 23 | return mockProtocolBinding; 24 | } 25 | 26 | public delegate void GetAttributeNameCallback(string headerName, out bool isCloudEventAttribute); 27 | 28 | public delegate string GetAttributeName(string headerName, out bool isCloudEventAttribute); 29 | 30 | public static void TestGetAttributeNameCallback(string headerName, out bool isCloudEventAttribute) 31 | { 32 | var attributeName = Regex.Replace(headerName, "^test-", ""); 33 | isCloudEventAttribute = attributeName != headerName; 34 | } 35 | 36 | public static string TestGetAttributeName(string headerName, out bool isCloudEventAttribute) 37 | { 38 | var attributeName = Regex.Replace(headerName, "^test-", ""); 39 | isCloudEventAttribute = attributeName != headerName; 40 | return attributeName; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.CloudEvents.Tests/RockLib.Messaging.CloudEvents.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | CA2007 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Http.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Http.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Http.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "WSL": { 4 | "commandName": "WSL2", 5 | "distributionName": "" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Http.Tests/RockLib.Messaging.Http.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | CA2007 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Kafka.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Kafka.Tests/DependencyInjectionTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Xunit; 4 | using RockLib.Messaging.Kafka.DependencyInjection; 5 | 6 | namespace RockLib.Messaging.Kafka.Tests 7 | { 8 | public static class DependencyInjectionTests 9 | { 10 | [Fact] 11 | public static void KafkaSenderTest() 12 | { 13 | var services = new ServiceCollection(); 14 | 15 | services.AddKafkaSender("mySender", options => 16 | { 17 | options.Topic= "SenderTopic"; 18 | options.BootstrapServers = "SenderServer"; 19 | options.MessageTimeoutMs = 555; 20 | }); 21 | 22 | var serviceProvider = services.BuildServiceProvider(); 23 | 24 | var sender = serviceProvider.GetRequiredService(); 25 | 26 | var kafkaSender = sender.Should().BeOfType().Subject; 27 | 28 | kafkaSender.Name.Should().Be("mySender"); 29 | kafkaSender.Topic.Should().Be("SenderTopic"); 30 | kafkaSender.Producer.Should().NotBeNull(); 31 | } 32 | 33 | [Fact] 34 | public static void KafkaReceiverTest() 35 | { 36 | var services = new ServiceCollection(); 37 | 38 | services.AddKafkaReceiver("myReceiver", options => 39 | { 40 | options.Topic = "ReceiverTopic"; 41 | options.BootstrapServers = "ReceiverServer"; 42 | options.GroupId = "ReceiverGroupId"; 43 | }); 44 | 45 | var serviceProvider = services.BuildServiceProvider(); 46 | 47 | var receiver = serviceProvider.GetRequiredService(); 48 | 49 | var kafkaReceiver = receiver.Should().BeOfType().Subject; 50 | 51 | kafkaReceiver.Name.Should().Be("myReceiver"); 52 | kafkaReceiver.Topic.Should().Be("ReceiverTopic"); 53 | kafkaReceiver.Consumer.Should().NotBeNull(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Kafka.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Kafka.Tests/RockLib.Messaging.Kafka.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | CA2007 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.NamedPipes.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.NamedPipes.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.NamedPipes.Tests/NamedPipesTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace RockLib.Messaging.NamedPipes.Tests 6 | { 7 | public static class NamedPipesTests 8 | { 9 | [Fact] 10 | public static async Task NamedPipeMessagesAreSentAndReceived() 11 | { 12 | using var waitHandle = new AutoResetEvent(false); 13 | 14 | using var receiver = new NamedPipeReceiver("foo", "test-pipe"); 15 | var payload = string.Empty; 16 | var headerValue = string.Empty; 17 | 18 | receiver.Start(async m => 19 | { 20 | payload = m.StringPayload; 21 | headerValue = m.Headers.GetValue("bar"); 22 | await m.AcknowledgeAsync(); 23 | waitHandle.Set(); 24 | }); 25 | 26 | using (var sender = new NamedPipeSender("foo", "test-pipe")) 27 | { 28 | await sender.SendAsync(new SenderMessage("Hello, world!") { Headers = { { "bar", "abc" } } }); 29 | waitHandle.WaitOne(); 30 | } 31 | 32 | Assert.Equal("Hello, world!", payload); 33 | Assert.Equal("abc", headerValue); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.NamedPipes.Tests/RockLib.Messaging.NamedPipes.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | CA2007 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.RabbitMQ.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.RabbitMQ.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.RabbitMQ.Tests/RockLib.Messaging.RabbitMQ.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | CA2007 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.SNS.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.SNS.Tests/DependencyInjectionTests.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | using FluentAssertions; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using RockLib.Dynamic; 5 | using RockLib.Messaging.DependencyInjection; 6 | using System; 7 | using Xunit; 8 | 9 | namespace RockLib.Messaging.SNS.Tests 10 | { 11 | public static class DependencyInjectionTests 12 | { 13 | [Fact] 14 | public static void SendViaSNSSender() 15 | { 16 | var services = new ServiceCollection(); 17 | services.Configure(options => { }); 18 | 19 | services.AddSNSSender("mySender", options => 20 | { 21 | options.TopicArn = "myTopicArn"; 22 | options.Region = "us-west-2"; 23 | }, false); 24 | 25 | var serviceProvider = services.BuildServiceProvider(); 26 | 27 | var sender = serviceProvider.GetRequiredService(); 28 | 29 | var snsSender = sender.Should().BeOfType().Subject; 30 | 31 | snsSender.Name.Should().Be("mySender"); 32 | snsSender.TopicArn.Should().Be("myTopicArn"); 33 | snsSender.SnsClient.Config.RegionEndpoint.Should().Be(RegionEndpoint.USWest2); 34 | } 35 | 36 | [Fact] 37 | public static void SendViaReloadingSender() 38 | { 39 | var reloadingSenderType = Type.GetType("RockLib.Messaging.DependencyInjection.ReloadingSender`1, RockLib.Messaging", true)! 40 | .MakeGenericType(typeof(SNSSenderOptions)); 41 | 42 | var services = new ServiceCollection(); 43 | services.Configure(options => { }); 44 | 45 | services.AddSNSSender("mySender", options => 46 | { 47 | options.TopicArn = "myTopicArn"; 48 | options.Region = "us-west-2"; 49 | }, true); 50 | 51 | var serviceProvider = services.BuildServiceProvider(); 52 | 53 | var sender = serviceProvider.GetRequiredService(); 54 | 55 | sender.Should().BeOfType(reloadingSenderType); 56 | 57 | var snsSender = (SNSSender)sender.Unlock().Sender; 58 | 59 | snsSender.Name.Should().Be("mySender"); 60 | snsSender.TopicArn.Should().Be("myTopicArn"); 61 | snsSender.SnsClient.Config.RegionEndpoint.Should().Be(RegionEndpoint.USWest2); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.SNS.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.SNS.Tests/RockLib.Messaging.SNS.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | CA2007 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.SNS.Tests/SNSTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using Amazon.SimpleNotificationService; 4 | using Amazon.SimpleNotificationService.Model; 5 | using Moq; 6 | using Xunit; 7 | 8 | namespace RockLib.Messaging.SNS.Tests 9 | { 10 | public static class SNSTests 11 | { 12 | [Fact] 13 | public static async Task SnsSenderSendsMessagesToItsIAmazonSqs() 14 | { 15 | var mockSns = new Mock(); 16 | 17 | using (var sender = new SNSSender(mockSns.Object, "foo", "http://url.com/foo")) 18 | await sender.SendAsync( 19 | new SenderMessage("Hello, world!") { Headers = { { "bar", "abc" } } }) 20 | ; 21 | 22 | mockSns.Verify(m => m.PublishAsync( 23 | It.Is(r => r.Message == "Hello, world!" 24 | && r.MessageAttributes[HeaderNames.OriginatingSystem].StringValue == "SNS" 25 | && r.MessageAttributes["bar"].StringValue == "abc"), 26 | It.IsAny())); 27 | } 28 | 29 | [Fact] 30 | public static async Task SnsSenderSendsMessageWithMessageGroupId() 31 | { 32 | var mockSns = new Mock(); 33 | 34 | using (var sender = new SNSSender(mockSns.Object, "foo", "http://url.com/foo")) 35 | await sender.SendAsync(new SenderMessage("Hello, world!") { Headers = { 36 | { "bar", "abc" }, 37 | { "messageGroupId", "this is my group id" } 38 | } }); 39 | 40 | mockSns.Verify(m => m.PublishAsync( 41 | It.Is(r => r.Message == "Hello, world!" 42 | && r.MessageAttributes[HeaderNames.OriginatingSystem].StringValue == "SNS" 43 | && r.MessageAttributes["bar"].StringValue == "abc" 44 | && r.MessageGroupId == "this is my group id"), 45 | It.IsAny())); 46 | } 47 | 48 | [Fact] 49 | public static async Task SnsSenderSendsMessageWithMessageDeduplicationId() 50 | { 51 | var mockSns = new Mock(); 52 | 53 | using (var sender = new SNSSender(mockSns.Object, "foo", "http://url.com/foo")) 54 | await sender.SendAsync(new SenderMessage("Hello, world!") { Headers = { 55 | { "bar", "abc" }, 56 | { "messageDeduplicationId", "this is my deduplication id" } 57 | } }); 58 | 59 | mockSns.Verify(m => m.PublishAsync( 60 | It.Is(r => r.Message == "Hello, world!" 61 | && r.MessageAttributes[HeaderNames.OriginatingSystem].StringValue == "SNS" 62 | && r.MessageAttributes["bar"].StringValue == "abc" 63 | && r.MessageDeduplicationId == "this is my deduplication id"), 64 | It.IsAny())); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.SQS.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.SQS.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.SQS.Tests/RockLib.Messaging.SQS.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | CA2007 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/AssemblySettings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/CustomConfigFiles/MultipleReceivers_appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "Receivers": [ 4 | { 5 | "Type": "RockLib.Messaging.Tests.FakeReceiver, RockLib.Messaging.Tests", 6 | "Value": { 7 | "Name": "Pipe1", 8 | "PipeName": "PipeName1" 9 | } 10 | }, 11 | { 12 | "Type": "RockLib.Messaging.Tests.FakeReceiver, RockLib.Messaging.Tests", 13 | "Value": { 14 | "Name": "Pipe2", 15 | "PipeName": "PipeName2" 16 | } 17 | } 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/CustomConfigFiles/MultipleSenders_appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "Senders": [ 4 | { 5 | "Type": "RockLib.Messaging.Tests.FakeSender, RockLib.Messaging.Tests", 6 | "Value": { 7 | "Name": "Pipe1", 8 | "PipeName": "PipeName1", 9 | "Compressed": true 10 | } 11 | }, 12 | { 13 | "Type": "RockLib.Messaging.Tests.FakeSender, RockLib.Messaging.Tests", 14 | "Value": { 15 | "Name": "Pipe2", 16 | "PipeName": "PipeName2", 17 | "Compressed": false 18 | } 19 | } 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/CustomConfigFiles/SingleReceiver_appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "Receivers": { 4 | "Type": "RockLib.Messaging.Tests.FakeReceiver, RockLib.Messaging.Tests", 5 | "Value": { 6 | "Name": "Pipe1", 7 | "PipeName": "PipeName1" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/CustomConfigFiles/SingleSender_appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "Senders": { 4 | "Type": "RockLib.Messaging.Tests.FakeSender, RockLib.Messaging.Tests", 5 | "Value": { 6 | "Name": "Pipe1", 7 | "PipeName": "PipeName1", 8 | "Compressed": true 9 | } 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | all 7 | Rocket Mortgage 8 | Copyright 2025 (c) Rocket Mortgage. All rights reserved. 9 | latest 10 | enable 11 | net48;net8.0 12 | NU1603,NU1701 13 | true 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/FakeMessageHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace RockLib.Messaging.Tests 5 | { 6 | public class FakeMessageHandler : IMessageHandler 7 | { 8 | #pragma warning disable CA1002 // Do not expose generic lists 9 | public List<(IReceiver Receiver, IReceiverMessage Message)> ReceivedMessages { get; } = new List<(IReceiver, IReceiverMessage)>(); 10 | #pragma warning restore CA1002 // Do not expose generic lists 11 | 12 | public Task OnMessageReceivedAsync(IReceiver receiver, IReceiverMessage message) 13 | { 14 | ReceivedMessages.Add((receiver, message)); 15 | return Task.FromResult(0); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/FakeReceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RockLib.Messaging.Tests 4 | { 5 | public sealed class FakeReceiver : IReceiver 6 | { 7 | #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. 8 | public string Name { get; set; } 9 | public string PipeName { get; set; } 10 | 11 | public IMessageHandler MessageHandler { get; set; } 12 | 13 | #pragma warning disable CS0067 // Event is never used 14 | public event EventHandler Connected; 15 | public event EventHandler Disconnected; 16 | public event EventHandler Error; 17 | #pragma warning restore CS0067 // Event is never used 18 | #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. 19 | 20 | public void Dispose() 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/FakeSender.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace RockLib.Messaging.Tests 6 | { 7 | public sealed class FakeSender : ISender 8 | { 9 | #pragma warning disable CA1002 // Do not expose generic lists 10 | public List SentMessages { get; } = new List(); 11 | #pragma warning restore CA1002 // Do not expose generic lists 12 | 13 | #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. 14 | public string Name { get; set; } 15 | public string PipeName { get; set; } 16 | #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. 17 | 18 | public void Dispose() 19 | { 20 | } 21 | 22 | public Task SendAsync(SenderMessage message, CancellationToken cancellationToken) 23 | { 24 | SentMessages.Add(message); 25 | return Task.CompletedTask; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/ForwardingMessageHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace RockLib.Messaging.Tests 6 | { 7 | public class ForwardingMessageHandlerTests 8 | { 9 | [Fact] 10 | public async Task OnMessageReceivedCallsInnerHandlerOnMessageReceivedWithForwardingReceiverMessage() 11 | { 12 | using var receiver = new FakeReceiver(); 13 | using var forwardingReceiver = new ForwardingReceiver("foo", receiver); 14 | var messageHandler = new FakeMessageHandler(); 15 | 16 | var handler = new ForwardingMessageHandler(forwardingReceiver, messageHandler); 17 | 18 | using var message = new FakeReceiverMessage("Hello, world!"); 19 | 20 | await handler.OnMessageReceivedAsync(receiver, message); 21 | 22 | messageHandler.ReceivedMessages.Should().ContainSingle(); 23 | messageHandler.ReceivedMessages[0].Receiver.Should().BeSameAs(forwardingReceiver); 24 | messageHandler.ReceivedMessages[0].Message.Should().BeOfType(); 25 | ((ForwardingReceiverMessage)messageHandler.ReceivedMessages[0].Message).Message.Should().BeSameAs(message); 26 | ((ForwardingReceiverMessage)messageHandler.ReceivedMessages[0].Message).ForwardingReceiver.Should().BeSameAs(forwardingReceiver); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/ForwardingReceiverTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Xunit; 3 | 4 | namespace RockLib.Messaging.Tests 5 | { 6 | public class ForwardingReceiverTests 7 | { 8 | [Fact] 9 | public void SettingMessageHandlerSetsTheMessageHandlerOfTheReceiverToAForwardingMessageHandler() 10 | { 11 | using var receiver = new FakeReceiver(); 12 | using var forwardingReceiver = new ForwardingReceiver("foo", receiver); 13 | 14 | var messageHandler = new FakeMessageHandler(); 15 | 16 | forwardingReceiver.MessageHandler = messageHandler; 17 | 18 | forwardingReceiver.MessageHandler.Should().BeSameAs(messageHandler); 19 | 20 | receiver.MessageHandler.Should().NotBeSameAs(messageHandler); 21 | receiver.MessageHandler.Should().BeOfType(); 22 | ((ForwardingMessageHandler)receiver.MessageHandler).MessageHandler.Should().BeSameAs(messageHandler); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/ReceiverExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Xunit; 3 | 4 | namespace RockLib.Messaging.Tests 5 | { 6 | public class ReceiverExtensionsTests 7 | { 8 | [Fact] 9 | public async Task TheCallbackPassedToStart1IsInvokedWhenAMessageIsReceived() 10 | { 11 | using var receiver = new FakeReceiver(); 12 | 13 | var received = false; 14 | 15 | receiver.Start(async m => 16 | { 17 | received = true; 18 | await m.AcknowledgeAsync(); 19 | }); 20 | 21 | using var message = new FakeReceiverMessage("Hello, world!"); 22 | await receiver.MessageHandler.OnMessageReceivedAsync(receiver, message); 23 | 24 | Assert.True(received); 25 | } 26 | 27 | [Fact] 28 | public async Task TheCallbackPassedToStart2IsInvokedWhenAMessageIsReceived() 29 | { 30 | using var receiver = new FakeReceiver(); 31 | 32 | var received = false; 33 | 34 | #pragma warning disable CS0618 // Type or member is obsolete 35 | receiver.Start(m => 36 | { 37 | received = true; 38 | m.Acknowledge(); 39 | }); 40 | #pragma warning restore CS0618 // Type or member is obsolete 41 | 42 | using var message = new FakeReceiverMessage("Hello, world!"); 43 | await receiver.MessageHandler.OnMessageReceivedAsync(receiver, message); 44 | 45 | Assert.True(received); 46 | } 47 | 48 | [Fact] 49 | public async Task TheCallbackPassedToStart3IsInvokedWhenAMessageIsReceived() 50 | { 51 | using var receiver = new FakeReceiver(); 52 | 53 | var received = false; 54 | 55 | receiver.Start(async (r, m) => 56 | { 57 | received = true; 58 | await m.AcknowledgeAsync(); 59 | }); 60 | 61 | using var message = new FakeReceiverMessage("Hello, world!"); 62 | await receiver.MessageHandler.OnMessageReceivedAsync(receiver, message); 63 | 64 | Assert.True(received); 65 | } 66 | 67 | [Fact] 68 | public async Task TheCallbackPassedToStart4IsInvokedWhenAMessageIsReceived() 69 | { 70 | using var receiver = new FakeReceiver(); 71 | 72 | var received = false; 73 | 74 | #pragma warning disable CS0618 // Type or member is obsolete 75 | receiver.Start((r, m) => 76 | { 77 | received = true; 78 | m.Acknowledge(); 79 | }); 80 | #pragma warning restore CS0618 // Type or member is obsolete 81 | 82 | using var message = new FakeReceiverMessage("Hello, world!"); 83 | await receiver.MessageHandler.OnMessageReceivedAsync(receiver, message); 84 | 85 | Assert.True(received); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/RockLib.Messaging.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | false 4 | CA2007 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | Always 27 | 28 | 29 | Always 30 | 31 | 32 | Always 33 | 34 | 35 | Always 36 | 37 | 38 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/ValidatingSenderTransactionTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Moq; 3 | using RockLib.Dynamic; 4 | using System; 5 | using System.Collections.Generic; 6 | using Xunit; 7 | 8 | namespace RockLib.Messaging.Tests 9 | { 10 | public class ValidatingSenderTransactionTests 11 | { 12 | [Fact] 13 | public void AddMethodHappyPath() 14 | { 15 | var sentMessages = new List(); 16 | 17 | var mockTransaction = new Mock(); 18 | Action validate = message => sentMessages.Add(message); 19 | 20 | ValidatingSenderTransaction validatingTransaction = typeof(ValidatingSenderTransaction).New(mockTransaction.Object, validate); 21 | 22 | var message1 = new SenderMessage("Hello, world!"); 23 | var message2 = new SenderMessage("Good-bye, cruel world!"); 24 | 25 | validatingTransaction.Add(message1); 26 | validatingTransaction.Add(message2); 27 | 28 | mockTransaction.Verify(m => m.Add(message1), Times.Once()); 29 | mockTransaction.Verify(m => m.Add(message2), Times.Once()); 30 | 31 | sentMessages.Should().HaveCount(2); 32 | sentMessages[0].Should().Be(message1); 33 | sentMessages[1].Should().Be(message2); 34 | } 35 | 36 | [Fact] 37 | public void CommitMethodHappyPath() 38 | { 39 | var mockTransaction = new Mock(); 40 | Action validate = message => { }; 41 | 42 | ValidatingSenderTransaction validatingTransaction = typeof(ValidatingSenderTransaction).New(mockTransaction.Object, validate); 43 | 44 | validatingTransaction.Commit(); 45 | 46 | mockTransaction.Verify(m => m.Commit(), Times.Once()); 47 | } 48 | 49 | [Fact] 50 | public void RollbackMethodHappyPath() 51 | { 52 | var mockTransaction = new Mock(); 53 | Action validate = message => { }; 54 | 55 | ValidatingSenderTransaction validatingTransaction = typeof(ValidatingSenderTransaction).New(mockTransaction.Object, validate); 56 | 57 | validatingTransaction.Rollback(); 58 | 59 | mockTransaction.Verify(m => m.Rollback(), Times.Once()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/RockLib.Messaging.Tests/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "RockLib.Messaging": { 3 | "Senders": [ 4 | { 5 | "Type": "RockLib.Messaging.Tests.FakeSender, RockLib.Messaging.Tests", 6 | "Value": { 7 | "Name": "MyFakeSender", 8 | "PipeName": "MyPipeName" 9 | } 10 | }, 11 | { 12 | "Type": "RockLib.Messaging.Tests.MessagingExtensionsTests+TestSenderDecorator, RockLib.Messaging.Tests", 13 | "Value": { 14 | "Name": "MyTestSenderDecorator", 15 | "SenderName": "MyFakeSender" 16 | } 17 | } 18 | ], 19 | "Receivers": [ 20 | { 21 | "Type": "RockLib.Messaging.Tests.FakeReceiver, RockLib.Messaging.Tests", 22 | "Value": { 23 | "Name": "MyFakeReceiver", 24 | "PipeName": "MyPipeName" 25 | } 26 | }, 27 | { 28 | "Type": "RockLib.Messaging.Tests.MessagingExtensionsTests+TestReceiverDecorator, RockLib.Messaging.Tests", 29 | "Value": { 30 | "Name": "MyTestReceiverDecorator", 31 | "ReceiverName": "MyFakeReceiver" 32 | } 33 | } 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /docs/Compressed.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 12 3 | --- 4 | 5 | # Sending and receiving compressed messages 6 | 7 | To send compressed messages with RockLib.Messaging, instantiate the `SenderMessage` object using either of its constructors that has a `payload` parameter. Pass the value of the `payload` parameter uncompressed and pass `true` as the value of the optional `compress` parameter. The payload will be gzip compressed and a header with the name `HeaderNames.IsCompressedPayload` is set to `true`, marking the message as compressed. *Note that if gzip compressing the payload does not make it smaller, then the payload is left uncompressed.* 8 | 9 | ```csharp 10 | ISender sender = // TODO: instantiate 11 | 12 | string stringPayload = // TODO: Load value 13 | await sender.SendAsync(new SenderMessage(stringPayload, compress: true)); 14 | 15 | byte[] binaryPayload = // TODO: Load value 16 | await sender.SendAsync(new SenderMessage(binaryPayload, compress: true)); 17 | ``` 18 | 19 | --- 20 | 21 | On the receiving side, there is nothing to do. All of the implementations of `IReceiverMessage` in this repository inherit from the `ReceiverMessage` base class, which automatically decompresses any messages that have a header named `HeaderNames.IsCompressedPayload` with a value of `true`. 22 | -------------------------------------------------------------------------------- /docs/SNS.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # How to use and configure RockLib.Messaging.SNS 6 | 7 | See the [.NET Core example] for a complete demo application. 8 | 9 | ## SNSSender 10 | 11 | The SNSSender class can be directly instantiated and has the following parameters: 12 | 13 | - name 14 | - The name of the instance of SNSSender. 15 | - topicArn 16 | - The ARN (Amazon Resource Name) of the SNS topic. 17 | - region (optional, defaults to null) 18 | - The region of the SNS topic. 19 | 20 | ```csharp 21 | ISender sender = new SNSSender("MySender", "arn:aws:sns:us-west-2:123456789012:MyTopic", "us-west-2"); 22 | ``` 23 | 24 | --- 25 | 26 | To add an SNSSender to a service collection for dependency injection, use the `AddSNSSender` method, optionally passing in a `configureOptions` callback: 27 | 28 | ```csharp 29 | services.AddSNSSender("MySender", options => 30 | { 31 | options.Region = "us-west-2"; 32 | options.TopicArn = "arn:aws:sns:us-west-2:123456789012:MyTopic"; 33 | });``` 34 | 35 | To bind `SNSSenderOptions` to a configuration section, use the name of the sender when calling the `Configure` method: 36 | 37 | ```csharp 38 | public void ConfigureServices(IServiceCollection services) 39 | { 40 | services.Configure("MySender", Configuration.GetSection("MySNSSender")); 41 | services.AddSNSSender("MySender"); 42 | } 43 | 44 | /* appsettings.json: 45 | { 46 | "MySNSSender": { 47 | "Region": "us-west-2", 48 | "TopicArn": "arn:aws:sns:us-west-2:123456789012:MyTopic" 49 | } 50 | } 51 | */ 52 | ``` 53 | 54 | --- 55 | 56 | MessagingScenarioFactory configured with an `SNSSender` named "commands" as follows: 57 | 58 | ```json 59 | { 60 | "RockLib.Messaging": { 61 | "Senders": { 62 | "Type": "RockLib.Messaging.SNS.SNSSender, RockLib.Messaging.SNS", 63 | "Value": { 64 | "Name": "commands", 65 | "TopicArn": "arn:aws:sns:us-west-2:123456789012:MyTopic", 66 | "Region": "us-west-2" 67 | } 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | ```csharp 74 | // Note that implementations of the ISender interface are somewhat expensive and intended to be 75 | // long-lived. They are thread-safe, so you can use a single instance throughout your application. 76 | // Instances should be disposed before the application exits. 77 | 78 | // MessagingScenarioFactory uses the above JSON configuration to create a SNSSender 79 | // Note that the Value object's properties in the json must map to a valid constructor since CreateSender Creates instances using [RockLib.Configuration.ObjectFactory](https://github.com/RockLib/RockLib.Configuration/tree/main/RockLib.Configuration.ObjectFactory#rocklibconfigurationobjectfactory) 80 | ISender sender = MessagingScenarioFactory.CreateSender("commands"); 81 | 82 | // Use the sender (for good, not evil): 83 | sender.Send("DROP DATABASE Production;"); 84 | 85 | // Always dispose the sender when done with it. 86 | sender.Dispose(); 87 | ``` 88 | 89 | --- 90 | 91 | [.NET Core example]: https://github.com/RockLib/RockLib.Messaging/blob/main/Examples/Example.Messaging.SNS/Program.cs 92 | -------------------------------------------------------------------------------- /docs/SendingMessages.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # How to send messages 6 | 7 | To send messages, an instance of the `ISender` interface is needed. Throughout this document, it's assumed that you have an `ISender` variable named `sender` declared and initialized somewhere. Like this: 8 | 9 | ```csharp 10 | ISender sender; // TODO: Initialize the sender variable 11 | ``` 12 | 13 | --- 14 | 15 | One way to send a message is by passing the message payload to the `SendAsync` extension method (note that these examples all use `string` payloads, but there are overloads for each method that take a `byte[]`): 16 | 17 | ```csharp 18 | CancellationTokenSource cancellation = new CancellationTokenSource(); 19 | await sender.SendAsync("", cancellation.Token); 20 | ``` 21 | 22 | --- 23 | 24 | If you don't need the call to be cancellable, omit the `CancellationToken`: 25 | 26 | ```csharp 27 | await sender.SendAsync(""); 28 | ``` 29 | 30 | --- 31 | 32 | If you need to make the call synchronously, use the `Send` extension method: 33 | 34 | ```csharp 35 | sender.Send(""); 36 | ``` 37 | 38 | --- 39 | 40 | If you need to set header values in the message, create an instance of `SenderMessage` and pass it to the `SendAsync` method: 41 | 42 | ```csharp 43 | CancellationTokenSource cancellation = new CancellationTokenSource(); 44 | 45 | SenderMessage message = new SenderMessage(""); 46 | message.Headers["IsDemoCode"] = true; 47 | 48 | await sender.SendAsync(message, cancellation.Token); 49 | ``` 50 | 51 | If you need to utilize the `MessageGroupId` and `MessageDeduplicationId` properties, add the values to the header: 52 | 53 | ```csharp 54 | CancellationTokenSource cancellation = new CancellationTokenSource(); 55 | 56 | SenderMessage message = new SenderMessage(""); 57 | message.Headers["messageGroupId"] = "message group id"; 58 | message.Headers["messageDeduplicationId"] = "message deduplication id"; 59 | 60 | await sender.SendAsync(message, cancellation.Token); 61 | ``` 62 | 63 | --- 64 | 65 | The `CancellationToken` can be omitted from this call as well: 66 | 67 | ```csharp 68 | SenderMessage message = new SenderMessage(""); 69 | message.Headers["CodeSnippetIndex"] = 5; 70 | 71 | await sender.SendAsync(message); 72 | ``` 73 | 74 | --- 75 | 76 | And there is a synchronous version as expected: 77 | 78 | ```csharp 79 | SenderMessage message = new SenderMessage(""); 80 | message.Headers["ExampleDescription"] = "boring"; 81 | 82 | sender.Send(message); 83 | ``` 84 | -------------------------------------------------------------------------------- /docs/TransactionalSend.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 13 3 | --- 4 | 5 | # Sending messages transactionally 6 | 7 | The RockLib.Messaging library defines an interface for sending messages tranactionally through the `ITransactionalSender` interface. Sending messages transactionally in this case means that one or more messages, all destined for the same sender, are added to a transaction, then the transaction is committed. The `ITransactionalSender` interface inherits from `ISender`, so it has the same functionality in addition to its `BeginTransaction` method. 8 | 9 | ```csharp 10 | ITransactionalSender transactionalSender = // TODO: Instantiate. 11 | 12 | ISenderTransaction transaction = transactionalSender.BeginTransaction(); 13 | 14 | transaction.Add(new SenderMessage("First message")); 15 | transaction.Add(new SenderMessage("Second message")); 16 | transaction.Add(new SenderMessage("Third message")); 17 | 18 | bool rollback = false; 19 | 20 | // Do stuff that may or may not succeed. If it does not, set 'rollback' to true. 21 | 22 | if (!rollback) 23 | transaction.Commit(); 24 | else 25 | transaction.Rollback(); 26 | ``` 27 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RockLib/RockLib.Messaging/f50493a1d94c301cf6876b0337d0126491799e22/icon.png --------------------------------------------------------------------------------