├── .codespellrc ├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── build.yml │ └── codespell.yml ├── .gitignore ├── .vscode ├── extensions.json ├── ltex.dictionary.en-US.txt └── settings.json ├── Directory.Build.props ├── Directory.Build.targets ├── GitVersion.yml ├── LICENSE ├── README.md ├── Tingle.EventBus.slnx ├── global.json ├── logo.png ├── samples ├── AmazonSqsAndSns │ ├── AmazonSqsAndSns.csproj │ ├── DoorOpened.cs │ ├── DoorOpenedConsumer.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── PublisherService.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── AotSupport │ ├── AotSupport.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── AzureIotHub │ ├── AzureIotEventsConsumer.cs │ ├── AzureIotHub.csproj │ ├── MyIotHubEvent.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── AzureManagedIdentity │ ├── AzureManagedIdentity.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── VehicleDoorOpenedEvent.cs │ ├── VehicleTelemetryEvent.cs │ ├── VehicleTelemetryEventsConsumer.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── ConfigSample │ ├── ConfigSample.csproj │ ├── ImageUploaded.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── VehicleDoorOpenedEvent.cs │ ├── VehicleTelemetryEventsConsumer.cs │ ├── VisualsProducerService.cs │ ├── VisualsUploadedConsumer.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── CustomEventConfigurator │ ├── CustomEventConfigurator.csproj │ ├── MyConfigurator.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleConsumer1.cs │ ├── SampleConsumer2.cs │ ├── SampleEvent1.cs │ ├── SampleEvent2.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── CustomEventSerializer │ ├── AzureDevOpsCodePushed.cs │ ├── AzureDevOpsEventSerializer.cs │ ├── AzureDevOpsEventsConsumer.cs │ ├── CustomEventSerializer.csproj │ ├── Models │ │ ├── AzureDevOpsEventResource.cs │ │ ├── AzureDevOpsEventResourceRefUpdate.cs │ │ ├── AzureDevOpsEventResourceRepository.cs │ │ └── AzureDevOpsEventResourceRepositoryProject.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ ├── appsettings.json │ └── git.push-sample.json ├── Directory.Build.props ├── HealthCheck │ ├── AzureServiceBusHealthCheck.cs │ ├── HealthCheck.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── VehicleDoorOpenedEvent.cs │ ├── VehicleTelemetryEvent.cs │ ├── VehicleTelemetryEventsConsumer.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── InMemoryBackgroundProcessing │ ├── InMemoryBackgroundProcessing.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── MultiEventsConsumer │ ├── DoorClosed.cs │ ├── DoorKind.cs │ ├── DoorOpened.cs │ ├── DoorState.cs │ ├── DummyProducerService.cs │ ├── MultiEventsConsumer.cs │ ├── MultiEventsConsumer.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── MultipleConsumers │ ├── DoorOpened.cs │ ├── FirstEventConsumer.cs │ ├── MultipleConsumers.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── PublisherService.cs │ ├── SecondEventConsumer.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── MultipleDifferentTransports │ ├── MultipleDifferentTransports.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── VehicleDoorOpenedEvent.cs │ ├── VehicleTelemetryEvent.cs │ ├── VehicleTelemetryEventsConsumer.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── MultipleSimilarTransports │ ├── MultipleSimilarTransports.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── appsettings.Development.json │ └── appsettings.json ├── SimpleConsumer │ ├── EventCounter.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SampleEvent.cs │ ├── SampleEventConsumer.cs │ ├── SimpleConsumer.csproj │ ├── appsettings.Development.json │ └── appsettings.json └── SimplePublisher │ ├── DoorOpened.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── PublisherService.cs │ ├── SimplePublisher.csproj │ ├── appsettings.Development.json │ └── appsettings.json ├── shared └── TrimmingHelper.cs ├── src ├── Directory.Build.props ├── Tingle.EventBus.Serializers.NewtonsoftJson │ ├── MessageStrings.cs │ ├── NewtonsoftJsonEventBusBuilderExtensions.cs │ ├── NewtonsoftJsonSerializer.cs │ ├── NewtonsoftJsonSerializerConfigureOptions.cs │ ├── NewtonsoftJsonSerializerOptions.cs │ └── Tingle.EventBus.Serializers.NewtonsoftJson.csproj ├── Tingle.EventBus.Transports.Amazon.Abstractions │ ├── AmazonSqsTransportException.cs │ ├── AmazonTransportConfigureOptions.cs │ ├── AmazonTransportOptions.cs │ ├── AmazonWebServiceResponseExtensions.cs │ └── Tingle.EventBus.Transports.Amazon.Abstractions.csproj ├── Tingle.EventBus.Transports.Amazon.Kinesis │ ├── AmazonKinesisConfigureOptions.cs │ ├── AmazonKinesisDefaults.cs │ ├── AmazonKinesisEventBusBuilderExtensions.cs │ ├── AmazonKinesisTransport.cs │ ├── AmazonKinesisTransportOptions.cs │ ├── BinaryDataExtensions.cs │ ├── ILoggerExtensions.cs │ └── Tingle.EventBus.Transports.Amazon.Kinesis.csproj ├── Tingle.EventBus.Transports.Amazon.Sqs │ ├── AmazonSqsConfigureOptions.cs │ ├── AmazonSqsDefaults.cs │ ├── AmazonSqsEventBusBuilderExtensions.cs │ ├── AmazonSqsTransport.cs │ ├── AmazonSqsTransportOptions.cs │ ├── ILoggerExtensions.cs │ ├── MessageAttributeExtensions.cs │ └── Tingle.EventBus.Transports.Amazon.Sqs.csproj ├── Tingle.EventBus.Transports.Azure.Abstractions │ ├── AzureTransportConfigureOptions.cs │ ├── AzureTransportCredentials.cs │ ├── AzureTransportOptions.cs │ └── Tingle.EventBus.Transports.Azure.Abstractions.csproj ├── Tingle.EventBus.Transports.Azure.EventHubs │ ├── AzureBlobStorageCredentials.cs │ ├── AzureEventHubsConfigureOptions.cs │ ├── AzureEventHubsDefaults.cs │ ├── AzureEventHubsEventBusBuilderExtensions.cs │ ├── AzureEventHubsEventContextExtensions.cs │ ├── AzureEventHubsEventRegistrationExtensions.cs │ ├── AzureEventHubsTransport.cs │ ├── AzureEventHubsTransportCredentials.cs │ ├── AzureEventHubsTransportOptions.cs │ ├── EventDataExtensions.cs │ ├── ILoggerExtensions.cs │ ├── IotHub │ │ ├── IotHubConnectionAuthMethod.cs │ │ ├── IotHubEvent.cs │ │ ├── IotHubEventContextExtensions.cs │ │ ├── IotHubEventDataExtensions.cs │ │ ├── IotHubEventSerializer.cs │ │ └── IotHubJsonSerializerContext.cs │ ├── MessageStrings.cs │ └── Tingle.EventBus.Transports.Azure.EventHubs.csproj ├── Tingle.EventBus.Transports.Azure.QueueStorage │ ├── AzureQueueStorageConfigureOptions.cs │ ├── AzureQueueStorageDefaults.cs │ ├── AzureQueueStorageEventBusBuilderExtensions.cs │ ├── AzureQueueStorageSchedulingId.cs │ ├── AzureQueueStorageTransport.cs │ ├── AzureQueueStorageTransportCredentials.cs │ ├── AzureQueueStorageTransportOptions.cs │ ├── ILoggerExtensions.cs │ └── Tingle.EventBus.Transports.Azure.QueueStorage.csproj ├── Tingle.EventBus.Transports.Azure.ServiceBus │ ├── AzureServiceBusConfigureOptions.cs │ ├── AzureServiceBusDefaults.cs │ ├── AzureServiceBusEventBusBuilderExtensions.cs │ ├── AzureServiceBusEventContextExtensions.cs │ ├── AzureServiceBusTransport.cs │ ├── AzureServiceBusTransportCredentials.cs │ ├── AzureServiceBusTransportOptions.cs │ ├── ILoggerExtensions.cs │ └── Tingle.EventBus.Transports.Azure.ServiceBus.csproj ├── Tingle.EventBus.Transports.InMemory │ ├── Client │ │ ├── BroadcastChannel.cs │ │ ├── BroadcastChannelWriter.cs │ │ ├── InMemoryClient.cs │ │ ├── InMemoryErrorSource.cs │ │ ├── InMemoryMessage.cs │ │ ├── InMemoryProcessor.cs │ │ ├── InMemoryProcessorOptions.cs │ │ ├── InMemoryReceivedMessage.cs │ │ ├── InMemorySender.cs │ │ ├── ProcessErrorEventArgs.cs │ │ ├── ProcessMessageEventArgs.cs │ │ └── SequenceNumberGenerator.cs │ ├── ILoggerExtensions.cs │ ├── InMemoryDefaults.cs │ ├── InMemoryEventBusBuilderExtensions.cs │ ├── InMemoryEventContextExtensions.cs │ ├── InMemoryTestHarness.cs │ ├── InMemoryTestHarnessConfigureOptions.cs │ ├── InMemoryTestHarnessOptions.cs │ ├── InMemoryTransport.cs │ ├── InMemoryTransportConfigureOptions.cs │ ├── InMemoryTransportOptions.cs │ └── Tingle.EventBus.Transports.InMemory.csproj ├── Tingle.EventBus.Transports.Kafka │ ├── ILoggerExtensions.cs │ ├── KafkaConfigureOptions.cs │ ├── KafkaDefaults.cs │ ├── KafkaEventBusBuilderExtensions.cs │ ├── KafkaExtensions.cs │ ├── KafkaTransport.cs │ ├── KafkaTransportOptions.cs │ └── Tingle.EventBus.Transports.Kafka.csproj ├── Tingle.EventBus.Transports.RabbitMQ │ ├── RabbitMqConfigureOptions.cs │ ├── RabbitMqDefaults.cs │ ├── RabbitMqEventBusBuilderExtensions.cs │ ├── RabbitMqTransport.cs │ ├── RabbitMqTransportOptions.cs │ └── Tingle.EventBus.Transports.RabbitMQ.csproj └── Tingle.EventBus │ ├── Configuration │ ├── Attributes │ │ ├── ConsumerNameAttribute.cs │ │ ├── EntityKindAttribute.cs │ │ ├── EventNameAttribute.cs │ │ ├── EventSerializerAttribute.cs │ │ └── EventTransportNameAttribute.cs │ ├── ConsumerNameSource.cs │ ├── DefaultEventBusConfigurationProvider.cs │ ├── DefaultEventBusConfigurator.cs │ ├── EntityKind.cs │ ├── EventConsumerRegistration.cs │ ├── EventIdFormat.cs │ ├── EventRegistration.cs │ ├── IEventBusConfigurationProvider.cs │ ├── IEventBusConfigurator.cs │ ├── MandatoryEventBusConfigurator.cs │ ├── NamingConvention.cs │ └── UnhandledConsumerErrorBehaviour.cs │ ├── DependencyInjection │ ├── EventBusBuilder.cs │ ├── EventBusConfigureOptions.cs │ ├── EventBusNamingOptions.cs │ ├── EventBusOptions.cs │ ├── EventBusSerializationOptions.cs │ ├── EventBusServiceCollectionExtensions.cs │ └── EventBusTransportRegistrationBuilder.cs │ ├── Diagnostics │ ├── ActivityExtensions.cs │ ├── ActivityNames.cs │ ├── ActivityTagNames.cs │ ├── EventBusActivitySource.cs │ ├── HeaderNames.cs │ └── LogCategoryNames.cs │ ├── EventBus.cs │ ├── EventContext.cs │ ├── Extensions │ └── ILoggerExtensions.cs │ ├── IEventConsumer.cs │ ├── Ids │ ├── DefaultEventIdGenerator.cs │ └── IEventIdGenerator.cs │ ├── Internal │ ├── DictionaryExtensions.cs │ ├── EventBusConcurrentDictionary.cs │ ├── EventBusDictionaryWrapper.cs │ ├── EventBusHost.cs │ ├── ExecutionHelper.cs │ └── ResiliencePipelineHelper.cs │ ├── MessageStrings.cs │ ├── MetadataNames.cs │ ├── Publisher │ ├── EventPublisher.cs │ ├── IEventPublisher.cs │ ├── IEventPublisherExtensions.cs │ └── WrappedEventPublisher.cs │ ├── Retries │ ├── AbstractRetryableEvent.cs │ ├── IRetryableEvent.cs │ └── IRetryableEventExtensions.cs │ ├── ScheduledResult.cs │ ├── Serialization │ ├── AbstractEventSerializer.cs │ ├── DefaultJsonEventSerializer.cs │ ├── DeserializationContext.cs │ ├── EventEnvelope.cs │ ├── HostInfo.cs │ ├── IEventEnvelope.cs │ ├── IEventSerializer.cs │ ├── SerializationContext.cs │ └── Xml │ │ ├── EventBusBuilderExtensions.cs │ │ ├── XmlEventEnvelope.cs │ │ ├── XmlEventSerializer.cs │ │ ├── XmlEventSerializerConfigureOptions.cs │ │ ├── XmlEventSerializerOptions.cs │ │ └── XmlHeader.cs │ ├── Tingle.EventBus.csproj │ └── Transports │ ├── EventBusTransport.cs │ ├── EventBusTransportConfigureOptions.cs │ ├── EventBusTransportOptions.cs │ ├── EventBusTransportProvider.cs │ ├── EventBusTransportRegistration.cs │ ├── EventConsumeResult.cs │ └── IEventBusTransport.cs └── tests ├── Directory.Build.props ├── Tingle.EventBus.Tests ├── Configurator │ ├── FakeEventSerializer1.cs │ ├── FakeEventSerializer2.cs │ ├── MandatoryEventBusConfiguratorTests.cs │ ├── TestConsumer1.cs │ ├── TestConsumer2.cs │ ├── TestEvent1.cs │ ├── TestEvent2.cs │ └── TestEvent3.cs ├── DefaultEventIdGeneratorTests.cs ├── EventBusBuilderTests.cs ├── EventBusNamingOptionsTests.cs ├── FakeHostEnvironment.cs └── Tingle.EventBus.Tests.csproj ├── Tingle.EventBus.Transports.Azure.EventHubs.Tests ├── AzureEventHubsTransportTests.cs ├── IotHubEventSerializerTests.cs ├── Samples │ ├── iot-hub-Telemetry.json │ ├── iot-hub-deviceConnectionStateEvents.json │ ├── iot-hub-deviceLifecycleEvents.json │ └── iot-hub-twinChangeEvents.json ├── TestSamples.cs └── Tingle.EventBus.Transports.Azure.EventHubs.Tests.csproj ├── Tingle.EventBus.Transports.Azure.ServiceBus.Tests ├── AzureServiceBusTransportTests.cs └── Tingle.EventBus.Transports.Azure.ServiceBus.Tests.csproj ├── Tingle.EventBus.Transports.InMemory.Tests ├── SampleEventConsumerTests.cs ├── SequenceNumberGeneratorTests.cs ├── SimpleCancellationTests.cs ├── SimplePublisherTests.cs └── Tingle.EventBus.Transports.InMemory.Tests.csproj └── Tingle.EventBus.Transports.Kafka.Tests ├── KafkaTransportTests.cs └── Tingle.EventBus.Transports.Kafka.Tests.csproj /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | skip = .git 3 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/dotnet 3 | { 4 | "name": "C# (.NET)", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/dotnet:8.0" 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 12 | // "forwardPorts": [5000, 5001], 13 | // "portsAttributes": { 14 | // "5001": { 15 | // "protocol": "https" 16 | // } 17 | // } 18 | 19 | // Use 'postCreateCommand' to run commands after the container is created. 20 | // "postCreateCommand": "dotnet restore", 21 | 22 | // Configure tool-specific properties. 23 | // "customizations": {}, 24 | 25 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 26 | // "remoteUser": "root" 27 | } 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # CSharp formatting rules: 7 | [*.cs] 8 | csharp_style_namespace_declarations =file_scoped:warning -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [mburumaxwell] 4 | patreon: maxwellweru # Replace with a single Patreon username 5 | open_collective: maxwellweru # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: 'nuget' 9 | directory: '/' 10 | schedule: 11 | interval: 'weekly' 12 | time: '02:00' 13 | open-pull-requests-limit: 10 14 | groups: 15 | aws-sdk: 16 | patterns: ['AWSSDK.*'] 17 | azure-sdk: 18 | patterns: ['Azure.*'] 19 | microsoft: 20 | patterns: ['Microsoft.*'] 21 | system: 22 | patterns: ['System.*'] 23 | xunit: 24 | patterns: ['Xunit*'] 25 | 26 | - package-ecosystem: 'dotnet-sdk' 27 | directory: '/' 28 | schedule: 29 | interval: 'weekly' 30 | time: '02:00' 31 | 32 | - package-ecosystem: 'github-actions' 33 | directory: '/' 34 | schedule: 35 | interval: 'weekly' 36 | time: '02:00' 37 | 38 | - package-ecosystem: 'devcontainers' 39 | directory: '/' 40 | schedule: 41 | interval: 'weekly' 42 | time: '02:00' 43 | -------------------------------------------------------------------------------- /.github/workflows/codespell.yml: -------------------------------------------------------------------------------- 1 | name: Codespell 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | codespell: 14 | name: Check for spelling errors 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | - name: Codespell 21 | uses: codespell-project/actions-codespell@v2 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "ms-dotnettools.vscodeintellicode-csharp", 8 | "streetsidesoftware.code-spell-checker", 9 | "ms-azuretools.vscode-docker", 10 | "shardulm94.trailing-spaces", 11 | "redhat.vscode-yaml", 12 | "github.vscode-github-actions" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/ltex.dictionary.en-US.txt: -------------------------------------------------------------------------------- 1 | serializer 2 | EventBus 3 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "AWSSDK", 4 | "Behaviour", 5 | "checkpointing", 6 | "Configurator", 7 | "configurators", 8 | "Deadletter", 9 | "Deduplication", 10 | "devcontainers", 11 | "Newtonsoft", 12 | "Serializers", 13 | "Xunit" 14 | ] 15 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0;net9.0 6 | latest 7 | enable 8 | enable 9 | false 10 | 11 | 12 | 13 | $(WarningsAsErrors),SYSLIB1045 14 | $(WarningsAsErrors),IL2026,IL2060,IL2091,IL2095,IL3050 15 | $(WarningsAsErrors),xUnit1051 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | True 8 | True 9 | Resources.resx 10 | 11 | 12 | 13 | True 14 | ResXFileCodeGenerator 15 | Resources.Designer.cs 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | # To get the effective configuration run 'gitversion /showconfig' 2 | branches: 3 | pull-request: 4 | label: pr 5 | main: 6 | label: ci 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 TINGLE SOFTWARE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "9.0.300", 4 | "allowPrerelease": false, 5 | "rollForward": "latestMinor" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinglesoftware/eventbus/cab00baad57a640c807d348d40c4463f3ecff005/logo.png -------------------------------------------------------------------------------- /samples/AmazonSqsAndSns/AmazonSqsAndSns.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | aedb10fe-1539-4990-8f43-981e8ed03904 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/AmazonSqsAndSns/DoorOpened.cs: -------------------------------------------------------------------------------- 1 | namespace AmazonSqsAndSns; 2 | 3 | public class DoorOpened 4 | { 5 | /// 6 | /// The vehicle who's door was opened. 7 | /// 8 | public string? VehicleId { get; set; } 9 | 10 | /// 11 | /// The kind of door that was opened. 12 | /// 13 | public OpenDoorKind Kind { get; set; } 14 | 15 | /// 16 | /// When the door was opened. 17 | /// 18 | public DateTimeOffset Opened { get; set; } 19 | } 20 | 21 | public enum OpenDoorKind 22 | { 23 | FrontLeft, 24 | FrontRight, 25 | RearLeft, 26 | ReadRight, 27 | Hood, 28 | Trunk, 29 | } 30 | -------------------------------------------------------------------------------- /samples/AmazonSqsAndSns/DoorOpenedConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace AmazonSqsAndSns; 2 | 3 | public class DoorOpenedConsumer(ILogger logger) : IEventConsumer 4 | { 5 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 6 | { 7 | logger.LogInformation("Received event Id: {Id}", context.Id); 8 | logger.LogInformation("Event body: {EventBody}", System.Text.Json.JsonSerializer.Serialize(context.Event)); 9 | return Task.CompletedTask; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/AmazonSqsAndSns/Program.cs: -------------------------------------------------------------------------------- 1 | using AmazonSqsAndSns; 2 | 3 | var host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices((hostContext, services) => 5 | { 6 | services.AddEventBus(builder => 7 | { 8 | builder.AddConsumer(); 9 | 10 | // Transport specific configuration 11 | builder.AddAmazonSqsTransport(options => 12 | { 13 | options.RegionName = "eu-west-1"; 14 | options.AccessKey = "my-access-key"; 15 | options.SecretKey = "my-secret-key"; 16 | }); 17 | }); 18 | 19 | services.AddHostedService(); 20 | }) 21 | .Build(); 22 | 23 | await host.RunAsync(); 24 | -------------------------------------------------------------------------------- /samples/AmazonSqsAndSns/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AmazonSqsAndSns": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "environmentVariables": { 7 | "DOTNET_ENVIRONMENT": "Development" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/AmazonSqsAndSns/PublisherService.cs: -------------------------------------------------------------------------------- 1 | namespace AmazonSqsAndSns; 2 | 3 | public class PublisherService(IEventPublisher publisher) : BackgroundService 4 | { 5 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 6 | { 7 | var delay = TimeSpan.FromSeconds(30); 8 | var times = 5; 9 | 10 | var rnd = new Random(DateTimeOffset.UtcNow.Millisecond); 11 | 12 | for (var i = 0; i < times; i++) 13 | { 14 | var evt = new DoorOpened 15 | { 16 | Kind = (OpenDoorKind)rnd.Next(0, 5), 17 | Opened = DateTimeOffset.UtcNow.AddMinutes(rnd.Next(-10, 10)), 18 | VehicleId = "123456", 19 | }; 20 | 21 | await publisher.PublishAsync(evt, cancellationToken: stoppingToken); 22 | 23 | await Task.Delay(delay, stoppingToken); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/AmazonSqsAndSns/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/AmazonSqsAndSns/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/AotSupport/AotSupport.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/AotSupport/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AotSupport": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/AotSupport/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/AotSupport/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/AzureIotHub/AzureIotHub.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 710bcdca-052b-456d-be46-4efede662bb2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/AzureIotHub/MyIotHubEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Tingle.EventBus.Transports.Azure.EventHubs.IotHub; 3 | 4 | namespace AzureIotHub; 5 | 6 | public record MyIotHubEvent : IotHubEvent { } 7 | 8 | public class MyIotHubTelemetry 9 | { 10 | public DateTimeOffset Timestamp { get; set; } 11 | 12 | [JsonExtensionData] 13 | public Dictionary? Extras { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /samples/AzureIotHub/Program.cs: -------------------------------------------------------------------------------- 1 | using AzureIotHub; 2 | using Tingle.EventBus.Configuration; 3 | 4 | var host = Host.CreateDefaultBuilder(args) 5 | .ConfigureServices((hostContext, services) => 6 | { 7 | var configuration = hostContext.Configuration; 8 | 9 | services.AddEventBus(builder => 10 | { 11 | builder.Configure(o => o.ConfigureEvent(reg => 12 | { 13 | reg.ConfigureAsIotHubEvent(configuration["IotHubEventHubName"]!) 14 | .UseIotHubEventSerializer(); 15 | })); 16 | 17 | builder.AddConsumer(); 18 | 19 | // Transport specific configuration 20 | builder.AddAzureEventHubsTransport(options => 21 | { 22 | options.Credentials = configuration.GetConnectionString("EventHub")!; 23 | options.BlobStorageCredentials = configuration.GetConnectionString("AzureStorage")!; 24 | }); 25 | }); 26 | }) 27 | .Build(); 28 | 29 | await host.RunAsync(); 30 | -------------------------------------------------------------------------------- /samples/AzureIotHub/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AzureIotHub": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/AzureIotHub/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/AzureIotHub/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | 10 | "ConnectionStrings:AzureStorage": "UseDevelopmentStorage=true;", 11 | "ConnectionStrings:EventHub": "Endpoint=sb://abcd.servicebus.windows.net/;SharedAccessKeyName=xyz;SharedAccessKey=AAAAAAAAAAAAAAAAAAAAAA==", 12 | "IotHubEventHubName": "iothub-ehub-test-dev-0000000-0aaaa000aa" 13 | } 14 | -------------------------------------------------------------------------------- /samples/AzureManagedIdentity/AzureManagedIdentity.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/AzureManagedIdentity/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Identity; 2 | using AzureManagedIdentity; 3 | using Tingle.EventBus.Configuration; 4 | 5 | var host = Host.CreateDefaultBuilder(args) 6 | .ConfigureServices((hostContext, services) => 7 | { 8 | var configuration = hostContext.Configuration; 9 | 10 | services.AddEventBus(builder => 11 | { 12 | builder.AddConsumer(); 13 | 14 | var credential = new DefaultAzureCredential(); 15 | 16 | // Transport specific configuration 17 | builder.AddAzureServiceBusTransport(options => 18 | { 19 | options.Credentials = new AzureServiceBusTransportCredentials 20 | { 21 | TokenCredential = credential, 22 | FullyQualifiedNamespace = "{your_namespace}.servicebus.windows.net" 23 | }; 24 | options.DefaultEntityKind = EntityKind.Queue; // required if using the basic SKU (does not support topics) 25 | }); 26 | }); 27 | }) 28 | .Build(); 29 | 30 | await host.RunAsync(); 31 | -------------------------------------------------------------------------------- /samples/AzureManagedIdentity/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "AzureManagedIdentity": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "environmentVariables": { 7 | "DOTNET_ENVIRONMENT": "Development" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/AzureManagedIdentity/VehicleDoorOpenedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace AzureManagedIdentity; 2 | 3 | public class VehicleDoorOpenedEvent 4 | { 5 | public string? VehicleId { get; set; } 6 | public VehicleDoorKind Kind { get; set; } 7 | public DateTimeOffset? Opened { get; set; } 8 | public DateTimeOffset? Closed { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /samples/AzureManagedIdentity/VehicleTelemetryEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace AzureManagedIdentity; 4 | 5 | internal class VehicleTelemetryEvent 6 | { 7 | public string? DeviceId { get; set; } 8 | 9 | public DateTimeOffset Timestamp { get; set; } 10 | 11 | public string? Action { get; set; } 12 | 13 | public VehicleDoorKind? VehicleDoorKind { get; set; } 14 | public VehicleDoorStatus? VehicleDoorStatus { get; set; } 15 | 16 | [JsonExtensionData] 17 | public Dictionary? Extras { get; set; } 18 | } 19 | 20 | public enum VehicleDoorStatus 21 | { 22 | Unknown, 23 | Open, 24 | Closed, 25 | } 26 | 27 | public enum VehicleDoorKind 28 | { 29 | FrontLeft, 30 | FrontRight, 31 | RearLeft, 32 | ReadRight, 33 | Hood, 34 | Trunk, 35 | } 36 | -------------------------------------------------------------------------------- /samples/AzureManagedIdentity/VehicleTelemetryEventsConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace AzureManagedIdentity; 2 | 3 | internal class VehicleTelemetryEventsConsumer(ILogger logger) : IEventConsumer 4 | { 5 | public async Task ConsumeAsync(EventContext context, CancellationToken cancellationToken) 6 | { 7 | var telemetry = context.Event; 8 | 9 | var status = telemetry.VehicleDoorStatus; 10 | if (status is not VehicleDoorStatus.Open and not VehicleDoorStatus.Closed) 11 | { 12 | logger.LogWarning("Vehicle Door status '{VehicleDoorStatus}' is not yet supported", status); 13 | return; 14 | } 15 | 16 | var kind = telemetry.VehicleDoorKind; 17 | if (kind is null) 18 | { 19 | logger.LogWarning("Vehicle Door kind '{VehicleDoorKind}' cannot be null", kind); 20 | return; 21 | } 22 | 23 | var timestamp = telemetry.Timestamp; 24 | var updateEvt = new VehicleDoorOpenedEvent 25 | { 26 | VehicleId = telemetry.DeviceId, 27 | Kind = kind.Value, 28 | Closed = status is VehicleDoorStatus.Closed ? timestamp : null, 29 | Opened = status is VehicleDoorStatus.Open ? timestamp : null, 30 | }; 31 | 32 | // the VehicleDoorOpenedEvent on a broadcast bus would notify all subscribers 33 | await context.PublishAsync(updateEvt, cancellationToken: cancellationToken); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/AzureManagedIdentity/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/AzureManagedIdentity/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/ConfigSample/ConfigSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fdb14b87-4a29-455c-9912-67a1e0c64081 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/ConfigSample/ImageUploaded.cs: -------------------------------------------------------------------------------- 1 | namespace ConfigSample; 2 | 3 | internal class ImageUploaded 4 | { 5 | public string? ImageId { get; set; } 6 | public string? Url { get; set; } 7 | public long SizeBytes { get; set; } 8 | } 9 | 10 | internal class VideoUploaded 11 | { 12 | public string? VideoId { get; set; } 13 | public string? Url { get; set; } 14 | public long SizeBytes { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /samples/ConfigSample/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Identity; 2 | using ConfigSample; 3 | using Tingle.EventBus.Transports.Azure.ServiceBus; 4 | 5 | var host = Host.CreateDefaultBuilder(args) 6 | .ConfigureServices((hostContext, services) => 7 | { 8 | var configuration = hostContext.Configuration; 9 | 10 | services.AddEventBus(builder => 11 | { 12 | builder.AddConsumer(); 13 | builder.AddConsumer(); 14 | 15 | // Add transports 16 | builder.AddAzureServiceBusTransport(); 17 | builder.AddInMemoryTransport("in-memory-images"); 18 | builder.AddInMemoryTransport("in-memory-videos"); 19 | 20 | // Transport specific configuration 21 | var credential = new DefaultAzureCredential(); 22 | builder.Services.PostConfigure( 23 | name: AzureServiceBusDefaults.Name, 24 | configureOptions: o => ((AzureServiceBusTransportCredentials)o.Credentials).TokenCredential = credential); 25 | }); 26 | 27 | services.AddHostedService(); 28 | }) 29 | .Build(); 30 | 31 | await host.RunAsync(); 32 | -------------------------------------------------------------------------------- /samples/ConfigSample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "ConfigSample": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "environmentVariables": { 7 | "DOTNET_ENVIRONMENT": "Development" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/ConfigSample/VehicleDoorOpenedEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace ConfigSample; 4 | 5 | internal class VehicleDoorOpenedEvent 6 | { 7 | public string? VehicleId { get; set; } 8 | public VehicleDoorKind Kind { get; set; } 9 | public DateTimeOffset? Opened { get; set; } 10 | public DateTimeOffset? Closed { get; set; } 11 | } 12 | 13 | internal class VehicleTelemetryEvent 14 | { 15 | public string? DeviceId { get; set; } 16 | public DateTimeOffset Timestamp { get; set; } 17 | public string? Action { get; set; } 18 | public VehicleDoorKind? VehicleDoorKind { get; set; } 19 | public VehicleDoorStatus? VehicleDoorStatus { get; set; } 20 | [JsonExtensionData] 21 | public Dictionary? Extras { get; set; } 22 | } 23 | 24 | internal enum VehicleDoorStatus { Unknown, Open, Closed, } 25 | internal enum VehicleDoorKind { FrontLeft, FrontRight, RearLeft, ReadRight, Hood, Trunk, } 26 | -------------------------------------------------------------------------------- /samples/ConfigSample/VehicleTelemetryEventsConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace ConfigSample; 2 | 3 | internal class VehicleTelemetryEventsConsumer(ILogger logger) : IEventConsumer 4 | { 5 | public async Task ConsumeAsync(EventContext context, CancellationToken cancellationToken) 6 | { 7 | var telemetry = context.Event; 8 | 9 | var status = telemetry.VehicleDoorStatus; 10 | if (status is not VehicleDoorStatus.Open and not VehicleDoorStatus.Closed) 11 | { 12 | logger.LogWarning("Vehicle Door status '{VehicleDoorStatus}' is not yet supported", status); 13 | return; 14 | } 15 | 16 | var kind = telemetry.VehicleDoorKind; 17 | if (kind is null) 18 | { 19 | logger.LogWarning("Vehicle Door kind '{VehicleDoorKind}' cannot be null", kind); 20 | return; 21 | } 22 | 23 | var timestamp = telemetry.Timestamp; 24 | var updateEvt = new VehicleDoorOpenedEvent 25 | { 26 | VehicleId = telemetry.DeviceId, 27 | Kind = kind.Value, 28 | Closed = status is VehicleDoorStatus.Closed ? timestamp : null, 29 | Opened = status is VehicleDoorStatus.Open ? timestamp : null, 30 | }; 31 | 32 | // the VehicleDoorOpenedEvent on a broadcast bus would notify all subscribers 33 | await context.PublishAsync(updateEvt, cancellationToken: cancellationToken); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/ConfigSample/VisualsProducerService.cs: -------------------------------------------------------------------------------- 1 | namespace ConfigSample; 2 | 3 | internal class VisualsProducerService(IEventPublisher publisher, ILogger logger) : BackgroundService 4 | { 5 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 6 | { 7 | await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken); // delays a little so that the logs are better visible in a better order (only ended for sample) 8 | 9 | logger.LogInformation("Starting production ..."); 10 | 11 | var delay = TimeSpan.FromSeconds(20); 12 | var times = 10; 13 | 14 | var rnd = new Random(DateTimeOffset.UtcNow.Millisecond); 15 | 16 | for (var i = 0; i < times; i++) 17 | { 18 | var id = Convert.ToUInt32(rnd.Next()).ToString(); 19 | var size = Convert.ToUInt32(rnd.Next()); 20 | var image = (i % 2) == 0; 21 | var url = $"https://localhost:8080/{(image ? "images" : "videos")}/{id}.{(image ? "png" : "flv")}"; 22 | 23 | _ = image 24 | ? await DoPublishAsync(new VideoUploaded { VideoId = id, SizeBytes = size, Url = url, }, stoppingToken) 25 | : await DoPublishAsync(new ImageUploaded { ImageId = id, SizeBytes = size, Url = url, }, stoppingToken); 26 | 27 | await Task.Delay(delay, stoppingToken); 28 | } 29 | } 30 | 31 | private async Task DoPublishAsync(T @event, CancellationToken cancellationToken) where T : class 32 | => await publisher.PublishAsync(@event, cancellationToken: cancellationToken); 33 | } 34 | -------------------------------------------------------------------------------- /samples/ConfigSample/VisualsUploadedConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace ConfigSample; 2 | 3 | internal class VisualsUploadedConsumer(ILogger logger) : IEventConsumer, IEventConsumer 4 | { 5 | private static readonly TimeSpan SimulationDuration = TimeSpan.FromSeconds(1.3f); 6 | 7 | public async Task ConsumeAsync(EventContext context, CancellationToken cancellationToken) 8 | { 9 | var id = context.Event.ImageId; 10 | var thumbnailUrl = $"https://localhost:8080/thumbnails/{id}.jpg"; 11 | 12 | await Task.Delay(SimulationDuration, cancellationToken); 13 | logger.LogInformation("Generated thumbnail from image '{ImageId}' at '{ThumbnailUrl}'.", id, thumbnailUrl); 14 | } 15 | 16 | public async Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 17 | { 18 | var id = context.Event.VideoId; 19 | var thumbnailUrl = $"https://localhost:8080/thumbnails/{id}.jpg"; 20 | 21 | await Task.Delay(SimulationDuration, cancellationToken); 22 | logger.LogInformation("Generated thumbnail from video '{VideoId}' at '{ThumbnailUrl}'.", id, thumbnailUrl); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/ConfigSample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/ConfigSample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | 10 | "EventBus": { 11 | "DefaultTransportWaitStarted": false, 12 | "Naming": { 13 | "Convention": "DotCase", 14 | "UseFullTypeNames": false 15 | }, 16 | "DefaultTransportName": "azure-service-bus", 17 | "Transports": { // keyed by name of the transport 18 | "azure-service-bus": { 19 | "DefaultEntityKind": "Queue", // required if using the basic SKU (does not support topics) 20 | "FullyQualifiedNamespace": "{your_namespace}.servicebus.windows.net" 21 | }, 22 | "in-memory-images": { 23 | "DefaultEventIdFormat": "DoubleLongHex" 24 | }, 25 | "in-memory-videos": { 26 | "DefaultEntityKind": "Queue" 27 | } 28 | }, 29 | "Events": { 30 | "ConfigSample.ImageUploaded": { // FullName of the type 31 | "TransportName": "in-memory-images" 32 | }, 33 | "ConfigSample.VideoUploaded": { // FullName of the type 34 | "TransportName": "in-memory-videos", 35 | "Consumers": { 36 | "ConfigSample.VisualsUploadedConsumer": { // FullName of the type 37 | "UnhandledErrorBehaviour": "Discard", 38 | "Metadata": { 39 | "generation": "2022" 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/CustomEventConfigurator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/MyConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | using Tingle.EventBus.Transports; 3 | 4 | namespace CustomEventConfigurator; 5 | 6 | class MyConfigurator : IEventBusConfigurator 7 | { 8 | public void Configure(EventBusOptions options) { } 9 | 10 | public void Configure(IConfiguration configuration, TOptions options) where TOptions : EventBusTransportOptions { } 11 | 12 | public void Configure(EventRegistration registration, EventBusOptions options) 13 | { 14 | if (registration.EventType == typeof(SampleEvent1)) 15 | { 16 | registration.EntityKind = EntityKind.Queue; 17 | } 18 | 19 | if (registration.EventType == typeof(SampleEvent2)) 20 | { 21 | registration.IdFormat = EventIdFormat.LongHex; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/Program.cs: -------------------------------------------------------------------------------- 1 | using CustomEventConfigurator; 2 | using Tingle.EventBus.Configuration; 3 | 4 | var host = Host.CreateDefaultBuilder(args) 5 | .ConfigureServices((hostContext, services) => 6 | { 7 | services.AddEventBus(builder => 8 | { 9 | // Transport agnostic configuration 10 | builder.Configure(o => 11 | { 12 | o.Naming.Scope = "dev"; // queues will be prefixed by 'dev' 13 | o.Naming.UseFullTypeNames = false; 14 | }); 15 | builder.AddConsumer(); 16 | builder.AddConsumer(); 17 | 18 | // Setup extra configurators 19 | // You can have as many as you like, these are called after the default one. 20 | builder.Services.AddSingleton(); 21 | 22 | // Transport specific configuration 23 | builder.AddAzureQueueStorageTransport(o => o.Credentials = "UseDevelopmentStorage=true;"); 24 | }); 25 | }) 26 | .Build(); 27 | 28 | await host.RunAsync(); 29 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CustomEventConfigurator": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/SampleConsumer1.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventConfigurator; 2 | 3 | public class SampleConsumer1(ILogger logger) : IEventConsumer 4 | { 5 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 6 | { 7 | logger.LogInformation("Received event Id: {Id}", context.Id); 8 | logger.LogInformation("Event body: {EventBody}", System.Text.Json.JsonSerializer.Serialize(context.Event)); 9 | return Task.CompletedTask; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/SampleConsumer2.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventConfigurator; 2 | 3 | public class SampleConsumer2(ILogger logger) : IEventConsumer 4 | { 5 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 6 | { 7 | logger.LogInformation("Received event Id: {Id}", context.Id); 8 | logger.LogInformation("Event body: {EventBody}", System.Text.Json.JsonSerializer.Serialize(context.Event)); 9 | return Task.CompletedTask; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/SampleEvent1.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventConfigurator; 2 | 3 | public class SampleEvent1 4 | { 5 | public string? Make { get; set; } 6 | public string? Model { get; set; } 7 | public int Year { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/SampleEvent2.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventConfigurator; 2 | 3 | public class SampleEvent2 4 | { 5 | public string? VehicleId { get; set; } 6 | public string? Kind { get; set; } 7 | public DateTimeOffset Opened { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/CustomEventConfigurator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/AzureDevOpsCodePushed.cs: -------------------------------------------------------------------------------- 1 | using CustomEventSerializer.Models; 2 | using Tingle.EventBus.Configuration; 3 | 4 | namespace CustomEventSerializer; 5 | 6 | [EventSerializer(typeof(AzureDevOpsEventSerializer))] 7 | public sealed class AzureDevOpsCodePushed 8 | { 9 | public AzureDevOpsEventResource? Resource { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/AzureDevOpsEventsConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventSerializer; 2 | 3 | internal class AzureDevOpsEventsConsumer(ILogger logger) : IEventConsumer 4 | { 5 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 6 | { 7 | var @event = context.Event; 8 | var resource = @event.Resource; 9 | var repository = resource?.Repository; 10 | var defaultBranch = repository?.DefaultBranch; 11 | 12 | // get the updated branches (refs) 13 | var updatedReferences = resource?.RefUpdates?.Select(ru => ru.Name).ToList() ?? []; 14 | logger.LogInformation("Default branch: ({DefaultBranch})", defaultBranch); 15 | logger.LogInformation("Updated branches (references):\r\n- {ChangedReferences}", 16 | string.Join("\r\n- ", updatedReferences)); 17 | return Task.CompletedTask; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/CustomEventSerializer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/Models/AzureDevOpsEventResource.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventSerializer.Models; 2 | 3 | public class AzureDevOpsEventResource 4 | { 5 | /// 6 | /// List of updated references. 7 | /// 8 | public List? RefUpdates { get; set; } 9 | 10 | /// 11 | /// Details about the repository. 12 | /// 13 | public AzureDevOpsEventResourceRepository? Repository { get; set; } 14 | } 15 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/Models/AzureDevOpsEventResourceRefUpdate.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventSerializer.Models; 2 | 3 | public class AzureDevOpsEventResourceRefUpdate 4 | { 5 | public string? Name { get; set; } 6 | public string? OldObjectId { get; set; } 7 | public string? NewObjectId { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/Models/AzureDevOpsEventResourceRepository.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventSerializer.Models; 2 | 3 | public class AzureDevOpsEventResourceRepository 4 | { 5 | /// 6 | /// The unique identifier of the repository. 7 | /// 8 | public Guid Id { get; set; } 9 | 10 | /// 11 | /// The name of the repository. 12 | /// 13 | public string? Name { get; set; } 14 | 15 | /// 16 | /// The details about the project which owns the repository. 17 | /// 18 | public AzureDevOpsEventResourceRepositoryProject? Project { get; set; } 19 | 20 | /// 21 | /// The default branch of the repository. 22 | /// 23 | public string? DefaultBranch { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/Models/AzureDevOpsEventResourceRepositoryProject.cs: -------------------------------------------------------------------------------- 1 | namespace CustomEventSerializer.Models; 2 | 3 | public class AzureDevOpsEventResourceRepositoryProject 4 | { 5 | /// 6 | /// The unique identifier of the project. 7 | /// 8 | public Guid Id { get; set; } 9 | 10 | /// 11 | /// The name of the project. 12 | /// 13 | public string? Name { get; set; } 14 | 15 | /// 16 | /// The URL for the project. 17 | /// 18 | /// https://dev.azure.com/tingle/_apis/projects/cea8cb01-dd13-4588-b27a-55fa170e4e94 19 | /// Useful for extraction of the name and URL of the organization. 20 | public string? Url { get; set; } 21 | } 22 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/Program.cs: -------------------------------------------------------------------------------- 1 | using CustomEventSerializer; 2 | 3 | var host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices((hostContext, services) => 5 | { 6 | services.AddEventBus(builder => 7 | { 8 | // Transport agnostic configuration 9 | builder.Configure(o => 10 | { 11 | o.Naming.Scope = "dev"; // queues will be prefixed by 'dev' 12 | o.Naming.UseFullTypeNames = false; 13 | }); 14 | builder.AddConsumer(); 15 | 16 | // Transport specific configuration 17 | builder.AddAzureQueueStorageTransport(o => o.Credentials = "UseDevelopmentStorage=true;"); 18 | }); 19 | }) 20 | .Build(); 21 | 22 | await host.RunAsync(); 23 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "CustomEventSerializer": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/CustomEventSerializer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/CustomEventSerializer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Exe 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/HealthCheck/AzureServiceBusHealthCheck.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.ServiceBus.Administration; 2 | using Microsoft.Extensions.Diagnostics.HealthChecks; 3 | using Microsoft.Extensions.Options; 4 | using Tingle.EventBus.Transports.Azure.ServiceBus; 5 | 6 | namespace HealthCheck; 7 | 8 | internal class AzureServiceBusHealthCheck(IOptionsMonitor optionsMonitor) : IHealthCheck 9 | { 10 | private const string QueueName = "health-check"; 11 | 12 | private readonly AzureServiceBusTransportOptions options = optionsMonitor?.Get(AzureServiceBusDefaults.Name) ?? throw new ArgumentNullException(nameof(optionsMonitor)); 13 | 14 | public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) 15 | { 16 | try 17 | { 18 | var cred = (AzureServiceBusTransportCredentials)options.Credentials.CurrentValue; 19 | var managementClient = new ServiceBusAdministrationClient( 20 | fullyQualifiedNamespace: cred.FullyQualifiedNamespace, 21 | credential: cred.TokenCredential); 22 | 23 | _ = await managementClient.GetQueueRuntimePropertiesAsync(QueueName, cancellationToken); 24 | 25 | return HealthCheckResult.Healthy(); 26 | } 27 | catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested) 28 | { 29 | // ignore long running calls 30 | return HealthCheckResult.Healthy(); 31 | } 32 | catch (Exception ex) 33 | { 34 | return new HealthCheckResult(context.Registration.FailureStatus, exception: ex); 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /samples/HealthCheck/HealthCheck.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/HealthCheck/Program.cs: -------------------------------------------------------------------------------- 1 | using Azure.Identity; 2 | using HealthCheck; 3 | using Tingle.EventBus.Configuration; 4 | 5 | var host = Host.CreateDefaultBuilder(args) 6 | .ConfigureServices((hostContext, services) => 7 | { 8 | var configuration = hostContext.Configuration; 9 | 10 | services.AddEventBus(builder => 11 | { 12 | builder.AddConsumer(); 13 | 14 | var credential = new DefaultAzureCredential(); 15 | 16 | // Transport specific configuration 17 | builder.AddAzureServiceBusTransport(options => 18 | { 19 | options.Credentials = new AzureServiceBusTransportCredentials 20 | { 21 | TokenCredential = credential, 22 | FullyQualifiedNamespace = "{your_namespace}.servicebus.windows.net" 23 | }; 24 | options.DefaultEntityKind = EntityKind.Queue; // required if using the basic SKU (does not support topics) 25 | }); 26 | 27 | builder.Services.AddHealthChecks() 28 | .AddCheck(name: "servicebus", tags: ["eventbus"]); 29 | }); 30 | }) 31 | .Build(); 32 | 33 | await host.RunAsync(); 34 | -------------------------------------------------------------------------------- /samples/HealthCheck/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "HealthCheck": { 4 | "commandName": "Project", 5 | "dotnetRunMessages": true, 6 | "environmentVariables": { 7 | "DOTNET_ENVIRONMENT": "Development" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/HealthCheck/VehicleDoorOpenedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace HealthCheck; 2 | 3 | public class VehicleDoorOpenedEvent 4 | { 5 | public string? VehicleId { get; set; } 6 | public VehicleDoorKind Kind { get; set; } 7 | public DateTimeOffset? Opened { get; set; } 8 | public DateTimeOffset? Closed { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /samples/HealthCheck/VehicleTelemetryEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace HealthCheck; 4 | 5 | internal class VehicleTelemetryEvent 6 | { 7 | public string? DeviceId { get; set; } 8 | 9 | public DateTimeOffset Timestamp { get; set; } 10 | 11 | public string? Action { get; set; } 12 | 13 | public VehicleDoorKind? VehicleDoorKind { get; set; } 14 | public VehicleDoorStatus? VehicleDoorStatus { get; set; } 15 | 16 | [JsonExtensionData] 17 | public Dictionary? Extras { get; set; } 18 | } 19 | 20 | public enum VehicleDoorStatus 21 | { 22 | Unknown, 23 | Open, 24 | Closed, 25 | } 26 | 27 | public enum VehicleDoorKind 28 | { 29 | FrontLeft, 30 | FrontRight, 31 | RearLeft, 32 | ReadRight, 33 | Hood, 34 | Trunk, 35 | } 36 | -------------------------------------------------------------------------------- /samples/HealthCheck/VehicleTelemetryEventsConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace HealthCheck; 2 | 3 | internal class VehicleTelemetryEventsConsumer(ILogger logger) : IEventConsumer 4 | { 5 | public async Task ConsumeAsync(EventContext context, CancellationToken cancellationToken) 6 | { 7 | var telemetry = context.Event; 8 | 9 | var status = telemetry.VehicleDoorStatus; 10 | if (status is not VehicleDoorStatus.Open and not VehicleDoorStatus.Closed) 11 | { 12 | logger.LogWarning("Vehicle Door status '{VehicleDoorStatus}' is not yet supported", status); 13 | return; 14 | } 15 | 16 | var kind = telemetry.VehicleDoorKind; 17 | if (kind is null) 18 | { 19 | logger.LogWarning("Vehicle Door kind '{VehicleDoorKind}' cannot be null", kind); 20 | return; 21 | } 22 | 23 | var timestamp = telemetry.Timestamp; 24 | var updateEvt = new VehicleDoorOpenedEvent 25 | { 26 | VehicleId = telemetry.DeviceId, 27 | Kind = kind.Value, 28 | Closed = status is VehicleDoorStatus.Closed ? timestamp : null, 29 | Opened = status is VehicleDoorStatus.Open ? timestamp : null, 30 | }; 31 | 32 | // the VehicleDoorOpenedEvent on a broadcast bus would notify all subscribers 33 | await context.PublishAsync(updateEvt, cancellationToken: cancellationToken); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/HealthCheck/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/HealthCheck/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/InMemoryBackgroundProcessing/InMemoryBackgroundProcessing.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/InMemoryBackgroundProcessing/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "InMemoryBackgroundProcessing": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/InMemoryBackgroundProcessing/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/InMemoryBackgroundProcessing/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/DoorClosed.cs: -------------------------------------------------------------------------------- 1 | namespace MultiEventsConsumer; 2 | 3 | public class DoorClosed 4 | { 5 | /// 6 | /// The vehicle who's door was closed. 7 | /// 8 | public string? VehicleId { get; set; } 9 | 10 | /// 11 | /// The kind of door that was opened. 12 | /// 13 | public DoorKind Kind { get; set; } 14 | 15 | /// 16 | /// When the door was opened. 17 | /// 18 | public DateTimeOffset Closed { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/DoorKind.cs: -------------------------------------------------------------------------------- 1 | namespace MultiEventsConsumer; 2 | 3 | public enum DoorKind 4 | { 5 | FrontLeft, 6 | FrontRight, 7 | RearLeft, 8 | ReadRight, 9 | Hood, 10 | Trunk, 11 | } 12 | -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/DoorOpened.cs: -------------------------------------------------------------------------------- 1 | namespace MultiEventsConsumer; 2 | 3 | public class DoorOpened 4 | { 5 | /// 6 | /// The vehicle who's door was opened. 7 | /// 8 | public string? VehicleId { get; set; } 9 | 10 | /// 11 | /// The kind of door that was opened. 12 | /// 13 | public DoorKind Kind { get; set; } 14 | 15 | /// 16 | /// When the door was opened. 17 | /// 18 | public DateTimeOffset Opened { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/DoorState.cs: -------------------------------------------------------------------------------- 1 | namespace MultiEventsConsumer; 2 | 3 | public enum DoorState 4 | { 5 | Open, 6 | Closed 7 | } 8 | -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/MultiEventsConsumer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/Program.cs: -------------------------------------------------------------------------------- 1 | var host = Host.CreateDefaultBuilder(args) 2 | .ConfigureServices((hostContext, services) => 3 | { 4 | services.AddDistributedMemoryCache(); 5 | 6 | services.AddEventBus(builder => 7 | { 8 | builder.AddXmlSerializer(); 9 | 10 | // Transport agnostic configuration 11 | builder.Configure(o => 12 | { 13 | o.Naming.Scope = "dev"; // queues will be prefixed by 'dev' 14 | o.Naming.UseFullTypeNames = false; 15 | }); 16 | builder.AddConsumer(); 17 | 18 | // Transport specific configuration 19 | builder.AddInMemoryTransport(); 20 | }); 21 | 22 | services.AddHostedService(); 23 | }) 24 | .Build(); 25 | 26 | await host.RunAsync(); 27 | -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MultiEventsConsumer": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/MultiEventsConsumer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/MultipleConsumers/DoorOpened.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleConsumers; 2 | 3 | public class DoorOpened 4 | { 5 | /// 6 | /// The vehicle who's door was opened. 7 | /// 8 | public string? VehicleId { get; set; } 9 | 10 | /// 11 | /// The kind of door that was opened. 12 | /// 13 | public OpenDoorKind Kind { get; set; } 14 | 15 | /// 16 | /// When the door was opened. 17 | /// 18 | public DateTimeOffset Opened { get; set; } 19 | } 20 | 21 | public enum OpenDoorKind 22 | { 23 | FrontLeft, 24 | FrontRight, 25 | RearLeft, 26 | ReadRight, 27 | Hood, 28 | Trunk, 29 | } 30 | -------------------------------------------------------------------------------- /samples/MultipleConsumers/FirstEventConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleConsumers; 2 | 3 | public class FirstEventConsumer(ILogger logger) : IEventConsumer 4 | { 5 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 6 | { 7 | var evt = context.Event; 8 | var vehicleId = evt.VehicleId; 9 | var kind = evt.Kind; 10 | logger.LogInformation("{DoorKind} door for {VehicleId} was opened at {Opened:r}.", kind, vehicleId, evt.Opened); 11 | return Task.CompletedTask; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/MultipleConsumers/MultipleConsumers.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/MultipleConsumers/Program.cs: -------------------------------------------------------------------------------- 1 | using MultipleConsumers; 2 | using Tingle.EventBus.Configuration; 3 | 4 | var host = Host.CreateDefaultBuilder(args) 5 | .ConfigureServices((hostContext, services) => 6 | { 7 | services.AddEventBus(builder => 8 | { 9 | builder.AddNewtonsoftJsonSerializer(); 10 | 11 | // Transport agnostic configuration 12 | builder.Configure(o => 13 | { 14 | o.Naming.Scope = "dev"; // queues will be prefixed by 'dev' 15 | o.Naming.UseFullTypeNames = false; 16 | }); 17 | builder.AddConsumer(); 18 | builder.AddConsumer(); 19 | 20 | // Transport specific configuration 21 | builder.AddInMemoryTransport(o => 22 | { 23 | // default to Broadcast kind so that we get pub-sub behaviour 24 | o.DefaultEntityKind = EntityKind.Broadcast; 25 | }); 26 | }); 27 | 28 | services.AddHostedService(); 29 | }) 30 | .Build(); 31 | 32 | await host.RunAsync(); 33 | -------------------------------------------------------------------------------- /samples/MultipleConsumers/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MultipleConsumers": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/MultipleConsumers/PublisherService.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleConsumers; 2 | 3 | public class PublisherService(IEventPublisher publisher) : BackgroundService 4 | { 5 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 6 | { 7 | var delay = TimeSpan.FromSeconds(30); 8 | var times = 5; 9 | 10 | var rnd = new Random(DateTimeOffset.UtcNow.Millisecond); 11 | 12 | for (var i = 0; i < times; i++) 13 | { 14 | var evt = new DoorOpened 15 | { 16 | Kind = (OpenDoorKind)rnd.Next(0, 5), 17 | Opened = DateTimeOffset.UtcNow.AddMinutes(rnd.Next(-10, 10)), 18 | VehicleId = "123456", 19 | }; 20 | 21 | await publisher.PublishAsync(evt, cancellationToken: stoppingToken); 22 | 23 | await Task.Delay(delay, stoppingToken); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/MultipleConsumers/SecondEventConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleConsumers; 2 | 3 | public class SecondEventConsumer(ILogger logger) : IEventConsumer 4 | { 5 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 6 | { 7 | var evt = context.Event; 8 | var vehicleId = evt.VehicleId; 9 | var kind = evt.Kind; 10 | logger.LogInformation("{DoorKind} door for {VehicleId} was opened at {Opened:r}.", kind, vehicleId, evt.Opened); 11 | return Task.CompletedTask; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/MultipleConsumers/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/MultipleConsumers/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/MultipleDifferentTransports/MultipleDifferentTransports.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 50e90b43-cf86-44cc-b5f8-2c8c5ff11362 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/MultipleDifferentTransports/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MultipleDifferentTransports": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/MultipleDifferentTransports/VehicleDoorOpenedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace MultipleDifferentTransports; 2 | 3 | public class VehicleDoorOpenedEvent 4 | { 5 | public string? VehicleId { get; set; } 6 | public VehicleDoorKind Kind { get; set; } 7 | public DateTimeOffset? Opened { get; set; } 8 | public DateTimeOffset? Closed { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /samples/MultipleDifferentTransports/VehicleTelemetryEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Tingle.EventBus.Transports.Azure.EventHubs.IotHub; 3 | 4 | namespace MultipleDifferentTransports; 5 | 6 | internal record VehicleTelemetryEvent : IotHubEvent { } 7 | 8 | internal class VehicleTelemetry 9 | { 10 | public DateTimeOffset Timestamp { get; set; } 11 | 12 | public string? Action { get; set; } 13 | 14 | public VehicleDoorKind? VehicleDoorKind { get; set; } 15 | public VehicleDoorStatus? VehicleDoorStatus { get; set; } 16 | 17 | [JsonExtensionData] 18 | public Dictionary? Extras { get; set; } 19 | } 20 | 21 | public enum VehicleDoorStatus 22 | { 23 | Unknown, 24 | Open, 25 | Closed, 26 | } 27 | 28 | public enum VehicleDoorKind 29 | { 30 | FrontLeft, 31 | FrontRight, 32 | RearLeft, 33 | ReadRight, 34 | Hood, 35 | Trunk, 36 | } 37 | -------------------------------------------------------------------------------- /samples/MultipleDifferentTransports/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/MultipleDifferentTransports/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | 10 | "ConnectionStrings:AzureStorage": "UseDevelopmentStorage=true;", 11 | "ConnectionStrings:EventHub": "Endpoint=sb://abcd.servicebus.windows.net/;SharedAccessKeyName=xyz;SharedAccessKey=AAAAAAAAAAAAAAAAAAAAAA==", 12 | "ConnectionStrings:ServiceBus": "Endpoint=sb://abcd.servicebus.windows.net/;SharedAccessKeyName=xyz;SharedAccessKey=AAAAAAAAAAAAAAAAAAAAAA==", 13 | "IotHubEventHubName": "iothub-ehub-test-dev-0000000-0aaaa000aa" 14 | } 15 | -------------------------------------------------------------------------------- /samples/MultipleSimilarTransports/MultipleSimilarTransports.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/MultipleSimilarTransports/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MultipleSimilarTransports": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/MultipleSimilarTransports/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/MultipleSimilarTransports/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/SimpleConsumer/EventCounter.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleConsumer; 2 | 3 | public class EventCounter 4 | { 5 | private int count = 0; 6 | 7 | public int Count => count; 8 | 9 | public void Consumed() 10 | { 11 | Interlocked.Increment(ref count); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/SimpleConsumer/Program.cs: -------------------------------------------------------------------------------- 1 | using SimpleConsumer; 2 | 3 | var host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices((hostContext, services) => 5 | { 6 | services.AddSingleton(); 7 | services.AddEventBus(builder => 8 | { 9 | // Transport agnostic configuration 10 | builder.Configure(o => 11 | { 12 | o.Naming.Scope = "dev"; // queues will be prefixed by 'dev' 13 | o.Naming.UseFullTypeNames = false; 14 | }); 15 | builder.AddConsumer(); 16 | 17 | // Transport specific configuration 18 | builder.AddAzureQueueStorageTransport(o => o.Credentials = "UseDevelopmentStorage=true;"); 19 | }); 20 | }) 21 | .Build(); 22 | 23 | await host.RunAsync(); 24 | -------------------------------------------------------------------------------- /samples/SimpleConsumer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "SimpleConsumer": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/SimpleConsumer/SampleEvent.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleConsumer; 2 | 3 | public class SampleEvent 4 | { 5 | public string? Registration { get; set; } 6 | public string? Make { get; set; } 7 | public string? Model { get; set; } 8 | public int Year { get; set; } 9 | public string? VIN { get; set; } 10 | } 11 | -------------------------------------------------------------------------------- /samples/SimpleConsumer/SampleEventConsumer.cs: -------------------------------------------------------------------------------- 1 | namespace SimpleConsumer; 2 | 3 | public class SampleEventConsumer(EventCounter counter, ILogger logger) : IEventConsumer 4 | { 5 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 6 | { 7 | logger.LogInformation("Received event Id: {Id}", context.Id); 8 | logger.LogInformation("Event body: {EventBody}", System.Text.Json.JsonSerializer.Serialize(context.Event)); 9 | counter.Consumed(); 10 | return Task.CompletedTask; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/SimpleConsumer/SimpleConsumer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/SimpleConsumer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/SimpleConsumer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /samples/SimplePublisher/DoorOpened.cs: -------------------------------------------------------------------------------- 1 | namespace SimplePublisher; 2 | 3 | public class DoorOpened 4 | { 5 | /// 6 | /// The vehicle who's door was opened. 7 | /// 8 | public string? VehicleId { get; set; } 9 | 10 | /// 11 | /// The kind of door that was opened. 12 | /// 13 | public OpenDoorKind Kind { get; set; } 14 | 15 | /// 16 | /// When the door was opened. 17 | /// 18 | public DateTimeOffset Opened { get; set; } 19 | } 20 | 21 | public enum OpenDoorKind 22 | { 23 | FrontLeft, 24 | FrontRight, 25 | RearLeft, 26 | ReadRight, 27 | Hood, 28 | Trunk, 29 | } 30 | -------------------------------------------------------------------------------- /samples/SimplePublisher/Program.cs: -------------------------------------------------------------------------------- 1 | using SimplePublisher; 2 | 3 | var host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices((hostContext, services) => 5 | { 6 | services.AddEventBus(builder => 7 | { 8 | // Transport agnostic configuration 9 | builder.Configure(o => 10 | { 11 | o.Naming.Scope = "dev"; // queues will be prefixed by 'dev' 12 | o.Naming.UseFullTypeNames = false; 13 | }); 14 | 15 | // Transport specific configuration 16 | builder.AddAzureQueueStorageTransport(o => o.Credentials = "UseDevelopmentStorage=true;"); 17 | }); 18 | 19 | services.AddHostedService(); 20 | }) 21 | .Build(); 22 | 23 | await host.RunAsync(); 24 | -------------------------------------------------------------------------------- /samples/SimplePublisher/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "SimplePublisher": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNET_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /samples/SimplePublisher/PublisherService.cs: -------------------------------------------------------------------------------- 1 | namespace SimplePublisher; 2 | 3 | public class PublisherService(IEventPublisher publisher) : BackgroundService 4 | { 5 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 6 | { 7 | var delay = TimeSpan.FromSeconds(30); 8 | var times = 5; 9 | 10 | var rnd = new Random(DateTimeOffset.UtcNow.Millisecond); 11 | 12 | for (var i = 0; i < times; i++) 13 | { 14 | var evt = new DoorOpened 15 | { 16 | Kind = (OpenDoorKind)rnd.Next(0, 5), 17 | Opened = DateTimeOffset.UtcNow.AddMinutes(rnd.Next(-10, 10)), 18 | VehicleId = "123456", 19 | }; 20 | 21 | await publisher.PublishAsync(evt, cancellationToken: stoppingToken); 22 | 23 | await Task.Delay(delay, stoppingToken); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/SimplePublisher/SimplePublisher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /samples/SimplePublisher/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "Microsoft": "Information", 6 | "System": "Information" 7 | }, 8 | "Console": { 9 | "FormatterName": "simple", 10 | "FormatterOptions": { 11 | "SingleLine": true, 12 | "TimestampFormat": "HH:mm:ss " 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/SimplePublisher/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /shared/TrimmingHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Tingle.EventBus.Internal; 4 | 5 | internal static class TrimmingHelper 6 | { 7 | internal const DynamicallyAccessedMemberTypes Serializer = DynamicallyAccessedMemberTypes.PublicConstructors; 8 | internal const DynamicallyAccessedMemberTypes Transport = DynamicallyAccessedMemberTypes.PublicConstructors; 9 | internal const DynamicallyAccessedMemberTypes Configurator = DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicConstructors; 10 | internal const DynamicallyAccessedMemberTypes Event = DynamicallyAccessedMemberTypes.PublicConstructors; 11 | internal const DynamicallyAccessedMemberTypes Consumer = DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicConstructors; 12 | } 13 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | netstandard2.1;net8.0;net9.0 7 | true 8 | true 9 | Tingle Software 10 | Tingle Software 11 | true 12 | 13 | 14 | 15 | 16 | true 17 | 18 | 19 | true 20 | 21 | 22 | true 23 | snupkg 24 | 25 | logo.png 26 | MIT 27 | $(PackageTags);EventBus 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Serializers.NewtonsoftJson/MessageStrings.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Serializers; 2 | 3 | internal class MessageStrings 4 | { 5 | public const string UnreferencedCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. Migrate to System.Text.Json which has support for native AOT applications."; 6 | } 7 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Serializers.NewtonsoftJson/NewtonsoftJsonEventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Tingle.EventBus.Serializers; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection; 5 | 6 | /// 7 | /// Extension methods on for NewtonsoftJson serializer. 8 | /// 9 | [RequiresUnreferencedCode(MessageStrings.UnreferencedCodeMessage)] 10 | public static class NewtonsoftJsonEventBusBuilderExtensions 11 | { 12 | /// 13 | /// Use the included NewtonsoftJson serializer as the default event serializer. 14 | /// 15 | /// 16 | /// 17 | /// 18 | public static EventBusBuilder AddNewtonsoftJsonSerializer(this EventBusBuilder builder, 19 | Action? configure = null) 20 | { 21 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 22 | 23 | // Configure the options for the serializer 24 | var services = builder.Services; 25 | if (configure != null) services.Configure(configure); 26 | services.ConfigureOptions(); 27 | 28 | // Add the serializer 29 | return builder.AddSerializer(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Serializers.NewtonsoftJson/NewtonsoftJsonSerializerConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Tingle.EventBus.Serializers; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection; 5 | 6 | /// 7 | /// A class to finish the configuration of instances of . 8 | /// 9 | internal class NewtonsoftJsonSerializerConfigureOptions : IValidateOptions 10 | { 11 | public ValidateOptionsResult Validate(string? name, NewtonsoftJsonSerializerOptions options) 12 | { 13 | // Ensure the settings are provided 14 | if (options.SerializerSettings == null) 15 | { 16 | return ValidateOptionsResult.Fail($"'{nameof(options.SerializerSettings)}' must be provided"); 17 | } 18 | 19 | return ValidateOptionsResult.Success; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Serializers.NewtonsoftJson/NewtonsoftJsonSerializerOptions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Serialization; 3 | 4 | namespace Tingle.EventBus.Serializers; 5 | 6 | /// 7 | /// Options for configuring NewtonsoftJson based serializer. 8 | /// 9 | public class NewtonsoftJsonSerializerOptions 10 | { 11 | /// 12 | /// The options to use for serialization. 13 | /// 14 | public JsonSerializerSettings SerializerSettings { get; set; } = new JsonSerializerSettings 15 | { 16 | Formatting = Formatting.None, // less data used 17 | NullValueHandling = NullValueHandling.Ignore, 18 | 19 | ContractResolver = new CamelCasePropertyNamesContractResolver(), 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Serializers.NewtonsoftJson/Tingle.EventBus.Serializers.NewtonsoftJson.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A serializer implementation for Tingle.EventBus using Newtonsoft.Json 5 | $(PackageTags);Serializers;Newtonsoft 6 | Tingle.EventBus.Serializers 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Abstractions/AmazonSqsTransportException.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports.Amazon; 2 | 3 | /// 4 | [Serializable] 5 | public class AmazonSqsTransportException : Exception 6 | { 7 | /// 8 | public AmazonSqsTransportException() { } 9 | 10 | /// 11 | public AmazonSqsTransportException(string message) : base(message) { } 12 | 13 | /// 14 | public AmazonSqsTransportException(string message, Exception innerException) : base(message, innerException) { } 15 | } 16 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Abstractions/AmazonTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using Amazon; 2 | using Amazon.Runtime; 3 | using Tingle.EventBus.Transports; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection; 6 | 7 | /// 8 | /// Abstraction for options of AWS-based transports 9 | /// 10 | public abstract class AmazonTransportOptions : EventBusTransportOptions 11 | { 12 | /// 13 | /// The system name of the region to connect to. 14 | /// For example eu-west-1. 15 | /// When not configured, must be provided. 16 | /// 17 | public string? RegionName { get; set; } 18 | 19 | /// 20 | /// The region to connect to. 21 | /// When not set, is used to set it. 22 | /// 23 | public RegionEndpoint? Region { get; set; } 24 | 25 | /// 26 | /// The name of the key granted the requisite access control rights. 27 | /// 28 | public string? AccessKey { get; set; } 29 | 30 | /// 31 | /// The secret associated with the . 32 | /// 33 | public string? SecretKey { get; set; } 34 | 35 | /// 36 | /// Credentials for accessing AWS services. 37 | /// This can be used in place of and . 38 | /// 39 | public AWSCredentials? Credentials { get; set; } 40 | } 41 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Abstractions/AmazonWebServiceResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Tingle.EventBus.Transports.Amazon; 3 | 4 | namespace Amazon.Runtime; 5 | 6 | /// 7 | /// Extension methods for . 8 | /// 9 | public static class AmazonWebServiceResponseExtensions 10 | { 11 | /// 12 | /// Checks if the request was successful. 13 | /// 14 | /// The to check. 15 | /// 16 | public static bool Successful(this AmazonWebServiceResponse response) 17 | { 18 | var statusCode = response.HttpStatusCode; 19 | return statusCode >= HttpStatusCode.OK && statusCode < HttpStatusCode.MultipleChoices; 20 | } 21 | 22 | /// 23 | /// Ensure the request was successful. 24 | /// 25 | /// The to check. 26 | /// If the request was not successful. 27 | public static void EnsureSuccess(this AmazonWebServiceResponse response) 28 | { 29 | if (response.Successful()) return; 30 | const string documentationUri = "https://aws.amazon.com/blogs/developer/logging-with-the-aws-sdk-for-net/"; 31 | 32 | var statusCode = response.HttpStatusCode; 33 | var requestId = response.ResponseMetadata.RequestId; 34 | 35 | throw new AmazonSqsTransportException($"Received unsuccessful response ({statusCode}) from AWS endpoint.\n" + 36 | $"See AWS SDK logs ({requestId}) for more details: {documentationUri}"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Abstractions/Tingle.EventBus.Transports.Amazon.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shared EventBus transport components for Amazon. 5 | $(PackageTags);Amazon;AWS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Tingle.EventBus.Transports.Amazon.Kinesis; 4 | 5 | /// Defaults for . 6 | public static class AmazonKinesisDefaults 7 | { 8 | /// Default name for . 9 | public const string Name = "amazon-kinesis"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisEventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.Amazon.Kinesis; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Extension methods on for Amazon Kinesis. 7 | /// 8 | public static class AmazonKinesisEventBusBuilderExtensions 9 | { 10 | /// Add Amazon Kinesis transport. 11 | /// The to add to. 12 | /// An to configure the transport options. 13 | /// 14 | public static EventBusBuilder AddAmazonKinesisTransport(this EventBusBuilder builder, Action? configure = null) 15 | => builder.AddAmazonKinesisTransport(AmazonKinesisDefaults.Name, configure); 16 | 17 | /// Add Amazon Kinesis transport. 18 | /// The to add to. 19 | /// The name of the transport 20 | /// An to configure the transport options. 21 | /// 22 | public static EventBusBuilder AddAmazonKinesisTransport(this EventBusBuilder builder, string name, Action? configure = null) 23 | => builder.AddTransport(name, configure); 24 | } 25 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Kinesis/AmazonKinesisTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using Amazon.Kinesis; 2 | using Tingle.EventBus; 3 | using Tingle.EventBus.Configuration; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection; 6 | 7 | /// 8 | /// Options for configuring Amazon Kinesis based event bus. 9 | /// 10 | public class AmazonKinesisTransportOptions : AmazonTransportOptions 11 | { 12 | /// 13 | public override EntityKind DefaultEntityKind { get; set; } = EntityKind.Broadcast; 14 | 15 | /// 16 | /// Configuration for Kinesis 17 | /// 18 | public AmazonKinesisConfig? KinesisConfig { get; set; } 19 | 20 | /// 21 | /// A function for selecting the partition key from an event context. 22 | /// This is called for event before publishing. 23 | /// Defaults function uses as the partition key. 24 | /// The value returned is hashed to determine the shard the event is sent to. 25 | /// 26 | public Func PartitionKeyResolver { get; set; } = (ctx) => ctx.Id; 27 | } 28 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Kinesis/BinaryDataExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace System; 2 | 3 | /// 4 | /// Extension methods for 5 | /// 6 | internal static class BinaryDataExtensions 7 | { 8 | /// 9 | /// Converts the to a . 10 | /// 11 | /// The to be converted. 12 | /// A representing the data. 13 | public static MemoryStream ToMemoryStream(this BinaryData data) => new(data.ToArray()); 14 | } 15 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Kinesis/ILoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Tingle.EventBus; 3 | using Tingle.EventBus.Internal; 4 | 5 | namespace Microsoft.Extensions.Logging; 6 | 7 | /// 8 | /// Extensions on for the EventBus 9 | /// 10 | internal static partial class ILoggerExtensions 11 | { 12 | [LoggerMessage(100, LogLevel.Information, "Sending {EventBusId} to '{StreamName}'. Scheduled: {Scheduled}.")] 13 | public static partial void SendingToStream(this ILogger logger, string? eventBusId, string streamName, DateTimeOffset? scheduled); 14 | 15 | [LoggerMessage(101, LogLevel.Information, "Sending {EventsCount} messages to '{StreamName}'. Scheduled: {Scheduled}. Events:\r\n- {EventBusIds}")] 16 | private static partial void SendingEventsToStream(this ILogger logger, int eventsCount, string streamName, DateTimeOffset? scheduled, string eventBusIds); 17 | 18 | public static void SendingEventsToStream(this ILogger logger, IList eventBusIds, string streamName, DateTimeOffset? scheduled) 19 | { 20 | if (!logger.IsEnabled(LogLevel.Information)) return; 21 | logger.SendingEventsToStream(eventsCount: eventBusIds.Count, 22 | streamName: streamName, 23 | scheduled: scheduled, 24 | eventBusIds: string.Join("\r\n- ", eventBusIds)); 25 | } 26 | 27 | public static void SendingEventsToStream<[DynamicallyAccessedMembers(TrimmingHelper.Event)] T>(this ILogger logger, IList> events, string entityPath, DateTimeOffset? scheduled = null) 28 | where T : class 29 | { 30 | if (!logger.IsEnabled(LogLevel.Information)) return; 31 | logger.SendingEventsToStream(events.Select(e => e.Id).ToList(), entityPath, scheduled); 32 | } 33 | 34 | [LoggerMessage(102, LogLevel.Warning, "Amazon Kinesis does not support delay or scheduled publish.")] 35 | public static partial void SchedulingNotSupported(this ILogger logger); 36 | } 37 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Kinesis/Tingle.EventBus.Transports.Amazon.Kinesis.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Amazon Kinesis transport implementation for Tingle.EventBus. 5 | $(PackageTags);Amazon;AWS;Kinesis 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Tingle.EventBus.Transports.Amazon.Sqs; 4 | 5 | /// Defaults for . 6 | public static class AmazonSqsDefaults 7 | { 8 | /// Default name for . 9 | public const string Name = "amazon-sqs"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsEventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.Amazon.Sqs; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Extension methods on for Amazon SQS. 7 | /// 8 | public static class AmazonSqsEventBusBuilderExtensions 9 | { 10 | /// Add Amazon SQS transport. 11 | /// The to add to. 12 | /// An to configure the transport options. 13 | /// 14 | public static EventBusBuilder AddAmazonSqsTransport(this EventBusBuilder builder, Action? configure = null) 15 | => builder.AddAmazonSqsTransport(AmazonSqsDefaults.Name, configure); 16 | 17 | /// Add Amazon SQS transport. 18 | /// The to add to. 19 | /// The name of the transport 20 | /// An to configure the transport options. 21 | /// 22 | public static EventBusBuilder AddAmazonSqsTransport(this EventBusBuilder builder, string name, Action? configure = null) 23 | => builder.AddTransport(name, configure); 24 | } 25 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Sqs/AmazonSqsTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using Amazon.SimpleNotificationService; 2 | using Amazon.SimpleNotificationService.Model; 3 | using Amazon.SQS; 4 | using Amazon.SQS.Model; 5 | using Tingle.EventBus.Configuration; 6 | 7 | namespace Microsoft.Extensions.DependencyInjection; 8 | 9 | /// 10 | /// Options for configuring Amazon SQS based event bus. 11 | /// 12 | public class AmazonSqsTransportOptions : AmazonTransportOptions 13 | { 14 | /// 15 | public override EntityKind DefaultEntityKind { get; set; } = EntityKind.Queue; 16 | 17 | /// 18 | /// Configuration for SQS 19 | /// 20 | public AmazonSQSConfig? SqsConfig { get; set; } 21 | 22 | /// 23 | /// Configuration for SNS 24 | /// 25 | public AmazonSimpleNotificationServiceConfig? SnsConfig { get; set; } 26 | 27 | /// 28 | /// A setup function for setting up settings for a topic. 29 | /// This is only called before creation. 30 | /// 31 | public Action? SetupCreateTopicRequest { get; set; } 32 | 33 | /// 34 | /// A setup function for setting up settings for a queue. 35 | /// This is only called before creation. 36 | /// 37 | public Action? SetupCreateQueueRequest { get; set; } 38 | } 39 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Amazon.Sqs/Tingle.EventBus.Transports.Amazon.Sqs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Amazon SQS transport implementation for Tingle.EventBus. 5 | $(PackageTags);Amazon;AWS;SQS;SNS 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.Abstractions/AzureTransportCredentials.cs: -------------------------------------------------------------------------------- 1 | using Azure.Core; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Abstractions for authenticating Azure-based transports without a connection strings. 7 | /// 8 | public abstract class AzureTransportCredentials 9 | { 10 | /// 11 | /// Credential used to authenticate requests. 12 | /// Depending on the transport resources, the relevant permissions must be available in the provided credential. 13 | /// For example to access Azure Blob Storage, you need Blob Storage Container Contributor permission. 14 | /// For more details on assigning tokens, 15 | /// see the official Azure SDK identity docs. 16 | /// 17 | public TokenCredential? TokenCredential { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.Abstractions/AzureTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using AnyOfTypes; 2 | using Tingle.EventBus.Transports; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection; 5 | 6 | /// 7 | /// Abstraction for options of Azure-based transports 8 | /// 9 | public abstract class AzureTransportOptions : EventBusTransportOptions where TCredential : AzureTransportCredentials 10 | { 11 | /// 12 | /// Authentication credentials. 13 | /// This can either be a connection string or . 14 | /// 15 | public AnyOf Credentials { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.Abstractions/Tingle.EventBus.Transports.Azure.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shared EventBus transport components for Azure. 5 | $(PackageTags);Azure 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.EventHubs/AzureBlobStorageCredentials.cs: -------------------------------------------------------------------------------- 1 | using Azure.Core; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Credentials for Azure Blob Storage backed by a . 7 | /// 8 | public class AzureBlobStorageCredentials : AzureTransportCredentials 9 | { 10 | /// 11 | /// A referencing the blob service. 12 | /// This is likely to be similar to "https://{account_name}.blob.core.windows.net". 13 | /// 14 | public Uri? ServiceUrl { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Tingle.EventBus.Transports.Azure.EventHubs; 4 | 5 | /// Defaults for . 6 | public static class AzureEventHubsDefaults 7 | { 8 | /// Default name for . 9 | public const string Name = "azure-event-hubs"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsEventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.Azure.EventHubs; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Extension methods on for Azure EventHubs 7 | /// 8 | public static class AzureEventHubsEventBusBuilderExtensions 9 | { 10 | /// Add Azure EventHubs transport. 11 | /// The to add to. 12 | /// An to configure the transport options. 13 | /// 14 | public static EventBusBuilder AddAzureEventHubsTransport(this EventBusBuilder builder, Action? configure = null) 15 | => builder.AddAzureEventHubsTransport(AzureEventHubsDefaults.Name, configure); 16 | 17 | /// Add Azure EventHubs transport. 18 | /// The to add to. 19 | /// The name of the transport 20 | /// An to configure the transport options. 21 | /// 22 | public static EventBusBuilder AddAzureEventHubsTransport(this EventBusBuilder builder, string name, Action? configure = null) 23 | => builder.AddTransport(name, configure); 24 | } 25 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.EventHubs/AzureEventHubsTransportCredentials.cs: -------------------------------------------------------------------------------- 1 | using Azure.Core; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Credentials for Azure EventHubs transport backed by a . 7 | /// 8 | public class AzureEventHubsTransportCredentials : AzureTransportCredentials 9 | { 10 | /// 11 | /// The fully qualified Event Hubs namespace to connect to. 12 | /// This is likely to be similar to {your_namespace}.servicebus.windows.net. 13 | /// 14 | public string? FullyQualifiedNamespace { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.EventHubs/IotHub/IotHubConnectionAuthMethod.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Tingle.EventBus.Transports.Azure.EventHubs.IotHub; 4 | 5 | /// 6 | /// Model representing the data found in iothub-connection-auth-method property. 7 | /// 8 | /// 9 | /// { 10 | /// "scope": "hub", 11 | /// "type": "sas", 12 | /// "issuer": "iothub", 13 | /// "acceptingIpFilterRule": null 14 | /// } 15 | /// 16 | public record IotHubConnectionAuthMethod 17 | { 18 | /// hub 19 | public virtual string? Scope { get; set; } 20 | 21 | /// sas 22 | public virtual string? Type { get; set; } 23 | 24 | /// iothub 25 | public virtual string? Issuer { get; set; } 26 | 27 | /// 28 | [JsonExtensionData] 29 | public Dictionary? Extras { get; set; } 30 | } 31 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.EventHubs/IotHub/IotHubJsonSerializerContext.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace Tingle.EventBus.Transports.Azure.EventHubs.IotHub; 4 | 5 | [JsonSerializable(typeof(IotHubConnectionAuthMethod))] 6 | [JsonSerializable(typeof(IotHubOperationalEventPayload))] 7 | internal partial class IotHubJsonSerializerContext : JsonSerializerContext { } 8 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.EventHubs/MessageStrings.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports.Azure.EventHubs; 2 | 3 | internal class MessageStrings 4 | { 5 | public const string SerializationUnreferencedCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. Use the serializers and types that takes a JsonSerializerContext, or make sure all of the required types are preserved."; 6 | public const string SerializationRequiresDynamicCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications."; 7 | } 8 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.EventHubs/Tingle.EventBus.Transports.Azure.EventHubs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Azure Event Hubs transport implementation for Tingle.EventBus. 5 | $(PackageTags);Azure;EventHubs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Tingle.EventBus.Transports.Azure.QueueStorage; 4 | 5 | /// Defaults for . 6 | public static class AzureQueueStorageDefaults 7 | { 8 | /// Default name for . 9 | public const string Name = "azure-queue-storage"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageEventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.Azure.QueueStorage; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Extension methods on for Azure Queue Storage. 7 | /// 8 | public static class AzureQueueStorageEventBusBuilderExtensions 9 | { 10 | /// Add Azure Queue Storage transport. 11 | /// The to add to. 12 | /// An to configure the transport options. 13 | /// 14 | public static EventBusBuilder AddAzureQueueStorageTransport(this EventBusBuilder builder, Action? configure = null) 15 | => builder.AddAzureQueueStorageTransport(AzureQueueStorageDefaults.Name, configure); 16 | 17 | /// Add Azure Queue Storage transport. 18 | /// The to add to. 19 | /// The name of the transport 20 | /// An to configure the transport options. 21 | /// 22 | public static EventBusBuilder AddAzureQueueStorageTransport(this EventBusBuilder builder, string name, Action? configure = null) 23 | => builder.AddTransport(name, configure); 24 | } 25 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageTransportCredentials.cs: -------------------------------------------------------------------------------- 1 | using Azure.Core; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Credentials for Azure Queue Storage transport backed by a . 7 | /// 8 | public class AzureQueueStorageTransportCredentials : AzureTransportCredentials 9 | { 10 | /// 11 | /// A referencing the queue service. 12 | /// This is likely to be similar to "https://{account_name}.queue.core.windows.net". 13 | /// 14 | public Uri? ServiceUrl { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.QueueStorage/AzureQueueStorageTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Options for configuring Azure Queue Storage based event bus. 7 | /// 8 | public class AzureQueueStorageTransportOptions : AzureTransportOptions 9 | { 10 | /// 11 | public override EntityKind DefaultEntityKind { get; set; } = EntityKind.Queue; 12 | } 13 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.QueueStorage/Tingle.EventBus.Transports.Azure.QueueStorage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Azure Queue Storage transport implementation for Tingle.EventBus. 5 | $(PackageTags);Azure;Queues;QueueStorage 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Tingle.EventBus.Transports.Azure.ServiceBus; 4 | 5 | /// Defaults for . 6 | public static class AzureServiceBusDefaults 7 | { 8 | /// Default name for . 9 | public const string Name = "azure-service-bus"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusEventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.Azure.ServiceBus; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Extension methods on for Azure Service Bus. 7 | /// 8 | public static class AzureServiceBusEventBusBuilderExtensions 9 | { 10 | /// Add Azure Service Bus transport. 11 | /// The to add to. 12 | /// An to configure the transport options. 13 | /// 14 | public static EventBusBuilder AddAzureServiceBusTransport(this EventBusBuilder builder, Action? configure = null) 15 | => builder.AddAzureServiceBusTransport(AzureServiceBusDefaults.Name, configure); 16 | 17 | /// Add Azure Service Bus transport. 18 | /// The to add to. 19 | /// The name of the transport 20 | /// An to configure the transport options. 21 | /// 22 | public static EventBusBuilder AddAzureServiceBusTransport(this EventBusBuilder builder, string name, Action? configure = null) 23 | => builder.AddTransport(name, configure); 24 | } 25 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.ServiceBus/AzureServiceBusTransportCredentials.cs: -------------------------------------------------------------------------------- 1 | using Azure.Core; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Credentials for Azure Service Bus transport backed by a . 7 | /// 8 | public class AzureServiceBusTransportCredentials : AzureTransportCredentials 9 | { 10 | /// 11 | /// The fully qualified Service Bus namespace to connect to. 12 | /// This is likely to be similar to {your_namespace}.servicebus.windows.net. 13 | /// 14 | public string? FullyQualifiedNamespace { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Azure.ServiceBus/Tingle.EventBus.Transports.Azure.ServiceBus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Azure Service Bus transport implementation for Tingle.EventBus. 5 | $(PackageTags);Azure;ServiceBus 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/Client/BroadcastChannel.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Channels; 2 | 3 | namespace Tingle.EventBus.Transports.InMemory.Client; 4 | 5 | internal class BroadcastChannel : Channel 6 | { 7 | public BroadcastChannel() 8 | { 9 | Writer = new BroadcastChannelWriter(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/Client/BroadcastChannelWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Channels; 2 | 3 | namespace Tingle.EventBus.Transports.InMemory.Client; 4 | 5 | internal class BroadcastChannelWriter(ICollection>? children = null) : ChannelWriter 6 | { 7 | public ICollection> Children { get; } = children ?? new List>(); 8 | 9 | /// 10 | public override bool TryWrite(T item) => Children.All(w => w.TryWrite(item)); 11 | 12 | /// 13 | public override ValueTask WaitToWriteAsync(CancellationToken cancellationToken = default) 14 | { 15 | throw new NotSupportedException("Waiting is not supported."); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/Client/InMemoryErrorSource.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports.InMemory.Client; 2 | 3 | /// 4 | /// The source of the error when is raised. 5 | /// 6 | public enum InMemoryErrorSource 7 | { 8 | /// Message receive operation. 9 | Receive, 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/Client/InMemoryProcessorOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports.InMemory; 2 | 3 | internal class InMemoryProcessorOptions 4 | { 5 | public InMemoryProcessorSubQueue SubQueue { get; set; } = InMemoryProcessorSubQueue.None; 6 | } 7 | 8 | internal enum InMemoryProcessorSubQueue 9 | { 10 | None, 11 | DeadLetter, 12 | } 13 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/Client/ProcessErrorEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports.InMemory.Client; 2 | 3 | /// 4 | /// Contains information about the entity whose processing threw an exception, as 5 | /// well as the exception that has been thrown. 6 | /// 7 | /// The exception that triggered the call to the error event handler. 8 | /// The source associated with the error. 9 | /// The entity path used when this exception occurred. 10 | /// 11 | /// The processor's instance which will be cancelled 12 | /// in the event that is called. 13 | /// 14 | internal class ProcessErrorEventArgs(Exception exception, InMemoryErrorSource errorSource, string entityPath, CancellationToken cancellationToken) : EventArgs 15 | { 16 | /// 17 | /// Gets the exception that triggered the call to the error event handler. 18 | /// 19 | public Exception Exception { get; } = exception; 20 | 21 | /// 22 | /// Gets the source associated with the error. 23 | /// 24 | public InMemoryErrorSource ErrorSource { get; } = errorSource; 25 | 26 | /// 27 | /// Gets the entity path associated with the error event. 28 | /// 29 | public string EntityPath { get; } = entityPath; 30 | 31 | /// 32 | /// Gets the processor's instance which will be 33 | /// cancelled when 34 | /// is called. 35 | /// 36 | public CancellationToken CancellationToken { get; } = cancellationToken; 37 | } 38 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/Client/ProcessMessageEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports.InMemory.Client; 2 | 3 | /// 4 | /// The contain event args that are specific 5 | /// to the that is being processed. 6 | /// 7 | /// The message to be processed. 8 | /// 9 | /// The processor's which will be cancelled 10 | /// in the event that is called. 11 | /// 12 | internal class ProcessMessageEventArgs(InMemoryReceivedMessage message, CancellationToken cancellationToken) : EventArgs 13 | { 14 | /// The received message to be processed. 15 | public InMemoryReceivedMessage Message { get; } = message; 16 | 17 | /// 18 | /// The processor's instance which will be cancelled 19 | /// when is called. 20 | /// 21 | public CancellationToken CancellationToken { get; } = cancellationToken; 22 | } 23 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/Client/SequenceNumberGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports.InMemory.Client; 2 | 3 | /// 4 | /// A sequence number generator for InMemory transport 5 | /// 6 | public class SequenceNumberGenerator 7 | { 8 | private long currentValue; 9 | 10 | /// 11 | public SequenceNumberGenerator() 12 | { 13 | var random = new Random(DateTimeOffset.UtcNow.Millisecond); 14 | currentValue = random.Next(); 15 | } 16 | 17 | /// Generate the next sequence number. 18 | public long Generate() => Interlocked.Increment(ref currentValue); 19 | } 20 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/InMemoryDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Tingle.EventBus.Transports.InMemory; 4 | 5 | /// Defaults for . 6 | public static class InMemoryDefaults 7 | { 8 | /// Default name for . 9 | public const string Name = "inmemory"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/InMemoryTestHarnessConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// A class to finish the configuration of instances of . 7 | /// 8 | internal class InMemoryTestHarnessConfigureOptions : IPostConfigureOptions 9 | { 10 | public void PostConfigure(string? name, InMemoryTestHarnessOptions options) 11 | { 12 | // intentionally left bank for future use 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/InMemoryTestHarnessOptions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.InMemory; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Configuration options for 7 | /// 8 | public class InMemoryTestHarnessOptions 9 | { 10 | /// 11 | /// The duration of time to delay. 12 | /// Defaults to 50ms (0.05 sec). 13 | /// 14 | public TimeSpan DefaultDelay { get; set; } = TimeSpan.FromMilliseconds(50); 15 | } 16 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/InMemoryTransportConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Tingle.EventBus.Configuration; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection; 5 | 6 | /// 7 | /// A class to finish the configuration of instances of . 8 | /// 9 | /// An instance.\ 10 | /// A list of to use when configuring options. 11 | /// An for bus configuration.\ 12 | internal class InMemoryTransportConfigureOptions(IEventBusConfigurationProvider configurationProvider, 13 | IEnumerable configurators, 14 | IOptions busOptionsAccessor) 15 | : EventBusTransportConfigureOptions(configurationProvider, configurators, busOptionsAccessor) 16 | { 17 | /// 18 | public override void PostConfigure(string? name, InMemoryTransportOptions options) 19 | { 20 | base.PostConfigure(name, options); 21 | if (name is null) throw new ArgumentNullException(nameof(name)); 22 | 23 | var registrations = BusOptions.GetRegistrations(name); 24 | foreach (var reg in registrations) 25 | { 26 | // Set the values using defaults 27 | options.SetValuesUsingDefaults(reg, BusOptions); 28 | 29 | // Ensure the entity type is allowed 30 | options.EnsureAllowedEntityKind(reg, EntityKind.Broadcast, EntityKind.Queue); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/InMemoryTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Options for configuring the in-memory based event bus. 7 | /// 8 | public class InMemoryTransportOptions : EventBusTransportOptions 9 | { 10 | // intentionally left blank for future use 11 | } 12 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.InMemory/Tingle.EventBus.Transports.InMemory.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | InMemory transport implementation for Tingle.EventBus (to be used primarily for testing purposes). 5 | $(PackageTags);InMemory 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Kafka/ILoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using Confluent.Kafka; 2 | 3 | namespace Microsoft.Extensions.Logging; 4 | 5 | /// 6 | /// Extensions on for the EventBus 7 | /// 8 | internal static partial class ILoggerExtensions 9 | { 10 | 11 | [LoggerMessage(200, LogLevel.Warning, "Kafka does not support delay or scheduled publish.")] 12 | public static partial void SchedulingNotSupported(this ILogger logger); 13 | 14 | [LoggerMessage(201, LogLevel.Warning, "Kafka does not support expiring events.")] 15 | public static partial void ExpiryNotSupported(this ILogger logger); 16 | 17 | [LoggerMessage(202, LogLevel.Warning, "Kafka does not support batching. The events will be looped through one by one.")] 18 | public static partial void BatchingNotSupported(this ILogger logger); 19 | 20 | 21 | [LoggerMessage(300, LogLevel.Information, "Consumer received data at {Offset}")] 22 | public static partial void ConsumerReceivedData(this ILogger logger, TopicPartitionOffset offset); 23 | 24 | [LoggerMessage(301, LogLevel.Trace, "Reached end of topic {Topic}, Partition: {Partition}, Offset: {Offset}.")] 25 | public static partial void EndOfTopic(this ILogger logger, string topic, Partition partition, Offset offset); 26 | 27 | [LoggerMessage(302, LogLevel.Debug, "Processing '{MessageKey}' from '{Topic}', Partition: '{Partition}'. Offset: '{Offset}'")] 28 | public static partial void ProcessingMessage(this ILogger logger, string messageKey, string topic, int partition, long offset); 29 | 30 | [LoggerMessage(303, LogLevel.Information, "Received event: '{EventBusId}' from '{Topic}', Partition: '{Partition}'. Offset: '{Offset}'")] 31 | public static partial void ReceivedEvent(this ILogger logger, string? eventBusId, string topic, int partition, long offset); 32 | 33 | [LoggerMessage(304, LogLevel.Debug, "Checkpointing '{Topic}', Partition: '{Partition}'. Offset: '{Offset}'")] 34 | public static partial void Checkpointing(this ILogger logger, string topic, int partition, long offset); 35 | } 36 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Kafka/KafkaDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Tingle.EventBus.Transports.Kafka; 4 | 5 | /// Defaults for . 6 | public static class KafkaDefaults 7 | { 8 | /// Default name for . 9 | public const string Name = "kafka"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Kafka/KafkaEventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.Kafka; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Extension methods on for Kafka. 7 | /// 8 | public static class KafkaEventBusBuilderExtensions 9 | { 10 | /// Add Kafka transport. 11 | /// The to add to. 12 | /// An to configure the transport options. 13 | /// 14 | public static EventBusBuilder AddKafkaTransport(this EventBusBuilder builder, Action? configure = null) 15 | => builder.AddKafkaTransport(KafkaDefaults.Name, configure); 16 | 17 | /// Add Kafka transport. 18 | /// The to add to. 19 | /// The name of the transport 20 | /// An to configure the transport options. 21 | /// 22 | public static EventBusBuilder AddKafkaTransport(this EventBusBuilder builder, string name, Action? configure = null) 23 | => builder.AddTransport(name, configure); 24 | } 25 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Kafka/KafkaExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Text; 3 | 4 | namespace Confluent.Kafka; 5 | 6 | internal static class KafkaExtensions 7 | { 8 | public static Headers AddIfNotNull(this Headers headers, string key, string? value) 9 | { 10 | if (!string.IsNullOrWhiteSpace(value)) 11 | { 12 | headers.Add(key, Encoding.UTF8.GetBytes(value)); 13 | } 14 | return headers; 15 | } 16 | 17 | public static bool TryGetValue(this Headers headers, string key, [NotNullWhen(true)] out string? value) 18 | { 19 | if (headers.TryGetLastBytes(key, out var bytes)) 20 | { 21 | value = Encoding.UTF8.GetString(bytes); 22 | return true; 23 | } 24 | value = null; 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.Kafka/Tingle.EventBus.Transports.Kafka.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Kafka transport implementation for Tingle.EventBus. 5 | $(PackageTags);Kafka 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqDefaults.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Tingle.EventBus.Transports.RabbitMQ; 4 | 5 | /// Defaults for . 6 | public static class RabbitMqDefaults 7 | { 8 | /// Default name for . 9 | public const string Name = "rabbitmq"; 10 | } 11 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqEventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.RabbitMQ; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// Extension methods on for RabbitMQ. 7 | /// 8 | public static class RabbitMqEventBusBuilderExtensions 9 | { 10 | /// Add RabbitMQ transport. 11 | /// The to add to. 12 | /// An to configure the transport options. 13 | /// 14 | public static EventBusBuilder AddRabbitMqTransport(this EventBusBuilder builder, Action? configure = null) 15 | => builder.AddRabbitMqTransport(RabbitMqDefaults.Name, configure); 16 | 17 | /// Add RabbitMQ transport. 18 | /// The to add to. 19 | /// The name of the transport 20 | /// An to configure the transport options. 21 | /// 22 | public static EventBusBuilder AddRabbitMqTransport(this EventBusBuilder builder, string name, Action? configure = null) 23 | => builder.AddTransport(name, configure); 24 | } 25 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.RabbitMQ/RabbitMqTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using RabbitMQ.Client; 2 | using Tingle.EventBus.Configuration; 3 | using Tingle.EventBus.Transports; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection; 6 | 7 | /// 8 | /// Options for configuring RabbitMQ based event bus. 9 | /// 10 | public class RabbitMqTransportOptions : EventBusTransportOptions 11 | { 12 | /// 13 | public override EntityKind DefaultEntityKind { get; set; } = EntityKind.Broadcast; 14 | 15 | /// 16 | /// The number of retries to make. 17 | /// 18 | public int RetryCount { get; set; } 19 | 20 | /// 21 | /// The host name of the broker. 22 | /// Defaults to localhost 23 | /// 24 | public string Hostname { get; set; } = "localhost"; 25 | 26 | /// 27 | /// The username for authenticating on the broker. 28 | /// Defaults to quest. 29 | /// 30 | public string Username { get; set; } = "guest"; 31 | 32 | /// 33 | /// The password for authenticating on the broker. 34 | /// Defaults to guest. 35 | /// 36 | public string Password { get; set; } = "guest"; 37 | 38 | /// 39 | /// The factory for creating instances when needed. 40 | /// When not provided, a factory is created from the settings available in this class. 41 | /// 42 | public ConnectionFactory? ConnectionFactory { get; set; } 43 | } 44 | -------------------------------------------------------------------------------- /src/Tingle.EventBus.Transports.RabbitMQ/Tingle.EventBus.Transports.RabbitMQ.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RabbitMQ transport implementation for Tingle.EventBus. 5 | $(PackageTags);RabbitMQ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/Attributes/ConsumerNameAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// Specify the ConsumerName used for this consumer type, overriding the generated one. 5 | /// 6 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] 7 | public sealed class ConsumerNameAttribute : Attribute 8 | { 9 | /// 10 | /// 11 | /// 12 | /// The consumer name to use for the consumer type 13 | public ConsumerNameAttribute(string consumerName) 14 | { 15 | if (string.IsNullOrWhiteSpace(consumerName)) 16 | { 17 | throw new ArgumentException($"'{nameof(consumerName)}' cannot be null or whitespace", nameof(consumerName)); 18 | } 19 | 20 | ConsumerName = consumerName; 21 | } 22 | 23 | /// 24 | /// The name of the consumer 25 | /// 26 | public string ConsumerName { get; } 27 | } 28 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/Attributes/EntityKindAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// Specify the EntityKind used for this event contract/type, overriding the generated one. 5 | /// 6 | /// The event kind to use for the event. 7 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 8 | public sealed class EntityKindAttribute(EntityKind kind) : Attribute 9 | { 10 | /// 11 | /// The name of the event mapped 12 | /// 13 | public EntityKind Kind { get; } = kind; 14 | } 15 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/Attributes/EventNameAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// Specify the EventName used for this event contract/type, overriding the generated one. 5 | /// 6 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 7 | public sealed class EventNameAttribute : Attribute 8 | { 9 | /// 10 | /// 11 | /// 12 | /// The event name to use for the event. 13 | public EventNameAttribute(string eventName) 14 | { 15 | if (string.IsNullOrWhiteSpace(eventName)) 16 | { 17 | throw new ArgumentException($"'{nameof(eventName)}' cannot be null or whitespace", nameof(eventName)); 18 | } 19 | 20 | EventName = eventName; 21 | } 22 | 23 | /// 24 | /// The name of the event mapped 25 | /// 26 | public string EventName { get; } 27 | } 28 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/Attributes/EventSerializerAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Tingle.EventBus.Internal; 3 | 4 | namespace Tingle.EventBus.Configuration; 5 | 6 | /// 7 | /// Specify the serializer type used for an event contract/type, overriding the default one. 8 | /// 9 | /// 10 | /// The type of serializer to use for the event type. 11 | /// It must implement . 12 | /// 13 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 14 | public sealed class EventSerializerAttribute([DynamicallyAccessedMembers(TrimmingHelper.Serializer)] Type serializerType) : Attribute 15 | { 16 | /// 17 | /// The type of serializer to be used. 18 | /// 19 | [DynamicallyAccessedMembers(TrimmingHelper.Serializer)] 20 | public Type SerializerType { get; } = serializerType ?? throw new ArgumentNullException(nameof(serializerType)); 21 | } 22 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/Attributes/EventTransportNameAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// Specify the name of the transport used for this event contract/type, overriding the default one. 5 | /// 6 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] 7 | public sealed class EventTransportNameAttribute : Attribute 8 | { 9 | /// 10 | /// 11 | /// 12 | /// The name of the transport to use for the event contract/type 13 | public EventTransportNameAttribute(string name) 14 | { 15 | if (string.IsNullOrWhiteSpace(name)) 16 | { 17 | throw new ArgumentException($"'{nameof(name)}' cannot be null or whitespace", nameof(name)); 18 | } 19 | 20 | Name = name; 21 | } 22 | 23 | /// 24 | /// The name of the transport to use for the event contract/type 25 | /// 26 | public string Name { get; } 27 | } 28 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/ConsumerNameSource.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// The source used when generating names for consumers. 5 | /// 6 | public enum ConsumerNameSource 7 | { 8 | /// 9 | /// The type name of the consumer is used. 10 | /// 11 | TypeName, 12 | 13 | /// 14 | /// The prefix provided in the bus options. 15 | /// 16 | Prefix, 17 | 18 | /// 19 | /// The prefix provided in the bus options 20 | /// and the type name of the consumer are combined. 21 | /// 22 | PrefixAndTypeName, 23 | } 24 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/DefaultEventBusConfigurationProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Tingle.EventBus.Configuration; 4 | 5 | /// 6 | /// Default implementation of . 7 | /// 8 | internal class DefaultEventBusConfigurationProvider(IConfiguration configuration) : IEventBusConfigurationProvider 9 | { 10 | private const string EventBusKey = "EventBus"; 11 | 12 | /// 13 | public IConfiguration Configuration => configuration.GetSection(EventBusKey); 14 | } -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/DefaultEventBusConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System.Diagnostics.CodeAnalysis; 4 | using Tingle.EventBus.Transports; 5 | 6 | namespace Tingle.EventBus.Configuration; 7 | 8 | /// 9 | /// Default implementation of . 10 | /// 11 | /// The instance. 12 | [RequiresDynamicCode(MessageStrings.BindingDynamicCodeMessage)] 13 | [RequiresUnreferencedCode(MessageStrings.BindingUnreferencedCodeMessage)] 14 | internal class DefaultEventBusConfigurator(IEventBusConfigurationProvider configurationProvider) : IEventBusConfigurator 15 | { 16 | /// 17 | public void Configure(EventBusOptions options) 18 | { 19 | configurationProvider.Configuration.Bind(options); 20 | } 21 | 22 | /// 23 | public void Configure(IConfiguration configuration, TOptions options) where TOptions : EventBusTransportOptions 24 | { 25 | configuration.Bind(options); 26 | } 27 | 28 | /// 29 | public void Configure(EventRegistration registration, EventBusOptions options) 30 | { 31 | if (registration is null) throw new ArgumentNullException(nameof(registration)); 32 | if (options is null) throw new ArgumentNullException(nameof(options)); 33 | 34 | // bind from IConfiguration 35 | var configuration = configurationProvider.Configuration.GetSection($"Events:{registration.EventType.FullName}"); 36 | configuration.Bind(registration); 37 | foreach (var ecr in registration.Consumers) 38 | { 39 | configuration.GetSection($"Consumers:{ecr.ConsumerType.FullName}").Bind(ecr); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/EntityKind.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// The preferred entity type for events. 5 | /// 6 | public enum EntityKind 7 | { 8 | /// 9 | /// Represents an entity that broadcasts to multiple destinations. 10 | /// Depending on the transport, this may be referred to as a topic, exchange, stream or hub 11 | /// 12 | Broadcast, 13 | 14 | /// 15 | /// Represents an entity that only maps the event to only one destination (itself). 16 | /// Depending on the transport, it may have a different name. 17 | /// 18 | Queue, 19 | } 20 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/EventIdFormat.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// The preferred format to use for values generated for . 5 | /// 6 | public enum EventIdFormat 7 | { 8 | /// 9 | /// Value is generated via . 10 | /// For example 17a2a99b-9b40-4f93-9333-7036154d20a8 11 | /// 12 | Guid, 13 | 14 | /// 15 | /// Value is generated via but the dashes are removed. 16 | /// For example 17a2a99b9b404f9393337036154d20a8 17 | /// 18 | GuidNoDashes, 19 | 20 | /// 21 | /// Value is generated via but only the first 8 bytes are changed to . 22 | /// For example 5734097450149521819 23 | /// 24 | Long, 25 | 26 | /// 27 | /// Value is generated via but only the first 8 bytes are changed to . 28 | /// For example 4f939b4017a2a99b 29 | /// 30 | LongHex, 31 | 32 | /// 33 | /// Value is generated via and converted to two concatenated. 34 | /// For example 573409745014952181912114767751129609107 35 | /// 36 | DoubleLong, 37 | 38 | /// 39 | /// Value is generated via and converted to two concatenated. 40 | /// For example 4f939b4017a2a99ba8204d1536703393 41 | /// 42 | DoubleLongHex, 43 | 44 | /// 45 | /// Value is generated via and the bytes converted to base 64. 46 | /// For example m6miF0Cbk0+TM3A2FU0gqA== 47 | /// 48 | Random, 49 | } 50 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/IEventBusConfigurationProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace Tingle.EventBus.Configuration; 4 | 5 | /// 6 | /// Provides an interface for implementing a construct that provides 7 | /// access to EventBus-related configuration sections. 8 | /// 9 | public interface IEventBusConfigurationProvider 10 | { 11 | /// 12 | /// Gets the where EventBus options are stored. 13 | /// 14 | IConfiguration Configuration { get; } 15 | } 16 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/IEventBusConfigurator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Tingle.EventBus.Transports; 4 | 5 | namespace Tingle.EventBus.Configuration; 6 | 7 | /// 8 | /// Abstraction for configuring instances of , , and . 9 | /// 10 | public interface IEventBusConfigurator 11 | { 12 | /// 13 | /// Configure an instance of . 14 | /// This is called once during startup. 15 | /// 16 | /// The instance. 17 | void Configure(EventBusOptions options); 18 | 19 | /// 20 | /// Configure options for a specific transport. 21 | /// 22 | /// The type of options. 23 | /// 24 | /// The instance to be configured. 25 | void Configure(IConfiguration configuration, TOptions options) where TOptions : EventBusTransportOptions; 26 | 27 | /// 28 | /// Configure an instance of . 29 | /// This is called once for each event either during startup 30 | /// (when there is a consumer) or on first publish. 31 | /// 32 | /// The to be configured. 33 | /// The instance. 34 | void Configure(EventRegistration registration, EventBusOptions options); 35 | } 36 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/NamingConvention.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// The naming convention used when generating names from types. 5 | /// 6 | public enum NamingConvention 7 | { 8 | /// 9 | /// The type name is unchanged. 10 | /// 11 | Unchanged, 12 | 13 | /// 14 | /// The type name is converted to Kebab case 15 | /// 16 | KebabCase, 17 | 18 | /// 19 | /// The type name is converted to Snake case. 20 | /// 21 | SnakeCase, 22 | 23 | /// 24 | /// The type name is transformed into a lower case string with a period between words. 25 | /// 26 | DotCase, 27 | } 28 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Configuration/UnhandledConsumerErrorBehaviour.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Configuration; 2 | 3 | /// 4 | /// The behaviour to follow when an unhandled error in a consumer's 5 | /// 6 | /// invocation results in an exception that is not handled. 7 | /// 8 | public enum UnhandledConsumerErrorBehaviour 9 | { 10 | /// 11 | /// Move the event to dead-letter entity. 12 | /// Handling of dead-letter is transport specific. 13 | /// 14 | Deadletter, 15 | 16 | /// 17 | /// Discard the event. 18 | /// Depending on the transport, the event can be ignored, abandoned or skipped. 19 | /// 20 | Discard, 21 | } 22 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/DependencyInjection/EventBusSerializationOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Tingle.EventBus.Serialization; 3 | 4 | namespace Microsoft.Extensions.DependencyInjection; 5 | 6 | /// 7 | /// Specified options for serialization. 8 | /// 9 | public class EventBusSerializationOptions 10 | { 11 | /// 12 | /// The options to use for serialization. 13 | /// 14 | public JsonSerializerOptions SerializerOptions { get; set; } = new JsonSerializerOptions 15 | { 16 | NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals 17 | | System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString, 18 | WriteIndented = false, // less data used 19 | DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault 20 | | System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, 21 | 22 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, 23 | PropertyNameCaseInsensitive = true, 24 | AllowTrailingCommas = true, 25 | ReadCommentHandling = JsonCommentHandling.Skip, 26 | }; 27 | 28 | /// 29 | /// The information about the host where the EventBus is running. 30 | /// 31 | public HostInfo? HostInfo { get; set; } 32 | } 33 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/DependencyInjection/EventBusTransportRegistrationBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Tingle.EventBus.Internal; 3 | using Tingle.EventBus.Transports; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection; 6 | 7 | /// Used to build s. 8 | /// The name of the transport being built. 9 | public class EventBusTransportRegistrationBuilder(string name) 10 | { 11 | /// Gets the name of the transport being built. 12 | public string Name { get; } = name; 13 | 14 | /// Gets or sets the display name for the transport being built. 15 | public string? DisplayName { get; set; } 16 | 17 | /// Gets or sets the type responsible for this transport. 18 | [DynamicallyAccessedMembers(TrimmingHelper.Transport)] 19 | public Type? TransportType { get; set; } 20 | 21 | /// Builds the instance. 22 | /// The . 23 | public EventBusTransportRegistration Build() 24 | { 25 | if (TransportType is null) 26 | { 27 | throw new InvalidOperationException($"{nameof(TransportType)} must be configured to build an {nameof(EventBusTransportRegistration)}."); 28 | } 29 | 30 | return new EventBusTransportRegistration(Name, DisplayName, TransportType); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Diagnostics/ActivityNames.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Diagnostics; 2 | 3 | /// 4 | /// Names for activities generated by EventBus 5 | /// 6 | public static class ActivityNames 7 | { 8 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 9 | public const string Consume = "EventBus.Consume"; 10 | public const string Publish = "EventBus.Publish"; 11 | public const string Cancel = "EventBus.Cancel"; 12 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 13 | } 14 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Diagnostics/ActivityTagNames.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Diagnostics; 2 | 3 | /// 4 | /// Names for tags added to activities generated by EventBus 5 | /// 6 | public static class ActivityTagNames 7 | { 8 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 9 | public const string MessagingSystem = "messaging.system"; 10 | public const string MessagingMessageId = "messaging.message_id"; 11 | public const string MessagingConversationId = "messaging.conversation_id"; 12 | public const string MessagingDestination = "messaging.destination"; 13 | public const string MessagingDestinationKind = "messaging.destination_kind"; 14 | public const string MessagingTempDestination = "messaging.temp_destination"; 15 | public const string MessagingProtocol = "messaging.protocol"; 16 | public const string MessagingProtocolVersion = "messaging.protocol_version"; 17 | public const string MessagingUrl = "messaging.url"; 18 | 19 | public const string NetPeerIp = "net.peer.ip"; 20 | public const string NetPeerName = "net.peer.name"; 21 | 22 | public const string EventBusEventType = "eventbus.event_type"; 23 | public const string EventBusSerializerType = "eventbus.serializer_type"; 24 | public const string EventBusConsumerType = "eventbus.consumer_type"; 25 | public const string EventBusEventsCount = "eventbus.events_count"; 26 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 27 | } 28 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Diagnostics/EventBusActivitySource.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Reflection; 3 | 4 | namespace Tingle.EventBus.Diagnostics; 5 | 6 | /// 7 | public static class EventBusActivitySource 8 | { 9 | private static readonly AssemblyName AssemblyName = typeof(EventBusActivitySource).Assembly.GetName(); 10 | private static readonly string ActivitySourceName = AssemblyName.Name!; 11 | private static readonly Version Version = AssemblyName.Version!; 12 | private static readonly ActivitySource ActivitySource = new(ActivitySourceName, Version.ToString()); 13 | 14 | /// 15 | /// Creates a new activity if there are active listeners for it, using the specified 16 | /// name, activity kind, and parent Id. 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static Activity? StartActivity(string name, ActivityKind kind = ActivityKind.Internal, string? parentId = null) 23 | { 24 | return parentId is not null 25 | ? ActivitySource.StartActivity(name: name, kind: kind, parentId: parentId) 26 | : ActivitySource.StartActivity(name: name, kind: kind); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Diagnostics/HeaderNames.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Diagnostics; 2 | 3 | /// 4 | /// Names of known headers added to events 5 | /// 6 | public static class HeaderNames 7 | { 8 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 9 | public const string EventType = "X-Event-Type"; 10 | public const string ActivityId = "X-Activity-Id"; 11 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 12 | } 13 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Diagnostics/LogCategoryNames.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Diagnostics; 2 | 3 | /// 4 | /// The category names to use for logging. 5 | /// 6 | internal static class LogCategoryNames 7 | { 8 | public const string EventBus = "Tingle.EventBus"; 9 | public const string Transports = EventBus + ".Transports"; 10 | public const string Serializers = EventBus + ".Serializers"; 11 | } 12 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/IEventConsumer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Tingle.EventBus.Internal; 3 | 4 | namespace Tingle.EventBus; 5 | 6 | /// 7 | /// Contract describing a consumer of one or more events. 8 | /// 9 | public interface IEventConsumer 10 | { 11 | // Intentionally left blank 12 | } 13 | 14 | /// 15 | /// Contract describing a consumer of an event. 16 | /// 17 | public interface IEventConsumer<[DynamicallyAccessedMembers(TrimmingHelper.Event)] T> : IEventConsumer where T : class 18 | { 19 | /// 20 | /// Consume an event of the provided type. 21 | /// 22 | /// The context of the event 23 | /// 24 | /// 25 | Task ConsumeAsync(EventContext context, CancellationToken cancellationToken); 26 | } 27 | 28 | /// 29 | /// Contract describing a consumer of a dead-lettered event. 30 | /// 31 | public interface IDeadLetteredEventConsumer<[DynamicallyAccessedMembers(TrimmingHelper.Event)] T> : IEventConsumer where T : class 32 | { 33 | /// 34 | /// Consume a dead-lettered event of the provided type. 35 | /// 36 | /// The context of the event 37 | /// 38 | /// 39 | Task ConsumeAsync(DeadLetteredEventContext context, CancellationToken cancellationToken); 40 | } 41 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Ids/DefaultEventIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Tingle.EventBus.Ids; 4 | 5 | /// 6 | /// Default implementation of . 7 | /// 8 | public class DefaultEventIdGenerator : IEventIdGenerator 9 | { 10 | /// 11 | public string Generate(EventRegistration reg) 12 | { 13 | if (reg is null) throw new ArgumentNullException(nameof(reg)); 14 | 15 | var id = Guid.NewGuid(); 16 | var bytes = id.ToByteArray(); 17 | 18 | return reg.IdFormat switch 19 | { 20 | EventIdFormat.Guid => id.ToString(), 21 | EventIdFormat.GuidNoDashes => id.ToString("n"), 22 | EventIdFormat.Long => BitConverter.ToUInt64(bytes, 0).ToString(), 23 | EventIdFormat.LongHex => BitConverter.ToUInt64(bytes, 0).ToString("x"), 24 | EventIdFormat.DoubleLong => $"{BitConverter.ToUInt64(bytes, 0)}{BitConverter.ToUInt64(bytes, 8)}", 25 | EventIdFormat.DoubleLongHex => $"{BitConverter.ToUInt64(bytes, 0):x}{BitConverter.ToUInt64(bytes, 8):x}", 26 | EventIdFormat.Random => Convert.ToBase64String(bytes), 27 | _ => throw new NotSupportedException($"'{nameof(EventIdFormat)}.{reg.IdFormat}' set on event '{reg.EventType.FullName}' is not supported."), 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Ids/IEventIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Tingle.EventBus.Ids; 4 | 5 | /// 6 | /// Generator for event identifiers 7 | /// 8 | public interface IEventIdGenerator 9 | { 10 | /// 11 | /// Generate value for . 12 | /// 13 | /// The for which to generate for. 14 | /// 15 | string Generate(EventRegistration reg); 16 | } 17 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Internal/DictionaryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Internal; 2 | 3 | /// Extension methods on . 4 | public static class DictionaryExtensions 5 | { 6 | /// 7 | /// Creates an 8 | /// wrapping around the provided . 9 | /// 10 | /// The type of keys in the dictionary. 11 | /// The type of values in the dictionary. 12 | /// The dictionary to use 13 | /// 14 | public static EventBusDictionaryWrapper ToEventBusWrapper(this IDictionary dictionary) 15 | where TKey : notnull => new(dictionary); 16 | } 17 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Internal/EventBusHost.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Tingle.EventBus.Internal; 5 | 6 | /// Host for . 7 | /// 8 | /// 9 | /// 10 | internal class EventBusHost(IHostApplicationLifetime lifetime, EventBus bus, ILogger logger) : BackgroundService 11 | { 12 | /// 13 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 14 | { 15 | if (!await WaitForAppStartupAsync(lifetime, stoppingToken).ConfigureAwait(false)) 16 | { 17 | logger.ApplicationDidNotStartup(); 18 | return; 19 | } 20 | 21 | await bus.StartAsync(stoppingToken).ConfigureAwait(false); 22 | } 23 | 24 | /// 25 | public override async Task StopAsync(CancellationToken cancellationToken) 26 | { 27 | await base.StopAsync(cancellationToken).ConfigureAwait(false); 28 | await bus.StopAsync(cancellationToken).ConfigureAwait(false); 29 | } 30 | 31 | private static async Task WaitForAppStartupAsync(IHostApplicationLifetime lifetime, CancellationToken stoppingToken) 32 | { 33 | var startedTcs = new TaskCompletionSource(); 34 | var cancelledTcs = new TaskCompletionSource(); 35 | 36 | // register result setting using the cancellation tokens 37 | lifetime.ApplicationStarted.Register(() => startedTcs.SetResult(new { })); 38 | stoppingToken.Register(() => cancelledTcs.SetResult(new { })); 39 | 40 | var completedTask = await Task.WhenAny(startedTcs.Task, cancelledTcs.Task).ConfigureAwait(false); 41 | 42 | // if the completed task was the "app started" one, return true 43 | return completedTask == startedTcs.Task; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/MessageStrings.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus; 2 | 3 | internal class MessageStrings 4 | { 5 | public const string XmlSerializationUnreferencedCodeMessage = "XML serialization and deserialization might require types that cannot be statically analyzed."; 6 | 7 | public const string JsonSerializationUnreferencedCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed. Use the serializers and types that takes a JsonSerializerContext, or make sure all of the required types are preserved."; 8 | public const string JsonSerializationRequiresDynamicCodeMessage = "JSON serialization and deserialization might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications."; 9 | 10 | public const string BindingUnreferencedCodeMessage = "In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed."; 11 | public const string BindingDynamicCodeMessage = "Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types."; 12 | 13 | public const string RequiresUnreferencedCodeMessage = "JSON serialization, deserialization, and binding strongly typed objects to configuration values might require types that cannot be statically analyzed."; 14 | public const string RequiresDynamicCodeMessage = "JSON serialization, deserialization, and binding strongly typed objects to configuration values might require types that cannot be statically analyzed and might need runtime code generation. Use System.Text.Json source generation for native AOT applications."; 15 | 16 | public const string GenericsUnreferencedCodeMessage = "The native code for this instantiation might require types that cannot be statically analyzed."; 17 | public const string GenericsDynamicCodeMessage = "The native code for this instantiation might not be available at runtime."; 18 | } 19 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/MetadataNames.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus; 2 | 3 | /// 4 | /// Name of attributes/properties/metadata added alongside a message/event if the transport supports. 5 | /// 6 | public static class MetadataNames 7 | { 8 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 9 | public const string Id = "Id"; 10 | public const string ContentType = "Content-Type"; 11 | public const string SequenceNumber = "SequenceNumber"; 12 | public const string Offset = "Offset"; 13 | public const string CorrelationId = "CorrelationId"; 14 | public const string RequestId = "RequestId"; 15 | public const string InitiatorId = "InitiatorId"; 16 | public const string FullyQualifiedNamespace = "FullyQualifiedNamespace"; 17 | public const string EntityUri = "EntityUri"; 18 | 19 | public const string ActivityId = "EventBus.ActivityId"; 20 | public const string EventType = "EventBus.EventType"; 21 | public const string EventName = "EventBus.EventName"; 22 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member 23 | } 24 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Retries/AbstractRetryableEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Retries; 2 | 3 | /// Abstract implementation of . 4 | public abstract record AbstractRetryableEvent : IRetryableEvent 5 | { 6 | /// 7 | /// 8 | /// 9 | protected AbstractRetryableEvent() { } 10 | 11 | /// 12 | /// 13 | /// 14 | /// Value for . 15 | protected AbstractRetryableEvent(IList delays) 16 | { 17 | Delays = delays ?? throw new ArgumentNullException(nameof(delays)); 18 | if (delays.Count == 0) 19 | { 20 | throw new ArgumentOutOfRangeException(nameof(delays), "You must have at least one delay entry."); 21 | } 22 | } 23 | 24 | /// 25 | /// 26 | /// 27 | /// Value for . 28 | /// Value for . 29 | protected AbstractRetryableEvent(IList delays, IList attempts) : this(delays) 30 | { 31 | Attempts = attempts ?? throw new ArgumentNullException(nameof(attempts)); 32 | } 33 | 34 | /// 35 | public IList Delays { get; set; } = new List(); 36 | 37 | /// 38 | public IList Attempts { get; set; } = new List(); 39 | } 40 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Retries/IRetryableEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Retries; 2 | 3 | /// 4 | /// Represents an event (or command) that can be retried and carries its own retry schedule. 5 | /// 6 | public interface IRetryableEvent 7 | { 8 | /// List of delays to be used for this command. 9 | IList Delays { get; set; } 10 | 11 | /// List of previous attempts. 12 | IList Attempts { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/EventEnvelope.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Serialization; 2 | 3 | /// 4 | /// An envelope of a event as represented when serialized. 5 | /// 6 | public class EventEnvelope : IEventEnvelope 7 | { 8 | /// 9 | public string? Id { get; set; } 10 | 11 | /// 12 | public string? RequestId { get; set; } 13 | 14 | /// 15 | public string? CorrelationId { get; set; } 16 | 17 | /// 18 | public string? InitiatorId { get; set; } 19 | 20 | /// 21 | public DateTimeOffset? Expires { get; set; } 22 | 23 | /// 24 | public DateTimeOffset? Sent { get; set; } 25 | 26 | /// 27 | public IDictionary Headers { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); 28 | 29 | /// 30 | public HostInfo? Host { get; set; } 31 | } 32 | 33 | /// 34 | /// An envelope of a event as represented when serialized. 35 | /// 36 | public class EventEnvelope : EventEnvelope, IEventEnvelope where T : class 37 | { 38 | /// 39 | public T? Event { get; set; } 40 | } 41 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/HostInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Serialization; 2 | 3 | /// 4 | /// Information about the host on which the event bus is running. 5 | /// 6 | public record HostInfo 7 | { 8 | /// 9 | /// The machine name (or role instance name) of the machine. 10 | /// 11 | /// WIN-HQ1243 12 | public virtual string? MachineName { get; set; } 13 | 14 | /// 15 | /// The name of the application. 16 | /// 17 | /// Tingle.EventBus.Examples.SimplePublisher 18 | public virtual string? ApplicationName { get; set; } 19 | 20 | /// 21 | /// The version of the application. 22 | /// 23 | /// 1.0.0.0 24 | public virtual string? ApplicationVersion { get; set; } 25 | 26 | /// 27 | /// The name of the environment the application is running in. 28 | /// 29 | /// Production 30 | public virtual string? EnvironmentName { get; set; } 31 | 32 | /// 33 | /// The version of the library. 34 | /// 35 | /// 1.0.0.0 36 | public virtual string? LibraryVersion { get; set; } 37 | 38 | /// 39 | /// The operating system hosting the application. 40 | /// 41 | /// Microsoft Windows NT 10.0.19042.0 42 | public virtual string? OperatingSystem { get; set; } 43 | } 44 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/IEventEnvelope.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Serialization; 2 | 3 | /// 4 | /// An envelope of a event as represented when serialized. 5 | /// 6 | public interface IEventEnvelope 7 | { 8 | /// 9 | /// The unique identifier of the event. 10 | /// 11 | string? Id { get; } 12 | 13 | /// 14 | /// The unique identifier of the request associated with the event. 15 | /// 16 | string? RequestId { get; } 17 | 18 | /// 19 | /// A value shared between related events. 20 | /// 21 | string? CorrelationId { get; } 22 | 23 | /// 24 | /// The unique identifier of the initiator of the event. 25 | /// 26 | string? InitiatorId { get; } 27 | 28 | /// 29 | /// The specific time at which the event expires. 30 | /// 31 | DateTimeOffset? Expires { get; } 32 | 33 | /// 34 | /// The specific time the event was sent. 35 | /// 36 | DateTimeOffset? Sent { get; } 37 | 38 | /// 39 | /// The headers published alongside the event. 40 | /// 41 | IDictionary Headers { get; } 42 | 43 | /// 44 | /// Information about the host on which the event was generated. 45 | /// 46 | HostInfo? Host { get; } 47 | } 48 | 49 | /// 50 | /// An envelope of a event as represented when serialized. 51 | /// 52 | public interface IEventEnvelope : IEventEnvelope where T : class 53 | { 54 | /// 55 | /// The event published or to be published. 56 | /// 57 | T? Event { get; } 58 | } 59 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/IEventSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Tingle.EventBus.Internal; 3 | 4 | namespace Tingle.EventBus.Serialization; 5 | 6 | /// 7 | /// A message serializer is responsible for serializing and deserializing an event. 8 | /// 9 | public interface IEventSerializer 10 | { 11 | /// Serialize an event into a stream of bytes. 12 | /// The event type to be serialized. 13 | /// The to use. 14 | /// 15 | Task SerializeAsync<[DynamicallyAccessedMembers(TrimmingHelper.Event)] T>(SerializationContext context, CancellationToken cancellationToken = default) where T : class; 16 | 17 | /// Deserialize an event from a stream of bytes. 18 | /// The event type to be deserialized. 19 | /// The to use. 20 | /// 21 | Task?> DeserializeAsync<[DynamicallyAccessedMembers(TrimmingHelper.Event)] T>(DeserializationContext context, CancellationToken cancellationToken = default) where T : class; 22 | } 23 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/SerializationContext.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Tingle.EventBus.Configuration; 3 | using Tingle.EventBus.Internal; 4 | 5 | namespace Tingle.EventBus.Serialization; 6 | 7 | /// Context for performing serialization. 8 | /// The event to be serialized. 9 | /// Registration for this event being deserialized. 10 | public sealed class SerializationContext<[DynamicallyAccessedMembers(TrimmingHelper.Event)] T>(EventContext @event, EventRegistration registration) where T : class 11 | { 12 | /// 13 | /// The event to be serialized. 14 | /// 15 | public EventContext Event { get; } = @event ?? throw new ArgumentNullException(nameof(@event)); 16 | 17 | /// 18 | /// The containing the raw data. 19 | /// 20 | public BinaryData? Body { get; internal set; } 21 | 22 | /// 23 | /// Registration for this event being deserialized. 24 | /// 25 | public EventRegistration Registration { get; } = registration ?? throw new ArgumentNullException(nameof(registration)); 26 | } 27 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/Xml/EventBusBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using Tingle.EventBus; 3 | using Tingle.EventBus.Serialization.Xml; 4 | 5 | namespace Microsoft.Extensions.DependencyInjection; 6 | 7 | /// 8 | /// Extension methods on for the XML event serializer. 9 | /// 10 | public static class EventBusBuilderExtensions 11 | { 12 | /// 13 | /// Use the included XML serializer as the default event serializer. 14 | /// 15 | /// 16 | /// 17 | /// 18 | [RequiresUnreferencedCode(MessageStrings.XmlSerializationUnreferencedCodeMessage)] 19 | public static EventBusBuilder AddXmlSerializer(this EventBusBuilder builder, Action? configure = null) 20 | { 21 | if (builder == null) throw new ArgumentNullException(nameof(builder)); 22 | 23 | // Configure the options for the serializer 24 | var services = builder.Services; 25 | if (configure is not null) services.Configure(configure); 26 | services.ConfigureOptions(); 27 | 28 | // Add the serializer 29 | return builder.AddSerializer(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/Xml/XmlEventEnvelope.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Tingle.EventBus.Serialization.Xml; 4 | 5 | /// 6 | [XmlRoot("EventEnvelope")] 7 | public class XmlEventEnvelope : IEventEnvelope where T : class 8 | { 9 | /// 10 | public XmlEventEnvelope() { } 11 | 12 | /// 13 | public XmlEventEnvelope(EventEnvelope envelope) 14 | { 15 | if (envelope is null) throw new ArgumentNullException(nameof(envelope)); 16 | 17 | Id = envelope.Id; 18 | RequestId = envelope.RequestId; 19 | CorrelationId = envelope.CorrelationId; 20 | InitiatorId = envelope.InitiatorId; 21 | Expires = envelope.Expires; 22 | Sent = envelope.Sent; 23 | Headers = envelope.Headers.Select(kvp => (XmlHeader)kvp).ToArray(); 24 | Host = envelope.Host; 25 | Event = envelope.Event; 26 | } 27 | 28 | /// 29 | public string? Id { get; set; } 30 | 31 | /// 32 | public string? RequestId { get; set; } 33 | 34 | /// 35 | public string? CorrelationId { get; set; } 36 | 37 | /// 38 | public string? InitiatorId { get; set; } 39 | 40 | /// 41 | public DateTimeOffset? Expires { get; set; } 42 | 43 | /// 44 | public DateTimeOffset? Sent { get; set; } 45 | 46 | /// 47 | [XmlIgnore] 48 | IDictionary IEventEnvelope.Headers 49 | { 50 | get 51 | { 52 | var pairs = Headers.Select(h => (KeyValuePair)h); 53 | return new Dictionary(pairs, StringComparer.OrdinalIgnoreCase); 54 | } 55 | } 56 | 57 | /// 58 | public XmlHeader[] Headers { get; set; } = Array.Empty(); 59 | 60 | /// 61 | public HostInfo? Host { get; set; } 62 | 63 | /// 64 | public T? Event { get; set; } 65 | } 66 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/Xml/XmlEventSerializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Logging; 3 | using Microsoft.Extensions.Options; 4 | using System.Diagnostics.CodeAnalysis; 5 | using System.Xml.Serialization; 6 | using Tingle.EventBus.Internal; 7 | 8 | namespace Tingle.EventBus.Serialization.Xml; 9 | 10 | /// 11 | /// The default implementation of for XML. 12 | /// 13 | /// The options for configuring the serializer. 14 | /// 15 | [RequiresUnreferencedCode(MessageStrings.XmlSerializationUnreferencedCodeMessage)] 16 | public class XmlEventSerializer(IOptionsMonitor optionsAccessor, ILoggerFactory loggerFactory) 17 | : AbstractEventSerializer(optionsAccessor, loggerFactory) 18 | { 19 | /// 20 | protected override IList SupportedMediaTypes => ["application/xml", "text/xml"]; 21 | 22 | /// 23 | protected override Task?> DeserializeToEnvelopeAsync<[DynamicallyAccessedMembers(TrimmingHelper.Event)] T>( 24 | Stream stream, 25 | DeserializationContext context, 26 | CancellationToken cancellationToken = default) 27 | { 28 | var serializer = new XmlSerializer(typeof(XmlEventEnvelope)); 29 | var envelope = (XmlEventEnvelope?)serializer.Deserialize(stream); 30 | return Task.FromResult?>(envelope); 31 | } 32 | 33 | /// 34 | protected override Task SerializeEnvelopeAsync<[DynamicallyAccessedMembers(TrimmingHelper.Event)] T>( 35 | Stream stream, 36 | EventEnvelope envelope, 37 | CancellationToken cancellationToken = default) 38 | { 39 | var serializer = new XmlSerializer(typeof(XmlEventEnvelope)); 40 | serializer.Serialize(stream, new XmlEventEnvelope(envelope)); 41 | return Task.CompletedTask; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/Xml/XmlEventSerializerConfigureOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | 3 | namespace Microsoft.Extensions.DependencyInjection; 4 | 5 | /// 6 | /// A class to finish the configuration of instances of . 7 | /// 8 | internal class XmlEventSerializerConfigureOptions : IValidateOptions 9 | { 10 | public ValidateOptionsResult Validate(string? name, XmlEventSerializerOptions options) 11 | { 12 | return ValidateOptionsResult.Success; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/Xml/XmlEventSerializerOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Extensions.DependencyInjection; 2 | 3 | /// 4 | /// Options for configuring XML based event serializer. 5 | /// 6 | public class XmlEventSerializerOptions 7 | { 8 | // intentionally left blank for future use 9 | } 10 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Serialization/Xml/XmlHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Serialization; 2 | 3 | namespace Tingle.EventBus.Serialization.Xml; 4 | 5 | /// 6 | [XmlType("Header")] 7 | public struct XmlHeader 8 | { 9 | /// 10 | public XmlHeader(string key, string value) : this() { Key = key; Value = value; } 11 | 12 | /// 13 | public string Key { get; set; } 14 | 15 | /// 16 | public string Value { get; set; } 17 | 18 | /// 19 | /// Convert to . 20 | /// 21 | /// 22 | public static implicit operator KeyValuePair(XmlHeader header) 23 | { 24 | return new KeyValuePair(header.Key, header.Value); 25 | } 26 | 27 | /// 28 | /// Convert to . 29 | /// 30 | /// 31 | public static implicit operator XmlHeader(KeyValuePair pair) 32 | { 33 | return new XmlHeader(pair.Key, pair.Value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Tingle.EventBus.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Event-Based framework for distributed applications. 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Tingle.EventBus/Transports/EventConsumeResult.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports; 2 | 3 | /// 4 | /// Represents the result from consuming an event. 5 | /// This only used by the transport(s) and should not be used by the final application. 6 | /// 7 | public struct EventConsumeResult 8 | { 9 | /// 10 | /// Create an instance of 11 | /// 12 | /// See . 13 | /// See . 14 | internal EventConsumeResult(bool successful, Exception? exception = null) 15 | { 16 | Successful = successful; 17 | Exception = exception; 18 | } 19 | 20 | /// 21 | /// Indicates if the consuming action was successful. 22 | /// 23 | public bool Successful { get; } 24 | 25 | /// 26 | /// Exception caught for failures. 27 | /// 28 | public Exception? Exception { get; } 29 | 30 | /// 31 | /// Deconstruct the result into parts 32 | /// 33 | /// See . 34 | /// See . 35 | public void Deconstruct(out bool successful, out Exception? exception) 36 | { 37 | successful = Successful; 38 | exception = Exception; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/Configurator/FakeEventSerializer1.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Serialization; 2 | 3 | namespace Tingle.EventBus.Tests.Configurator; 4 | 5 | internal class FakeEventSerializer1 : IEventSerializer 6 | { 7 | /// 8 | public Task?> DeserializeAsync(DeserializationContext context, CancellationToken cancellationToken = default) where T : class 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | /// 14 | public Task SerializeAsync(SerializationContext context, CancellationToken cancellationToken = default) where T : class 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/Configurator/FakeEventSerializer2.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Tests.Configurator; 2 | 3 | internal class FakeEventSerializer2 { } // should not implement IEventSerializer 4 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/Configurator/TestConsumer1.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Tests.Configurator; 2 | 3 | internal class TestConsumer1 : IEventConsumer 4 | { 5 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 6 | { 7 | throw new NotImplementedException(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/Configurator/TestConsumer2.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Tingle.EventBus.Tests.Configurator; 4 | 5 | [ConsumerName("sample-consumer")] 6 | internal class TestConsumer2 : IEventConsumer 7 | { 8 | public Task ConsumeAsync(EventContext context, CancellationToken cancellationToken = default) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/Configurator/TestEvent1.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Tests.Configurator; 2 | 3 | internal class TestEvent1 4 | { 5 | public string? Value1 { get; set; } 6 | public string? Value2 { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/Configurator/TestEvent2.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Tingle.EventBus.Tests.Configurator; 4 | 5 | [EventName("sample-event")] 6 | [EventSerializer(typeof(FakeEventSerializer1))] 7 | internal class TestEvent2 8 | { 9 | public string? Value1 { get; set; } 10 | public string? Value2 { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/Configurator/TestEvent3.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Tingle.EventBus.Tests.Configurator; 4 | 5 | [EntityKind(EntityKind.Broadcast)] 6 | [EventSerializer(typeof(FakeEventSerializer2))] 7 | internal class TestEvent3 8 | { 9 | public string? Value1 { get; set; } 10 | public string? Value2 { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/DefaultEventIdGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | using Tingle.EventBus.Ids; 3 | 4 | namespace Tingle.EventBus.Tests; 5 | 6 | public class DefaultEventIdGeneratorTests 7 | { 8 | [Theory] 9 | [InlineData(EventIdFormat.Guid)] 10 | [InlineData(EventIdFormat.GuidNoDashes)] 11 | [InlineData(EventIdFormat.Long)] 12 | [InlineData(EventIdFormat.LongHex)] 13 | [InlineData(EventIdFormat.DoubleLong)] 14 | [InlineData(EventIdFormat.DoubleLongHex)] 15 | [InlineData(EventIdFormat.Random)] 16 | public void GenerateEventId_Works(EventIdFormat format) 17 | { 18 | var generator = new DefaultEventIdGenerator(); 19 | var reg = EventRegistration.Create(); 20 | reg.IdFormat = format; 21 | var id = generator.Generate(reg); 22 | Assert.NotNull(id); 23 | } 24 | 25 | [Fact] 26 | public void GenerateEventId_Throws_InvalidOperationExecption() 27 | { 28 | var generator = new DefaultEventIdGenerator(); 29 | var reg = EventRegistration.Create(); 30 | reg.IdFormat = null; 31 | var ex = Assert.Throws(() => generator.Generate(reg)); 32 | Assert.Equal($"'{nameof(EventIdFormat)}.{reg.IdFormat}' set on event '{reg.EventType.FullName}' is not supported.", ex.Message); 33 | } 34 | 35 | internal class SampleEvent 36 | { 37 | public string? Value1 { get; set; } 38 | public string? Value2 { get; set; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/FakeHostEnvironment.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.FileProviders; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace Tingle.EventBus.Tests; 5 | 6 | public class FakeHostEnvironment : IHostEnvironment 7 | { 8 | public FakeHostEnvironment() { } // Required for DI 9 | 10 | public FakeHostEnvironment(string applicationName) => ApplicationName = applicationName; 11 | 12 | public string EnvironmentName { get; set; } = default!; 13 | public string ApplicationName { get; set; } = default!; 14 | public string ContentRootPath { get; set; } = default!; 15 | public IFileProvider ContentRootFileProvider { get; set; } = default!; 16 | } 17 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Tests/Tingle.EventBus.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.EventHubs.Tests/AzureEventHubsTransportTests.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Tingle.EventBus.Transports.Azure.EventHubs.Tests; 4 | 5 | public class AzureEventHubsTransportTests 6 | { 7 | [Theory] 8 | [InlineData(true, null, true)] 9 | [InlineData(true, UnhandledConsumerErrorBehaviour.Deadletter, true)] 10 | [InlineData(true, UnhandledConsumerErrorBehaviour.Discard, true)] 11 | [InlineData(false, null, false)] 12 | [InlineData(false, UnhandledConsumerErrorBehaviour.Deadletter, true)] 13 | [InlineData(false, UnhandledConsumerErrorBehaviour.Discard, true)] 14 | public void CanCheckpoint_Works(bool successful, UnhandledConsumerErrorBehaviour? behaviour, bool expected) 15 | { 16 | var actual = AzureEventHubsTransport.CanCheckpoint(successful, behaviour); 17 | Assert.Equal(expected, actual); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.EventHubs.Tests/Samples/iot-hub-Telemetry.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2022-12-22T12:10:51Z", 3 | "door": "frontLeft" 4 | } -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.EventHubs.Tests/Samples/iot-hub-deviceConnectionStateEvents.json: -------------------------------------------------------------------------------- 1 | { 2 | "sequenceNumber": "000000000000000001D7F744182052190000000C000000000000000000000001" 3 | } 4 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.EventHubs.Tests/Samples/iot-hub-deviceLifecycleEvents.json: -------------------------------------------------------------------------------- 1 | { 2 | "deviceId": "1234567890", 3 | "etag": "AAAAAAAAAAE=", 4 | "version": 2, 5 | "properties": { 6 | "desired": { 7 | "$metadata": { "$lastUpdated": "2022-01-16T16:16:24.0465798Z" }, 8 | "$version": 1 9 | }, 10 | "reported": { 11 | "$metadata": { "$lastUpdated": "2022-01-16T16:16:24.0465798Z" }, 12 | "$version": 1 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.EventHubs.Tests/Samples/iot-hub-twinChangeEvents.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "tags": { "tagValue": 5 }, 4 | "properties": { 5 | "desired": { 6 | "desiredValue": 55, 7 | "$metadata": { 8 | "$lastUpdated": "2022-01-16T16:36:53.8146535Z", 9 | "$lastUpdatedVersion": 2, 10 | "desiredValue": { 11 | "$lastUpdated": "2022-01-16T16:36:53.8146535Z", 12 | "$lastUpdatedVersion": 2 13 | } 14 | }, 15 | "$version": 2 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.EventHubs.Tests/TestSamples.cs: -------------------------------------------------------------------------------- 1 | namespace Tingle.EventBus.Transports.Azure.EventHubs.Tests; 2 | 3 | internal class TestSamples 4 | { 5 | public static Stream GetResourceAsStream(string fileName) 6 | => typeof(TestSamples).Assembly.GetManifestResourceStream(string.Join(".", typeof(TestSamples).Namespace, "Samples", fileName))!; 7 | 8 | public static Stream GetIotHubTelemetry() => GetResourceAsStream("iot-hub-Telemetry.json"); 9 | public static Stream GetIotHubTwinChangeEvents() => GetResourceAsStream("iot-hub-twinChangeEvents.json"); 10 | public static Stream GetIotHubDeviceLifecycleEvents() => GetResourceAsStream("iot-hub-deviceLifecycleEvents.json"); 11 | public static Stream GetIotHubDeviceConnectionStateEvents() => GetResourceAsStream("iot-hub-deviceConnectionStateEvents.json"); 12 | } 13 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.EventHubs.Tests/Tingle.EventBus.Transports.Azure.EventHubs.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.ServiceBus.Tests/AzureServiceBusTransportTests.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Tingle.EventBus.Transports.Azure.ServiceBus.Tests; 4 | 5 | public class AzureServiceBusTransportTests 6 | { 7 | [Theory] 8 | [InlineData(false, null, false, PostConsumeAction.Abandon)] 9 | [InlineData(false, null, true, PostConsumeAction.Throw)] 10 | [InlineData(false, UnhandledConsumerErrorBehaviour.Deadletter, false, PostConsumeAction.Deadletter)] 11 | [InlineData(false, UnhandledConsumerErrorBehaviour.Deadletter, true, PostConsumeAction.Throw)] 12 | [InlineData(false, UnhandledConsumerErrorBehaviour.Discard, false, PostConsumeAction.Complete)] 13 | [InlineData(false, UnhandledConsumerErrorBehaviour.Discard, true, null)] 14 | [InlineData(true, null, false, PostConsumeAction.Complete)] 15 | [InlineData(true, null, true, null)] 16 | [InlineData(true, UnhandledConsumerErrorBehaviour.Deadletter, false, PostConsumeAction.Complete)] 17 | [InlineData(true, UnhandledConsumerErrorBehaviour.Deadletter, true, null)] 18 | [InlineData(true, UnhandledConsumerErrorBehaviour.Discard, false, PostConsumeAction.Complete)] 19 | [InlineData(true, UnhandledConsumerErrorBehaviour.Discard, true, null)] 20 | internal void DecideAction_Works(bool successful, UnhandledConsumerErrorBehaviour? behaviour, bool autoComplete, PostConsumeAction? expected) 21 | { 22 | var actual = AzureServiceBusTransport.DecideAction(successful: successful, 23 | behaviour: behaviour, 24 | autoComplete: autoComplete); 25 | 26 | Assert.Equal(expected, actual); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Azure.ServiceBus.Tests/Tingle.EventBus.Transports.Azure.ServiceBus.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.InMemory.Tests/SequenceNumberGeneratorTests.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Transports.InMemory.Client; 2 | 3 | namespace Tingle.EventBus.Tests.InMemory; 4 | 5 | public class SequenceNumberGeneratorTests 6 | { 7 | [Fact] 8 | public async Task Generate_Works() 9 | { 10 | var sng = new SequenceNumberGenerator(); 11 | var current = sng.Generate(); 12 | await Task.Delay(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken); 13 | var next = sng.Generate(); 14 | Assert.Equal(1, next - current); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.InMemory.Tests/Tingle.EventBus.Transports.InMemory.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Kafka.Tests/KafkaTransportTests.cs: -------------------------------------------------------------------------------- 1 | using Tingle.EventBus.Configuration; 2 | 3 | namespace Tingle.EventBus.Transports.Kafka.Tests; 4 | 5 | public class KafkaTransportTests 6 | { 7 | [Theory] 8 | [InlineData(true, null, true)] 9 | [InlineData(true, UnhandledConsumerErrorBehaviour.Deadletter, true)] 10 | [InlineData(true, UnhandledConsumerErrorBehaviour.Discard, true)] 11 | [InlineData(false, null, false)] 12 | [InlineData(false, UnhandledConsumerErrorBehaviour.Deadletter, true)] 13 | [InlineData(false, UnhandledConsumerErrorBehaviour.Discard, true)] 14 | public void CanCheckpoint_Works(bool successful, UnhandledConsumerErrorBehaviour? behaviour, bool expected) 15 | { 16 | var actual = KafkaTransport.CanCheckpoint(successful, behaviour); 17 | Assert.Equal(expected, actual); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Tingle.EventBus.Transports.Kafka.Tests/Tingle.EventBus.Transports.Kafka.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------