├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .vscode └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── Rebus.AzureServiceBus.Tests ├── AsbTestConfig.cs ├── AzureServiceBusBasicSendReceive.cs ├── AzureServiceBusConfigurationExtensionsDoNotConfigureTopicTest.cs ├── AzureServiceBusContentTypeTest.cs ├── AzureServiceBusDoNotCreateQueue.cs ├── AzureServiceBusManyMessages.cs ├── AzureServiceBusMessageExpiration.cs ├── AzureServiceBusPeekLockRenewalTest.cs ├── AzureServiceBusPeekLockRenewalTest_ShorterDuration.cs ├── AzureServiceBusPrefetchTest.cs ├── AzureServiceBusTransportClientSettingsDoNotConfigureTopicTest.cs ├── AzureServiceBusTransportDoNotConfigureTopicTest.cs ├── AzureServiceBusTransportSettingsDoNotConfigureTopicTest.cs ├── Bugs │ ├── CanPublishToTopicWithSameNameAsQueue.cs │ ├── DeferredMessagesAreDeadletteredJustFine.cs │ ├── DoesNotDieOnHeaderNullValue.cs │ ├── EndpointMappingsDoNotInterfereWithPubSub.cs │ ├── FastSubscriber.cs │ ├── OneWayClientCanDeferMessageOntoAnotherQueue.cs │ ├── QueueDeleter.cs │ ├── TooBigHeadersTest.cs │ ├── TopicDeleter.cs │ ├── VerifyAssumptionAboutDisposingAsyncTaskMultipleTimes.cs │ ├── VerifyDeferredMessagesWorkAsExpected.cs │ ├── VerifyHowThingsWorkWhenConnectionStringEndpointHasPath.cs │ ├── VerifyQueueNameWhenUsingLegacyNaming.cs │ ├── VerifyRenewAndDifferentInput.cs │ ├── VerifyTopicsAreCreatedAsNeeded.cs │ ├── VerifyTopicsCanHaveSlashesInThem.cs │ ├── WorkWithIntegratedAuth.cs │ └── WorksWithDefaultCredential.cs ├── CanChangeDefaultLockDuration.cs ├── CanUseSlashInQueueNames.cs ├── CheckPeekLockStuff.cs ├── Checks │ ├── CanDeferLocal.cs │ ├── CanIndeedHaveTopicNameWithForwardSlash.cs │ ├── CheckPublishLatency.cs │ ├── CheckSendLatency.cs │ ├── CheckTheServiceBusMessageInTheTimeAfterCompletingIt.cs │ ├── CheckWhatHappensWhenSubscriptionHasAutodeleteOnIdleSet.cs │ ├── DeadletterMessageToBeInspected.cs │ ├── SeeIfWeCanDeadletterThisWay.cs │ └── TestNativeDeliveryCount.cs ├── Examples │ ├── ShowDefaultReturnAddress.cs │ └── TheMostInterestingHandlerInTheWorldWorks.cs ├── Factories │ ├── AzureServiceBusBusFactory.cs │ └── AzureServiceBusTransportFactory.cs ├── FailsWhenSendingToNonExistentQueue.cs ├── LegacyNamingTest.cs ├── NativeDeferTest.cs ├── NotCreatingQueueTest.cs ├── Rebus.AzureServiceBus.Tests.csproj ├── ServerSettingsBuilderHasSameSettingsAsClientBuilder.cs ├── SharedAccessSignatureWorks.cs ├── SpikeTest.cs ├── TestAsbNaming.cs ├── TestAsbTopicsPubSub.cs ├── TestExceptionIgnorant.cs ├── TestShutdownTime.cs ├── TestUtilities │ └── StubTokenCredential.cs ├── TokenProviderTest.cs ├── UseNativeHeaders.cs ├── VerifyPayloadLimitWhenBatching.cs ├── WorksWhenEnablingPartitioning.cs └── WorksWithAutoDeleteOnIdle.cs ├── Rebus.AzureServiceBus.sln ├── Rebus.AzureServiceBus ├── AzureServiceBus │ ├── AzureServiceBusTransport.cs │ ├── ConnectionStringParser.cs │ ├── DefaultAzureServiceBusTopicNameConvention.cs │ ├── DisabledTimeoutManager.cs │ ├── Messages │ │ ├── DefaultMessageConverter.cs │ │ ├── ExtraHeaders.cs │ │ ├── IMessageConverter.cs │ │ └── RemoveHeaders.cs │ ├── NameFormat │ │ ├── DefaultNameFormatter.cs │ │ ├── INameFormatter.cs │ │ ├── LegacyNameFormatter.cs │ │ ├── LegacyV3NameFormatter.cs │ │ └── PrefixNameFormatter.cs │ ├── OutgoingMessage.cs │ └── ReceivedMessage.cs ├── Config │ ├── AdditionalAzureServiceBusConfigurationExtensions.cs │ ├── AzureServiceBusConfigurationExtensions.cs │ ├── AzureServiceBusTransportClientSettings.cs │ └── AzureServiceBusTransportSettings.cs ├── Internals │ ├── AsyncHelpers.cs │ ├── DisposableExtensions.cs │ ├── EnumerableExtensions.cs │ ├── ExceptionIgnorant.cs │ ├── InternalsVisibleTo.cs │ ├── ManagementExtensions.cs │ ├── MessageLockRenewer.cs │ ├── SeviceBusSenderExtensions.cs │ └── StringExtensions.cs └── Rebus.AzureServiceBus.csproj ├── appveyor.yml ├── artwork └── little_rebusbus2_copy-500x500.png ├── scripts ├── build.cmd ├── push.cmd └── release.cmd └── tools └── NuGet └── nuget.exe /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | Rebus is [MIT-licensed](https://opensource.org/licenses/MIT). The code submitted in this pull request needs to carry the MIT license too. By leaving this text in, __I hereby acknowledge that the code submitted in the pull request has the MIT license and can be merged with the Rebus codebase__. 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | obj 2 | bin 3 | deploy 4 | deploy/* 5 | _ReSharper.* 6 | *.csproj.user 7 | *.resharper.user 8 | *.ReSharper.user 9 | *.teamcity.user 10 | *.TeamCity.user 11 | *.resharper 12 | *.DotSettings.user 13 | *.dotsettings.user 14 | *.ncrunchproject 15 | *.ncrunchsolution 16 | *.suo 17 | *.cache 18 | ~$* 19 | .idea/* 20 | .vs 21 | .vs/* 22 | _NCrunch_* 23 | *.user 24 | *.backup 25 | 26 | # MS Guideline 27 | **/packages/* 28 | !**/packages/build/ 29 | 30 | asb_connection_string.txt 31 | 32 | AssemblyInfo_Patch.cs 33 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | "/property:GenerateFullPaths=true", 13 | "/consoleloggerparameters:NoSummary" 14 | ], 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | }, 19 | "presentation": { 20 | "reveal": "silent" 21 | }, 22 | "problemMatcher": "$msCompile" 23 | }, 24 | { 25 | "label": "test", 26 | "command": "dotnet", 27 | "type": "shell", 28 | "args": [ 29 | "test", 30 | "/property:GenerateFullPaths=true", 31 | "/consoleloggerparameters:NoSummary" 32 | ], 33 | "group": { 34 | "kind": "test", 35 | "isDefault": true 36 | }, 37 | "presentation": { 38 | "reveal": "silent" 39 | }, 40 | "problemMatcher": "$msCompile" 41 | } 42 | ] 43 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | 3 | ## 2.0.0-a1 4 | * Test release 5 | 6 | ## 2.0.0-b01 7 | * Test release 8 | 9 | ## 2.0.0 10 | * Release 2.0.0 11 | 12 | ## 2.1.0 13 | * Update Azure Service Bus dependency to 3.4.0 14 | 15 | ## 2.2.0 16 | * Allow `/` in queue and topic names 17 | 18 | ## 3.0.0 19 | * Update to Rebus 3 20 | 21 | ## 4.0.0 22 | * Update to Rebus 4 23 | * Update to new project structure (.NET Core unfortunately not support by the driver at this time) 24 | 25 | ## 4.0.1 26 | * Add .NET Standard 2.0 target specifically to handle dependency on `ConfigurationManager` 27 | * `.ConfigureAwait(false)` everywhere something is `await`ed - thanks [lezzi] 28 | 29 | ## 5.0.0 30 | * Remove ability to run on the "Basic" tier because it makes the world simpler, and you should at least be using "Standard" anyway 31 | * Add ability to set actual message ID on the `BrokeredMessage` by using Rebus' message ID as the value 32 | 33 | ## 5.0.1 34 | * Fix handling of connection string when it contains the `EntityPath` element. Makes it possible to use a connection string with only RECEIVE rights to the input queue 35 | 36 | ## 6.0.0 37 | * Update to Microsoft's new driver and thus gain .NET Core support - finally! 38 | * Add ability configure (and re-configure if possible) these queue settings: partitioning, peek lock duration, default message TTL 39 | 40 | ## 6.0.1 41 | * Port aforementioned (v. 5.0.1) `EntityPath` handling forward 42 | 43 | ## 6.0.3 44 | * Fix bug that would result in `MessagingEntityNotFoundException`s when publishing to non-existent topics 45 | 46 | ## 6.0.4 47 | * Small improvement of subscription registration performance by avoiding an update if the subscription looks as it should 48 | 49 | ## 6.0.5 50 | * Fix bug that would "forget" to stop automatic peek lock renewal in cases where message handler throws an exception, generating unnecessary noise in the log 51 | 52 | ## 6.0.6 53 | * Update Azure Service Bus dependency to 3.2.1 54 | 55 | ## 6.0.7 56 | * Fix bug that would result in always require a manage permission in the shared access policy, even if the queues were already created - thanks [ehabelgindy] 57 | 58 | ## 7.0.0 59 | * Several adjustments to how queue names are validated and how topic names are generated. Please note that this is a BREAKING CHANGE, because queue names and topic names are no longer automatically lowercased (because it's not necessary), and topic names can now have . in them (because that has always been possible). If you update to 7, you must update ALL of your endpoints, otherwise pub/sub will not work! 60 | * Fix bug that would "forget" to stop automatic peek lock renewal in cases where message handler throws an exception, generating unnecessary noise in the log 61 | * Add ability to run in "legacy naming mode", meaning that topics are more conservatively sanitized to work the same way as all versions of the transport prior to version 7 62 | * Fix bug that accidentally replaced `/` in topic names when publishing, which would cause topics with `/` to be unreachable 63 | * Fix one-way client legacy naming bug (one-way client would not adhere to legacy naming convention, even when `.UseLegacyNaming()` was called on the configuration builder) 64 | * Default to using topics nested beneath their assemblies, so e.g. `await bus.Subscribe()` will result in the creation of a topic named `mscorlib/System.String`, which will be formatted as a topic named `System.String` nested beneath `mscorlib` in tools that support it 65 | * Pluggable naming strategy via `INameFormatter`, allowing for customizing all aspects of how e.g. .NET types are named when creating topics from them, how queue names are normalized/sanitized, etc. - thanks [jr01] 66 | * Added transport setting for overriding the Receive OperationTimeout - thanks [jr01] 67 | * Update Microsoft.Azure.ServiceBus to version 4.1.1 to avoid reconnection bug described here: https://github.com/Azure/azure-service-bus-dotnet/issues/639 68 | * Change it so that topics are initialized by both subscribers and publishers, the philosophy being: If someone subscribes/publishes to it, it must mean that it exists and thus should have an ASB entity representing it 69 | * Ensure that the sequence of operations is bulletproof when renewing peek locks - thanks [jr01] 70 | * Change topic and subscription creation to be more defensive to avoid exceptions as part of normal program flow 71 | * Fix potential deadlock in initialization of topic clients - thanks [jr01] 72 | * Fix bug that prevented one-way clients from deferring messages 73 | * Update to Rebus 6 74 | 75 | ## 7.1.0 76 | * Add ability to authenticate by prioviding an `ITokenProvider`, enabling much more fine-grained access control - thanks [eeskildsen] 77 | 78 | ## 7.1.1 79 | * Fix bug that would make the transport unable to receive a message with a NULL value present in the headers dictionary 80 | 81 | ## 7.1.2 82 | * Make peek lock renewal more robust and less resource-intensive 83 | 84 | ## 7.1.3 85 | * Update Microsoft.Azure.ServiceBus dependency to 4.1.2 86 | 87 | ## 7.1.4 88 | * Use transport type from connection string - thanks [benne] 89 | 90 | ## 7.1.5 91 | * Fix bug that would cause deferred messages to be sent to the wrong queue in cases where a custom queue naming convention was used that would somehow mess with the magic deferred messages queue name 92 | 93 | ## 7.1.6 94 | * Actually fix bug described in previous entry 95 | 96 | ## 8.0.0 97 | * Update Microsoft.Azure.ServiceBus dependency to 5.1.0 98 | * Update Microsoft.Identity.Client dependency to 4.22.0 99 | 100 | ## 8.0.1 101 | * Fix bug that would result in inability to dead-letter a deferred message 102 | 103 | ## 8.1.0 104 | * Add ability to use native dead-lettering by calling `t => t.UseNativeDeadlettering()` on the transport configurer. The basic of this functionality is the addition of `Message` and `MessageReceiver` to the transaction context, which makes it possible for user code to take over the responsibility for ACK/NACK/Dead-lettering the message 105 | 106 | ## 8.1.1 107 | * Trim dead letter reason/description to not exceed 4096 characters, which is the maximum length of an ASB header value 108 | 109 | ## 8.1.5 110 | * Add intelligent batching to send logic, ensuring that request payload stays below 256 kB (and make it configurable!) 111 | 112 | ## 9.0.0 113 | * Port to new Azure Service Bus driver (Azure.Messaging.ServiceBus) - thanks [binick] 114 | * Use the new driver's built-in ability to create message batches 115 | 116 | ## 9.0.5 117 | * Update Azure Service Bus driver 118 | 119 | ## 9.0.6 120 | * Fix bug that would result in creating massive amounts of topic client message senders, which would never be disposed 121 | 122 | ## 9.0.7 123 | * Log it when using Azure Service Bus' built-in dead-lettering on a message - thanks [hjalle] 124 | 125 | ## 9.0.8 126 | * Update Azure.Messaging.ServiceBus dependency to 7.5.1 127 | 128 | ## 9.1.0 129 | * Enable the use of sessions - thanks [georgechond94] 130 | * Update Azure.Messaging.ServiceBus dependency to 7.10.0 131 | * Update azure.identity dependency to 1.7.0 132 | * Update Rebus dependency to 6.6.5 133 | 134 | ## 9.2.0 135 | * Remove broken session support 136 | * Remove potential race condition that came with session support 137 | 138 | ## 9.3.0 139 | * Include the headers of the transport message when NACKing or natively dead-lettering the message, thus providing the ability to mutate them for each delivery attempt - thanks [jorgenbosman] 140 | 141 | ## 9.3.1 142 | * Don't pass shutdown cancellation token when ACK/NACKing to avoid cancelling the call immediately when shutting down 143 | 144 | ## 9.3.2 145 | * Avoid logging failed peek lock renewal attempts when there's a race between renewal and ACK/NACK by detecting if the message has been removed from the list of messages to automatically renew 146 | 147 | ## 9.3.3 148 | * Additional cancellation tweaks: Proceed with sending outgoing messages even during shutdown, because the user's code must have finished executing without errors, and therefore we should try to complete the message transaction to make for the least surprising outcome 149 | 150 | ## 9.3.4 151 | * Fix bug that would cause the transport to lose the TransportType part of its connection string, thus making it impossible to configure it to use AmqpWebSockets 152 | 153 | ## 9.3.5 154 | * Move removal of message ID from list of message lock renewers to the beginning of OnCompleted, so it will be executed first thing and will not be affected by failures during ACK 155 | * Update Azure.Identity dep to 1.8.2 156 | 157 | ## 10.0.0 158 | * Update to Rebus 8 159 | 160 | ## 10.1.0 161 | * Update to Rebus 8.2.0 and Azure.Messaging.ServiceBus 7.17.2 162 | * Add ability to provide native message delivery count in optional Rebus header 163 | 164 | ## 10.1.1 165 | * Fix bug that would accidentally leave a `SessionId` header on messages sent to the error queue, even though there was no session ID on the ASB message 166 | 167 | ## 10.2.0 168 | * Update Azure.Identity dependency to 1.11.0 169 | * Update Azure.Messaging.ServiceBus dependency to 7.17.5 170 | * Update Rebus dependency to 8.4.2 171 | 172 | ## 10.3.0 173 | * Update Azure.Identity dependency to 1.13.2 174 | * Update Azure.Messaging.ServiceBus dependency to 7.18.4 175 | 176 | ## 10.4.0 177 | * Add ability to "leave the topics alone" to enable running with less privileges - thanks [RenanZanelato] 178 | 179 | ## 10.4.1 180 | * Fix ability to "leave the topics along" - thanks [rafilsk0] 181 | 182 | [benne]: https://github.com/benne 183 | [binick]: https://github.com/binick 184 | [eeskildsen]: https://github.com/eeskildsen 185 | [ehabelgindy]: https://github.com/ehabelgindy 186 | [georgechond94]: https://github.com/georgechond94 187 | [hjalle]: https://github.com/hjalle 188 | [jorgenbosman]: https://github.com/jorgenbosman 189 | [jr01]: https://github.com/jr01 190 | [lezzi]: https://github.com/lezzi 191 | [Meyce]: https://github.com/Meyce 192 | [rafilsk0]: https://github.com/rafilsk0 193 | [RenanZanelato]: https://github.com/RenanZanelato 194 | 195 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributions are most welcome! :) 2 | 3 | I do prefer it if we communicate a little bit before you send PRs, though. 4 | This is because _I value your time_, and it would be a shame if you spent time 5 | working on something that could be made better in another way, or wasn't 6 | actually needed because what you wanted to achieve could be done better in 7 | another way, etc. 8 | 9 | ## "Beginners" 10 | 11 | Contributions are ALSO very welcome if you consider yourself a beginner 12 | at open source. Everyone has to start somewhere, right? 13 | 14 | Here's how you would ideally do it if you were to contribute to Rebus: 15 | 16 | * Pick an [issue](https://github.com/rebus-org/Rebus/issues) you're interested in doing, 17 | or dream up something yourself that you feel is missing. 18 | * If you talk to me first (either via comments on the issue or by email), I 19 | will guarantee that your contribution is accepted. 20 | * Send me a "pull request" (which is how you make contributions on GitHub) 21 | 22 | ### Here's how you create a pull request/PR 23 | 24 | * Fork Rebus ([Fork A Repo @ GitHub docs](https://help.github.com/articles/fork-a-repo/)) 25 | * Clone your fork ([Cloning A Repository @ GitHub docs](https://help.github.com/articles/cloning-a-repository/)) 26 | * Make changes to your local copy (e.g. `git commit -am"bam!!!11"` - check [this](https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository) out for more info) 27 | * Push your local changes to your fork ([Pushing To A Remote @ GitHub docs](https://help.github.com/articles/pushing-to-a-remote/)) 28 | * Send me a pull request ([Using Pull Requests @ GitHub docs](https://help.github.com/articles/using-pull-requests/)) 29 | 30 | When you do this, your changes become visible to me. I can then review it, and we can discuss 31 | each line of code if necessary. 32 | 33 | If you push additional changes to your fork during this process, 34 | the changes become immediately available in the pull request. 35 | 36 | When all is good, I accept your PR by merging it, and then you're (even more) awesome! 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Rebus is licensed under [The MIT License (MIT)](http://opensource.org/licenses/MIT) 2 | 3 | # The MIT License (MIT) 4 | 5 | Copyright (c) 2012-2016 Mogens Heller Grabe 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | 25 | ------- 26 | 27 | This license was chosen with the intention of making it easy for everyone to use Rebus. If the license has the opposite effect for your specific usage/organization/whatever, please contact me and we'll see if we can work something out. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rebus.AzureServiceBus 2 | 3 | [![install from nuget](https://img.shields.io/nuget/v/Rebus.AzureServiceBus.svg?style=flat-square)](https://www.nuget.org/packages/Rebus.AzureServiceBus) 4 | 5 | Provides an Azure Service Bus transport for [Rebus](https://github.com/rebus-org/Rebus). 6 | 7 | ![](https://raw.githubusercontent.com/rebus-org/Rebus/master/artwork/little_rebusbus2_copy-200x200.png) 8 | 9 | --- 10 | 11 | The transport works requires the Standard tier, because it uses Azure Service Bus' topics. -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AsbTestConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace Rebus.AzureServiceBus.Tests; 7 | 8 | static class AsbTestConfig 9 | { 10 | static AsbTestConfig() 11 | { 12 | ConnectionString = GetConnectionString(); 13 | 14 | //ConnectionStringFromFileOrNull(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "asb_connection_string.txt")) 15 | // ?? ConnectionStringFromEnvironmentVariable("rebus2_asb_connection_string") 16 | // ?? throw new ConfigurationErrorsException("Could not find Azure Service Bus connection string!"); 17 | } 18 | 19 | static string GetConnectionString() 20 | { 21 | var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "asb_connection_string.txt"); 22 | 23 | if (File.Exists(filePath)) 24 | { 25 | return ConnectionStringFromFile(filePath); 26 | } 27 | 28 | const string variableName = "rebus2_asb_connection_string"; 29 | var environmentVariable = Environment.GetEnvironmentVariable(variableName); 30 | 31 | if (!string.IsNullOrWhiteSpace(environmentVariable)) return ConnectionStringFromEnvironmentVariable(variableName); 32 | 33 | throw new ApplicationException($@"Could not get Azure Service Bus connection string. Tried to load from file 34 | 35 | {filePath} 36 | 37 | but the file did not exist. Also tried to get the environment variable named 38 | 39 | {variableName} 40 | 41 | but it was empty (or didn't exist). 42 | 43 | Please provide a connection string through one of the methods mentioned above. 44 | 45 | "); 46 | } 47 | 48 | /// 49 | /// Gets full connection string on the form Endpoint=<endpoint-uri>;SharedAccessKeyName=<key-name>;SharedAccessKey=<access-key> 50 | /// 51 | public static string ConnectionString { get; } 52 | 53 | /// 54 | /// Parses and gets the value from the "Endpoint" key-value pair, e.g. something like sb://whatever.servicebus.windows.net 55 | /// 56 | public static string GetEndpointUriFromConnectionString() => 57 | ConnectionString.Split(';') 58 | .Select(token => token.Split('=')) 59 | .Select(parts => new KeyValuePair(parts.First(), parts.Skip(1).FirstOrDefault())) 60 | .FirstOrDefault(kvp => string.Equals("endpoint", kvp.Key, StringComparison.OrdinalIgnoreCase)).Value; 61 | 62 | /// 63 | /// Gets the value from and trims the scheme and slashes parts from it, getting only the hostname 64 | /// 65 | public static string GetHostnameFromConnectionString() => new Uri(GetEndpointUriFromConnectionString()).Host; 66 | 67 | static string ConnectionStringFromFile(string filePath) 68 | { 69 | Console.WriteLine("Using Azure Service Bus connection string from file {0}", filePath); 70 | 71 | var connectionString = File.ReadAllLines(filePath).FirstOrDefault(line => !line.StartsWith("#") && !string.IsNullOrWhiteSpace(line)) 72 | ?? throw new FormatException("Could not find any non-empty, un-out-commented lines in the connection string file"); 73 | 74 | return connectionString; 75 | } 76 | 77 | static string ConnectionStringFromEnvironmentVariable(string environmentVariableName) 78 | { 79 | var value = Environment.GetEnvironmentVariable(environmentVariableName); 80 | 81 | Console.WriteLine("Using Azure Service Bus connection string from env variable {0}", environmentVariableName); 82 | 83 | return value; 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusBasicSendReceive.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.AzureServiceBus.Tests.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.AzureServiceBus.Tests; 6 | 7 | [TestFixture] 8 | public class AzureServiceBusBasicSendReceive : BasicSendReceive 9 | { 10 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusConfigurationExtensionsDoNotConfigureTopicTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using NUnit.Framework; 4 | using Rebus.Activation; 5 | using Rebus.AzureServiceBus.NameFormat; 6 | using Rebus.Config; 7 | using Rebus.Logging; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Threading.TaskParallelLibrary; 10 | 11 | namespace Rebus.AzureServiceBus.Tests 12 | { 13 | [TestFixture] 14 | public class AzureServiceBusConfigurationExtensionsDoNotConfigureTopicTest : FixtureBase 15 | { 16 | static readonly string TestQueueName = TestConfig.GetName("test-queue"); 17 | readonly ConsoleLoggerFactory _consoleLoggerFactory = new ConsoleLoggerFactory(false); 18 | 19 | [Test] 20 | public void UseAzureServiceBus_WithDoNotConfigureTopic_SetsDoNotConfigureTopicEnabledToTrue() 21 | { 22 | var transport = new AzureServiceBusTransport( 23 | AsbTestConfig.ConnectionString, 24 | TestQueueName, 25 | _consoleLoggerFactory, 26 | new TplAsyncTaskFactory(_consoleLoggerFactory), 27 | new DefaultNameFormatter(), 28 | new AzureServiceBus.Messages.DefaultMessageConverter()); 29 | 30 | Using(transport); 31 | 32 | transport.Initialize(); 33 | 34 | var settings = new AzureServiceBusTransportSettings(); 35 | settings.DoNotConfigureTopic(); 36 | 37 | transport.DoNotConfigureTopicEnabled = settings.DoNotConfigureTopicEnabled; 38 | 39 | Assert.That(transport.DoNotConfigureTopicEnabled, Is.True); 40 | } 41 | 42 | [Test] 43 | public void UseAzureServiceBusAsOneWayClient_WithDoNotConfigureTopic_SetsDoNotConfigureTopicEnabledToTrue() 44 | { 45 | var transport = new AzureServiceBusTransport( 46 | AsbTestConfig.ConnectionString, 47 | null, 48 | _consoleLoggerFactory, 49 | new TplAsyncTaskFactory(_consoleLoggerFactory), 50 | new DefaultNameFormatter(), 51 | new AzureServiceBus.Messages.DefaultMessageConverter()); 52 | 53 | Using(transport); 54 | 55 | var settings = new AzureServiceBusTransportClientSettings(); 56 | settings.DoNotConfigureTopic(); 57 | 58 | transport.DoNotConfigureTopicEnabled = settings.DoNotConfigureTopicEnabled; 59 | 60 | Assert.That(transport.DoNotConfigureTopicEnabled, Is.True); 61 | } 62 | 63 | [Test] 64 | public void UseAzureServiceBus_KeepsDoNotConfigureTopicEnabled_FalseByDefault() 65 | { 66 | var transport = new AzureServiceBusTransport( 67 | AsbTestConfig.ConnectionString, 68 | TestQueueName, 69 | _consoleLoggerFactory, 70 | new TplAsyncTaskFactory(_consoleLoggerFactory), 71 | new DefaultNameFormatter(), 72 | new AzureServiceBus.Messages.DefaultMessageConverter()); 73 | 74 | Using(transport); 75 | 76 | transport.Initialize(); 77 | 78 | Assert.That(transport.DoNotConfigureTopicEnabled, Is.False); 79 | } 80 | 81 | [Test] 82 | public void UseAzureServiceBusAsOneWayClient_KeepsDoNotConfigureTopicEnabled_FalseByDefault() 83 | { 84 | var transport = new AzureServiceBusTransport( 85 | AsbTestConfig.ConnectionString, 86 | null, 87 | _consoleLoggerFactory, 88 | new TplAsyncTaskFactory(_consoleLoggerFactory), 89 | new DefaultNameFormatter(), 90 | new AzureServiceBus.Messages.DefaultMessageConverter()); 91 | 92 | Using(transport); 93 | 94 | Assert.That(transport.DoNotConfigureTopicEnabled, Is.False); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusContentTypeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Rebus.Activation; 4 | using Rebus.AzureServiceBus.Tests.Bugs; 5 | using Rebus.Config; 6 | using Rebus.Tests.Contracts; 7 | 8 | namespace Rebus.AzureServiceBus.Tests; 9 | 10 | [TestFixture] 11 | public class AzureServiceBusContentTypeTest : FixtureBase 12 | { 13 | static readonly string ConnectionString = AsbTestConfig.ConnectionString; 14 | 15 | [Test] 16 | public void LooksGood() 17 | { 18 | Using(new QueueDeleter("contenttypetest")); 19 | 20 | using var activator = new BuiltinHandlerActivator(); 21 | 22 | Console.WriteLine(ConnectionString); 23 | 24 | var bus = Configure.With(activator) 25 | .Transport(t => t.UseAzureServiceBus(ConnectionString, "contenttypetest")) 26 | .Options(o => o.SetNumberOfWorkers(0)) 27 | .Start(); 28 | 29 | bus.Advanced.Workers.SetNumberOfWorkers(0); 30 | 31 | var message = new RigtigBesked 32 | { 33 | Text = "hej med dig min ven! DER ER JSON HERI!!!", 34 | Embedded = new RigtigEmbedded 35 | { 36 | Whatever = new[] {1, 2, 3}, 37 | Message = "I'm in here!!" 38 | } 39 | }; 40 | 41 | bus.SendLocal(message).Wait(); 42 | } 43 | } 44 | 45 | public class RigtigEmbedded 46 | { 47 | public string Message { get; set; } 48 | public int[] Whatever { get; set; } 49 | } 50 | 51 | public class RigtigBesked 52 | { 53 | public string Text { get; set; } 54 | public RigtigEmbedded Embedded { get; set; } 55 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusDoNotCreateQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.Messages; 7 | using Rebus.AzureServiceBus.NameFormat; 8 | using Rebus.Config; 9 | using Rebus.Logging; 10 | using Rebus.Tests.Contracts; 11 | using Rebus.Tests.Contracts.Extensions; 12 | using Rebus.Threading.TaskParallelLibrary; 13 | using Rebus.Transport; 14 | #pragma warning disable 1998 15 | 16 | namespace Rebus.AzureServiceBus.Tests; 17 | 18 | [TestFixture] 19 | public class BasicAzureServiceBusBasicReceiveOnly : FixtureBase 20 | { 21 | static readonly string QueueName = TestConfig.GetName("input"); 22 | 23 | [Test] 24 | [TestCase(5)] 25 | [TestCase(10)] 26 | public async Task DoesntIgnoreDefinedTimeoutWhenReceiving(int operationTimeoutInSeconds) 27 | { 28 | var operationTimeout = TimeSpan.FromSeconds(operationTimeoutInSeconds); 29 | 30 | var connString = AsbTestConfig.ConnectionString; 31 | 32 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 33 | var transport = new AzureServiceBusTransport(connString, QueueName, consoleLoggerFactory, new TplAsyncTaskFactory(consoleLoggerFactory), new DefaultNameFormatter(), new DefaultMessageConverter()); 34 | transport.ReceiveOperationTimeout = TimeSpan.FromSeconds(operationTimeoutInSeconds); 35 | Using(transport); 36 | 37 | transport.Initialize(); 38 | 39 | transport.PurgeInputQueue(); 40 | //Create the queue for the receiver since it cannot create it self beacuse of lacking rights on the namespace 41 | transport.CreateQueue(QueueName); 42 | 43 | var senderActivator = new BuiltinHandlerActivator(); 44 | 45 | var senderBus = Configure.With(senderActivator) 46 | .Transport(t => t.UseAzureServiceBus(connString, "sender")) 47 | .Start(); 48 | 49 | Using(senderBus); 50 | 51 | // queue 3 messages 52 | await senderBus.Advanced.Routing.Send(QueueName, "message to receiver"); 53 | await senderBus.Advanced.Routing.Send(QueueName, "message to receiver2"); 54 | await senderBus.Advanced.Routing.Send(QueueName, "message to receiver3"); 55 | 56 | //await Task.Delay(TimeSpan.FromSeconds(2)); // wait a bit to make sure the messages are queued. 57 | 58 | // receive 1 59 | using (var scope = new RebusTransactionScope()) 60 | { 61 | var sw = System.Diagnostics.Stopwatch.StartNew(); 62 | var msg = await transport.Receive(scope.TransactionContext, CancellationToken.None); 63 | sw.Stop(); 64 | await scope.CompleteAsync(); 65 | 66 | Assert.That(msg, Is.Not.Null); 67 | Assert.That(sw.Elapsed, Is.LessThan(TimeSpan.FromMilliseconds(1500))); 68 | } 69 | 70 | // receive 2 71 | using (var scope = new RebusTransactionScope()) 72 | { 73 | var sw = System.Diagnostics.Stopwatch.StartNew(); 74 | var msg = await transport.Receive(scope.TransactionContext, CancellationToken.None); 75 | sw.Stop(); 76 | await scope.CompleteAsync(); 77 | 78 | Assert.That(msg, Is.Not.Null); 79 | Assert.That(sw.Elapsed, Is.LessThan(TimeSpan.FromMilliseconds(1500))); 80 | } 81 | 82 | // receive 3 83 | using (var scope = new RebusTransactionScope()) 84 | { 85 | var sw = System.Diagnostics.Stopwatch.StartNew(); 86 | var msg = await transport.Receive(scope.TransactionContext, CancellationToken.None); 87 | sw.Stop(); 88 | await scope.CompleteAsync(); 89 | 90 | Assert.That(msg, Is.Not.Null); 91 | Assert.That(sw.Elapsed, Is.LessThan(TimeSpan.FromMilliseconds(1500))); 92 | } 93 | 94 | // receive 4 - NOTHING 95 | using (var scope = new RebusTransactionScope()) 96 | { 97 | var sw = System.Diagnostics.Stopwatch.StartNew(); 98 | var msg = await transport.Receive(scope.TransactionContext, CancellationToken.None); 99 | sw.Stop(); 100 | await scope.CompleteAsync(); 101 | 102 | Assert.That(msg, Is.Null); 103 | Assert.That(sw.Elapsed, Is.LessThan(operationTimeout.Add(TimeSpan.FromSeconds(2))).And.GreaterThan(operationTimeout.Subtract(TimeSpan.FromSeconds(2)))); 104 | } 105 | 106 | // put 1 more message 107 | await senderBus.Advanced.Routing.Send(QueueName, "message to receiver5"); 108 | 109 | await Task.Delay(TimeSpan.FromSeconds(2)); // wait a bit to make sure the messages are queued. 110 | 111 | // receive 5 112 | using (var scope = new RebusTransactionScope()) 113 | { 114 | var sw = System.Diagnostics.Stopwatch.StartNew(); 115 | var msg = await transport.Receive(scope.TransactionContext, CancellationToken.None); 116 | sw.Stop(); 117 | await scope.CompleteAsync(); 118 | 119 | Assert.That(msg, Is.Not.Null); 120 | Assert.That(sw.Elapsed, Is.LessThan(TimeSpan.FromMilliseconds(1500))); 121 | } 122 | 123 | // receive 6 - NOTHING 124 | using (var scope = new RebusTransactionScope()) 125 | { 126 | var sw = System.Diagnostics.Stopwatch.StartNew(); 127 | var msg = await transport.Receive(scope.TransactionContext, CancellationToken.None); 128 | sw.Stop(); 129 | await scope.CompleteAsync(); 130 | 131 | Assert.That(msg, Is.Null); 132 | Assert.That(sw.Elapsed, Is.LessThan(operationTimeout.Add(TimeSpan.FromSeconds(2))).And.GreaterThan(operationTimeout.Subtract(TimeSpan.FromSeconds(2)))); 133 | } 134 | } 135 | 136 | [Test] 137 | public async Task ShouldBeAbleToRecieveEvenWhenNotCreatingQueue() 138 | { 139 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 140 | var transport = new AzureServiceBusTransport(AsbTestConfig.ConnectionString, QueueName, consoleLoggerFactory, new TplAsyncTaskFactory(consoleLoggerFactory), new DefaultNameFormatter(), new DefaultMessageConverter()); 141 | transport.PurgeInputQueue(); 142 | //Create the queue for the receiver since it cannot create it self beacuse of lacking rights on the namespace 143 | transport.CreateQueue(QueueName); 144 | 145 | var recieverActivator = new BuiltinHandlerActivator(); 146 | var senderActivator = new BuiltinHandlerActivator(); 147 | 148 | var gotMessage = new ManualResetEvent(false); 149 | 150 | recieverActivator.Handle(async (bus, context, message) => 151 | { 152 | gotMessage.Set(); 153 | Console.WriteLine("got message in readonly mode"); 154 | }); 155 | 156 | var receiverBus = Configure.With(recieverActivator) 157 | .Logging(l => l.ColoredConsole()) 158 | .Transport(t => 159 | t.UseAzureServiceBus(AsbTestConfig.ConnectionString, QueueName) 160 | .DoNotCreateQueues()) 161 | .Start(); 162 | 163 | var senderBus = Configure.With(senderActivator) 164 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, "sender")) 165 | .Start(); 166 | 167 | Using(receiverBus); 168 | Using(senderBus); 169 | 170 | await senderBus.Advanced.Routing.Send(QueueName, "message to receiver"); 171 | 172 | gotMessage.WaitOrDie(TimeSpan.FromSeconds(10)); 173 | } 174 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusManyMessages.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.AzureServiceBus.Tests.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.AzureServiceBus.Tests; 6 | 7 | [TestFixture] 8 | public class AzureServiceBusManyMessages : TestManyMessages { } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusMessageExpiration.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.AzureServiceBus.Tests.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.AzureServiceBus.Tests; 6 | 7 | [TestFixture] 8 | public class AzureServiceBusMessageExpiration : MessageExpiration { } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusPeekLockRenewalTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.NameFormat; 7 | using Rebus.Bus; 8 | using Rebus.Config; 9 | using Rebus.Extensions; 10 | using Rebus.Logging; 11 | using Rebus.Messages; 12 | using Rebus.Tests.Contracts; 13 | using Rebus.Tests.Contracts.Extensions; 14 | using Rebus.Tests.Contracts.Utilities; 15 | using Rebus.Threading.TaskParallelLibrary; 16 | using Rebus.Transport; 17 | // ReSharper disable ArgumentsStyleLiteral 18 | 19 | namespace Rebus.AzureServiceBus.Tests; 20 | 21 | [TestFixture] 22 | public class AzureServiceBusPeekLockRenewalTest : FixtureBase 23 | { 24 | static readonly string ConnectionString = AsbTestConfig.ConnectionString; 25 | static readonly string QueueName = TestConfig.GetName("input"); 26 | 27 | readonly ConsoleLoggerFactory _consoleLoggerFactory = new ConsoleLoggerFactory(false); 28 | 29 | BuiltinHandlerActivator _activator; 30 | AzureServiceBusTransport _transport; 31 | IBus _bus; 32 | IBusStarter _busStarter; 33 | 34 | protected override void SetUp() 35 | { 36 | _transport = new AzureServiceBusTransport(ConnectionString, QueueName, _consoleLoggerFactory, new TplAsyncTaskFactory(_consoleLoggerFactory), new DefaultNameFormatter(), new Messages.DefaultMessageConverter()); 37 | 38 | Using(_transport); 39 | 40 | _transport.Initialize(); 41 | _transport.PurgeInputQueue(); 42 | 43 | _activator = new BuiltinHandlerActivator(); 44 | 45 | _busStarter = Configure.With(_activator) 46 | .Logging(l => l.Use(new ListLoggerFactory(outputToConsole: true, detailed: true))) 47 | .Transport(t => t.UseAzureServiceBus(ConnectionString, QueueName).AutomaticallyRenewPeekLock()) 48 | .Options(o => 49 | { 50 | o.SetNumberOfWorkers(1); 51 | o.SetMaxParallelism(1); 52 | }) 53 | .Create(); 54 | 55 | _bus = _busStarter.Bus; 56 | 57 | Using(_bus); 58 | } 59 | 60 | [Test, Ignore("Can be used to check silencing behavior when receive errors occur")] 61 | public void ReceiveExceptions() 62 | { 63 | Using(_transport); 64 | 65 | Thread.Sleep(TimeSpan.FromMinutes(10)); 66 | } 67 | 68 | [Test] 69 | public async Task ItWorks() 70 | { 71 | var gotMessage = new ManualResetEvent(false); 72 | 73 | _activator.Handle(async (bus, context, message) => 74 | { 75 | Console.WriteLine($"Got message with ID {context.Headers.GetValue(Headers.MessageId)} - waiting 6 minutes...."); 76 | 77 | // longer than the longest asb peek lock in the world... 78 | //await Task.Delay(TimeSpan.FromSeconds(3)); 79 | await Task.Delay(TimeSpan.FromMinutes(6)); 80 | 81 | Console.WriteLine("done waiting"); 82 | 83 | gotMessage.Set(); 84 | }); 85 | 86 | _busStarter.Start(); 87 | 88 | await _bus.SendLocal("hej med dig min ven!"); 89 | 90 | gotMessage.WaitOrDie(TimeSpan.FromMinutes(6.5)); 91 | 92 | // shut down bus 93 | _bus.Dispose(); 94 | 95 | // see if queue is empty 96 | using var scope = new RebusTransactionScope(); 97 | 98 | var message = await _transport.Receive(scope.TransactionContext, CancellationToken.None); 99 | 100 | await scope.CompleteAsync(); 101 | 102 | if (message != null) 103 | { 104 | throw new AssertionException( 105 | $"Did not expect to receive a message - got one with ID {message.Headers.GetValue(Headers.MessageId)}"); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusPeekLockRenewalTest_ShorterDuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.NameFormat; 7 | using Rebus.Bus; 8 | using Rebus.Config; 9 | using Rebus.Extensions; 10 | using Rebus.Logging; 11 | using Rebus.Messages; 12 | using Rebus.Tests.Contracts; 13 | using Rebus.Tests.Contracts.Extensions; 14 | using Rebus.Tests.Contracts.Utilities; 15 | using Rebus.Threading.TaskParallelLibrary; 16 | using Rebus.Transport; 17 | // ReSharper disable ArgumentsStyleLiteral 18 | // ReSharper disable ArgumentsStyleOther 19 | 20 | namespace Rebus.AzureServiceBus.Tests; 21 | 22 | [TestFixture] 23 | public class AzureServiceBusPeekLockRenewalTest_ShorterDuration : FixtureBase 24 | { 25 | static readonly string ConnectionString = AsbTestConfig.ConnectionString; 26 | static readonly string QueueName = TestConfig.GetName("input"); 27 | 28 | readonly ConsoleLoggerFactory _consoleLoggerFactory = new ConsoleLoggerFactory(false); 29 | 30 | BuiltinHandlerActivator _activator; 31 | AzureServiceBusTransport _transport; 32 | IBusStarter _busStarter; 33 | IBus _bus; 34 | 35 | protected override void SetUp() 36 | { 37 | _transport = new AzureServiceBusTransport(ConnectionString, QueueName, _consoleLoggerFactory, new TplAsyncTaskFactory(_consoleLoggerFactory), new DefaultNameFormatter(), new Messages.DefaultMessageConverter()); 38 | 39 | Using(_transport); 40 | 41 | _transport.Initialize(); 42 | _transport.PurgeInputQueue(); 43 | 44 | _activator = new BuiltinHandlerActivator(); 45 | 46 | _busStarter = Configure.With(_activator) 47 | .Logging(l => l.Use(new ListLoggerFactory(outputToConsole: true, detailed: true))) 48 | .Transport(t => 49 | { 50 | t.UseAzureServiceBus(ConnectionString, QueueName) 51 | .SetMessagePeekLockDuration(messagePeekLockDuration: TimeSpan.FromMinutes(1)) 52 | .AutomaticallyRenewPeekLock(); 53 | }) 54 | .Options(o => 55 | { 56 | o.SetNumberOfWorkers(1); 57 | o.SetMaxParallelism(1); 58 | }) 59 | .Create(); 60 | 61 | _bus = _busStarter.Bus; 62 | 63 | Using(_bus); 64 | } 65 | 66 | [Test] 67 | public async Task ItWorks() 68 | { 69 | var gotMessage = new ManualResetEvent(false); 70 | 71 | _activator.Handle(async (bus, context, message) => 72 | { 73 | Console.WriteLine($"Got message with ID {context.Headers.GetValue(Headers.MessageId)} - waiting 2 minutes...."); 74 | await Task.Delay(TimeSpan.FromMinutes(2)); 75 | Console.WriteLine("done waiting"); 76 | 77 | gotMessage.Set(); 78 | }); 79 | 80 | _busStarter.Start(); 81 | 82 | await _bus.SendLocal("hej med dig min ven!"); 83 | 84 | gotMessage.WaitOrDie(TimeSpan.FromMinutes(2.1)); 85 | 86 | // shut down bus 87 | _bus.Dispose(); 88 | 89 | // see if queue is empty 90 | using var scope = new RebusTransactionScope(); 91 | 92 | var message = await _transport.Receive(scope.TransactionContext, CancellationToken.None); 93 | 94 | await scope.CompleteAsync(); 95 | 96 | if (message != null) 97 | { 98 | throw new AssertionException( 99 | $"Did not expect to receive a message - got one with ID {message.Headers.GetValue(Headers.MessageId)}"); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusPrefetchTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | using NUnit.Framework; 9 | using Rebus.Activation; 10 | using Rebus.AzureServiceBus.NameFormat; 11 | using Rebus.Config; 12 | using Rebus.Extensions; 13 | using Rebus.Logging; 14 | using Rebus.Messages; 15 | using Rebus.Tests.Contracts; 16 | using Rebus.Tests.Contracts.Utilities; 17 | using Rebus.Threading.TaskParallelLibrary; 18 | using Rebus.Transport; 19 | #pragma warning disable 1998 20 | 21 | namespace Rebus.AzureServiceBus.Tests; 22 | 23 | [TestFixture] 24 | public class AzureServiceBusPrefetchTest : FixtureBase 25 | { 26 | readonly string _queueName = TestConfig.GetName("prefetch"); 27 | 28 | /// 29 | /// Initial: 30 | /// Receiving 1000 messages took 98,5 s - that's 10,2 msg/s 31 | /// 32 | /// Removing auto-peek lock renewal: 33 | /// Receiving 1000 messages took 4,8 s - that's 210,0 msg/s 34 | /// Receiving 10000 messages took 71,9 s - that's 139,1 msg/s 35 | /// Receiving 10000 messages took 85,1 s - that's 117,6 msg/s 36 | /// Receiving 10000 messages took 127,5 s - that's 78,4 msg/s 37 | /// Receiving 10000 messages took 98,1 s - that's 101,9 msg/s 38 | /// 39 | /// With prefetch 10: 40 | /// Receiving 10000 messages took 35,7 s - that's 280,3 msg/s 41 | /// Receiving 10000 messages took 55,1 s - that's 181,5 msg/s 42 | /// 43 | /// With prefetch 100: 44 | /// Receiving 10000 messages took 31,3 s - that's 319,4 msg/s 45 | /// 46 | /// With prefetch 20: 47 | /// Receiving 10000 messages took 30,3 s - that's 330,1 msg/s 48 | /// 49 | /// With prefetch 10: 50 | /// Receiving 10000 messages took 28,8 s - that's 347,6 msg/s 51 | /// 52 | /// 53 | [TestCase(10, 1000, 10)] 54 | [TestCase(50, 1000, 10)] 55 | [TestCase(100, 1000, 10)] 56 | [TestCase(100, 1000, 50)] 57 | [TestCase(100, 1000, 100)] 58 | [TestCase(10, 10000, 10, Ignore = "takes too long to run always")] 59 | [TestCase(20, 10000, 10, Ignore = "takes too long to run always")] 60 | [TestCase(30, 10000, 10, Ignore = "takes too long to run always")] 61 | [TestCase(50, 10000, 10, Ignore = "takes too long to run always")] 62 | [TestCase(100, 10000, 10, Ignore = "takes too long to run always")] 63 | public void WorksWithPrefetch(int prefetch, int numberOfMessages, int maxParallelism) 64 | { 65 | var activator = Using(new BuiltinHandlerActivator()); 66 | var counter = new SharedCounter(numberOfMessages); 67 | 68 | Using(counter); 69 | 70 | activator.Handle(async str => 71 | { 72 | counter.Decrement(); 73 | }); 74 | 75 | Console.WriteLine("Sending {0} messages", numberOfMessages); 76 | 77 | var transport = GetTransport(); 78 | var tasks = Enumerable.Range(0, numberOfMessages) 79 | .Select(i => $"THIS IS MESSAGE # {i}") 80 | .Select(async msg => 81 | { 82 | using var scope = new RebusTransactionScope(); 83 | 84 | var headers = DefaultHeaders(); 85 | var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(msg)); 86 | var transportMessage = new TransportMessage(headers, body); 87 | 88 | await transport.Send(_queueName, transportMessage, scope.TransactionContext); 89 | 90 | await scope.CompleteAsync(); 91 | }) 92 | .ToArray(); 93 | 94 | Task.WhenAll(tasks).Wait(); 95 | 96 | Console.WriteLine("Receiving {0} messages", numberOfMessages); 97 | 98 | var stopwatch = Stopwatch.StartNew(); 99 | 100 | Configure.With(activator) 101 | .Logging(l => l.Console(LogLevel.Info)) 102 | .Transport(t => 103 | { 104 | t.UseAzureServiceBus(AsbTestConfig.ConnectionString, _queueName) 105 | .EnablePrefetching(prefetch); 106 | }) 107 | .Options(o => 108 | { 109 | o.SetNumberOfWorkers(1); 110 | o.SetMaxParallelism(maxParallelism); 111 | }) 112 | .Start(); 113 | 114 | counter.WaitForResetEvent(timeoutSeconds: (int)(numberOfMessages * 0.1 + 3)); 115 | 116 | var elapsedSeconds = stopwatch.Elapsed.TotalSeconds; 117 | 118 | Console.WriteLine("Receiving {0} messages took {1:0.0} s - that's {2:0.0} msg/s", 119 | numberOfMessages, elapsedSeconds, numberOfMessages / elapsedSeconds); 120 | } 121 | 122 | protected override void TearDown() 123 | { 124 | //AzureServiceBusTransportFactory.DeleteQueue(_queueName); 125 | } 126 | 127 | Dictionary DefaultHeaders() 128 | { 129 | return new Dictionary 130 | { 131 | {Headers.MessageId, Guid.NewGuid().ToString()}, 132 | {Headers.ContentType, "application/json;charset=utf-8"}, 133 | {Headers.Type, typeof(TMessage).GetSimpleAssemblyQualifiedName()}, 134 | }; 135 | } 136 | 137 | ITransport GetTransport() 138 | { 139 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 140 | var asyncTaskFactory = new TplAsyncTaskFactory(consoleLoggerFactory); 141 | var connectionString = AsbTestConfig.ConnectionString; 142 | 143 | //var transport = new AzureServiceBusTransport(connectionString, _queueName, consoleLoggerFactory, asyncTaskFactory); 144 | var transport = new AzureServiceBusTransport(connectionString, _queueName, consoleLoggerFactory, asyncTaskFactory, new DefaultNameFormatter(), new Messages.DefaultMessageConverter()); 145 | Using(transport); 146 | transport.Initialize(); 147 | transport.PurgeInputQueue(); 148 | 149 | return transport; 150 | } 151 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusTransportClientSettingsDoNotConfigureTopicTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Config; 3 | 4 | namespace Rebus.AzureServiceBus.Tests; 5 | 6 | [TestFixture] 7 | public class AzureServiceBusTransportClientSettingsDoNotConfigureTopicTest 8 | { 9 | [Test] 10 | public void DoNotConfigureTopic_SetsDoNotConfigureTopicEnabled_ToTrue() 11 | { 12 | var settings = new AzureServiceBusTransportClientSettings(); 13 | 14 | settings.DoNotConfigureTopic(); 15 | 16 | Assert.That(settings.DoNotConfigureTopicEnabled, Is.True); 17 | } 18 | 19 | [Test] 20 | public void DoNotConfigureTopicEnabled_IsFalse_ByDefault() 21 | { 22 | var settings = new AzureServiceBusTransportClientSettings(); 23 | 24 | Assert.That(settings.DoNotConfigureTopicEnabled, Is.False); 25 | } 26 | 27 | [Test] 28 | public void DoNotConfigureTopic_ReturnsSelf_ForChaining() 29 | { 30 | var settings = new AzureServiceBusTransportClientSettings(); 31 | 32 | var result = settings.DoNotConfigureTopic(); 33 | 34 | Assert.That(result, Is.SameAs(settings)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusTransportDoNotConfigureTopicTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.AzureServiceBus.NameFormat; 6 | using Rebus.Logging; 7 | using Rebus.Tests.Contracts; 8 | using Rebus.Threading.TaskParallelLibrary; 9 | 10 | namespace Rebus.AzureServiceBus.Tests; 11 | 12 | [TestFixture] 13 | public class AzureServiceBusTransportDoNotConfigureTopicTest : FixtureBase 14 | { 15 | static readonly string TestQueueName = TestConfig.GetName("test-queue"); 16 | readonly ConsoleLoggerFactory _consoleLoggerFactory = new ConsoleLoggerFactory(false); 17 | 18 | [Test] 19 | public async Task RegisterSubscriber_SkipsTopicConfiguration_WhenDoNotConfigureTopicEnabledIsTrue() 20 | { 21 | var transport = new AzureServiceBusTransport( 22 | AsbTestConfig.ConnectionString, 23 | TestQueueName, 24 | _consoleLoggerFactory, 25 | new TplAsyncTaskFactory(_consoleLoggerFactory), 26 | new DefaultNameFormatter(), 27 | new AzureServiceBus.Messages.DefaultMessageConverter()); 28 | 29 | Using(transport); 30 | 31 | transport.Initialize(); 32 | transport.DoNotConfigureTopicEnabled = true; 33 | 34 | await transport.RegisterSubscriber("test-topic", TestQueueName); 35 | 36 | Assert.Pass(); 37 | } 38 | 39 | [Test] 40 | public async Task UnregisterSubscriber_SkipsTopicConfiguration_WhenDoNotConfigureTopicEnabledIsTrue() 41 | { 42 | var transport = new AzureServiceBusTransport( 43 | AsbTestConfig.ConnectionString, 44 | TestQueueName, 45 | _consoleLoggerFactory, 46 | new TplAsyncTaskFactory(_consoleLoggerFactory), 47 | new DefaultNameFormatter(), 48 | new AzureServiceBus.Messages.DefaultMessageConverter()); 49 | 50 | Using(transport); 51 | 52 | transport.Initialize(); 53 | transport.DoNotConfigureTopicEnabled = true; 54 | 55 | await transport.UnregisterSubscriber("test-topic", TestQueueName); 56 | 57 | Assert.Pass(); 58 | } 59 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/AzureServiceBusTransportSettingsDoNotConfigureTopicTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Config; 3 | 4 | namespace Rebus.AzureServiceBus.Tests; 5 | 6 | [TestFixture] 7 | public class AzureServiceBusTransportSettingsDoNotConfigureTopicTest 8 | { 9 | [Test] 10 | public void DoNotConfigureTopic_SetsDoNotConfigureTopicEnabled_ToTrue() 11 | { 12 | var settings = new AzureServiceBusTransportSettings(); 13 | 14 | settings.DoNotConfigureTopic(); 15 | Assert.That(settings.DoNotConfigureTopicEnabled, Is.True); 16 | } 17 | 18 | [Test] 19 | public void DoNotConfigureTopicEnabled_IsFalse_ByDefault() 20 | { 21 | var settings = new AzureServiceBusTransportSettings(); 22 | Assert.That(settings.DoNotConfigureTopicEnabled, Is.False); 23 | } 24 | 25 | [Test] 26 | public void DoNotConfigureTopic_ReturnsSelf_ForChaining() 27 | { 28 | var settings = new AzureServiceBusTransportSettings(); 29 | 30 | var result = settings.DoNotConfigureTopic(); 31 | Assert.That(result, Is.SameAs(settings)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/CanPublishToTopicWithSameNameAsQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using NUnit.Framework; 3 | using Rebus.Activation; 4 | using Rebus.Config; 5 | using Rebus.Tests.Contracts; 6 | 7 | namespace Rebus.AzureServiceBus.Tests.Bugs; 8 | 9 | [TestFixture] 10 | [Ignore("No, we can't. Topics and queues exist in the same namespace, so it's just not possible.")] 11 | public class CanPublishToTopicWithSameNameAsQueue : FixtureBase 12 | { 13 | [Test] 14 | public async Task CanDoIt() 15 | { 16 | using var activator = new BuiltinHandlerActivator(); 17 | 18 | var bus = Configure.With(activator) 19 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, "thenameisthesame")) 20 | .Start(); 21 | 22 | await bus.Advanced.Topics.Publish("thenameisthesame", new TextCarrier("text")); 23 | } 24 | 25 | record TextCarrier(string Text); 26 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/DeferredMessagesAreDeadletteredJustFine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | using Rebus.Activation; 8 | using Rebus.AzureServiceBus.Messages; 9 | using Rebus.AzureServiceBus.NameFormat; 10 | using Rebus.Bus; 11 | using Rebus.Config; 12 | using Rebus.Extensions; 13 | using Rebus.Logging; 14 | using Rebus.Messages; 15 | using Rebus.Pipeline; 16 | using Rebus.Tests.Contracts; 17 | using Rebus.Tests.Contracts.Extensions; 18 | using Rebus.Tests.Contracts.Utilities; 19 | using Rebus.Threading.TaskParallelLibrary; 20 | 21 | #pragma warning disable 1998 22 | 23 | namespace Rebus.AzureServiceBus.Tests.Bugs; 24 | 25 | [TestFixture] 26 | public class DeferredMessagesAreDeadletteredJustFine : FixtureBase 27 | { 28 | AzureServiceBusTransport _deadletters; 29 | ListLoggerFactory _loggerFactory; 30 | 31 | protected override void SetUp() 32 | { 33 | _loggerFactory = new ListLoggerFactory(outputToConsole: false); 34 | 35 | _deadletters = GetAsbTransport("error"); 36 | Using(_deadletters); 37 | _deadletters.Initialize(); 38 | 39 | _deadletters.PurgeInputQueue(); 40 | } 41 | 42 | [Test] 43 | public async Task WhatTheClassSays() 44 | { 45 | var queueName = TestConfig.GetName("defer-deadletter"); 46 | 47 | PurgeQueue(queueName); 48 | 49 | var deliveryAttempts = 0; 50 | 51 | Using(new QueueDeleter(queueName)); 52 | 53 | using var activator = new BuiltinHandlerActivator(); 54 | 55 | activator.Handle(async _ => 56 | { 57 | deliveryAttempts++; 58 | throw new ArgumentException("can't take it"); 59 | }); 60 | 61 | var bus = Configure.With(activator) 62 | .Logging(l => l.Use(_loggerFactory)) 63 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 64 | .Start(); 65 | 66 | var knownMessageId = Guid.NewGuid().ToString(); 67 | 68 | await bus.DeferLocal(TimeSpan.FromSeconds(1), new Poison(), new Dictionary{["custom-id"] = knownMessageId}); 69 | 70 | await Task.Delay(TimeSpan.FromSeconds(1)); 71 | 72 | var transportMessage = await _deadletters.WaitForNextMessage(timeoutSeconds: 5); 73 | 74 | Assert.That(transportMessage.Headers.GetValue("custom-id"), Is.EqualTo(knownMessageId)); 75 | Assert.That(deliveryAttempts, Is.EqualTo(5), "Expected exactly five delivery attempts followed by dead-lettering"); 76 | 77 | var errorLogLines = _loggerFactory.Where(log => log.Level == LogLevel.Error).ToList(); 78 | 79 | Assert.That(errorLogLines.Count, Is.EqualTo(1), "Expected one single 'message was dead-lettered' log line"); 80 | } 81 | 82 | void PurgeQueue(string queueName) 83 | { 84 | using var transport = GetAsbTransport(queueName); 85 | transport.Initialize(); 86 | transport.PurgeInputQueue(); 87 | } 88 | 89 | AzureServiceBusTransport GetAsbTransport(string queueName) => 90 | new AzureServiceBusTransport( 91 | AsbTestConfig.ConnectionString, 92 | queueName, 93 | _loggerFactory, 94 | new TplAsyncTaskFactory(_loggerFactory), 95 | new DefaultNameFormatter(), 96 | new DefaultMessageConverter(), 97 | CancellationToken.None 98 | ); 99 | 100 | class Poison { } 101 | } 102 | 103 | public class Sladrehank : IIncomingStep 104 | { 105 | public async Task Process(IncomingStepContext context, Func next) 106 | { 107 | var transportMessage = context.Load(); 108 | var messageId = transportMessage.GetMessageId(); 109 | 110 | Console.WriteLine($"HANDLING MSG {messageId}"); 111 | 112 | await next(); 113 | } 114 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/DoesNotDieOnHeaderNullValue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Config; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Tests.Contracts.Extensions; 10 | // ReSharper disable ArgumentsStyleOther 11 | // ReSharper disable ArgumentsStyleLiteral 12 | #pragma warning disable 1998 13 | 14 | namespace Rebus.AzureServiceBus.Tests.Bugs; 15 | 16 | [TestFixture] 17 | [Description("Verifies that a NULL value present in the headers dictionary => no trouble")] 18 | public class DoesNotDieOnHeaderNullValue : FixtureBase 19 | { 20 | [Test] 21 | public async Task NoItDoesNot() 22 | { 23 | var queueName = TestConfig.GetName("not-null"); 24 | 25 | Using(new QueueDeleter(queueName)); 26 | 27 | var headerKey = Guid.NewGuid().ToString("N"); 28 | var gotTheMessageAndTheMessageWasGood = new ManualResetEvent(initialState: false); 29 | var activator = Using(new BuiltinHandlerActivator()); 30 | 31 | activator.Handle(async (bus, context, message) => 32 | { 33 | var headers = context.Headers; 34 | 35 | if (headers.TryGetValue(headerKey, out var value) 36 | && value == null) 37 | { 38 | gotTheMessageAndTheMessageWasGood.Set(); 39 | } 40 | }); 41 | 42 | Configure.With(activator) 43 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 44 | .Start(); 45 | 46 | var problematicHeadersBecauseOfNullValue = new Dictionary{{headerKey, null}}; 47 | 48 | await activator.Bus.SendLocal("HEJ MED DIG", problematicHeadersBecauseOfNullValue); 49 | 50 | gotTheMessageAndTheMessageWasGood.WaitOrDie(timeout: TimeSpan.FromSeconds(3)); 51 | } 52 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/EndpointMappingsDoNotInterfereWithPubSub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Bus; 7 | using Rebus.Config; 8 | using Rebus.Routing.TypeBased; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Extensions; 11 | #pragma warning disable CS1998 12 | 13 | // ReSharper disable AccessToDisposedClosure 14 | 15 | namespace Rebus.AzureServiceBus.Tests.Bugs; 16 | 17 | [TestFixture] 18 | [Description("Tried to reproduce an issue, but was unsuccessful.")] 19 | public class EndpointMappingsDoNotInterfereWithPubSub : FixtureBase 20 | { 21 | [Test] 22 | public async Task ItWorksAsItShould() 23 | { 24 | using var gotTheEvent = new ManualResetEvent(initialState: false); 25 | 26 | var publisher = CreateBus("publisher"); 27 | var subscriber = CreateBus("subscriber", messageHandler: _ => gotTheEvent.Set()); 28 | 29 | await subscriber.Subscribe(); 30 | 31 | await publisher.Publish(new MyEvent()); 32 | 33 | gotTheEvent.WaitOrDie(TimeSpan.FromSeconds(5)); 34 | } 35 | 36 | IBus CreateBus(string queueName, Action messageHandler = null) 37 | { 38 | var activator = Using(new BuiltinHandlerActivator()); 39 | 40 | if (messageHandler != null) 41 | { 42 | activator.Handle(async msg => messageHandler(msg)); 43 | } 44 | 45 | return Configure.With(activator) 46 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 47 | .Routing(r => r.TypeBased().Map("somethingsomething completely invalid full of invalid characters like :/\\😈 and such")) 48 | .Start(); 49 | } 50 | 51 | record MyEvent; 52 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/FastSubscriber.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Azure.Messaging.ServiceBus.Administration; 6 | using NUnit.Framework; 7 | using Rebus.Activation; 8 | using Rebus.Config; 9 | using Rebus.Logging; 10 | using Rebus.Tests.Contracts; 11 | #pragma warning disable 1998 12 | 13 | namespace Rebus.AzureServiceBus.Tests.Bugs; 14 | 15 | [TestFixture] 16 | public class FastSubscriber : FixtureBase 17 | { 18 | [TestCase(10)] 19 | public async Task TakeTime(int topicCount) 20 | { 21 | Using(new QueueDeleter("my-input-queue")); 22 | 23 | using var activator = new BuiltinHandlerActivator(); 24 | 25 | var bus = Configure.With(activator) 26 | .Logging(l => l.Console(LogLevel.Warn)) 27 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, "my-input-queue")) 28 | .Start(); 29 | 30 | var stopwatch = Stopwatch.StartNew(); 31 | 32 | Task.WaitAll(Enumerable.Range(0, topicCount) 33 | .Select(async n => 34 | { 35 | var topicName = $"topic-{n}"; 36 | 37 | Using(new TopicDeleter(topicName)); 38 | 39 | await bus.Advanced.Topics.Subscribe(topicName); 40 | }) 41 | .ToArray()); 42 | 43 | var elapsedSeconds = stopwatch.Elapsed.TotalSeconds; 44 | 45 | Console.WriteLine($"Subscribing to {topicCount} topics took {elapsedSeconds:0.0}s - that's {elapsedSeconds / topicCount:0.0} s/subscription"); 46 | } 47 | 48 | [Test] 49 | [Explicit("run manually")] 50 | public async Task DeleteAllTopics() 51 | { 52 | var managementClient = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 53 | 54 | await foreach (var topic in managementClient.GetTopicsAsync()) 55 | { 56 | Console.Write($"Deleting '{topic.Name}'... "); 57 | await managementClient.DeleteTopicAsync(topic.Name); 58 | Console.WriteLine("OK"); 59 | } 60 | } 61 | 62 | [Test] 63 | [Explicit("run manually")] 64 | public async Task DeleteAllQueues() 65 | { 66 | var managementClient = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 67 | 68 | await foreach (var queue in managementClient.GetQueuesAsync()) 69 | { 70 | Console.Write($"Deleting '{queue.Name}'... "); 71 | await managementClient.DeleteQueueAsync(queue.Name); 72 | Console.WriteLine("OK"); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/OneWayClientCanDeferMessageOntoAnotherQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Routing.TypeBased; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Tests.Contracts.Extensions; 10 | // ReSharper disable ArgumentsStyleOther 11 | // ReSharper disable ArgumentsStyleStringLiteral 12 | 13 | // ReSharper disable ArgumentsStyleLiteral 14 | #pragma warning disable 1998 15 | 16 | namespace Rebus.AzureServiceBus.Tests.Bugs; 17 | 18 | [TestFixture] 19 | public class OneWayClientCanDeferMessageOntoAnotherQueue : FixtureBase 20 | { 21 | [Test] 22 | public async Task ItWorksAsItShould() 23 | { 24 | var connectionString = AsbTestConfig.ConnectionString; 25 | var queueName = TestConfig.GetName("some-target-queue"); 26 | var gotTheDeferredMessage = new ManualResetEvent(initialState: false); 27 | 28 | // start bus instance, which is supposed to receive the deferred message 29 | var activator = Using(new BuiltinHandlerActivator()); 30 | 31 | activator.Handle(async msg => gotTheDeferredMessage.Set()); 32 | 33 | Configure.With(activator) 34 | .Transport(t => t.UseAzureServiceBus(connectionString, queueName)) 35 | .Start(); 36 | 37 | // create one-way client 38 | var sender = Configure.With(Using(new BuiltinHandlerActivator())) 39 | .Transport(t => t.UseAzureServiceBusAsOneWayClient(connectionString)) 40 | .Routing(r => r.TypeBased().Map(queueName)) 41 | .Start(); 42 | 43 | await sender.Defer(TimeSpan.FromSeconds(1), new DeferredMessage()); 44 | 45 | gotTheDeferredMessage.WaitOrDie(timeout: TimeSpan.FromSeconds(50), 46 | errorMessage: "Did not receive the message within 5 s timeout"); 47 | } 48 | 49 | class DeferredMessage { } 50 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/QueueDeleter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Azure.Messaging.ServiceBus; 3 | using Azure.Messaging.ServiceBus.Administration; 4 | using Rebus.Internals; 5 | 6 | namespace Rebus.AzureServiceBus.Tests.Bugs; 7 | 8 | public class QueueDeleter : IDisposable 9 | { 10 | readonly string _queueName; 11 | 12 | public QueueDeleter(string queueName) 13 | { 14 | _queueName = queueName; 15 | } 16 | 17 | public void Dispose() 18 | { 19 | var managementClient = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 20 | 21 | AsyncHelpers.RunSync(async () => 22 | { 23 | try 24 | { 25 | var topicDescription = await managementClient.GetQueueAsync(_queueName); 26 | 27 | await managementClient.DeleteQueueAsync(topicDescription.Value.Name); 28 | 29 | Console.WriteLine($"Deleted queue '{_queueName}'"); 30 | } 31 | catch (ServiceBusException) 32 | { 33 | // it's ok 34 | } 35 | }); 36 | } 37 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/TooBigHeadersTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.Messages; 7 | using Rebus.AzureServiceBus.NameFormat; 8 | using Rebus.Config; 9 | using Rebus.Logging; 10 | using Rebus.Messages; 11 | using Rebus.Retry.Simple; 12 | using Rebus.Tests.Contracts; 13 | using Rebus.Threading.TaskParallelLibrary; 14 | using Rebus.Transport; 15 | 16 | namespace Rebus.AzureServiceBus.Tests.Bugs; 17 | 18 | [TestFixture] 19 | public class TooBigHeadersTest : FixtureBase 20 | { 21 | AzureServiceBusTransport errorQueueTransport; 22 | 23 | protected override void SetUp() 24 | { 25 | var loggerFactory = new ConsoleLoggerFactory(false); 26 | 27 | errorQueueTransport = new AzureServiceBusTransport(AsbTestConfig.ConnectionString, "error", loggerFactory, new TplAsyncTaskFactory(loggerFactory), new DefaultNameFormatter(), new DefaultMessageConverter()); 28 | 29 | Using(errorQueueTransport); 30 | 31 | errorQueueTransport.Initialize(); 32 | errorQueueTransport.PurgeInputQueue(); 33 | } 34 | 35 | [Test] 36 | public async Task ItWorks() 37 | { 38 | const int MaxDeliveryAttempts = 3; 39 | 40 | var queueName = TestConfig.GetName("test-queue"); 41 | 42 | Using(new QueueDeleter(queueName)); 43 | 44 | var activator = new BuiltinHandlerActivator(); 45 | 46 | Using(activator); 47 | 48 | var done = new ManualResetEvent(false); 49 | 50 | Using(done); 51 | 52 | activator.Handle(message => 53 | { 54 | var exceptionMessage = new string('a', 30 * 1024); // 30 KB * 3 < 256KB max message size. 55 | 56 | throw new InvalidOperationException(exceptionMessage); 57 | }); 58 | 59 | Configure.With(activator) 60 | .Logging(l => l.Console(minLevel: LogLevel.Error)) 61 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 62 | .Options(o => o.RetryStrategy(maxDeliveryAttempts: MaxDeliveryAttempts)) 63 | .Start(); 64 | 65 | await activator.Bus.SendLocal("Hello World"); 66 | 67 | var nextMessage = await GetNextMessageFromErrorQueue(timeoutSeconds: 10); 68 | 69 | Assert.That(nextMessage.Headers, Contains.Key(Headers.ErrorDetails)); 70 | 71 | var errorDetails = nextMessage.Headers[Headers.ErrorDetails]; 72 | 73 | Console.WriteLine($@"------------------------------------------------------------------------------------------------ 74 | The following error details got attached to the message: 75 | 76 | {errorDetails}"); 77 | } 78 | 79 | async Task GetNextMessageFromErrorQueue(int timeoutSeconds) 80 | { 81 | var cancellationTokenSource = new CancellationTokenSource(); 82 | var cancellationToken = cancellationTokenSource.Token; 83 | var timeout = TimeSpan.FromSeconds(timeoutSeconds); 84 | 85 | cancellationTokenSource.CancelAfter(timeout); 86 | 87 | try 88 | { 89 | while (true) 90 | { 91 | cancellationToken.ThrowIfCancellationRequested(); 92 | 93 | using (var scope = new RebusTransactionScope()) 94 | { 95 | var message = await errorQueueTransport.Receive(scope.TransactionContext, cancellationToken); 96 | 97 | try 98 | { 99 | if (message != null) return message; 100 | } 101 | finally 102 | { 103 | await scope.CompleteAsync(); 104 | } 105 | } 106 | } 107 | } 108 | catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) 109 | { 110 | throw new TimeoutException($"Did not receive message in queue '{errorQueueTransport.Address}' within timeout of {timeout}"); 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/TopicDeleter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Azure.Messaging.ServiceBus; 3 | using Azure.Messaging.ServiceBus.Administration; 4 | using Rebus.Internals; 5 | 6 | namespace Rebus.AzureServiceBus.Tests.Bugs; 7 | 8 | public class TopicDeleter : IDisposable 9 | { 10 | readonly string _topicName; 11 | 12 | public TopicDeleter(string topicName) 13 | { 14 | _topicName = topicName; 15 | } 16 | 17 | public void Dispose() 18 | { 19 | var managementClient = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 20 | 21 | AsyncHelpers.RunSync(async () => 22 | { 23 | try 24 | { 25 | var topicDescription = await managementClient.GetTopicAsync(_topicName); 26 | 27 | await managementClient.DeleteTopicAsync(topicDescription.Value.Name); 28 | 29 | Console.WriteLine($"Deleted topic '{_topicName}'"); 30 | } 31 | catch (ServiceBusException) 32 | { 33 | // it's ok 34 | } 35 | }); 36 | } 37 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/VerifyAssumptionAboutDisposingAsyncTaskMultipleTimes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | using Rebus.Logging; 5 | using Rebus.Tests.Contracts; 6 | using Rebus.Threading.TaskParallelLibrary; 7 | #pragma warning disable 1998 8 | 9 | namespace Rebus.AzureServiceBus.Tests.Bugs; 10 | 11 | [TestFixture] 12 | public class VerifyAssumptionAboutDisposingAsyncTaskMultipleTimes : FixtureBase 13 | { 14 | [Test] 15 | public async Task NoProblem() 16 | { 17 | var consoleLoggerFactory = new ConsoleLoggerFactory(colored: false); 18 | var factory = new TplAsyncTaskFactory(consoleLoggerFactory); 19 | var logger = consoleLoggerFactory.GetLogger(); 20 | var task = factory.Create("test-task", intervalSeconds: 1, action: async () => logger.Info("Called back")); 21 | 22 | task.Start(); 23 | 24 | await Task.Delay(TimeSpan.FromSeconds(4)); 25 | 26 | task.Dispose(); 27 | task.Dispose(); 28 | task.Dispose(); 29 | task.Dispose(); 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/VerifyDeferredMessagesWorkAsExpected.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Routing.TypeBased; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Tests.Contracts.Extensions; 10 | 11 | #pragma warning disable 1998 12 | 13 | namespace Rebus.AzureServiceBus.Tests.Bugs; 14 | 15 | [TestFixture] 16 | public class VerifyDeferredMessagesWorkAsExpected : FixtureBase 17 | { 18 | [Test] 19 | public async Task CanDeferWithRouting() 20 | { 21 | using var messageReceived = new ManualResetEvent(initialState: false); 22 | using var senderActivator = new BuiltinHandlerActivator(); 23 | using var receiverActivator = new BuiltinHandlerActivator(); 24 | 25 | receiverActivator.Handle(async _ => messageReceived.Set()); 26 | 27 | var senderQueueName = TestConfig.GetName("deferral-routing-sender"); 28 | var receiverQueueName = TestConfig.GetName("deferral-routing-receiver"); 29 | 30 | var bus = Configure.With(senderActivator) 31 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, senderQueueName)) 32 | .Routing(t => t.TypeBased().Map(receiverQueueName)) 33 | .Start(); 34 | 35 | Configure.With(receiverActivator) 36 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, receiverQueueName)) 37 | .Start(); 38 | 39 | //await bus.DeferLocal(TimeSpan.FromSeconds(0.1), new RoutedEvent()); 40 | await bus.Defer(TimeSpan.FromSeconds(0.1), new RoutedEvent()); 41 | 42 | messageReceived.WaitOrDie(timeout: TimeSpan.FromSeconds(3), errorMessage: "Did not receive RoutedEvent within 3 s of waiting"); 43 | } 44 | 45 | class RoutedEvent { } 46 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/VerifyHowThingsWorkWhenConnectionStringEndpointHasPath.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | using Rebus.Activation; 5 | using Rebus.Config; 6 | using Rebus.Tests.Contracts; 7 | 8 | namespace Rebus.AzureServiceBus.Tests.Bugs; 9 | 10 | [TestFixture] 11 | public class VerifyHowThingsWorkWhenConnectionStringEndpointHasPath : FixtureBase 12 | { 13 | [Test] 14 | public async Task ItWorks() 15 | { 16 | var name = TestConfig.GetName("namespace"); 17 | 18 | Using(new QueueDeleter("namespace/test-queue")); 19 | 20 | var conn = new ConnectionStringParser(AsbTestConfig.ConnectionString); 21 | var newConn = new ConnectionStringParser($"{conn.Endpoint.TrimEnd('/')}/{name}", conn.SharedAccessKeyName, conn.SharedAccessKey, conn.EntityPath); 22 | 23 | using var activator = new BuiltinHandlerActivator(); 24 | 25 | Configure.With(activator) 26 | .Transport(t => t.UseAzureServiceBus(newConn.GetConnectionString(), "test-queue")) 27 | .Start(); 28 | 29 | await Task.Delay(TimeSpan.FromSeconds(3)); 30 | } 31 | 32 | [Test] 33 | public async Task QueueWithSlash() 34 | { 35 | var name = TestConfig.GetName("namespace"); 36 | 37 | Using(new QueueDeleter("namespace/test-queue")); 38 | 39 | using var activator = new BuiltinHandlerActivator(); 40 | 41 | Configure.With(activator) 42 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, $"{name}/test-queue")) 43 | .Start(); 44 | 45 | await Task.Delay(TimeSpan.FromSeconds(3)); 46 | } 47 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/VerifyQueueNameWhenUsingLegacyNaming.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Tests.Contracts; 8 | using Rebus.Tests.Contracts.Extensions; 9 | #pragma warning disable 1998 10 | 11 | namespace Rebus.AzureServiceBus.Tests.Bugs; 12 | 13 | [TestFixture] 14 | public class VerifyQueueNameWhenUsingLegacyNaming : FixtureBase 15 | { 16 | [TestCase("tester_input", false)] 17 | [TestCase("tester.input", false)] 18 | [TestCase("tester/input", false)] 19 | [TestCase("tester_input", true)] 20 | [TestCase("tester.input", true)] 21 | [TestCase("tester/input", true)] 22 | public async Task ItWorks(string queueName, bool useLegacyMode) 23 | { 24 | Console.WriteLine($"Checking that stuff works with queue name '{queueName}' and legacy mode = {useLegacyMode}"); 25 | 26 | var gotTheMessage = new ManualResetEvent(false); 27 | var activator = new BuiltinHandlerActivator(); 28 | 29 | Using(activator); 30 | 31 | activator.Handle(async message => gotTheMessage.Set()); 32 | 33 | Configure.With(activator) 34 | .Transport(t => 35 | { 36 | var settings = t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName); 37 | 38 | if (useLegacyMode) 39 | { 40 | settings.UseLegacyNaming(); 41 | } 42 | }) 43 | .Start(); 44 | 45 | await activator.Bus.Subscribe(); 46 | 47 | await activator.Bus.Publish("HEJ MED DIG MIN VEEEEN"); 48 | 49 | gotTheMessage.WaitOrDie(TimeSpan.FromSeconds(3)); 50 | } 51 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/VerifyRenewAndDifferentInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Tests.Contracts; 8 | using Rebus.Tests.Contracts.Extensions; 9 | 10 | namespace Rebus.AzureServiceBus.Tests.Bugs; 11 | 12 | [TestFixture] 13 | public class VerifyRenewAndDifferentInput : FixtureBase 14 | { 15 | [Test] 16 | public async Task ItWorks() 17 | { 18 | var activator = new BuiltinHandlerActivator(); 19 | var done = new ManualResetEvent(false); 20 | 21 | activator.Handle(async message => 22 | { 23 | await Task.Delay(TimeSpan.FromMinutes(1)); 24 | 25 | done.Set(); 26 | }); 27 | 28 | Using(activator); 29 | 30 | var bus = Configure.With(activator) 31 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, "renew-tjek") 32 | .AutomaticallyRenewPeekLock() 33 | .SetMessagePeekLockDuration(TimeSpan.FromSeconds(10))) 34 | .Start(); 35 | 36 | await bus.SendLocal("HEJ MED DIG MIN VEN!"); 37 | 38 | done.WaitOrDie(TimeSpan.FromMinutes(1.5), 39 | errorMessage: "Message did not finish handling within 1.5 minute timeout"); 40 | } 41 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/VerifyTopicsAreCreatedAsNeeded.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Messaging.ServiceBus.Administration; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Config; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Tests.Contracts.Extensions; 10 | #pragma warning disable 1998 11 | 12 | namespace Rebus.AzureServiceBus.Tests.Bugs; 13 | 14 | [TestFixture] 15 | public class VerifyTopicsAreCreatedAsNeeded : FixtureBase 16 | { 17 | BuiltinHandlerActivator _activator; 18 | IBusStarter _busStarter; 19 | 20 | protected override void SetUp() 21 | { 22 | _activator = new BuiltinHandlerActivator(); 23 | 24 | Using(_activator); 25 | 26 | _busStarter = Configure.With(_activator) 27 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, TestConfig.GetName("topictest"))) 28 | .Create(); 29 | } 30 | 31 | [Test] 32 | public async Task CanUseTopicWithRandomName() 33 | { 34 | await DeleteAllTopics(); 35 | 36 | var topicName = Guid.NewGuid().ToString("N"); 37 | 38 | // try to ensure we remove the topic afterwards 39 | Using(new TopicDeleter(topicName)); 40 | 41 | var eventWasReceived = new ManualResetEvent(false); 42 | 43 | _activator.Handle(async str => eventWasReceived.Set()); 44 | 45 | _busStarter.Start(); 46 | 47 | var bus = _activator.Bus; 48 | 49 | await bus.Advanced.Topics.Subscribe(topicName); 50 | 51 | await bus.Advanced.Topics.Publish(topicName, "hej med dig min veeeeen!"); 52 | 53 | eventWasReceived.WaitOrDie(timeout: TimeSpan.FromSeconds(5)); 54 | } 55 | 56 | [Test] 57 | public async Task CanPublishToTopicThatDoesNotExist() 58 | { 59 | _busStarter.Start(); 60 | 61 | await DeleteAllTopics(); 62 | 63 | var topicName = Guid.NewGuid().ToString("N"); 64 | 65 | // try to ensure we remove the topic afterwards 66 | Using(new TopicDeleter(topicName)); 67 | 68 | var bus = _activator.Bus; 69 | 70 | // must not throw! 71 | await bus.Advanced.Topics.Publish(topicName, "hej med dig min veeeeen!"); 72 | } 73 | 74 | static async Task DeleteAllTopics() 75 | { 76 | var managementClient = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 77 | 78 | await foreach (var topic in managementClient.GetTopicsAsync()) 79 | { 80 | Console.WriteLine($"Deleting topic '{topic.Name}'"); 81 | await managementClient.DeleteTopicAsync(topic.Name); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/VerifyTopicsCanHaveSlashesInThem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Messaging.ServiceBus.Administration; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Config; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Tests.Contracts.Extensions; 10 | 11 | #pragma warning disable 1998 12 | 13 | namespace Rebus.AzureServiceBus.Tests.Bugs; 14 | 15 | [TestFixture] 16 | public class VerifyTopicsCanHaveSlashesInThem : FixtureBase 17 | { 18 | [Test] 19 | public async Task ItsTrueTheyCan() 20 | { 21 | var queueName = TestConfig.GetName("topics-with-slashes"); 22 | const string topicNameWithSlash = "primitives/string"; 23 | 24 | Using(new TopicDeleter(topicNameWithSlash)); 25 | Using(new QueueDeleter(queueName)); 26 | 27 | var activator = new BuiltinHandlerActivator(); 28 | 29 | Using(activator); 30 | 31 | var gotTheString = new ManualResetEvent(false); 32 | 33 | activator.Handle(async message => 34 | { 35 | gotTheString.Set(); 36 | }); 37 | 38 | var bus = Configure.With(activator) 39 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 40 | .Start(); 41 | 42 | await bus.Advanced.Topics.Subscribe(topicNameWithSlash); 43 | 44 | await bus.Advanced.Topics.Publish(topicNameWithSlash, "WHOA DET VIRKER!!"); 45 | 46 | gotTheString.WaitOrDie(TimeSpan.FromSeconds(5)); 47 | 48 | 49 | 50 | // make a final verification: the topic has a / in it 51 | 52 | var managementClient = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 53 | var topicDescription = await managementClient.GetTopicAsync(topicNameWithSlash); 54 | 55 | Assert.That(topicDescription.Value.Name, Contains.Substring(topicNameWithSlash)); 56 | } 57 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/WorkWithIntegratedAuth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Identity; 5 | using Azure.Messaging.ServiceBus.Administration; 6 | using NUnit.Framework; 7 | using Rebus.Activation; 8 | using Rebus.Config; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Extensions; 11 | 12 | // ReSharper disable AccessToDisposedClosure 13 | #pragma warning disable 1998 14 | 15 | namespace Rebus.AzureServiceBus.Tests.Bugs; 16 | 17 | [TestFixture] 18 | public class WorkWithIntegratedAuth : FixtureBase 19 | { 20 | [Test] 21 | [Explicit("run manually")] 22 | public async Task HowAboutThis() 23 | { 24 | var administrationClient = new ServiceBusAdministrationClient("", new ManagedIdentityCredential()); 25 | 26 | var queues = administrationClient.GetQueuesAsync(); 27 | 28 | await foreach (var queue in queues) 29 | { 30 | Console.WriteLine($"🐄: {queue.Name}"); 31 | } 32 | } 33 | 34 | [Test] 35 | [Explicit("run manually")] 36 | public async Task SureDoes() 37 | { 38 | var connectionString = AsbTestConfig.ConnectionString; 39 | 40 | using var activator = new BuiltinHandlerActivator(); 41 | using var gotTheMessage = new ManualResetEvent(initialState: false); 42 | 43 | activator.Handle(async _ => gotTheMessage.Set()); 44 | 45 | Configure.With(activator) 46 | .Transport(t => t.UseAzureServiceBus(connectionString, "integrationtest")) 47 | .Start(); 48 | 49 | await activator.Bus.SendLocal("HEJ 🙂"); 50 | 51 | gotTheMessage.WaitOrDie(timeout: TimeSpan.FromSeconds(5), "Did not receive the string within 5 s"); 52 | } 53 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Bugs/WorksWithDefaultCredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Azure.Identity; 4 | using Azure.Messaging.ServiceBus.Administration; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Config; 8 | using Rebus.Tests.Contracts; 9 | 10 | namespace Rebus.AzureServiceBus.Tests.Bugs; 11 | 12 | [TestFixture] 13 | [Explicit("Requires bring logged in as an appropriate Azure user in Visual Studio")] 14 | public class WorksWithDefaultCredential : FixtureBase 15 | { 16 | static string EndpointOnlyConnectionString => $"Endpoint={AsbTestConfig.GetEndpointUriFromConnectionString()}"; 17 | 18 | [Test] 19 | public async Task CanStartBusWithDefaultCredential() 20 | { 21 | using var activator = new BuiltinHandlerActivator(); 22 | 23 | Configure.With(activator) 24 | .Transport(t => t.UseAzureServiceBus(EndpointOnlyConnectionString, "test-queue", GetDefaultAzureCredential())) 25 | .Start(); 26 | 27 | await Task.Delay(TimeSpan.FromSeconds(2)); 28 | } 29 | 30 | [Test] 31 | public async Task CheckRaw() 32 | { 33 | var credential = GetDefaultAzureCredential(); 34 | 35 | var client = new ServiceBusAdministrationClient(AsbTestConfig.GetHostnameFromConnectionString(), credential); 36 | 37 | var queues = client.GetQueuesAsync(); 38 | 39 | await foreach (var queue in queues) 40 | { 41 | Console.WriteLine(queue.Name); 42 | } 43 | } 44 | 45 | static DefaultAzureCredential GetDefaultAzureCredential() => new(new DefaultAzureCredentialOptions 46 | { 47 | Diagnostics = 48 | { 49 | LoggedHeaderNames = { "x-ms-request-id" }, 50 | LoggedQueryParameters = { "api-version" }, 51 | IsLoggingContentEnabled = true, 52 | IsAccountIdentifierLoggingEnabled = true, 53 | IsDistributedTracingEnabled = true, 54 | IsLoggingEnabled = true, 55 | IsTelemetryEnabled = true, 56 | }, 57 | 58 | ExcludeAzureCliCredential = true, 59 | ExcludeAzurePowerShellCredential = true, 60 | ExcludeEnvironmentCredential = true, 61 | ExcludeInteractiveBrowserCredential = true, 62 | ExcludeVisualStudioCodeCredential = true, 63 | ExcludeSharedTokenCacheCredential = true, 64 | ExcludeManagedIdentityCredential = true, 65 | }); 66 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/CanChangeDefaultLockDuration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Azure.Messaging.ServiceBus.Administration; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Internals; 8 | using Rebus.Tests.Contracts; 9 | 10 | namespace Rebus.AzureServiceBus.Tests; 11 | 12 | [TestFixture] 13 | public class CanChangeDefaultLockDuration : FixtureBase 14 | { 15 | ServiceBusAdministrationClient _managementClient; 16 | string _queueName; 17 | 18 | protected override void SetUp() 19 | { 20 | _managementClient = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 21 | _queueName = TestConfig.GetName("lockduration"); 22 | 23 | AsyncHelpers.RunSync(() => _managementClient.DeleteQueueIfExistsAsync(_queueName)); 24 | } 25 | 26 | protected override void TearDown() 27 | { 28 | AsyncHelpers.RunSync(() => _managementClient.DeleteQueueIfExistsAsync(_queueName)); 29 | } 30 | 31 | [Test] 32 | public async Task CanConfigureDuplicateDetection() 33 | { 34 | var duration = TimeSpan.FromHours(2); 35 | 36 | Configure.With(Using(new BuiltinHandlerActivator())) 37 | .Transport(t => 38 | { 39 | t.UseAzureServiceBus(AsbTestConfig.ConnectionString, _queueName) 40 | .SetDuplicateDetectionHistoryTimeWindow(duration); 41 | }) 42 | .Start(); 43 | 44 | CleanUpDisposables(); 45 | 46 | var queueDescription = await _managementClient.GetQueueAsync(_queueName); 47 | 48 | Assert.That(queueDescription.Value.RequiresDuplicateDetection, Is.True); 49 | Assert.That(queueDescription.Value.DuplicateDetectionHistoryTimeWindow, Is.EqualTo(duration)); 50 | } 51 | 52 | [Test] 53 | public async Task CanChangeTheseSettingsAfterTheFact() 54 | { 55 | void InitializeBusWith(TimeSpan peekLockDuration, TimeSpan defaultMessageTtl, TimeSpan autoDeleteOnIdle) 56 | { 57 | Configure.With(Using(new BuiltinHandlerActivator())) 58 | .Transport(t => 59 | { 60 | t.UseAzureServiceBus(AsbTestConfig.ConnectionString, _queueName) 61 | .SetMessagePeekLockDuration(peekLockDuration) 62 | .SetDefaultMessageTimeToLive(defaultMessageTtl) 63 | .SetAutoDeleteOnIdle(autoDeleteOnIdle); 64 | }) 65 | .Start(); 66 | } 67 | 68 | InitializeBusWith( 69 | peekLockDuration: TimeSpan.FromMinutes(2), 70 | defaultMessageTtl: TimeSpan.FromDays(5), 71 | autoDeleteOnIdle: TimeSpan.FromHours(1) 72 | ); 73 | 74 | CleanUpDisposables(); 75 | 76 | // wait a while because some of the settings seem to be updating slowly 77 | await Task.Delay(TimeSpan.FromSeconds(5)); 78 | 79 | InitializeBusWith( 80 | peekLockDuration: TimeSpan.FromMinutes(1), 81 | defaultMessageTtl: TimeSpan.FromDays(1), 82 | autoDeleteOnIdle: TimeSpan.FromHours(5) 83 | ); 84 | 85 | CleanUpDisposables(); 86 | 87 | // wait a while because some of the settings seem to be updating slowly 88 | await Task.Delay(TimeSpan.FromSeconds(5)); 89 | 90 | var queueDescription = await _managementClient.GetQueueAsync(_queueName); 91 | 92 | Assert.That(queueDescription.Value.DefaultMessageTimeToLive, Is.EqualTo(TimeSpan.FromDays(1))); 93 | Assert.That(queueDescription.Value.LockDuration, Is.EqualTo(TimeSpan.FromMinutes(1))); 94 | Assert.That(queueDescription.Value.AutoDeleteOnIdle, Is.EqualTo(TimeSpan.FromHours(5))); 95 | } 96 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/CanUseSlashInQueueNames.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using NUnit.Framework; 3 | using Rebus.Activation; 4 | using Rebus.AzureServiceBus.Tests.Bugs; 5 | using Rebus.Config; 6 | using Rebus.Tests.Contracts; 7 | using Rebus.Tests.Contracts.Utilities; 8 | #pragma warning disable 1998 9 | 10 | namespace Rebus.AzureServiceBus.Tests; 11 | 12 | [TestFixture] 13 | public class CanUseSlashInQueueNames : FixtureBase 14 | { 15 | [Test] 16 | public async Task ItJustWorks() 17 | { 18 | var counter = new SharedCounter(2); 19 | var queueName = $"department/subdepartment/{TestConfig.GetName("slash")}"; 20 | 21 | Using(new QueueDeleter(queueName)); 22 | 23 | using var activator = new BuiltinHandlerActivator(); 24 | 25 | activator.Handle(async _ => counter.Decrement()); 26 | 27 | var bus = Configure.With(activator) 28 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 29 | .Start(); 30 | 31 | await bus.Subscribe(); 32 | 33 | await bus.Publish("this message was published"); 34 | await bus.SendLocal("this message was sent"); 35 | 36 | counter.WaitForResetEvent(); 37 | } 38 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/CheckPeekLockStuff.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Azure.Messaging.ServiceBus; 4 | using Azure.Messaging.ServiceBus.Administration; 5 | using NUnit.Framework; 6 | using Rebus.Internals; 7 | using Rebus.Tests.Contracts; 8 | 9 | namespace Rebus.AzureServiceBus.Tests; 10 | 11 | [TestFixture] 12 | public class CheckPeekLockStuff : FixtureBase 13 | { 14 | ServiceBusAdministrationClient _managementClient; 15 | ServiceBusSender _messageSender; 16 | ServiceBusReceiver _messageReceiver; 17 | string _queueName; 18 | 19 | protected override void SetUp() 20 | { 21 | var connectionString = AsbTestConfig.ConnectionString; 22 | 23 | _queueName = TestConfig.GetName("test-queue"); 24 | 25 | _managementClient = new ServiceBusAdministrationClient(connectionString); 26 | var client = new ServiceBusClient(connectionString); 27 | _messageSender = client.CreateSender(_queueName); 28 | _messageReceiver = client.CreateReceiver(_queueName); 29 | 30 | AsyncHelpers.RunSync(async () => await _managementClient.DeleteQueueIfExistsAsync(_queueName)); 31 | } 32 | 33 | [Test] 34 | public async Task CanGrabPeekLock() 35 | { 36 | await _managementClient.CreateQueueAsync(_queueName); 37 | 38 | var messageId = Guid.NewGuid().ToString(); 39 | 40 | await _messageSender.SendMessageAsync(new ServiceBusMessage 41 | { 42 | MessageId = messageId, 43 | Body = new BinaryData(new byte[] {1, 2, 3}.AsMemory()) 44 | }); 45 | 46 | var message = await _messageReceiver.ReceiveMessageAsync(TimeSpan.FromSeconds(2)); 47 | 48 | Assert.That(message, Is.Not.Null); 49 | Assert.That(message.MessageId, Is.EqualTo(messageId)); 50 | Assert.That(await _messageReceiver.ReceiveMessageAsync(TimeSpan.FromSeconds(2)), Is.Null); 51 | 52 | var lockedUntilUtc = message.LockedUntil; 53 | 54 | Console.WriteLine($"The message is locked until {lockedUntilUtc} (message ID = {message.MessageId}, lock token = {message.LockToken})"); 55 | 56 | await _messageReceiver.CompleteMessageAsync(message); 57 | 58 | Assert.That(await _messageReceiver.ReceiveMessageAsync(TimeSpan.FromSeconds(2)), Is.Null); 59 | 60 | while (DateTime.UtcNow < lockedUntilUtc.AddSeconds(5)) 61 | { 62 | await Task.Delay(TimeSpan.FromSeconds(1)); 63 | } 64 | 65 | var otherMessage = await _messageReceiver.ReceiveMessageAsync(TimeSpan.FromSeconds(2)); 66 | 67 | Assert.That(otherMessage, Is.Null, () => $"Got message at time {DateTime.UtcNow} (message ID = {otherMessage.MessageId}, lock token = {otherMessage.LockToken})"); 68 | } 69 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Checks/CanDeferLocal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Tests.Contracts; 8 | using Rebus.Tests.Contracts.Extensions; 9 | 10 | // ReSharper disable AccessToDisposedClosure 11 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 12 | 13 | namespace Rebus.AzureServiceBus.Tests.Checks; 14 | 15 | [TestFixture] 16 | public class CanDeferLocal : FixtureBase 17 | { 18 | [Test] 19 | public async Task WhyWouldThatBeProblematic() 20 | { 21 | var queueName = TestConfig.GetName("my-queue"); 22 | 23 | using var gotTheMessage = new ManualResetEvent(initialState: false); 24 | 25 | using var activator = new BuiltinHandlerActivator(); 26 | 27 | activator.Handle(async _ => gotTheMessage.Set()); 28 | 29 | var bus = Configure.With(activator) 30 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 31 | .Start(); 32 | 33 | await bus.DeferLocal(TimeSpan.FromSeconds(1), new TheMessage()); 34 | 35 | gotTheMessage.WaitOrDie(timeout: TimeSpan.FromSeconds(5)); 36 | } 37 | 38 | record TheMessage; 39 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Checks/CanIndeedHaveTopicNameWithForwardSlash.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Azure.Messaging.ServiceBus.Administration; 3 | using NUnit.Framework; 4 | using Rebus.AzureServiceBus.Tests.Bugs; 5 | using Rebus.Internals; 6 | using Rebus.Tests.Contracts; 7 | 8 | namespace Rebus.AzureServiceBus.Tests.Checks; 9 | 10 | [TestFixture] 11 | [Description("Just an example that shows that you can indeed use / in topic names")] 12 | public class CanIndeedHaveTopicNameWithForwardSlash : FixtureBase 13 | { 14 | const string QueueName = "MyServiceBus/MyConsumer"; 15 | const string TopicName = "MyServiceBus/MyServiceBus.Messages.MySomethingSomethingMessage"; 16 | 17 | protected override void SetUp() 18 | { 19 | base.SetUp(); 20 | 21 | Using(new TopicDeleter(TopicName)); 22 | Using(new QueueDeleter(QueueName)); 23 | } 24 | 25 | [Test] 26 | public async Task LookItWorks() 27 | { 28 | var admin = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 29 | 30 | await admin.CreateTopicAsync(TopicName); 31 | await admin.CreateQueueIfNotExistsAsync(QueueName); 32 | } 33 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Checks/CheckSendLatency.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Azure.Messaging.ServiceBus; 8 | using Azure.Messaging.ServiceBus.Administration; 9 | using Newtonsoft.Json; 10 | using NUnit.Framework; 11 | using Rebus.Activation; 12 | using Rebus.AzureServiceBus.Tests.Bugs; 13 | using Rebus.Bus; 14 | using Rebus.Config; 15 | using Rebus.Internals; 16 | using Rebus.Logging; 17 | using Rebus.Pipeline; 18 | using Rebus.Routing.TypeBased; 19 | using Rebus.Tests.Contracts; 20 | using Rebus.Tests.Contracts.Extensions; 21 | // ReSharper disable MethodSupportsCancellation 22 | #pragma warning disable CS4014 23 | 24 | #pragma warning disable CS1998 25 | 26 | namespace Rebus.AzureServiceBus.Tests.Checks; 27 | 28 | [TestFixture] 29 | [Description("Simple check just to get some kind of idea of some numbers")] 30 | [Explicit] 31 | public class CheckSendLatency : FixtureBase 32 | { 33 | [TestCase(1)] 34 | [TestCase(10)] 35 | [TestCase(100, Explicit = true)] 36 | [Repeat(5)] 37 | public async Task CheckEndToEndLatency(int count) 38 | { 39 | var receiveTimes = new ConcurrentQueue(); 40 | 41 | async Task RegisterReceiveTime(IBus bus, IMessageContext messageContext, TimedEvent evt) 42 | { 43 | var actualReceiveTime = DateTimeOffset.Now; 44 | var receiveInfo = new ReceiveInfo(actualReceiveTime, evt.Time); 45 | receiveTimes.Enqueue(receiveInfo); 46 | } 47 | 48 | Using(new QueueDeleter("sender")); 49 | var sender = GetBus("sender", routing: r => r.Map("receiver")); 50 | 51 | Using(new QueueDeleter("receiver")); 52 | GetBus("receiver", handlers: activator => activator.Handle(RegisterReceiveTime)); 53 | 54 | await Parallel.ForEachAsync(Enumerable.Range(0, count), 55 | async (_, _) => await sender.Send(new TimedEvent(DateTimeOffset.Now))); 56 | 57 | await receiveTimes.WaitUntil(q => q.Count == count, timeoutSeconds: 10 + count * 2); 58 | 59 | var latencies = receiveTimes.Select(a => a.Latency().TotalSeconds).ToList(); 60 | 61 | var average = latencies.Average(); 62 | var median = latencies.Median(); 63 | var min = latencies.Min(); 64 | var max = latencies.Max(); 65 | 66 | Console.WriteLine($"AVG: {average:0.0} s, MED: {median:0.0} s, MIN: {min:0.0} s, MAX: {max:0.0} s"); 67 | } 68 | 69 | [TestCase(1)] 70 | [TestCase(10)] 71 | [TestCase(100, Explicit = true)] 72 | [Repeat(5)] 73 | public async Task CheckEndToEndLatency_NoRebus_ServiceBusProcessor(int count) 74 | { 75 | var receiveTimes = new ConcurrentQueue(); 76 | 77 | async Task RegisterReceiveTime(TimedEvent evt) 78 | { 79 | var actualReceiveTime = DateTimeOffset.Now; 80 | var receiveInfo = new ReceiveInfo(actualReceiveTime, evt.Time); 81 | receiveTimes.Enqueue(receiveInfo); 82 | } 83 | 84 | var client = new ServiceBusClient(AsbTestConfig.ConnectionString); 85 | var admin = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 86 | 87 | Using(new QueueDeleter("receiver")); 88 | await admin.CreateQueueIfNotExistsAsync("receiver"); 89 | 90 | var sender = client.CreateSender("receiver"); 91 | var receiver = client.CreateProcessor("receiver"); 92 | 93 | receiver.ProcessMessageAsync += async args => 94 | { 95 | var message = args.Message; 96 | try 97 | { 98 | var bytes = message.Body.ToArray(); 99 | var json = Encoding.UTF8.GetString(bytes); 100 | var timedEvent = JsonConvert.DeserializeObject(json); 101 | 102 | await RegisterReceiveTime(timedEvent); 103 | 104 | await args.CompleteMessageAsync(message); 105 | } 106 | catch 107 | { 108 | await args.AbandonMessageAsync(message); 109 | throw; 110 | } 111 | }; 112 | receiver.ProcessErrorAsync += async _ => { }; 113 | 114 | try 115 | { 116 | await receiver.StartProcessingAsync(); 117 | 118 | await Parallel.ForEachAsync(Enumerable.Range(0, count), 119 | async (_, token) => 120 | await sender.SendMessageAsync( 121 | new ServiceBusMessage(JsonConvert.SerializeObject(new TimedEvent(DateTimeOffset.Now))), token)); 122 | 123 | await receiveTimes.WaitUntil(q => q.Count == count, timeoutSeconds: 30 + count * 5); 124 | 125 | var latencies = receiveTimes.Select(a => a.Latency().TotalSeconds).ToList(); 126 | 127 | var average = latencies.Average(); 128 | var median = latencies.Median(); 129 | var min = latencies.Min(); 130 | var max = latencies.Max(); 131 | 132 | Console.WriteLine($"AVG: {average:0.0} s, MED: {median:0.0} s, MIN: {min:0.0} s, MAX: {max:0.0} s"); 133 | } 134 | finally 135 | { 136 | await receiver.StopProcessingAsync(); 137 | } 138 | } 139 | 140 | [TestCase(1)] 141 | [TestCase(10)] 142 | [TestCase(100, Explicit = true)] 143 | [Repeat(5)] 144 | public async Task CheckEndToEndLatency_NoRebus_ServiceBusReceiver(int count) 145 | { 146 | var receiveTimes = new ConcurrentQueue(); 147 | 148 | async Task RegisterReceiveTime(TimedEvent evt) 149 | { 150 | var actualReceiveTime = DateTimeOffset.Now; 151 | var receiveInfo = new ReceiveInfo(actualReceiveTime, evt.Time); 152 | receiveTimes.Enqueue(receiveInfo); 153 | } 154 | 155 | var client = new ServiceBusClient(AsbTestConfig.ConnectionString); 156 | var admin = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 157 | 158 | Using(new QueueDeleter("receiver")); 159 | await admin.CreateQueueIfNotExistsAsync("receiver"); 160 | 161 | var sender = client.CreateSender("receiver"); 162 | var receiver = client.CreateReceiver("receiver"); 163 | 164 | using var stopReceiver = new CancellationTokenSource(); 165 | var cancellationToken = stopReceiver.Token; 166 | 167 | Task.Run(async () => 168 | { 169 | while (!cancellationToken.IsCancellationRequested) 170 | { 171 | try 172 | { 173 | var message = await receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(50), cancellationToken); 174 | 175 | try 176 | { 177 | var bytes = message.Body.ToArray(); 178 | var json = Encoding.UTF8.GetString(bytes); 179 | var timedEvent = JsonConvert.DeserializeObject(json); 180 | 181 | await RegisterReceiveTime(timedEvent); 182 | 183 | await receiver.CompleteMessageAsync(message); 184 | } 185 | catch 186 | { 187 | await receiver.AbandonMessageAsync(message); 188 | } 189 | } 190 | catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) 191 | { 192 | // we're exiting 193 | } 194 | catch (Exception exception) 195 | { 196 | Console.WriteLine($"Unhandled exception in receiver loop: {exception}"); 197 | } 198 | } 199 | }, cancellationToken); 200 | 201 | try 202 | { 203 | await Parallel.ForEachAsync(Enumerable.Range(0, count), 204 | async (_, token) => 205 | await sender.SendMessageAsync( 206 | new ServiceBusMessage(JsonConvert.SerializeObject(new TimedEvent(DateTimeOffset.Now))), token)); 207 | 208 | await receiveTimes.WaitUntil(q => q.Count == count, timeoutSeconds: 30 + count * 5); 209 | 210 | var latencies = receiveTimes.Select(a => a.Latency().TotalSeconds).ToList(); 211 | 212 | var average = latencies.Average(); 213 | var median = latencies.Median(); 214 | var min = latencies.Min(); 215 | var max = latencies.Max(); 216 | 217 | Console.WriteLine($"AVG: {average:0.0} s, MED: {median:0.0} s, MIN: {min:0.0} s, MAX: {max:0.0} s"); 218 | } 219 | finally 220 | { 221 | stopReceiver.Cancel(); 222 | } 223 | } 224 | 225 | record TimedEvent(DateTimeOffset Time); 226 | 227 | record ReceiveInfo(DateTimeOffset ReceiveTime, DateTimeOffset SendTime) 228 | { 229 | public TimeSpan Latency() => ReceiveTime - SendTime; 230 | } 231 | 232 | IBus GetBus(string queueName, Action handlers = null, Action routing = null) 233 | { 234 | var activator = Using(new BuiltinHandlerActivator()); 235 | 236 | handlers?.Invoke(activator); 237 | 238 | Configure.With(activator) 239 | .Logging(l => l.Console(minLevel: LogLevel.Warn)) 240 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 241 | .Routing(r => routing?.Invoke(r.TypeBased())) 242 | .Options(o => o.SetMaxParallelism(10)) 243 | .Start(); 244 | 245 | return activator.Bus; 246 | } 247 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Checks/CheckTheServiceBusMessageInTheTimeAfterCompletingIt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Azure.Messaging.ServiceBus; 4 | using Azure.Messaging.ServiceBus.Administration; 5 | using NUnit.Framework; 6 | using Rebus.AzureServiceBus.Tests.Bugs; 7 | using Rebus.Internals; 8 | using Rebus.Tests.Contracts; 9 | 10 | namespace Rebus.AzureServiceBus.Tests.Checks; 11 | 12 | [TestFixture] 13 | public class CheckTheServiceBusMessageInTheTimeAfterCompletingIt : FixtureBase 14 | { 15 | [Test] 16 | public async Task WhatIsTheState() 17 | { 18 | void PrintMessage(ServiceBusReceivedMessage msg) 19 | { 20 | var amqp = msg.GetRawAmqpMessage(); 21 | 22 | Console.WriteLine($@" 23 | Message ID: {msg.MessageId} 24 | Lock Token: {msg.LockToken} 25 | Locked until: {msg.LockedUntil} 26 | State: {msg.State} 27 | Expires at: {msg.ExpiresAt} 28 | 29 | "); 30 | } 31 | 32 | const string queueName = "test-queue"; 33 | 34 | Using(new QueueDeleter(queueName)); 35 | 36 | var connectionString = AsbTestConfig.ConnectionString; 37 | var admin = new ServiceBusAdministrationClient(connectionString); 38 | 39 | await admin.CreateQueueIfNotExistsAsync(queueName); 40 | 41 | var serviceBusClient = new ServiceBusClient(connectionString); 42 | 43 | var sender = serviceBusClient.CreateSender(queueName); 44 | var receiver = serviceBusClient.CreateReceiver(queueName); 45 | 46 | await sender.SendMessageAsync(new ServiceBusMessage("HEJ")); 47 | 48 | var roundtripped = await receiver.ReceiveMessageAsync(); 49 | 50 | PrintMessage(roundtripped); 51 | 52 | await receiver.CompleteMessageAsync(roundtripped); 53 | 54 | PrintMessage(roundtripped); 55 | } 56 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Checks/CheckWhatHappensWhenSubscriptionHasAutodeleteOnIdleSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Azure.Messaging.ServiceBus; 4 | using Azure.Messaging.ServiceBus.Administration; 5 | using NUnit.Framework; 6 | using Rebus.AzureServiceBus.Tests.Bugs; 7 | using Rebus.Internals; 8 | using Rebus.Tests.Contracts; 9 | 10 | // ReSharper disable ArgumentsStyleLiteral 11 | // ReSharper disable ConvertToUsingDeclaration 12 | // ReSharper disable AccessToDisposedClosure 13 | #pragma warning disable CS1998 14 | 15 | namespace Rebus.AzureServiceBus.Tests.Checks; 16 | 17 | [TestFixture] 18 | public class CheckWhatHappensWhenSubscriptionHasAutodeleteOnIdleSet : FixtureBase 19 | { 20 | [Test] 21 | [Description("Emulates the topology created by Rebus when creating a topic with a single subscriber and checks that AutoDeleteOnIdle can be set")] 22 | [Ignore("Ignored because it fails. Seems like it's not possible to combine forwarding with auto-delete-on-idle on a subscription - it will simply ignore the auto-delete timeout.")] 23 | public async Task EstablishSubscriptionWithAutoDeleteOnIdle() 24 | { 25 | var topicName = Guid.NewGuid().ToString("N"); 26 | var queueName = Guid.NewGuid().ToString("N"); 27 | 28 | // clean up afterwards 29 | Using(new TopicDeleter(topicName)); 30 | Using(new QueueDeleter(queueName)); 31 | 32 | var adminClient = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 33 | 34 | await adminClient.CreateTopicAsync(topicName); 35 | await adminClient.CreateQueueAsync(queueName); 36 | 37 | var options = new CreateSubscriptionOptions(topicName: topicName, subscriptionName: queueName) 38 | { 39 | AutoDeleteOnIdle = TimeSpan.FromMinutes(5), 40 | ForwardTo = queueName 41 | }; 42 | 43 | var subscription = await adminClient.CreateSubscriptionAsync(options); 44 | 45 | var props = subscription.Value; 46 | 47 | var serviceBusClient = new ServiceBusClient(AsbTestConfig.ConnectionString); 48 | 49 | Assert.That(props.ForwardTo, Is.EqualTo(serviceBusClient.CreateSender(queueName).GetQueuePath())); 50 | Assert.That(props.AutoDeleteOnIdle, Is.EqualTo(TimeSpan.FromMinutes(5))); 51 | } 52 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Checks/DeadletterMessageToBeInspected.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using NUnit.Framework; 3 | using Rebus.Activation; 4 | using Rebus.AzureServiceBus.Tests.Bugs; 5 | using Rebus.Config; 6 | using Rebus.Exceptions; 7 | using Rebus.Tests.Contracts; 8 | 9 | #pragma warning disable 1998 10 | 11 | namespace Rebus.AzureServiceBus.Tests.Checks; 12 | 13 | [TestFixture] 14 | public class DeadletterMessageToBeInspected : FixtureBase 15 | { 16 | [Test] 17 | public async Task ItIsThisEasy() 18 | { 19 | var queueName = TestConfig.GetName("deadlettering"); 20 | 21 | Using(new QueueDeleter(queueName)); 22 | 23 | using var activator = new BuiltinHandlerActivator(); 24 | 25 | activator.Handle(async _ => throw new FailFastException("failing fast")); 26 | 27 | var bus = Configure.With(activator) 28 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 29 | .Start(); 30 | 31 | await bus.SendLocal(new TestMessage()); 32 | } 33 | 34 | class TestMessage { } 35 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Checks/SeeIfWeCanDeadletterThisWay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | using Rebus.Activation; 5 | using Rebus.AzureServiceBus.Tests.Bugs; 6 | using Rebus.Config; 7 | using Rebus.Exceptions; 8 | using Rebus.Retry.Simple; 9 | using Rebus.Tests.Contracts; 10 | 11 | #pragma warning disable 1998 12 | 13 | namespace Rebus.AzureServiceBus.Tests.Checks; 14 | 15 | [TestFixture] 16 | public class UseAzureServiceBusNativeDeadlettering : FixtureBase 17 | { 18 | [Test] 19 | public async Task ItIsThisEasy() 20 | { 21 | var queueName = TestConfig.GetName("deadlettering"); 22 | 23 | Using(new QueueDeleter(queueName)); 24 | 25 | using var activator = new BuiltinHandlerActivator(); 26 | 27 | activator.Handle(async _ => throw new FailFastException("failing fast")); 28 | 29 | var bus = Configure.With(activator) 30 | .Transport(t => 31 | { 32 | t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName); 33 | t.UseNativeDeadlettering(); 34 | }) 35 | .Options(o => o.RetryStrategy(maxDeliveryAttempts: 1)) 36 | .Start(); 37 | 38 | await bus.SendLocal(new TestMessage()); 39 | 40 | await Task.Delay(TimeSpan.FromSeconds(5)); 41 | } 42 | 43 | class TestMessage { } 44 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Checks/TestNativeDeliveryCount.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.Tests.Bugs; 7 | using Rebus.Config; 8 | using Rebus.Messages; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Extensions; 11 | 12 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 13 | 14 | namespace Rebus.AzureServiceBus.Tests.Checks; 15 | 16 | [TestFixture] 17 | public class TestNativeDeliveryCount : FixtureBase 18 | { 19 | [Test] 20 | public async Task ItIsThisEasy() 21 | { 22 | var queueName = TestConfig.GetName("deadlettering"); 23 | var deliveryCounts = new ConcurrentQueue(); 24 | 25 | Using(new QueueDeleter(queueName)); 26 | 27 | using var activator = new BuiltinHandlerActivator(); 28 | 29 | activator.Handle(async (_, context, _) => 30 | { 31 | if (context.Headers.TryGetValue(Headers.DeliveryCount, out var str) && 32 | int.TryParse(str, out var deliveryCount)) 33 | { 34 | deliveryCounts.Enqueue(deliveryCount); 35 | } 36 | 37 | throw new ApplicationException("just pretend something is wrong"); 38 | }); 39 | 40 | var bus = Configure.With(activator) 41 | .Transport(t => 42 | { 43 | t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName) 44 | .UseNativeMessageDeliveryCount(); 45 | }) 46 | .Start(); 47 | 48 | await bus.SendLocal(new TestMessage()); 49 | 50 | await deliveryCounts.WaitUntil(c => c.Count == 5); 51 | 52 | Assert.That(deliveryCounts, Is.EqualTo(new[] { 1, 2, 3, 4, 5 })); 53 | } 54 | 55 | class TestMessage { } 56 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Examples/ShowDefaultReturnAddress.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.Tests.Bugs; 7 | using Rebus.Config; 8 | using Rebus.Routing.TypeBased; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Extensions; 11 | 12 | // ReSharper disable AccessToDisposedClosure 13 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 14 | 15 | namespace Rebus.AzureServiceBus.Tests.Examples; 16 | 17 | [TestFixture] 18 | public class ShowDefaultReturnAddress : FixtureBase 19 | { 20 | [Test] 21 | public async Task ItWorks() 22 | { 23 | using var gotTheFinalReply = new ManualResetEvent(initialState: false); 24 | 25 | var receiverQueueName = Guid.NewGuid().ToString("N"); 26 | var finalDestinationQueueName = Guid.NewGuid().ToString("N"); 27 | 28 | Using(new QueueDeleter(receiverQueueName)); 29 | Using(new QueueDeleter(finalDestinationQueueName)); 30 | 31 | using var receiver = new BuiltinHandlerActivator(); 32 | 33 | receiver.Handle(async (b, _) => await b.Reply(new FinalReply())); 34 | 35 | using var finalDestination = new BuiltinHandlerActivator(); 36 | 37 | finalDestination.Handle(async _ => gotTheFinalReply.Set()); 38 | 39 | Configure.With(receiver) 40 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, receiverQueueName)) 41 | .Start(); 42 | 43 | Configure.With(finalDestination) 44 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, finalDestinationQueueName)) 45 | .Start(); 46 | 47 | using var bus = Configure.OneWayClient() 48 | .Transport(t => t.UseAzureServiceBusAsOneWayClient(AsbTestConfig.ConnectionString)) 49 | .Routing(r => r.TypeBased().Map(receiverQueueName)) 50 | .Options(o => o.SetDefaultReturnAddress(finalDestinationQueueName)) 51 | .Start(); 52 | 53 | await bus.Send(new InitialMessage()); 54 | 55 | gotTheFinalReply.WaitOrDie(TimeSpan.FromSeconds(5)); 56 | } 57 | 58 | record InitialMessage; 59 | 60 | record FinalReply; 61 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Examples/TheMostInterestingHandlerInTheWorldWorks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.Tests.Bugs; 7 | using Rebus.Config; 8 | using Rebus.Handlers; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Utilities; 11 | 12 | namespace Rebus.AzureServiceBus.Tests.Examples; 13 | 14 | [TestFixture] 15 | public class TheMostInterestingHandlerInTheWorldWorks : FixtureBase 16 | { 17 | [Test] 18 | public async Task OfCourseHandlerCanBeSimpleLikeThis() 19 | { 20 | var queueName = Guid.NewGuid().ToString("n"); 21 | 22 | Using(new QueueDeleter(queueName)); 23 | 24 | var logs = new ListLoggerFactory(); 25 | 26 | using var activator = new BuiltinHandlerActivator(); 27 | 28 | activator.Register(() => new TheMostInterestingHandlerInTheWorld()); 29 | 30 | var bus = Configure.With(activator) 31 | .Logging(l => l.Use(logs)) 32 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 33 | .Start(); 34 | 35 | await bus.SendLocal(new MyMessage()); 36 | 37 | // wait a short while 38 | await Task.Delay(TimeSpan.FromSeconds(1)); 39 | 40 | Assert.That(logs.Select(line => line.Text), Does.Not.Contain("InvalidOperationException")); 41 | } 42 | 43 | record MyMessage; 44 | 45 | class TheMostInterestingHandlerInTheWorld : IHandleMessages 46 | { 47 | public Task Handle(MyMessage message) 48 | { 49 | Console.WriteLine("Receive my message"); 50 | return Task.CompletedTask; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Factories/AzureServiceBusBusFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Rebus.Activation; 5 | using Rebus.AzureServiceBus.Messages; 6 | using Rebus.AzureServiceBus.NameFormat; 7 | using Rebus.Bus; 8 | using Rebus.Config; 9 | using Rebus.Logging; 10 | using Rebus.Tests.Contracts; 11 | using Rebus.Tests.Contracts.Transports; 12 | using Rebus.Threading.TaskParallelLibrary; 13 | 14 | namespace Rebus.AzureServiceBus.Tests.Factories; 15 | 16 | public class AzureServiceBusBusFactory : IBusFactory 17 | { 18 | readonly List _stuffToDispose = new(); 19 | 20 | public IBus GetBus(string inputQueueAddress, Func handler) 21 | { 22 | var builtinHandlerActivator = new BuiltinHandlerActivator(); 23 | 24 | builtinHandlerActivator.Handle(handler); 25 | 26 | var queueName = TestConfig.GetName(inputQueueAddress); 27 | 28 | PurgeQueue(queueName); 29 | 30 | var bus = Configure.With(builtinHandlerActivator) 31 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 32 | .Options(o => 33 | { 34 | o.SetNumberOfWorkers(10); 35 | o.SetMaxParallelism(10); 36 | }) 37 | .Start(); 38 | 39 | _stuffToDispose.Add(bus); 40 | 41 | return bus; 42 | } 43 | 44 | static void PurgeQueue(string queueName) 45 | { 46 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 47 | var asyncTaskFactory = new TplAsyncTaskFactory(consoleLoggerFactory); 48 | var connectionString = AsbTestConfig.ConnectionString; 49 | 50 | using var transport = new AzureServiceBusTransport(connectionString, queueName, consoleLoggerFactory, asyncTaskFactory, new DefaultNameFormatter(), new DefaultMessageConverter()); 51 | 52 | transport.PurgeInputQueue(); 53 | } 54 | 55 | public void Cleanup() 56 | { 57 | _stuffToDispose.ForEach(d => d.Dispose()); 58 | _stuffToDispose.Clear(); 59 | } 60 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Factories/AzureServiceBusTransportFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using Rebus.AzureServiceBus.Messages; 4 | using Rebus.AzureServiceBus.NameFormat; 5 | using Rebus.AzureServiceBus.Tests.Bugs; 6 | using Rebus.Logging; 7 | using Rebus.Tests.Contracts.Transports; 8 | using Rebus.Threading.TaskParallelLibrary; 9 | using Rebus.Transport; 10 | 11 | namespace Rebus.AzureServiceBus.Tests.Factories; 12 | 13 | public class AzureServiceBusTransportFactory : ITransportFactory 14 | { 15 | readonly ConcurrentStack _disposables = new(); 16 | 17 | public ITransport CreateOneWayClient() => Create(null); 18 | 19 | public ITransport Create(string inputQueueAddress) 20 | { 21 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 22 | var asyncTaskFactory = new TplAsyncTaskFactory(consoleLoggerFactory); 23 | 24 | if (inputQueueAddress == null) 25 | { 26 | var onewayClientTransport = new AzureServiceBusTransport(AsbTestConfig.ConnectionString, null, consoleLoggerFactory, asyncTaskFactory, new DefaultNameFormatter(), new DefaultMessageConverter()); 27 | 28 | onewayClientTransport.Initialize(); 29 | 30 | _disposables.Push(onewayClientTransport); 31 | 32 | return onewayClientTransport; 33 | } 34 | 35 | _disposables.Push(new QueueDeleter(inputQueueAddress)); 36 | 37 | var transport = new AzureServiceBusTransport(AsbTestConfig.ConnectionString, inputQueueAddress, consoleLoggerFactory, asyncTaskFactory, new DefaultNameFormatter(), new DefaultMessageConverter()); 38 | 39 | transport.ReceiveOperationTimeout = TimeSpan.FromSeconds(5); 40 | 41 | transport.PurgeInputQueue(); 42 | transport.Initialize(); 43 | 44 | _disposables.Push(transport); 45 | 46 | return transport; 47 | } 48 | 49 | public void CleanUp() 50 | { 51 | while (_disposables.TryPop(out var disposable)) 52 | { 53 | disposable.Dispose(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/FailsWhenSendingToNonExistentQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Azure.Messaging.ServiceBus; 3 | using NUnit.Framework; 4 | using Rebus.Activation; 5 | using Rebus.AzureServiceBus.Tests.Bugs; 6 | using Rebus.Config; 7 | using Rebus.Exceptions; 8 | using Rebus.Tests.Contracts; 9 | 10 | namespace Rebus.AzureServiceBus.Tests; 11 | 12 | [TestFixture] 13 | public class FailsWhenSendingToNonExistentQueue : FixtureBase 14 | { 15 | static readonly string ConnectionString = AsbTestConfig.ConnectionString; 16 | 17 | [Test] 18 | public void YesItDoes() 19 | { 20 | Using(new QueueDeleter("bimmelim")); 21 | 22 | using var activator = new BuiltinHandlerActivator(); 23 | 24 | Configure.With(activator) 25 | .Transport(t => t.UseAzureServiceBus(ConnectionString, "bimmelim")) 26 | .Start(); 27 | 28 | var exception = Assert.ThrowsAsync(async () => await activator.Bus.Advanced.Routing.Send("yunoexist", "hej med dig min ven!")); 29 | 30 | Console.WriteLine(exception); 31 | 32 | var notFoundException = (ServiceBusException) exception.InnerException; 33 | 34 | Console.WriteLine(notFoundException); 35 | } 36 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/LegacyNamingTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Config; 8 | using Rebus.Extensions; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Extensions; 11 | 12 | #pragma warning disable 1998 13 | 14 | namespace Rebus.AzureServiceBus.Tests; 15 | 16 | [TestFixture] 17 | public class LegacyNamingTest : FixtureBase 18 | { 19 | [Test] 20 | public async Task CanReceiveFromPublisherWithLegacyModeEnabled() 21 | { 22 | var publisher = Configure.With(Using(new BuiltinHandlerActivator())) 23 | .Transport(t => t.UseAzureServiceBusAsOneWayClient(AsbTestConfig.ConnectionString).UseLegacyNaming()) 24 | .Start(); 25 | 26 | var subscriber = new BuiltinHandlerActivator(); 27 | var gotTheEvent = new ManualResetEvent(false); 28 | 29 | subscriber.Handle(async e => gotTheEvent.Set()); 30 | 31 | Configure.With(Using(subscriber)) 32 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, "subscriber")) 33 | .Start(); 34 | 35 | // simulate legacy style subscription 36 | await subscriber.Bus.Advanced.Topics.Subscribe(NormalizeLegacyStyle(typeof(JustAnEvent).GetSimpleAssemblyQualifiedName())); 37 | 38 | await publisher.Publish(new JustAnEvent()); 39 | 40 | gotTheEvent.WaitOrDie(TimeSpan.FromSeconds(3)); 41 | } 42 | 43 | [Test] 44 | public async Task CanPublishToSubscriberWithLegacyModeEnabled() 45 | { 46 | var publisher = Configure.With(Using(new BuiltinHandlerActivator())) 47 | .Transport(t => t.UseAzureServiceBusAsOneWayClient(AsbTestConfig.ConnectionString)) 48 | .Start(); 49 | 50 | var subscriber = new BuiltinHandlerActivator(); 51 | var gotTheEvent = new ManualResetEvent(false); 52 | 53 | subscriber.Handle(async e => gotTheEvent.Set()); 54 | 55 | Configure.With(Using(subscriber)) 56 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, "subscriber").UseLegacyNaming()) 57 | .Start(); 58 | 59 | await subscriber.Bus.Subscribe(); 60 | 61 | // simulate legacy style publish 62 | await publisher.Advanced.Topics.Publish(NormalizeLegacyStyle(typeof(JustAnEvent).GetSimpleAssemblyQualifiedName()), new JustAnEvent()); 63 | 64 | gotTheEvent.WaitOrDie(TimeSpan.FromSeconds(3)); 65 | } 66 | 67 | [Test] 68 | public async Task CanPublishToSubscriberWithLegacyModeEnabled_OneWayClient() 69 | { 70 | var publisher = Configure.With(Using(new BuiltinHandlerActivator())) 71 | .Transport(t => t.UseAzureServiceBusAsOneWayClient(AsbTestConfig.ConnectionString).UseLegacyNaming()) 72 | .Start(); 73 | 74 | var subscriber = new BuiltinHandlerActivator(); 75 | var gotTheEvent = new ManualResetEvent(false); 76 | 77 | subscriber.Handle(async e => gotTheEvent.Set()); 78 | 79 | Configure.With(Using(subscriber)) 80 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, "subscriber").UseLegacyNaming()) 81 | .Start(); 82 | 83 | await subscriber.Bus.Subscribe(); 84 | 85 | // simulate legacy style publish 86 | await publisher.Publish(new JustAnEvent()); 87 | 88 | gotTheEvent.WaitOrDie(TimeSpan.FromSeconds(3)); 89 | } 90 | 91 | static string NormalizeLegacyStyle(string topic) 92 | { 93 | return new string(topic.Select(c => 94 | { 95 | if (!char.IsLetterOrDigit(c)) return '_'; 96 | 97 | return char.ToLowerInvariant(c); 98 | }) 99 | .ToArray()); 100 | } 101 | 102 | class JustAnEvent { } 103 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/NativeDeferTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.Messages; 7 | using Rebus.AzureServiceBus.NameFormat; 8 | using Rebus.Bus; 9 | using Rebus.Config; 10 | using Rebus.Logging; 11 | using Rebus.Messages; 12 | using Rebus.Routing.TypeBased; 13 | using Rebus.Tests.Contracts; 14 | using Rebus.Tests.Contracts.Extensions; 15 | using Rebus.Threading.TaskParallelLibrary; 16 | 17 | #pragma warning disable 1998 18 | 19 | namespace Rebus.AzureServiceBus.Tests; 20 | 21 | [TestFixture] 22 | public class NativeDeferTest : FixtureBase 23 | { 24 | static readonly string QueueName = TestConfig.GetName("input"); 25 | BuiltinHandlerActivator _activator; 26 | IBus _bus; 27 | IBusStarter _busStarter; 28 | 29 | protected override void SetUp() 30 | { 31 | var connectionString = AsbTestConfig.ConnectionString; 32 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 33 | var asyncTaskFactory = new TplAsyncTaskFactory(consoleLoggerFactory); 34 | 35 | using (var transport = new AzureServiceBusTransport(connectionString, QueueName, consoleLoggerFactory, asyncTaskFactory, new DefaultNameFormatter(), new DefaultMessageConverter())) 36 | { 37 | transport.PurgeInputQueue(); 38 | } 39 | 40 | _activator = new BuiltinHandlerActivator(); 41 | 42 | _busStarter = Configure.With(_activator) 43 | .Transport(t => t.UseAzureServiceBus(connectionString, QueueName)) 44 | .Routing(r => r.TypeBased().Map(QueueName)) 45 | .Options(o => 46 | { 47 | o.LogPipeline(); 48 | }) 49 | .Create(); 50 | 51 | _bus = _busStarter.Bus; 52 | 53 | Using(_bus); 54 | } 55 | 56 | [Test] 57 | public async Task UsesNativeDeferralMechanism() 58 | { 59 | var done = new ManualResetEvent(false); 60 | var receiveTime = DateTimeOffset.MinValue; 61 | var hadDeferredUntilHeader = false; 62 | 63 | _activator.Handle(async (bus, context, message) => 64 | { 65 | receiveTime = DateTimeOffset.Now; 66 | 67 | hadDeferredUntilHeader = context.TransportMessage.Headers.ContainsKey(Headers.DeferredUntil); 68 | 69 | done.Set(); 70 | }); 71 | 72 | _busStarter.Start(); 73 | 74 | var sendTime = DateTimeOffset.Now; 75 | 76 | await _bus.Defer(TimeSpan.FromSeconds(5), new TimedMessage { Time = sendTime }); 77 | 78 | done.WaitOrDie(TimeSpan.FromSeconds(8), "Did not receive 5s-deferred message within 8 seconds of waiting...."); 79 | 80 | var delay = receiveTime - sendTime; 81 | 82 | Console.WriteLine($"Message was delayed {delay}"); 83 | 84 | Assert.That(delay, Is.GreaterThan(TimeSpan.FromSeconds(2.5)), "The message not delayed ~5 seconds as expected!"); 85 | Assert.That(delay, Is.LessThan(TimeSpan.FromSeconds(8)), "The message not delayed ~5 seconds as expected!"); 86 | 87 | Assert.That(hadDeferredUntilHeader, Is.False, "Received message still had the '{0}' header - we must remove that", Headers.DeferredUntil); 88 | } 89 | 90 | class TimedMessage 91 | { 92 | public DateTimeOffset Time { get; set; } 93 | } 94 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/NotCreatingQueueTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Azure.Messaging.ServiceBus.Administration; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Injection; 8 | using Rebus.Tests.Contracts; 9 | 10 | namespace Rebus.AzureServiceBus.Tests; 11 | 12 | [TestFixture] 13 | public class NotCreatingQueueTest : FixtureBase 14 | { 15 | [Test] 16 | public async Task ShouldNotCreateInputQueueWhenConfiguredNotTo() 17 | { 18 | var connectionString = AsbTestConfig.ConnectionString; 19 | var managementClient = new ServiceBusAdministrationClient(connectionString); 20 | 21 | var queueName = Guid.NewGuid().ToString("N"); 22 | 23 | var response = await managementClient.QueueExistsAsync(queueName); 24 | Assert.That(response.Value, Is.False); 25 | 26 | var activator = Using(new BuiltinHandlerActivator()); 27 | 28 | var exception = Assert.Throws(() => 29 | { 30 | Configure.With(activator) 31 | .Logging(l => l.ColoredConsole()) 32 | .Transport(t => 33 | { 34 | t.UseAzureServiceBus(connectionString, queueName) 35 | .DoNotCreateQueues(); 36 | }) 37 | .Start(); 38 | }); 39 | 40 | Console.WriteLine(exception); 41 | 42 | var exceptionMessage = exception.ToString(); 43 | 44 | Assert.That(exceptionMessage, Contains.Substring(queueName), "The exception message did not contain the queue name"); 45 | } 46 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/Rebus.AzureServiceBus.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/ServerSettingsBuilderHasSameSettingsAsClientBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | using Rebus.Config; 5 | 6 | namespace Rebus.AzureServiceBus.Tests; 7 | 8 | [TestFixture] 9 | [Description("BEcause inheritance collides with the builder pattern, we use this test fixture to ensure parity between AzureServiceBusTransportClientSettings and AzureServiceBusTransportSettings")] 10 | public class ServerSettingsBuilderHasSameSettingsAsClientBuilder 11 | { 12 | [Test] 13 | public void AllClientConfigurationOptionsAreAvailableOnServerToo() 14 | { 15 | var clientBuilder = typeof(AzureServiceBusTransportClientSettings); 16 | var serverBuilder = typeof(AzureServiceBusTransportSettings); 17 | 18 | foreach (var method in clientBuilder.GetMethods()) 19 | { 20 | var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); 21 | 22 | var correspondingMethod = serverBuilder.GetMethod(method.Name, parameterTypes) 23 | ?? throw new ArgumentException($@"Could not find configuration method on {serverBuilder} matching this signature: 24 | 25 | {method.Name}({string.Join(", ", parameterTypes.Select(type => type.Name))}) 26 | 27 | All methods present on AzureServiceBusTransportClientSettings must be available on AzureServiceBusTransportSettings as well."); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/SharedAccessSignatureWorks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Routing.TypeBased; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Tests.Contracts.Extensions; 10 | #pragma warning disable 1998 11 | 12 | namespace Rebus.AzureServiceBus.Tests; 13 | 14 | [TestFixture] 15 | [Ignore("Requires some manual setup")] 16 | public class SharedAccessSignatureWorks : FixtureBase 17 | { 18 | /// 19 | /// Intentionally configured to be a const, because the connection string grants access to this queue explicitly 20 | /// 21 | const string QueueName = "sastest"; 22 | 23 | BuiltinHandlerActivator _server; 24 | 25 | protected override void SetUp() 26 | { 27 | _server = new BuiltinHandlerActivator(); 28 | 29 | Using(_server); 30 | 31 | Configure.With(_server) 32 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, QueueName)) 33 | .Start(); 34 | 35 | } 36 | 37 | [Test] 38 | public async Task CanInitializeOneWayClientWithSasToken() 39 | { 40 | var gotTheString = new ManualResetEvent(false); 41 | 42 | _server.Handle(async _ => gotTheString.Set()); 43 | 44 | var configurer = Configure.With(new BuiltinHandlerActivator()) 45 | .Transport(t => t.UseAzureServiceBusAsOneWayClient("")) 46 | .Routing(r => r.TypeBased().Map(QueueName)); 47 | 48 | using (var client = configurer.Start()) 49 | { 50 | await client.Send("HEJ MED DIG MIN VEN"); 51 | } 52 | 53 | gotTheString.WaitOrDie(TimeSpan.FromSeconds(5)); 54 | } 55 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/SpikeTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Azure.Messaging.ServiceBus; 6 | using Azure.Messaging.ServiceBus.Administration; 7 | using NUnit.Framework; 8 | using Rebus.AzureServiceBus.Tests.Bugs; 9 | using Rebus.Internals; 10 | using Rebus.Tests.Contracts; 11 | // ReSharper disable RedundantArgumentDefaultValue 12 | #pragma warning disable 1998 13 | 14 | namespace Rebus.AzureServiceBus.Tests; 15 | 16 | [TestFixture] 17 | public class SpikeTest : FixtureBase 18 | { 19 | static readonly string ConnectionString = AsbTestConfig.ConnectionString; 20 | ServiceBusAdministrationClient _managementClient; 21 | 22 | protected override void SetUp() 23 | { 24 | _managementClient = new ServiceBusAdministrationClient(ConnectionString); 25 | } 26 | 27 | [Test] 28 | [Description("Test doesn't work if the CreateQueueIfNotExistsAsync method is defective")] 29 | public async Task CanDeleteQueueThatDoesNotExist() 30 | { 31 | var queueName = TestConfig.GetName("delete"); 32 | Using(new QueueDeleter(queueName)); 33 | 34 | await _managementClient.CreateQueueIfNotExistsAsync(queueName); 35 | await _managementClient.DeleteQueueIfExistsAsync(queueName); 36 | 37 | Assert.That((await _managementClient.QueueExistsAsync(queueName)).Value, Is.False, $"The queue {queueName} still exists"); 38 | } 39 | 40 | [Test] 41 | [Description("Test doesn't work if the DeleteQueueIfExistsAsync method is defective")] 42 | public async Task CanCreateQueue() 43 | { 44 | var queueName = TestConfig.GetName("create"); 45 | Using(new QueueDeleter(queueName)); 46 | 47 | await _managementClient.DeleteQueueIfExistsAsync(queueName); 48 | 49 | await _managementClient.CreateQueueIfNotExistsAsync(queueName); 50 | await _managementClient.CreateQueueIfNotExistsAsync(queueName); 51 | await _managementClient.CreateQueueIfNotExistsAsync(queueName); 52 | 53 | Assert.That((await _managementClient.QueueExistsAsync(queueName)).Value, Is.True, $"The queue {queueName} does not exist, even after several attempts at creating it"); 54 | } 55 | 56 | [Test] 57 | public async Task CanPurgeQueue() 58 | { 59 | var queueName = TestConfig.GetName("send"); 60 | Using(new QueueDeleter(queueName)); 61 | 62 | await _managementClient.CreateQueueIfNotExistsAsync(queueName); 63 | 64 | var clientOptions = new ServiceBusClientOptions 65 | { 66 | RetryOptions = new ServiceBusRetryOptions 67 | { 68 | Mode = ServiceBusRetryMode.Exponential, 69 | Delay = TimeSpan.FromMilliseconds(100), 70 | MaxDelay = TimeSpan.FromSeconds(5), 71 | MaxRetries = 10 72 | } 73 | }; 74 | var processorOptions = new ServiceBusProcessorOptions 75 | { 76 | ReceiveMode = ServiceBusReceiveMode.PeekLock 77 | }; 78 | var client = new ServiceBusClient(ConnectionString, clientOptions); 79 | 80 | var queueSender = client.CreateSender(queueName); 81 | 82 | await queueSender.SendMessageAsync(new ServiceBusMessage(Encoding.UTF8.GetBytes("Hej med dig min ven!"))); 83 | await queueSender.SendMessageAsync(new ServiceBusMessage(Encoding.UTF8.GetBytes("Hej med dig min ven!"))); 84 | await queueSender.SendMessageAsync(new ServiceBusMessage(Encoding.UTF8.GetBytes("Hej med dig min ven!"))); 85 | await queueSender.SendMessageAsync(new ServiceBusMessage(Encoding.UTF8.GetBytes("Hej med dig min ven!"))); 86 | 87 | await ManagementExtensions.PurgeQueue(ConnectionString, queueName); 88 | 89 | var queueProcessor = client.CreateProcessor(queueName, processorOptions); 90 | var somethingWasReceived = new ManualResetEvent(false); 91 | queueProcessor.ProcessMessageAsync += _ => Task.FromResult(somethingWasReceived.Set()); 92 | 93 | Assert.That(somethingWasReceived.WaitOne(TimeSpan.FromSeconds(1)), Is.False, $"Looks like a message was received from the queue '{queueName}' even though it was purged :o"); 94 | } 95 | 96 | [Test] 97 | public async Task CanSendAndReceiveMessage() 98 | { 99 | var queueName = TestConfig.GetName("send-receive"); 100 | Using(new QueueDeleter(queueName)); 101 | 102 | await _managementClient.CreateQueueIfNotExistsAsync(queueName); 103 | 104 | var clientOptions = new ServiceBusClientOptions 105 | { 106 | RetryOptions = new ServiceBusRetryOptions 107 | { 108 | Mode = ServiceBusRetryMode.Exponential, 109 | Delay = TimeSpan.FromMilliseconds(100), 110 | MaxDelay = TimeSpan.FromSeconds(5), 111 | MaxRetries = 10 112 | } 113 | }; 114 | var receiverOptions = new ServiceBusReceiverOptions 115 | { 116 | ReceiveMode = ServiceBusReceiveMode.PeekLock 117 | }; 118 | var client = new ServiceBusClient(ConnectionString, clientOptions); 119 | 120 | var queueSender = client.CreateSender(queueName); 121 | 122 | await ManagementExtensions.PurgeQueue(ConnectionString, queueName); 123 | 124 | await queueSender.SendMessageAsync(new ServiceBusMessage(Encoding.UTF8.GetBytes("Hej med dig min ven! Det spiller!"))); 125 | 126 | var messageReceiver = client.CreateReceiver(queueName, receiverOptions); 127 | 128 | var message = await messageReceiver.ReceiveMessageAsync(TimeSpan.FromSeconds(2)); 129 | 130 | Assert.That(message, Is.Not.Null); 131 | 132 | var text = Encoding.UTF8.GetString(message.Body); 133 | 134 | Assert.That(text, Is.EqualTo("Hej med dig min ven! Det spiller!")); 135 | } 136 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/TestAsbTopicsPubSub.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.AzureServiceBus.Tests.Bugs; 7 | using Rebus.Config; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Tests.Contracts.Extensions; 10 | #pragma warning disable 1998 11 | 12 | namespace Rebus.AzureServiceBus.Tests; 13 | 14 | [TestFixture] 15 | public class TestAsbTopicsPubSub : FixtureBase 16 | { 17 | readonly string _inputQueueName1 = TestConfig.GetName("pubsub1"); 18 | readonly string _inputQueueName2 = TestConfig.GetName("pubsub2"); 19 | readonly string _inputQueueName3 = TestConfig.GetName("pubsub3"); 20 | readonly string _connectionString = AsbTestConfig.ConnectionString; 21 | 22 | BuiltinHandlerActivator _bus1; 23 | IBusStarter _bus1Starter; 24 | BuiltinHandlerActivator _bus2; 25 | IBusStarter _bus2Starter; 26 | BuiltinHandlerActivator _bus3; 27 | IBusStarter _bus3Starter; 28 | 29 | protected override void SetUp() 30 | { 31 | Using(new TopicDeleter(new DefaultAzureServiceBusTopicNameConvention().GetTopic(typeof(string)))); 32 | 33 | (_bus1, _bus1Starter) = CreateBus(_inputQueueName1); 34 | (_bus2, _bus2Starter) = CreateBus(_inputQueueName2); 35 | (_bus3, _bus3Starter) = CreateBus(_inputQueueName3); 36 | } 37 | 38 | void StartBuses() 39 | { 40 | _bus1Starter.Start(); 41 | _bus2Starter.Start(); 42 | _bus3Starter.Start(); 43 | } 44 | 45 | [Test] 46 | public async Task PubSubSeemsToWork() 47 | { 48 | var gotString1 = new ManualResetEvent(false); 49 | var gotString2 = new ManualResetEvent(false); 50 | 51 | _bus1.Handle(async str => gotString1.Set()); 52 | _bus2.Handle(async str => gotString2.Set()); 53 | 54 | StartBuses(); 55 | 56 | await _bus1.Bus.Subscribe(); 57 | await _bus2.Bus.Subscribe(); 58 | 59 | await Task.Delay(500); 60 | 61 | await _bus3.Bus.Publish("hello there!!!!"); 62 | 63 | gotString1.WaitOrDie(TimeSpan.FromSeconds(3)); 64 | gotString2.WaitOrDie(TimeSpan.FromSeconds(3)); 65 | } 66 | 67 | [Test] 68 | public async Task PubSubSeemsToWorkAlsoWithUnsubscribe() 69 | { 70 | var gotString1 = new ManualResetEvent(false); 71 | var subscriber2GotTheMessage = false; 72 | 73 | _bus1.Handle(async str => gotString1.Set()); 74 | 75 | _bus2.Handle(async str => 76 | { 77 | subscriber2GotTheMessage = true; 78 | }); 79 | 80 | StartBuses(); 81 | 82 | await _bus1.Bus.Subscribe(); 83 | await _bus2.Bus.Subscribe(); 84 | 85 | await Task.Delay(500); 86 | 87 | await _bus2.Bus.Unsubscribe(); 88 | 89 | await Task.Delay(500); 90 | 91 | await _bus3.Bus.Publish("hello there!!!!"); 92 | 93 | gotString1.WaitOrDie(TimeSpan.FromSeconds(3)); 94 | 95 | Assert.That(subscriber2GotTheMessage, Is.False, "Didn't expect subscriber 2 to get the string since it was unsubscribed"); 96 | } 97 | 98 | (BuiltinHandlerActivator, IBusStarter) CreateBus(string inputQueue) 99 | { 100 | var activator = Using(new BuiltinHandlerActivator()); 101 | 102 | var starter = Configure.With(activator) 103 | .Transport(t => t.UseAzureServiceBus(_connectionString, inputQueue)) 104 | .Create(); 105 | 106 | return (activator, starter); 107 | } 108 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/TestExceptionIgnorant.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | using Rebus.Internals; 5 | #pragma warning disable 1998 6 | #pragma warning disable 4014 7 | 8 | namespace Rebus.AzureServiceBus.Tests; 9 | 10 | [TestFixture] 11 | public class TestExceptionIgnorant 12 | { 13 | [Test] 14 | public async Task IgnoresExceptionsAsExpected() 15 | { 16 | var attempt = 1; 17 | 18 | await new ExceptionIgnorant() 19 | .Ignore() 20 | .Execute(async () => 21 | { 22 | attempt++; 23 | 24 | if (attempt < 4) 25 | { 26 | throw new InvalidOperationException("SHOULD NOT ESCAPE"); 27 | } 28 | }); 29 | } 30 | 31 | [Test] 32 | public async Task OnlyIgnoresExceptionsThatMatchCriteria() 33 | { 34 | var executions = 0; 35 | 36 | var applicationException = Assert.ThrowsAsync(async () => 37 | { 38 | await new ExceptionIgnorant() 39 | .Ignore(a => a.Message.Contains("not in the message")) 40 | .Execute(async () => 41 | { 42 | executions++; 43 | throw new ApplicationException("I'm out!!"); 44 | }); 45 | }); 46 | 47 | Console.WriteLine(applicationException); 48 | 49 | Assert.That(executions, Is.EqualTo(1), "Verify that only one single execution was performed, meaning that the exception 'escaped' immediately"); 50 | } 51 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/TestShutdownTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using NUnit.Framework; 5 | using Rebus.Activation; 6 | using Rebus.Config; 7 | using Rebus.Tests.Contracts; 8 | 9 | namespace Rebus.AzureServiceBus.Tests; 10 | 11 | [TestFixture] 12 | public class TestShutdownTime : FixtureBase 13 | { 14 | static readonly string ConnectionString = AsbTestConfig.ConnectionString; 15 | static readonly string QueueName = TestConfig.GetName("timeouttest"); 16 | 17 | [Test] 18 | [Description("Verifies that all pending receive operations are cancelled when the bus is disposed")] 19 | public void FoundWayToCancelAllPendingReceiveOperations() 20 | { 21 | var stopwatch = new Stopwatch(); 22 | 23 | using (var activator = new BuiltinHandlerActivator()) 24 | { 25 | Configure.With(activator) 26 | .Transport(t => t.UseAzureServiceBus(ConnectionString, QueueName)) 27 | .Start(); 28 | 29 | Thread.Sleep(1000); 30 | 31 | stopwatch.Start(); 32 | } 33 | 34 | stopwatch.Stop(); 35 | 36 | var shutdownDuration = stopwatch.Elapsed; 37 | 38 | Console.WriteLine($"Shutdown took {shutdownDuration}"); 39 | } 40 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/TestUtilities/StubTokenCredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Core; 5 | using Microsoft.Identity.Client; 6 | 7 | namespace Rebus.AzureServiceBus.Tests.TestUtilities; 8 | 9 | internal class StubTokenCredential : TokenCredential 10 | { 11 | private readonly string clientId; 12 | private readonly string clientSecret; 13 | private readonly string tenantId; 14 | 15 | public StubTokenCredential(string clientId, string clientSecret, string tenantId) 16 | { 17 | this.clientId = clientId; 18 | this.clientSecret = clientSecret; 19 | this.tenantId = tenantId; 20 | } 21 | 22 | public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) 23 | { 24 | AccessToken token = default; 25 | 26 | Task.Run(async () => token = await this.GetTokenAsync(requestContext, cancellationToken)).Wait(); 27 | 28 | return token; 29 | } 30 | 31 | public override async ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) 32 | { 33 | IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId) 34 | .WithAuthority($"https://login.windows.net/{tenantId}") 35 | .WithClientSecret(clientSecret) 36 | .Build(); 37 | 38 | var serviceBusAudience = new Uri("https://servicebus.azure.net"); 39 | 40 | var authResult = await app.AcquireTokenForClient(new string[] { $"{serviceBusAudience}/.default" }).ExecuteAsync(cancellationToken); 41 | 42 | return new AccessToken(authResult.AccessToken, authResult.ExpiresOn); 43 | } 44 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/TokenProviderTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Activation; 3 | using Rebus.Config; 4 | using Rebus.Routing.TypeBased; 5 | using Rebus.Tests.Contracts; 6 | using Rebus.Tests.Contracts.Extensions; 7 | using System; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Rebus.AzureServiceBus.Tests.TestUtilities; 11 | 12 | #pragma warning disable 1998 13 | 14 | namespace Rebus.AzureServiceBus.Tests; 15 | 16 | [TestFixture] 17 | [Ignore("Requires some manual setup")] 18 | public class TokenProviderTest : FixtureBase 19 | { 20 | const string QueueName = "token-provider-server"; 21 | 22 | BuiltinHandlerActivator _server; 23 | 24 | protected override void SetUp() 25 | { 26 | _server = new BuiltinHandlerActivator(); 27 | 28 | Using(_server); 29 | 30 | Configure.With(_server) 31 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, QueueName)) 32 | .Start(); 33 | 34 | } 35 | 36 | [Test] 37 | public async Task CanInitializeClientWithTokenProvider() 38 | { 39 | var gotTheString = new ManualResetEvent(false); 40 | 41 | _server.Handle(async _ => gotTheString.Set()); 42 | 43 | var configurer = Configure.With(new BuiltinHandlerActivator()) 44 | .Transport(t => t 45 | .UseAzureServiceBus("", "", new StubTokenCredential("", "", "")) 46 | .DoNotCheckQueueConfiguration() 47 | .DoNotCreateQueues() 48 | ) 49 | .Routing(r => r.TypeBased().Map(QueueName)); 50 | 51 | using (var client = configurer.Start()) 52 | { 53 | await client.Send("HEJ MED DIG MIN VEN"); 54 | } 55 | 56 | gotTheString.WaitOrDie(TimeSpan.FromSeconds(5)); 57 | } 58 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/UseNativeHeaders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Azure.Messaging.ServiceBus; 6 | using NUnit.Framework; 7 | using Rebus.Activation; 8 | using Rebus.AzureServiceBus.Messages; 9 | using Rebus.AzureServiceBus.Tests.Bugs; 10 | using Rebus.Config; 11 | using Rebus.Messages; 12 | using Rebus.Tests.Contracts; 13 | using Rebus.Tests.Contracts.Extensions; 14 | // ReSharper disable AccessToDisposedClosure 15 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 16 | 17 | namespace Rebus.AzureServiceBus.Tests; 18 | 19 | [TestFixture] 20 | public class UseNativeHeaders : FixtureBase 21 | { 22 | [Test] 23 | public async Task ShouldNotPublishRebusHeadersWhenConfiguredNotTo() 24 | { 25 | var queueName = $"publish-native-{Guid.NewGuid():N}"; 26 | 27 | Using(new QueueDeleter(queueName)); 28 | 29 | using var activator = new BuiltinHandlerActivator(); 30 | using var gotTheMessage = new ManualResetEvent(initialState: false); 31 | 32 | ServiceBusReceivedMessage receivedMessage = null; 33 | 34 | activator.Handle(async (_, c, _) => 35 | { 36 | receivedMessage = c.TransactionContext.Items.GetOrDefault("asb-message") as ServiceBusReceivedMessage; 37 | gotTheMessage.Set(); 38 | }); 39 | 40 | var bus = Configure.With(activator) 41 | .Transport(t => t 42 | .UseNativeHeaders() 43 | .UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 44 | .Start(); 45 | 46 | await bus.SendLocal("hello"); 47 | 48 | gotTheMessage.WaitOrDie(timeout: TimeSpan.FromSeconds(5)); 49 | 50 | Assert.That(receivedMessage, Is.Not.Null); 51 | 52 | var headersAlreadyPresentOnNativeAsbMessage = new[] 53 | { 54 | Headers.MessageId, 55 | Headers.CorrelationId, 56 | Headers.ContentType, 57 | ExtraHeaders.SessionId, 58 | }; 59 | 60 | Assert.That(receivedMessage.ApplicationProperties.Keys.Intersect(headersAlreadyPresentOnNativeAsbMessage).Count(), Is.Zero, 61 | $@"Did not expect the ApplicationProperties dictionary on the ASB transport message to contain any of the following headers: 62 | 63 | {string.Join(Environment.NewLine, headersAlreadyPresentOnNativeAsbMessage.Select(header => $" {header}"))} 64 | 65 | but the following were present: 66 | 67 | {string.Join(Environment.NewLine, receivedMessage.ApplicationProperties.Select(p => $" {p.Key}"))}"); 68 | } 69 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/VerifyPayloadLimitWhenBatching.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | using Rebus.Activation; 5 | using Rebus.AzureServiceBus.Tests.Bugs; 6 | using Rebus.Config; 7 | using Rebus.Logging; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Transport; 10 | #pragma warning disable 1998 11 | 12 | namespace Rebus.AzureServiceBus.Tests; 13 | 14 | [TestFixture] 15 | public class VerifyPayloadLimitWhenBatching : FixtureBase 16 | { 17 | [Test] 18 | public async Task DoesNotHitTheLimit() 19 | { 20 | var queueName = TestConfig.GetName("payload-limit"); 21 | 22 | Using(new QueueDeleter(queueName)); 23 | 24 | var activator = Using(new BuiltinHandlerActivator()); 25 | 26 | activator.Handle(async _ => { }); 27 | 28 | var bus = Configure.With(activator) 29 | .Logging(l => l.Console(LogLevel.Info)) 30 | .Transport(t => t.UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName)) 31 | .Start(); 32 | 33 | using (var scope = new RebusTransactionScope()) 34 | { 35 | var strings = Enumerable.Range(0, 1000) 36 | .Select(n => new MessageWithText($"message {n}")); 37 | 38 | await Task.WhenAll(strings.Select(str => bus.SendLocal(str))); 39 | 40 | await scope.CompleteAsync(); 41 | } 42 | } 43 | 44 | record MessageWithText(string Text); 45 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/WorksWhenEnablingPartitioning.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using NUnit.Framework; 3 | using Rebus.Activation; 4 | using Rebus.AzureServiceBus.Tests.Bugs; 5 | using Rebus.AzureServiceBus.Tests.Factories; 6 | using Rebus.Config; 7 | using Rebus.Tests.Contracts; 8 | using Rebus.Tests.Contracts.Utilities; 9 | 10 | #pragma warning disable 1998 11 | 12 | namespace Rebus.AzureServiceBus.Tests; 13 | 14 | [TestFixture] 15 | public class WorksWhenEnablingPartitioning : FixtureBase 16 | { 17 | readonly string _queueName = TestConfig.GetName("input"); 18 | readonly string _connectionString = AsbTestConfig.ConnectionString; 19 | 20 | [Test] 21 | public async Task YesItDoes() 22 | { 23 | Using(new QueueDeleter(_queueName)); 24 | 25 | using var activator = new BuiltinHandlerActivator(); 26 | 27 | var counter = new SharedCounter(1); 28 | 29 | Using(counter); 30 | 31 | activator.Handle(async str => counter.Decrement()); 32 | 33 | var bus = Configure.With(activator) 34 | .Transport(t => t.UseAzureServiceBus(_connectionString, _queueName).EnablePartitioning()) 35 | .Start(); 36 | 37 | await bus.SendLocal("hej med dig min ven!!!"); 38 | 39 | counter.WaitForResetEvent(); 40 | } 41 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.Tests/WorksWithAutoDeleteOnIdle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Azure.Messaging.ServiceBus; 5 | using Azure.Messaging.ServiceBus.Administration; 6 | using NUnit.Framework; 7 | using Rebus.Activation; 8 | using Rebus.Config; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Extensions; 11 | 12 | #pragma warning disable 1998 13 | 14 | namespace Rebus.AzureServiceBus.Tests; 15 | 16 | [TestFixture] 17 | public class WorksWithAutoDeleteOnIdle : FixtureBase 18 | { 19 | [Test] 20 | [Ignore("takes a long time to execute")] 21 | public async Task AutomaticallyKeepsQueueAlive() 22 | { 23 | var client = new ServiceBusAdministrationClient(AsbTestConfig.ConnectionString); 24 | var activator = Using(new BuiltinHandlerActivator()); 25 | var gotTheString = Using(new ManualResetEvent(false)); 26 | 27 | activator.Handle(async message => gotTheString.Set()); 28 | 29 | var queueName = $"auto-delete-test-{Guid.NewGuid()}"; 30 | 31 | var bus = Configure.With(activator) 32 | .Transport(transport => transport 33 | .UseAzureServiceBus(AsbTestConfig.ConnectionString, queueName) 34 | .SetAutoDeleteOnIdle(TimeSpan.FromMinutes(5))) 35 | .Start(); 36 | 37 | // verify that queue exists 38 | var queueDescription = await client.GetQueueAsync(queueName); 39 | Assert.That(queueDescription.Value.AutoDeleteOnIdle, Is.EqualTo(TimeSpan.FromMinutes(5))); 40 | 41 | await Task.Delay(TimeSpan.FromMinutes(8)); 42 | 43 | // verify that the queue still exists 44 | try 45 | { 46 | await client.GetQueueAsync(queueName); 47 | } 48 | catch (ServiceBusException exception) 49 | { 50 | throw new ServiceBusException($"Could not get queue description for '{queueName}' after 8 minutes of waiting!", reason: ServiceBusFailureReason.MessagingEntityDisabled, innerException: exception); 51 | } 52 | 53 | // verify we can get the message 54 | await bus.SendLocal("HEJ MED DIG MIN VEN!!!!!!"); 55 | gotTheString.WaitOrDie(TimeSpan.FromSeconds(5), errorMessage: "Did not receive the message within 5 s after sending it - was the queue deleted??"); 56 | 57 | // dispose all the things 58 | CleanUpDisposables(); 59 | 60 | await Task.Delay(TimeSpan.FromMinutes(8)); 61 | 62 | var notFoundException = Assert.ThrowsAsync(async () => 63 | { 64 | await client.GetQueueAsync(queueName); 65 | }); 66 | 67 | Console.WriteLine(notFoundException); 68 | } 69 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.5.33502.453 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stuff", "stuff", "{1ABDD7C1-1F8D-402D-8692-3E4B66962DE0}" 6 | ProjectSection(SolutionItems) = preProject 7 | appveyor.yml = appveyor.yml 8 | CHANGELOG.md = CHANGELOG.md 9 | CONTRIBUTING.md = CONTRIBUTING.md 10 | LICENSE.md = LICENSE.md 11 | README.md = README.md 12 | EndProjectSection 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebus.AzureServiceBus", "Rebus.AzureServiceBus\Rebus.AzureServiceBus.csproj", "{DD32B8FB-6D0D-4FF1-9659-EDAA6DB5A93F}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebus.AzureServiceBus.Tests", "Rebus.AzureServiceBus.Tests\Rebus.AzureServiceBus.Tests.csproj", "{A8707703-0514-4D8C-9164-B9D20CA41EB5}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {DD32B8FB-6D0D-4FF1-9659-EDAA6DB5A93F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {DD32B8FB-6D0D-4FF1-9659-EDAA6DB5A93F}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {DD32B8FB-6D0D-4FF1-9659-EDAA6DB5A93F}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {DD32B8FB-6D0D-4FF1-9659-EDAA6DB5A93F}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {A8707703-0514-4D8C-9164-B9D20CA41EB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {A8707703-0514-4D8C-9164-B9D20CA41EB5}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {A8707703-0514-4D8C-9164-B9D20CA41EB5}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {A8707703-0514-4D8C-9164-B9D20CA41EB5}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/ConnectionStringParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Azure.Messaging.ServiceBus; 5 | using Rebus.Extensions; 6 | 7 | namespace Rebus.AzureServiceBus; 8 | 9 | class ConnectionStringParser 10 | { 11 | readonly Dictionary _parts; 12 | 13 | public string ConnectionString { get; } 14 | 15 | public ConnectionStringParser(string endpoint, string sharedAccessKeyName, string sharedAccessKey, string entityPath) 16 | { 17 | _parts = new Dictionary 18 | { 19 | {"Endpoint", endpoint}, 20 | {"SharedAccessKeyName", sharedAccessKeyName}, 21 | {"SharedAccessKey", sharedAccessKey}, 22 | {"EntityPath", entityPath} 23 | }; 24 | } 25 | 26 | public ConnectionStringParser(string connectionString) 27 | { 28 | ConnectionString = connectionString; 29 | 30 | _parts = connectionString.Split(';') 31 | .Select(token => token.Trim()) 32 | .Where(token => !string.IsNullOrWhiteSpace(token)) 33 | .Select(token => 34 | { 35 | var index = token.IndexOf('='); 36 | 37 | if (index < 0) throw new FormatException($"Could not interpret '{token}' as a key-value pair"); 38 | 39 | return new 40 | { 41 | key = token.Substring(0, index), 42 | value = token.Substring(index + 1) 43 | }; 44 | }) 45 | .ToDictionary(a => a.key, a => a.value); 46 | } 47 | 48 | public string Endpoint => _parts.GetValue("Endpoint").TrimEnd('/'); 49 | public string SharedAccessKeyName => _parts.GetValue("SharedAccessKeyName"); 50 | public string SharedAccessKey => _parts.GetValue("SharedAccessKey"); 51 | public string EntityPath => _parts.GetValueOrNull("EntityPath"); 52 | public ServiceBusTransportType Transport => (ServiceBusTransportType)Enum.Parse(typeof(ServiceBusTransportType), _parts.GetValueOrNull("TransportType") ?? nameof(ServiceBusTransportType.AmqpTcp)); 53 | 54 | public bool Contains(string name, string value, StringComparison comparison) => _parts.Any(p => string.Equals(p.Key, name, comparison) && string.Equals(p.Value, value, comparison)); 55 | 56 | public override string ToString() 57 | { 58 | return $@"{ConnectionString} 59 | Endpoint: {Endpoint} 60 | SharedAccessKeyName: {SharedAccessKeyName} 61 | SharedAccessKey: {SharedAccessKey}"; 62 | } 63 | 64 | public string GetConnectionStringWithoutEntityPath() => string.Join(";", _parts.Where(p => !string.Equals(p.Key, "EntityPath")).Select(kvp => $"{kvp.Key}={kvp.Value}")); 65 | 66 | public string GetConnectionString() => string.Join(";", _parts.Select(kvp => $"{kvp.Key}={kvp.Value}")); 67 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/DefaultAzureServiceBusTopicNameConvention.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Extensions; 3 | using Rebus.Topic; 4 | 5 | namespace Rebus.AzureServiceBus; 6 | 7 | /// 8 | /// Helper responsible for implementing how various names turn out 9 | /// 10 | public class DefaultAzureServiceBusTopicNameConvention : ITopicNameConvention 11 | { 12 | readonly bool _useLegacyNaming; 13 | 14 | /// 15 | /// Creates the name helper, using legacy topic naming if is true. 16 | /// 17 | public DefaultAzureServiceBusTopicNameConvention(bool useLegacyNaming = false) 18 | { 19 | _useLegacyNaming = useLegacyNaming; 20 | } 21 | 22 | /// 23 | /// Gets a topic name from the given 24 | /// 25 | public string GetTopic(Type eventType) 26 | { 27 | if (!_useLegacyNaming) 28 | { 29 | var assemblyName = eventType.Assembly.GetName().Name; 30 | var typeName = eventType.FullName; 31 | 32 | return $"{assemblyName}/{typeName}"; 33 | } 34 | 35 | var simpleAssemblyQualifiedName = eventType.GetSimpleAssemblyQualifiedName(); 36 | 37 | return simpleAssemblyQualifiedName; 38 | } 39 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/DisabledTimeoutManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Rebus.Extensions; 5 | using Rebus.Messages; 6 | using Rebus.Timeouts; 7 | 8 | #pragma warning disable 1998 9 | 10 | namespace Rebus.AzureServiceBus; 11 | 12 | class DisabledTimeoutManager : ITimeoutManager 13 | { 14 | public async Task Defer(DateTimeOffset approximateDueTime, Dictionary headers, byte[] body) 15 | { 16 | var messageIdToPrint = headers.GetValueOrNull(Headers.MessageId) ?? ""; 17 | 18 | var message = 19 | $"Received message with ID {messageIdToPrint} which is supposed to be deferred until {approximateDueTime} -" + 20 | " this is a problem, because the internal handling of deferred messages is" + 21 | " disabled when using Azure Service Bus as the transport layer in, which" + 22 | " case the native support for a specific future enqueueing time is used..."; 23 | 24 | throw new InvalidOperationException(message); 25 | } 26 | 27 | public async Task GetDueMessages() 28 | { 29 | return DueMessagesResult.Empty; 30 | } 31 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/Messages/DefaultMessageConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Azure.Messaging.ServiceBus; 4 | using Rebus.Bus; 5 | using Rebus.Extensions; 6 | using Rebus.Internals; 7 | using Rebus.Messages; 8 | 9 | namespace Rebus.AzureServiceBus.Messages; 10 | 11 | class DefaultMessageConverter : IMessageConverter 12 | { 13 | public TransportMessage ToTransport(ServiceBusReceivedMessage message) 14 | { 15 | var applicationProperties = message.ApplicationProperties; 16 | var headers = applicationProperties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToString()); 17 | 18 | headers[Headers.TimeToBeReceived] = message.TimeToLive.ToString(); 19 | headers[Headers.ContentType] = message.ContentType; 20 | headers[Headers.CorrelationId] = message.CorrelationId; 21 | headers[Headers.MessageId] = message.MessageId; 22 | 23 | // only add the session ID 24 | if (!string.IsNullOrWhiteSpace(message.SessionId)) 25 | { 26 | headers[ExtraHeaders.SessionId] = message.SessionId; 27 | } 28 | 29 | return new TransportMessage(headers, message.Body.ToMemory().ToArray()); 30 | } 31 | 32 | public ServiceBusMessage ToServiceBus(TransportMessage transportMessage) 33 | { 34 | var message = new ServiceBusMessage(transportMessage.Body); 35 | var headers = transportMessage.Headers.Clone(); 36 | 37 | if (headers.TryGetValue(Headers.TimeToBeReceived, out var timeToBeReceivedStr)) 38 | { 39 | var timeToBeReceived = TimeSpan.Parse(timeToBeReceivedStr); 40 | message.TimeToLive = timeToBeReceived; 41 | headers.Remove(Headers.TimeToBeReceived); 42 | } 43 | 44 | if (headers.TryGetValue(Headers.DeferredUntil, out var deferUntilTime)) 45 | { 46 | var deferUntilDateTimeOffset = deferUntilTime.ToDateTimeOffset(); 47 | message.ScheduledEnqueueTime = deferUntilDateTimeOffset; 48 | headers.Remove(Headers.DeferredUntil); 49 | } 50 | 51 | if (headers.TryGetValue(Headers.ContentType, out var contentType)) 52 | { 53 | message.ContentType = contentType; 54 | } 55 | 56 | if (headers.TryGetValue(Headers.CorrelationId, out var correlationId)) 57 | { 58 | message.CorrelationId = correlationId; 59 | } 60 | 61 | if (headers.TryGetValue(Headers.MessageId, out var messageId)) 62 | { 63 | message.MessageId = messageId; 64 | } 65 | 66 | if (headers.TryGetValue(ExtraHeaders.SessionId, out var sessionId)) 67 | { 68 | message.SessionId = sessionId; 69 | } 70 | 71 | message.Subject = transportMessage.GetMessageLabel(); 72 | 73 | if (headers.TryGetValue(Headers.ErrorDetails, out var errorDetails)) 74 | { 75 | // this particular header has a tendency to grow out of hand 76 | headers[Headers.ErrorDetails] = errorDetails.TrimTo(32000); 77 | } 78 | 79 | foreach (var kvp in headers) 80 | { 81 | message.ApplicationProperties[kvp.Key] = kvp.Value; 82 | } 83 | 84 | return message; 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/Messages/ExtraHeaders.cs: -------------------------------------------------------------------------------- 1 | namespace Rebus.AzureServiceBus.Messages; 2 | 3 | internal static class ExtraHeaders 4 | { 5 | public const string SessionId = "SessionId"; 6 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/Messages/IMessageConverter.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.ServiceBus; 2 | using Rebus.Messages; 3 | 4 | namespace Rebus.AzureServiceBus.Messages; 5 | 6 | public interface IMessageConverter 7 | { 8 | TransportMessage ToTransport(ServiceBusReceivedMessage message); 9 | ServiceBusMessage ToServiceBus(TransportMessage transportMessage); 10 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/Messages/RemoveHeaders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Azure.Messaging.ServiceBus; 3 | using Rebus.Messages; 4 | 5 | namespace Rebus.AzureServiceBus.Messages; 6 | 7 | class RemoveHeaders : IMessageConverter 8 | { 9 | readonly IMessageConverter _messageConverter; 10 | 11 | public RemoveHeaders(IMessageConverter messageConverter) => _messageConverter = messageConverter ?? throw new ArgumentNullException(nameof(messageConverter)); 12 | 13 | TransportMessage IMessageConverter.ToTransport(ServiceBusReceivedMessage message) 14 | { 15 | if (message == null) throw new ArgumentNullException(nameof(message)); 16 | return _messageConverter.ToTransport(message); 17 | } 18 | 19 | ServiceBusMessage IMessageConverter.ToServiceBus(TransportMessage transportMessage) 20 | { 21 | if (transportMessage == null) throw new ArgumentNullException(nameof(transportMessage)); 22 | 23 | var message = _messageConverter.ToServiceBus(transportMessage); 24 | var properties = message.ApplicationProperties; 25 | 26 | properties.Remove(Headers.MessageId); 27 | properties.Remove(Headers.CorrelationId); 28 | properties.Remove(Headers.ContentType); 29 | properties.Remove(ExtraHeaders.SessionId); 30 | 31 | return message; 32 | } 33 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/NameFormat/DefaultNameFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace Rebus.AzureServiceBus.NameFormat; 4 | 5 | /// 6 | /// A formatter that formats queue, topic and subscription names using a default convention. 7 | /// 8 | public class DefaultNameFormatter : INameFormatter 9 | { 10 | readonly char[] _additionalValidCharacters; 11 | 12 | /// 13 | /// Creates the name formatter. 14 | /// 15 | public DefaultNameFormatter() 16 | { 17 | _additionalValidCharacters = new[] { '.', '-', '_' }; 18 | } 19 | 20 | private string ReplaceInvalidCharacters(string str) 21 | { 22 | var name = string.Concat(str.Select(c => 23 | { 24 | if (c == '/') return '/'; 25 | 26 | return IsValidCharacter(c) ? c : '_'; 27 | })); 28 | 29 | return name; 30 | } 31 | 32 | bool IsValidCharacter(char c) 33 | { 34 | return char.IsLetterOrDigit(c) 35 | || _additionalValidCharacters.Contains(c); 36 | } 37 | 38 | /// 39 | /// Formats the queue name into a usable name on ASB, normalizing if needed. 40 | /// 41 | public string FormatQueueName(string queueName) 42 | { 43 | return ReplaceInvalidCharacters(queueName); 44 | } 45 | 46 | /// 47 | /// Formats the subscription name into a usable name on ASB, normalizing if needed. 48 | /// 49 | public string FormatSubscriptionName(string subscriptionName) 50 | { 51 | // queue names can have multiple segments in them separated by / - subscription names cannot! 52 | return ReplaceInvalidCharacters(subscriptionName.Replace("/", "_")); 53 | } 54 | 55 | /// 56 | /// Formats the topic name into a usable name on ASB, normalizing if needed. 57 | /// 58 | public string FormatTopicName(string topicName) 59 | { 60 | return ReplaceInvalidCharacters(topicName); 61 | } 62 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/NameFormat/INameFormatter.cs: -------------------------------------------------------------------------------- 1 | namespace Rebus.AzureServiceBus.NameFormat; 2 | 3 | /// 4 | /// Formatter for queue, topic and subscription names on ASB. 5 | /// 6 | public interface INameFormatter 7 | { 8 | /// 9 | /// Formats the queue name into a usable name on ASB, normalizing if needed. 10 | /// 11 | string FormatQueueName(string queueName); 12 | 13 | /// 14 | /// Formats the subscription name into a usable name on ASB, normalizing if needed. 15 | /// 16 | string FormatSubscriptionName(string subscriptionName); 17 | 18 | /// 19 | /// Formats the topic name into a usable name on ASB, normalizing if needed. 20 | /// 21 | string FormatTopicName(string topicName); 22 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/NameFormat/LegacyNameFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Rebus.AzureServiceBus.NameFormat; 5 | 6 | /// 7 | /// Formats the names how it was done from v6.0.4 and higher. 8 | /// 9 | public class LegacyNameFormatter : INameFormatter 10 | { 11 | readonly char[] _additionalValidCharacters; 12 | 13 | /// 14 | /// Creates the formatter. 15 | /// 16 | public LegacyNameFormatter() 17 | { 18 | _additionalValidCharacters = new[] { '_' }; 19 | } 20 | 21 | private string ReplaceInvalidCharacters(string str) 22 | { 23 | var name = string.Concat(str.Select(c => 24 | { 25 | if (c == '/') return '/'; 26 | 27 | return IsValidCharacter(c) ? c : '_'; 28 | })); 29 | 30 | return name.ToLowerInvariant(); 31 | } 32 | 33 | bool IsValidCharacter(char c) 34 | { 35 | return char.IsLetterOrDigit(c) || _additionalValidCharacters.Contains(c); 36 | } 37 | 38 | /// 39 | /// Formats the queue name into a usable name on ASB, normalizing if needed. 40 | /// 41 | public string FormatQueueName(string queueName) 42 | { 43 | return ReplaceInvalidCharacters(queueName); 44 | } 45 | 46 | /// 47 | /// Formats the subscription name into a usable name on ASB, normalizing if needed. 48 | /// 49 | public string FormatSubscriptionName(string subscriptionName) 50 | { 51 | var idx = subscriptionName.LastIndexOf("/", StringComparison.Ordinal) + 1; 52 | subscriptionName = subscriptionName.Substring(idx); 53 | return ReplaceInvalidCharacters(subscriptionName); 54 | } 55 | 56 | /// 57 | /// Formats the topic name into a usable name on ASB, normalizing if needed. 58 | /// 59 | public string FormatTopicName(string topicName) 60 | { 61 | return ReplaceInvalidCharacters(topicName); 62 | } 63 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/NameFormat/LegacyV3NameFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Rebus.AzureServiceBus.NameFormat; 5 | 6 | /// 7 | /// Formats the names according to how it was done since at least v3, up to v6.0.3. 8 | /// 9 | public class LegacyV3NameFormatter : INameFormatter 10 | { 11 | /// 12 | /// Formats the queue name into a usable name on ASB, normalizing if needed. 13 | /// 14 | public string FormatQueueName(string queueName) 15 | { 16 | var name = string.Concat(queueName.Select(c => 17 | { 18 | return (char.IsLetterOrDigit(c) || c == '/' || c == '.') ? c : '_'; 19 | })); 20 | 21 | return name.ToLowerInvariant(); 22 | } 23 | 24 | /// 25 | /// Formats the subscription name into a usable name on ASB, normalizing if needed. 26 | /// 27 | public string FormatSubscriptionName(string subscriptionName) 28 | { 29 | var idx = subscriptionName.LastIndexOf("/", StringComparison.Ordinal) + 1; 30 | subscriptionName = subscriptionName.Substring(idx); 31 | 32 | subscriptionName = string.Concat(subscriptionName.Select(c => 33 | { 34 | return (char.IsLetterOrDigit(c) || c == '/') ? c : '_'; 35 | })); 36 | 37 | subscriptionName = subscriptionName.ToLowerInvariant(); 38 | 39 | return subscriptionName; 40 | } 41 | 42 | /// 43 | /// Formats the topic name into a usable name on ASB, normalizing if needed. 44 | /// 45 | public string FormatTopicName(string topicName) 46 | { 47 | var name = string.Concat(topicName.Select(c => 48 | { 49 | return (char.IsLetterOrDigit(c) || c == '/') ? c : '_'; 50 | })); 51 | 52 | return name.ToLowerInvariant(); 53 | } 54 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/NameFormat/PrefixNameFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Rebus.AzureServiceBus.NameFormat; 4 | 5 | /// 6 | /// Formats all queue and topic names using a prefix. 7 | /// 8 | public class PrefixNameFormatter : INameFormatter 9 | { 10 | private readonly string _prefix; 11 | private readonly INameFormatter _innerFormatter; 12 | 13 | /// 14 | /// Creates the name formatter. 15 | /// 16 | public PrefixNameFormatter(string prefix) : this(prefix, new DefaultNameFormatter()) { } 17 | 18 | /// 19 | /// Creates the name formatter using a specified inner name formatter. 20 | /// 21 | public PrefixNameFormatter(string prefix, INameFormatter innerFormatter) 22 | { 23 | _prefix = prefix ?? throw new ArgumentNullException(nameof(prefix)); 24 | _innerFormatter = innerFormatter ?? throw new ArgumentNullException(nameof(innerFormatter)); 25 | } 26 | 27 | /// 28 | /// Formats the queue name into a usable name on ASB, normalizing if needed. 29 | /// 30 | public string FormatQueueName(string queueName) 31 | { 32 | return _innerFormatter.FormatQueueName(_prefix + queueName); 33 | } 34 | 35 | /// 36 | /// Formats the subscription name into a usable name on ASB, normalizing if needed. 37 | /// 38 | public string FormatSubscriptionName(string subscriptionName) 39 | { 40 | return _innerFormatter.FormatSubscriptionName(subscriptionName); 41 | } 42 | 43 | /// 44 | /// Formats the topic name into a usable name on ASB, normalizing if needed. 45 | /// 46 | public string FormatTopicName(string topicName) 47 | { 48 | return _innerFormatter.FormatTopicName(_prefix + topicName); 49 | } 50 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/OutgoingMessage.cs: -------------------------------------------------------------------------------- 1 | using Rebus.Messages; 2 | 3 | namespace Rebus.AzureServiceBus; 4 | 5 | class OutgoingMessage 6 | { 7 | public string DestinationAddress { get; } 8 | public TransportMessage TransportMessage { get; } 9 | 10 | public OutgoingMessage(string destinationAddress, TransportMessage transportMessage) 11 | { 12 | DestinationAddress = destinationAddress; 13 | TransportMessage = transportMessage; 14 | } 15 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/AzureServiceBus/ReceivedMessage.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.ServiceBus; 2 | 3 | namespace Rebus.AzureServiceBus; 4 | 5 | class ReceivedMessage 6 | { 7 | public ServiceBusReceivedMessage Message { get; } 8 | public ServiceBusReceiver MessageReceiver { get; } 9 | 10 | public ReceivedMessage(ServiceBusReceivedMessage message, ServiceBusReceiver messageReceiver) 11 | { 12 | Message = message; 13 | MessageReceiver = messageReceiver; 14 | } 15 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Config/AdditionalAzureServiceBusConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Azure.Messaging.ServiceBus; 4 | using Rebus.Internals; 5 | using Rebus.Messages; 6 | using Rebus.Retry; 7 | using Rebus.Transport; 8 | using Rebus.Logging; 9 | using System.Linq; 10 | using Rebus.AzureServiceBus.Messages; 11 | 12 | namespace Rebus.Config; 13 | 14 | /// 15 | /// Experimental configuration extensions for changing the way dead-lettering works with Rebus 16 | /// 17 | public static class AdditionalAzureServiceBusConfigurationExtensions 18 | { 19 | /// 20 | /// Extends Rebus' built-in deadlettering with the ability to use Azure Service Bus' built-in deadlettering 21 | /// 22 | public static void UseNativeDeadlettering(this StandardConfigurer configurer) 23 | { 24 | configurer 25 | .OtherService() 26 | .Decorate(c => new BuiltInDeadletteringErrorHandler(c.Get(), c.Get())); 27 | } 28 | 29 | /// 30 | /// Stop publishing `rbs2-*` headers for values that are already available on the ServiceBusMessage. 31 | /// 32 | public static StandardConfigurer UseNativeHeaders(this StandardConfigurer configurer) 33 | { 34 | configurer 35 | .OtherService() 36 | .Decorate(c => new RemoveHeaders(c.Get())); 37 | 38 | return configurer; 39 | } 40 | 41 | class BuiltInDeadletteringErrorHandler : IErrorHandler 42 | { 43 | readonly IErrorHandler _errorHandler; 44 | readonly ILog _log; 45 | 46 | public BuiltInDeadletteringErrorHandler(IErrorHandler errorHandler, IRebusLoggerFactory rebusLoggerFactory) 47 | { 48 | _errorHandler = errorHandler ?? throw new ArgumentNullException(nameof(errorHandler)); 49 | _log = rebusLoggerFactory?.GetLogger() ?? throw new ArgumentNullException(nameof(rebusLoggerFactory)); 50 | } 51 | 52 | public async Task HandlePoisonMessage(TransportMessage transportMessage, ITransactionContext transactionContext, ExceptionInfo exception) 53 | { 54 | if (transactionContext.Items.TryGetValue("asb-message", out var messageObject) 55 | && messageObject is ServiceBusReceivedMessage message 56 | && transactionContext.Items.TryGetValue("asb-message-receiver", out var messageReceiverObject) 57 | && messageReceiverObject is ServiceBusReceiver messageReceiver) 58 | { 59 | const int headerValueMaxLength = 4096; 60 | 61 | var deadLetterReason = exception.Message.TrimTo(maxLength: headerValueMaxLength); 62 | var deadLetterErrorDescription = exception.ToString().TrimTo(maxLength: headerValueMaxLength); 63 | 64 | if (!transportMessage.Headers.TryGetValue(Headers.MessageId, out var messageId)) 65 | { 66 | messageId = ""; 67 | } 68 | 69 | _log.Error("Dead-lettering message with ID {messageId}, reason={deadLetterReason}, exception info: {exceptionInfo}", 70 | messageId, deadLetterReason, exception); 71 | await messageReceiver.DeadLetterMessageAsync(message, transportMessage.Headers.ToDictionary(k => k.Key, v => (object)v.Value), deadLetterReason, deadLetterErrorDescription); 72 | 73 | // remove the message from the context, so the transport doesn't try to complete the message 74 | transactionContext.Items.TryRemove("asb-message", out _); 75 | } 76 | else 77 | { 78 | await _errorHandler.HandlePoisonMessage(transportMessage, transactionContext, exception); 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Config/AzureServiceBusConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using Azure.Core; 4 | using Rebus.AzureServiceBus; 5 | using Rebus.AzureServiceBus.NameFormat; 6 | using Rebus.Logging; 7 | using Rebus.Pipeline; 8 | using Rebus.Pipeline.Receive; 9 | using Rebus.Subscriptions; 10 | using Rebus.Threading; 11 | using Rebus.Timeouts; 12 | using Rebus.Topic; 13 | using Rebus.Transport; 14 | // ReSharper disable ArgumentsStyleNamedExpression 15 | // ReSharper disable ArgumentsStyleLiteral 16 | // ReSharper disable once CheckNamespace 17 | 18 | namespace Rebus.Config; 19 | 20 | /// 21 | /// Configuration extensions for the Azure Service Bus transport 22 | /// 23 | public static class AzureServiceBusConfigurationExtensions 24 | { 25 | const string AsbSubStorageText = "The Azure Service Bus transport was inserted as the subscriptions storage because it has native support for pub/sub messaging"; 26 | const string AsbTimeoutManagerText = "A disabled timeout manager was installed as part of the Azure Service Bus configuration, becuase the transport has native support for deferred messages"; 27 | 28 | /// 29 | /// Configures Rebus to use Azure Service Bus to transport messages as a one-way client (i.e. will not be able to receive any messages) 30 | /// 31 | public static AzureServiceBusTransportClientSettings UseAzureServiceBusAsOneWayClient(this StandardConfigurer configurer, string connectionString, TokenCredential tokenCredential = null) 32 | { 33 | var settingsBuilder = new AzureServiceBusTransportClientSettings(); 34 | 35 | configurer.OtherService().Decorate(c => 36 | { 37 | var options = c.Get(); 38 | options.ExternalTimeoutManagerAddressOrNull = AzureServiceBusTransport.MagicDeferredMessagesAddress; 39 | return options; 40 | }); 41 | 42 | configurer 43 | .OtherService() 44 | .Register(c => 45 | { 46 | var cancellationToken = c.Get(); 47 | var rebusLoggerFactory = c.Get(); 48 | var asyncTaskFactory = c.Get(); 49 | var nameFormatter = c.Get(); 50 | 51 | var transport = new AzureServiceBusTransport( 52 | connectionString: connectionString, 53 | queueName: null, 54 | rebusLoggerFactory: rebusLoggerFactory, 55 | asyncTaskFactory: asyncTaskFactory, 56 | nameFormatter: nameFormatter, 57 | messageConverter: c.Get(), 58 | cancellationToken: cancellationToken, 59 | tokenCredential: tokenCredential 60 | ); 61 | transport.DoNotConfigureTopicEnabled = settingsBuilder.DoNotConfigureTopicEnabled; 62 | return transport; 63 | }); 64 | 65 | RegisterServices(configurer, () => settingsBuilder.LegacyNamingEnabled); 66 | 67 | OneWayClientBackdoor.ConfigureOneWayClient(configurer); 68 | 69 | return settingsBuilder; 70 | } 71 | 72 | /// 73 | /// Configures Rebus to use Azure Service Bus queues to transport messages, connecting to the service bus instance pointed to by the connection string 74 | /// (or the connection string with the specified name from the current app.config) 75 | /// 76 | public static AzureServiceBusTransportSettings UseAzureServiceBus(this StandardConfigurer configurer, string connectionString, string inputQueueAddress, TokenCredential tokenCredential = null) 77 | { 78 | var settingsBuilder = new AzureServiceBusTransportSettings(); 79 | 80 | // register the actual transport as itself 81 | configurer 82 | .OtherService() 83 | .Register(c => 84 | { 85 | var nameFormatter = c.Get(); 86 | var cancellationToken = c.Get(); 87 | var rebusLoggerFactory = c.Get(); 88 | var asyncTaskFactory = c.Get(); 89 | 90 | var transport = new AzureServiceBusTransport( 91 | connectionString: connectionString, 92 | queueName: inputQueueAddress, 93 | rebusLoggerFactory: rebusLoggerFactory, 94 | asyncTaskFactory: asyncTaskFactory, 95 | nameFormatter: nameFormatter, 96 | messageConverter: c.Get(), 97 | cancellationToken: cancellationToken, 98 | tokenCredential: tokenCredential 99 | ); 100 | 101 | if (settingsBuilder.PrefetchingEnabled) 102 | { 103 | transport.PrefetchMessages(settingsBuilder.NumberOfMessagesToPrefetch); 104 | } 105 | 106 | transport.AutomaticallyRenewPeekLock = settingsBuilder.AutomaticPeekLockRenewalEnabled; 107 | transport.PartitioningEnabled = settingsBuilder.PartitioningEnabled; 108 | transport.DoNotCreateQueuesEnabled = settingsBuilder.DoNotCreateQueuesEnabled; 109 | transport.DefaultMessageTimeToLive = settingsBuilder.DefaultMessageTimeToLive; 110 | transport.DoNotCheckQueueConfigurationEnabled = settingsBuilder.DoNotCheckQueueConfigurationEnabled; 111 | transport.DoNotConfigureTopicEnabled = settingsBuilder.DoNotConfigureTopicEnabled; 112 | transport.LockDuration = settingsBuilder.LockDuration; 113 | transport.AutoDeleteOnIdle = settingsBuilder.AutoDeleteOnIdle; 114 | transport.DuplicateDetectionHistoryTimeWindow = settingsBuilder.DuplicateDetectionHistoryTimeWindow; 115 | transport.NativeMessageDeliveryCountEnabled = settingsBuilder.NativeMessageDeliveryCountEnabled; 116 | 117 | if (settingsBuilder.ReceiveOperationTimeout != null) 118 | { 119 | transport.ReceiveOperationTimeout = settingsBuilder.ReceiveOperationTimeout.Value; 120 | } 121 | 122 | return transport; 123 | }); 124 | 125 | RegisterServices(configurer, () => settingsBuilder.LegacyNamingEnabled); 126 | 127 | // remove deferred messages step 128 | configurer.OtherService().Decorate(c => 129 | { 130 | var pipeline = c.Get(); 131 | 132 | return new PipelineStepRemover(pipeline) 133 | .RemoveIncomingStep(s => s.GetType() == typeof(HandleDeferredMessagesStep)); 134 | }); 135 | 136 | return settingsBuilder; 137 | } 138 | 139 | static void RegisterServices(StandardConfigurer configurer, Func legacyNamingEnabled) 140 | { 141 | // map ITransport to transport implementation 142 | configurer.Register(c => c.Get()); 143 | 144 | // map subscription storage to transport 145 | configurer 146 | .OtherService() 147 | .Register(c => c.Get(), description: AsbSubStorageText); 148 | 149 | // disable timeout manager 150 | configurer.OtherService().Register(c => new DisabledTimeoutManager(), description: AsbTimeoutManagerText); 151 | 152 | configurer.OtherService().Register(c => 153 | { 154 | // lazy-evaluated setting because the builder needs a chance to be built upon before getting its settings 155 | var useLegacyNaming = legacyNamingEnabled(); 156 | 157 | if (useLegacyNaming) return new LegacyNameFormatter(); 158 | else return new DefaultNameFormatter(); 159 | }); 160 | 161 | configurer.OtherService().Register(c => 162 | { 163 | // lazy-evaluated setting because the builder needs a chance to be built upon before getting its settings 164 | var useLegacyNaming = legacyNamingEnabled(); 165 | 166 | return new DefaultAzureServiceBusTopicNameConvention(useLegacyNaming: useLegacyNaming); 167 | }); 168 | 169 | configurer.OtherService().Register(c => c.Get()); 170 | configurer.OtherService().Register(_ => new AzureServiceBus.Messages.DefaultMessageConverter()); 171 | } 172 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Config/AzureServiceBusTransportClientSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Rebus.Config; 2 | 3 | /// 4 | /// Allows for configuring additional options for the Azure Service Bus transport (when running in one-way client mode) 5 | /// 6 | public class AzureServiceBusTransportClientSettings 7 | { 8 | internal bool LegacyNamingEnabled { get; set; } 9 | 10 | /// 11 | /// Gets/sets whether to skip checking topics configuration 12 | /// 13 | internal bool DoNotConfigureTopicEnabled { get; set; } 14 | 15 | /// 16 | /// Enables "legacy naming", which means that queue names are lowercased, and topic names are "normalized" to be in accordance 17 | /// with how v6 of the transport did it. 18 | /// 19 | public AzureServiceBusTransportClientSettings UseLegacyNaming() 20 | { 21 | LegacyNamingEnabled = true; 22 | return this; 23 | } 24 | 25 | /// 26 | /// Skips topic verification. Can be used when the connection string does not have administration access 27 | /// When enabled 28 | /// - will not create the topic, subscription, or configure forwarding to the input queue. 29 | /// - When needed subscribe method, will not delete the subscription for the specified topic. 30 | /// 31 | /// This flag is particularly useful in scenarios where: 32 | /// - The application has only "Listen" permissions and lacks administrative rights to manage 33 | /// topics and subscriptions in Azure Service Bus. 34 | /// - The infrastructure is centrally managed, and topics/subscriptions are provisioned 35 | /// manually or by deployment scripts. 36 | /// - Security restrictions require more controlled and audited modifications to Service Bus entities. 37 | /// 38 | /// However, enabling this flag introduces the following considerations: 39 | /// - Manual Provisioning Required: Topics, subscriptions, and forwarding must be manually configured 40 | /// in the Azure Service Bus namespace. 41 | /// - Potential Message Loss: If the subscription does not exist or is misconfigured, 42 | /// messages will not be received and may be lost if not handled with a retry mechanism or DLQ (Dead Letter Queue). 43 | /// - Higher Maintenance Overhead: Scaling or deploying new instances requires explicit verification 44 | /// that all Service Bus entities are in place and correctly configured. 45 | /// 46 | public AzureServiceBusTransportClientSettings DoNotConfigureTopic() 47 | { 48 | DoNotConfigureTopicEnabled = true; 49 | return this; 50 | } 51 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/AsyncHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Runtime.ExceptionServices; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | // ReSharper disable AsyncVoidLambda 7 | 8 | namespace Rebus.Internals; 9 | 10 | static class AsyncHelpers 11 | { 12 | /// 13 | /// Executes a task synchronously on the calling thread by installing a temporary synchronization context that queues continuations 14 | /// 15 | public static void RunSync(Func task) 16 | { 17 | var currentContext = SynchronizationContext.Current; 18 | var customContext = new CustomSynchronizationContext(task); 19 | 20 | try 21 | { 22 | SynchronizationContext.SetSynchronizationContext(customContext); 23 | 24 | customContext.Run(); 25 | } 26 | finally 27 | { 28 | SynchronizationContext.SetSynchronizationContext(currentContext); 29 | } 30 | } 31 | 32 | /// 33 | /// Synchronization context that can be "pumped" in order to have it execute continuations posted back to it 34 | /// 35 | class CustomSynchronizationContext : SynchronizationContext 36 | { 37 | readonly ConcurrentQueue> _items = new(); 38 | readonly AutoResetEvent _workItemsWaiting = new(false); 39 | readonly Func _task; 40 | 41 | ExceptionDispatchInfo _caughtException; 42 | 43 | bool _done; 44 | 45 | public CustomSynchronizationContext(Func task) 46 | { 47 | _task = task ?? throw new ArgumentNullException(nameof(task), "Please remember to pass a Task to be executed"); 48 | } 49 | 50 | public override void Post(SendOrPostCallback function, object state) 51 | { 52 | _items.Enqueue(Tuple.Create(function, state)); 53 | _workItemsWaiting.Set(); 54 | } 55 | 56 | /// 57 | /// Enqueues the function to be executed and executes all resulting continuations until it is completely done 58 | /// 59 | public void Run() 60 | { 61 | Post(async _ => 62 | { 63 | try 64 | { 65 | await _task().ConfigureAwait(false); 66 | } 67 | catch (Exception exception) 68 | { 69 | _caughtException = ExceptionDispatchInfo.Capture(exception); 70 | throw; 71 | } 72 | finally 73 | { 74 | Post(_ => _done = true, null); 75 | } 76 | }, null); 77 | 78 | while (!_done) 79 | { 80 | if (_items.TryDequeue(out var task)) 81 | { 82 | task.Item1(task.Item2); 83 | 84 | if (_caughtException == null) continue; 85 | 86 | _caughtException.Throw(); 87 | } 88 | else 89 | { 90 | _workItemsWaiting.WaitOne(); 91 | } 92 | } 93 | } 94 | 95 | public override SynchronizationContext CreateCopy() => this; 96 | 97 | public override void Send(SendOrPostCallback d, object state) => throw new NotSupportedException("Cannot send to same thread"); 98 | } 99 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/DisposableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Rebus.Internals; 5 | 6 | static class DisposableExtensions 7 | { 8 | public static void DisposeCollection(this IEnumerable disposables) 9 | { 10 | foreach (var disposable in disposables) 11 | { 12 | disposable.Dispose(); 13 | } 14 | } 15 | 16 | public static IDisposable AsDisposable(this T instance, Action disposeAction) => new Disposable(() => disposeAction(instance)); 17 | 18 | class Disposable : IDisposable 19 | { 20 | readonly Action _action; 21 | public Disposable(Action action) => _action = action ?? throw new ArgumentNullException(nameof(action)); 22 | public void Dispose() => _action(); 23 | } 24 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Rebus.Internals; 5 | 6 | static class EnumerableExtensions 7 | { 8 | public static IEnumerable> Batch(this IEnumerable items, int maxBatchSize) 9 | { 10 | var list = new List(); 11 | 12 | foreach (var item in items) 13 | { 14 | list.Add(item); 15 | 16 | if (list.Count < maxBatchSize) continue; 17 | 18 | yield return list.ToArray(); 19 | 20 | list.Clear(); 21 | } 22 | 23 | if (list.Any()) 24 | { 25 | yield return list.ToArray(); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/ExceptionIgnorant.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Rebus.Internals; 9 | 10 | class ExceptionIgnorant 11 | { 12 | readonly List _exceptionsToIgnore = new(); 13 | readonly TimeSpan _delayBetweenAttempts; 14 | readonly int _maxAttemps; 15 | 16 | public ExceptionIgnorant(int maxAttemps = 5, TimeSpan delayBetweenAttempts = default) 17 | { 18 | _maxAttemps = maxAttemps; 19 | _delayBetweenAttempts = delayBetweenAttempts; 20 | } 21 | 22 | public ExceptionIgnorant Ignore(Func ignoreCriteria = null) where TException : Exception 23 | { 24 | _exceptionsToIgnore.Add(new ExceptionToIgnore(typeof(TException), exception => ignoreCriteria?.Invoke((TException)exception) ?? true)); 25 | return this; 26 | } 27 | 28 | public async Task Execute(Func function, CancellationToken cancellationToken = default) 29 | { 30 | var attempt = 0; 31 | 32 | bool TriedTooManyTimes() => attempt >= _maxAttemps; 33 | 34 | bool ShouldIgnoreException(Exception ex) => _exceptionsToIgnore.Any(e => e.ShouldIgnore(ex)); 35 | 36 | while (true) 37 | { 38 | attempt++; 39 | 40 | try 41 | { 42 | await function(); 43 | break; 44 | } 45 | catch (Exception exception) 46 | { 47 | if (TriedTooManyTimes() || !ShouldIgnoreException(exception)) 48 | { 49 | ExceptionDispatchInfo.Capture(exception).Throw(); 50 | } 51 | 52 | await Task.Delay(_delayBetweenAttempts, cancellationToken); 53 | } 54 | } 55 | } 56 | 57 | class ExceptionToIgnore 58 | { 59 | readonly Type _exceptionType; 60 | readonly Func _ignoreCriteria; 61 | 62 | public ExceptionToIgnore(Type exceptionType, Func ignoreCriteria) 63 | { 64 | _exceptionType = exceptionType ?? throw new ArgumentNullException(nameof(exceptionType)); 65 | _ignoreCriteria = ignoreCriteria ?? throw new ArgumentNullException(nameof(ignoreCriteria)); 66 | } 67 | 68 | public bool ShouldIgnore(Exception exception) => _exceptionType.IsInstanceOfType(exception) 69 | && _ignoreCriteria(exception); 70 | } 71 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Rebus.AzureServiceBus.Tests")] -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/ManagementExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Azure.Messaging.ServiceBus; 6 | using Azure.Messaging.ServiceBus.Administration; 7 | 8 | namespace Rebus.Internals; 9 | 10 | static class ManagementExtensions 11 | { 12 | public static async Task DeleteQueueIfExistsAsync(this ServiceBusAdministrationClient client, string queuePath, CancellationToken cancellationToken = default) 13 | { 14 | try 15 | { 16 | await client.DeleteQueueAsync(queuePath, cancellationToken).ConfigureAwait(false); 17 | } 18 | catch (ServiceBusException) 19 | { 20 | // it's ok man 21 | } 22 | } 23 | 24 | public static async Task CreateQueueIfNotExistsAsync(this ServiceBusAdministrationClient client, string queuePath, CancellationToken cancellationToken = default) 25 | { 26 | try 27 | { 28 | await client.CreateQueueAsync(queuePath, cancellationToken).ConfigureAwait(false); 29 | } 30 | catch (ServiceBusException) 31 | { 32 | // it's ok man 33 | } 34 | } 35 | 36 | public static async Task PurgeQueue(string connectionString, string queueName, CancellationToken cancellationToken = default) 37 | { 38 | var messageReceiver = new ServiceBusClient(connectionString).CreateReceiver(queueName, new ServiceBusReceiverOptions 39 | { 40 | ReceiveMode = ServiceBusReceiveMode.ReceiveAndDelete 41 | }); 42 | 43 | try 44 | { 45 | while (true) 46 | { 47 | var messages = await messageReceiver 48 | .ReceiveMessagesAsync(100, TimeSpan.FromSeconds(2), cancellationToken) 49 | .ConfigureAwait(false); 50 | 51 | if (messages == null) break; 52 | if (!messages.Any()) break; 53 | } 54 | } 55 | catch (ServiceBusException) 56 | { 57 | // ignore it then 58 | } 59 | finally 60 | { 61 | await messageReceiver.CloseAsync(cancellationToken).ConfigureAwait(false); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/MessageLockRenewer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Azure.Messaging.ServiceBus; 4 | 5 | namespace Rebus.Internals; 6 | 7 | class MessageLockRenewer 8 | { 9 | readonly ServiceBusReceivedMessage _message; 10 | readonly ServiceBusReceiver _messageReceiver; 11 | 12 | DateTimeOffset _nextRenewal; 13 | 14 | public MessageLockRenewer(ServiceBusReceivedMessage message, ServiceBusReceiver messageReceiver) 15 | { 16 | _message = message; 17 | _messageReceiver = messageReceiver; 18 | _nextRenewal = GetTimeOfNextRenewal(); 19 | } 20 | 21 | public string MessageId => _message.MessageId; 22 | 23 | public bool IsDue => DateTimeOffset.Now >= _nextRenewal; 24 | 25 | public async Task Renew() 26 | { 27 | // intentionally let exceptions bubble out here, so the caller can log it as a warning 28 | await _messageReceiver.RenewMessageLockAsync(_message); 29 | 30 | _nextRenewal = GetTimeOfNextRenewal(); 31 | } 32 | 33 | DateTimeOffset GetTimeOfNextRenewal() 34 | { 35 | var now = DateTimeOffset.Now; 36 | 37 | var remainingTime = LockedUntil - now; 38 | var halfOfRemainingTime = TimeSpan.FromMinutes(0.5 * remainingTime.TotalMinutes); 39 | 40 | return now + halfOfRemainingTime; 41 | } 42 | 43 | DateTimeOffset LockedUntil => _message.LockedUntil; 44 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/SeviceBusSenderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.ServiceBus; 2 | 3 | namespace Rebus.Internals; 4 | 5 | static class SeviceBusSenderExtensions 6 | { 7 | public static string GetQueuePath(this ServiceBusSender sender) => $"http://{sender.FullyQualifiedNamespace}/{sender.EntityPath}"; 8 | 9 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Internals/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Rebus.Internals; 2 | 3 | static class StringExtensions 4 | { 5 | public static string TrimTo(this string str, int maxLength) 6 | { 7 | if (str == null) return null; 8 | 9 | if (str.Length < maxLength) return str; 10 | 11 | const string ellipsis = " (...)"; 12 | 13 | return string.Concat(str.Substring(0, maxLength - ellipsis.Length), ellipsis); 14 | } 15 | } -------------------------------------------------------------------------------- /Rebus.AzureServiceBus/Rebus.AzureServiceBus.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | Rebus 5 | 11 6 | netstandard2.0 7 | mookid8000 8 | https://rebus.fm/what-is-rebus/ 9 | Copyright Rebus FM ApS 2012 10 | rebus events 11 | Provides an Azure Service Bus transport for Rebus 12 | https://github.com/rebus-org/Rebus.AzureServiceBus 13 | git 14 | MIT 15 | little_rebusbus2_copy-500x500.png 16 | README.md 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | True 26 | 27 | 28 | 29 | True 30 | \ 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | shallow_clone: true 4 | 5 | cache: 6 | - packages -> **\packages.config 7 | - '%LocalAppData%\NuGet\Cache' 8 | 9 | before_build: 10 | - appveyor-retry dotnet restore -v Minimal 11 | 12 | build_script: 13 | - dotnet build Rebus.AzureServiceBus -c Release --no-restore 14 | 15 | test_script: 16 | - dotnet test Rebus.AzureServiceBus.Tests -c Release --no-restore 17 | -------------------------------------------------------------------------------- /artwork/little_rebusbus2_copy-500x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.AzureServiceBus/f2ab590cf54d91340e2495893c9d462291d8e018/artwork/little_rebusbus2_copy-500x500.png -------------------------------------------------------------------------------- /scripts/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set scriptsdir=%~dp0 4 | set root=%scriptsdir%\.. 5 | set project=%1 6 | set version=%2 7 | 8 | if "%project%"=="" ( 9 | echo Please invoke the build script with a project name as its first argument. 10 | echo. 11 | goto exit_fail 12 | ) 13 | 14 | if "%version%"=="" ( 15 | echo Please invoke the build script with a version as its second argument. 16 | echo. 17 | goto exit_fail 18 | ) 19 | 20 | set Version=%version% 21 | 22 | pushd %root% 23 | 24 | dotnet restore --interactive 25 | if %ERRORLEVEL% neq 0 ( 26 | popd 27 | goto exit_fail 28 | ) 29 | 30 | dotnet build "%root%\%project%" -c Release --no-restore 31 | if %ERRORLEVEL% neq 0 ( 32 | popd 33 | goto exit_fail 34 | ) 35 | 36 | popd 37 | 38 | goto exit_success 39 | :exit_fail 40 | exit /b 1 41 | :exit_success -------------------------------------------------------------------------------- /scripts/push.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set version=%1 4 | 5 | if "%version%"=="" ( 6 | echo Please remember to specify which version to push as an argument. 7 | goto exit_fail 8 | ) 9 | 10 | set reporoot=%~dp0\.. 11 | set destination=%reporoot%\deploy 12 | 13 | if not exist "%destination%" ( 14 | echo Could not find %destination% 15 | echo. 16 | echo Did you remember to build the packages before running this script? 17 | ) 18 | 19 | set nuget=%reporoot%\tools\NuGet\NuGet.exe 20 | 21 | if not exist "%nuget%" ( 22 | echo Could not find NuGet here: 23 | echo. 24 | echo "%nuget%" 25 | echo. 26 | goto exit_fail 27 | ) 28 | 29 | 30 | "%nuget%" push "%destination%\*.%version%.nupkg" -Source https://nuget.org 31 | if %ERRORLEVEL% neq 0 ( 32 | echo NuGet push failed. 33 | goto exit_fail 34 | ) 35 | 36 | 37 | 38 | 39 | 40 | 41 | goto exit_success 42 | :exit_fail 43 | exit /b 1 44 | :exit_success 45 | -------------------------------------------------------------------------------- /scripts/release.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set scriptsdir=%~dp0 4 | set root=%scriptsdir%\.. 5 | set deploydir=%root%\deploy 6 | set project=%1 7 | set version=%2 8 | 9 | if "%project%"=="" ( 10 | echo Please invoke the build script with a project name as its first argument. 11 | echo. 12 | goto exit_fail 13 | ) 14 | 15 | if "%version%"=="" ( 16 | echo Please invoke the build script with a version as its second argument. 17 | echo. 18 | goto exit_fail 19 | ) 20 | 21 | set Version=%version% 22 | 23 | if exist "%deploydir%" ( 24 | rd "%deploydir%" /s/q 25 | ) 26 | 27 | pushd %root% 28 | 29 | dotnet restore --interactive 30 | if %ERRORLEVEL% neq 0 ( 31 | popd 32 | goto exit_fail 33 | ) 34 | 35 | dotnet pack "%root%/%project%" -c Release -o "%deploydir%" -p:PackageVersion=%version% --no-restore 36 | if %ERRORLEVEL% neq 0 ( 37 | popd 38 | goto exit_fail 39 | ) 40 | 41 | call scripts\push.cmd "%version%" 42 | 43 | popd 44 | 45 | 46 | 47 | 48 | 49 | 50 | goto exit_success 51 | :exit_fail 52 | exit /b 1 53 | :exit_success -------------------------------------------------------------------------------- /tools/NuGet/nuget.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.AzureServiceBus/f2ab590cf54d91340e2495893c9d462291d8e018/tools/NuGet/nuget.exe --------------------------------------------------------------------------------