├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── Rebus.SqlServer.Tests ├── Assumptions │ ├── TestSpWho.cs │ ├── TestTableName.cs │ └── TestUintBuckets.cs ├── Bugs │ ├── TestBugWhenCustomizingExpiredMessagesCleanupInterval.cs │ ├── TestBugWhenFinishingSagaAndAuditingIsEnabled.cs │ ├── TestBugWhenSendingMessagesInParallel.cs │ ├── TestBugWhereOutboxMessagesAreSentWhenSecondLevelRetryHandlerCompletes.cs │ ├── TestConcurrencyAndSnapshotIsolation.cs │ ├── TestDatabaseExceptionWhenSendingMessageUsingSqlTransport.cs │ ├── TestErrorMessageWhenConnectionStringHasErrors.cs │ ├── TestErrorMessageWhenUsingSqlTransportAndRegisteringTimeoutManager.cs │ ├── TestLeaseBasedTransportAndConcurrency.cs │ ├── TestNativeDeferToSomeoneElse.cs │ ├── TestRebusTransactionScopeAndSyncBus.cs │ ├── TestSagaDataMultipleStuff.cs │ └── TestSqlTransportAndDedicatedTimeoutManager.cs ├── Categories.cs ├── DataBus │ ├── SqlServerDataBusStorageFactory.cs │ ├── SqlServerDataBusStorageTest.cs │ ├── TestSqlServerDataBusLazyRead.cs │ └── TestSqlServerDataBusStorage.cs ├── DisposableCallback.cs ├── Examples │ ├── TestSagaOptimisticConcurrency.cs │ └── TestSqlConnectionTransactionExtraction.cs ├── Extensions │ ├── ConnectionExtensions.cs │ ├── TestExtensions.cs │ └── TransportExtensions.cs ├── FakeRebusTime.cs ├── Integration │ ├── NativeDeferTest.cs │ ├── TestConnectionCallback.cs │ ├── TestNumberOfSqlConnections.cs │ └── TestSqlAllTheWay.cs ├── Math │ └── TestMathUtil.cs ├── Outbox │ ├── FlakySenderTransportDecorator.cs │ ├── FlakySenderTransportDecoratorSettings.cs │ ├── RandomUnluckyException.cs │ ├── TestIdGenerationAssumptions.cs │ ├── TestMathExtensions.cs │ ├── TestOutbox_InsideRebusHandler.cs │ ├── TestOutbox_OutsideOfRebusHandler.cs │ └── TestSqlServerOutboxStorage.cs ├── Rebus.SqlServer.Tests.csproj ├── Sagas │ ├── SqlServerSagaSnapshotStorageTest.cs │ ├── SqlServerSagaStorageFactory.cs │ ├── SqlServerSnapshotStorageFactory.cs │ ├── TestSagaCorrelationSql.cs │ ├── TestSqlSagaStorageSpeed.cs │ ├── TestSqlServerSagaStorage.cs │ └── TestSqlServerSagaStoragePerformance.cs ├── SqlTestHelper.cs ├── Subscriptions │ ├── SqlServerSubscriptionStorageBasicSubscriptionOperations.cs │ ├── SqlServerSubscriptionStorageFactory.cs │ └── TestSqlServerSubscriptionStorage.cs ├── Timeouts │ ├── SqlServerBasicStoreAndRetrieveOperations.cs │ └── SqlServerTimeoutManagerFactory.cs └── Transport │ ├── Contract │ ├── Factories │ │ ├── SqlLeaseTransportFactory.cs │ │ ├── SqlServerBusFactory.cs │ │ ├── SqlServerLeaseBusFactory.cs │ │ └── SqlTransportFactory.cs │ ├── SqlServerLeaseTestManyMessages.cs │ ├── SqlServerLeaseTransportBasicSendReceive.cs │ ├── SqlServerLeaseTransportMessageExpiration.cs │ ├── SqlServerTestManyMessages.cs │ ├── SqlServerTransportBasicSendReceive.cs │ └── SqlServerTransportMessageExpiration.cs │ ├── TestDbConnectionProvider.cs │ ├── TestMessagePriority.cs │ ├── TestSqlServerTransport.cs │ ├── TestSqlServerTransportAutoDelete.cs │ ├── TestSqlServerTransportCleanup.cs │ ├── TestSqlServerTransportMessageOrdering.cs │ └── TestSqlTransportReceivePerformance.cs ├── Rebus.SqlServer.sln ├── Rebus.SqlServer ├── Config │ ├── Outbox │ │ ├── OutboxConnectionProvider.cs │ │ ├── OutboxExtensions.cs │ │ └── SqlServerOutboxConfigurationExtensions.cs │ ├── SqlServerDataBusConfigurationExtensions.cs │ ├── SqlServerDataBusOptions.cs │ ├── SqlServerLeaseTransportOptions.cs │ ├── SqlServerOptions.cs │ ├── SqlServerSagaConfigurationExtensions.cs │ ├── SqlServerSagaSnapshotStorageOptions.cs │ ├── SqlServerSagaSnapshotsConfigurationExtensions.cs │ ├── SqlServerSagaStorageOptions.cs │ ├── SqlServerSubscriptionsConfigurationExtensions.cs │ ├── SqlServerTimeoutManagerOptions.cs │ ├── SqlServerTimeoutsConfigurationExtensions.cs │ ├── SqlServerTransportConfigurationExtensions.cs │ ├── SqlServerTransportOptions.cs │ └── SqlServerTransportOptionsExtensions.cs ├── Rebus.SqlServer.csproj └── SqlServer │ ├── AsyncHelpers.cs │ ├── ConnectionLocker.cs │ ├── DataBus │ ├── SqlServerDataBusStorage.cs │ └── StreamWrapper.cs │ ├── DbConnectionFactoryProvider.cs │ ├── DbConnectionProvider.cs │ ├── DbConnectionWrapper.cs │ ├── DisabledTimeoutManager.cs │ ├── IDbConnection.cs │ ├── IDbConnectionProvider.cs │ ├── InternalsVisibleTo.cs │ ├── IsExternalInit.cs │ ├── MathExtensions.cs │ ├── MathUtil.cs │ ├── Outbox │ ├── Archive │ │ └── OutboxTransportDecorator.cs │ ├── IOutboxConnectionProvider.cs │ ├── IOutboxStorage.cs │ ├── OutboxClientTransportDecorator.cs │ ├── OutboxConnection.cs │ ├── OutboxForwarder.cs │ ├── OutboxIncomingStep.cs │ ├── OutboxMessage.cs │ ├── OutboxMessageBatch.cs │ └── SqlServerOutboxStorage.cs │ ├── Ponder.cs │ ├── Retrier.cs │ ├── Sagas │ ├── CachedSagaTypeNamingStrategy.cs │ ├── HumanReadableHashedSagaTypeNamingStrategy.cs │ ├── ISagaTypeNamingStrategy.cs │ ├── LegacySagaTypeNamingStrategy.cs │ ├── Serialization │ │ ├── DefaultSagaSerializer.cs │ │ └── ISagaSerializer.cs │ ├── Sha512SagaTypeNamingStrategy.cs │ ├── SqlServerSagaSnapshotStorage.cs │ └── SqlServerSagaStorage.cs │ ├── SqlServerMagic.cs │ ├── Subscriptions │ └── SqlServerSubscriptionStorage.cs │ ├── TableName.cs │ ├── Timeouts │ └── SqlServerTimeoutManager.cs │ └── Transport │ ├── SqlServerLeaseTransport.cs │ └── SqlServerTransport.cs ├── RebusOutboxWebApp ├── Controllers │ └── HomeController.cs ├── Extensions │ └── RebusOutboxMiddleware.cs ├── Handlers │ └── SendMessageCommandHandler.cs ├── Messages │ └── SendMessageCommand.cs ├── Models │ └── ErrorViewModel.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── RebusOutboxWebApp.csproj ├── Startup.cs ├── Views │ ├── Home │ │ ├── Index.cshtml │ │ └── Privacy.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── css │ └── site.css │ ├── favicon.ico │ ├── js │ └── site.js │ └── lib │ ├── bootstrap │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── jquery-validation-unobtrusive │ ├── LICENSE.txt │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── RebusOutboxWebAppEfCore ├── Controllers │ └── HomeController.cs ├── Entities │ ├── PostedMessage.cs │ └── WebAppDbContext.cs ├── Handlers │ └── ProcessMessageCommandHandler.cs ├── Messages │ └── ProcessMessageCommand.cs ├── Models │ └── ErrorViewModel.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── RebusOutboxWebAppEfCore.csproj ├── Startup.cs ├── Views │ ├── Home │ │ ├── Index.cshtml │ │ └── Privacy.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _Layout.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── css │ └── site.css │ ├── favicon.ico │ ├── js │ └── site.js │ └── lib │ ├── bootstrap │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── jquery-validation-unobtrusive │ ├── LICENSE.txt │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── appveyor.yml ├── artwork ├── little_rebusbus2_copy-200x200.png └── little_rebusbus2_copy-500x500.png ├── scripts ├── build.cmd ├── push.cmd └── release.cmd └── tools ├── NuGet └── nuget.exe └── aversion └── Aversion.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 | .vs 20 | .vs/* 21 | _NCrunch_* 22 | *.user 23 | *.backup 24 | .idea 25 | 26 | # MS Guideline 27 | **/packages/* 28 | !**/packages/build/ 29 | AssemblyInfo_Patch.cs 30 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Assumptions/TestSpWho.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace Rebus.SqlServer.Tests.Assumptions; 6 | 7 | [TestFixture] 8 | [Ignore("run if you must")] 9 | public class TestSpWho 10 | { 11 | [Test] 12 | public void DropTableThatDoesNotExist() 13 | { 14 | SqlTestHelper.DropTable("bimse"); 15 | } 16 | 17 | [Test] 18 | public void CanGetActiveConnections() 19 | { 20 | var who = SqlTestHelper.ExecSpWho(); 21 | 22 | Console.WriteLine(string.Join(Environment.NewLine, 23 | who.Select(d => string.Join(", ", d.Select(kvp => $"{kvp.Key} = {kvp.Value}"))))); 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Assumptions/TestUintBuckets.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Tests.Contracts; 3 | using Rebus.Tests.Contracts.Extensions; 4 | 5 | namespace Rebus.SqlServer.Tests.Assumptions; 6 | 7 | [TestFixture] 8 | public class TestUintBuckets : FixtureBase 9 | { 10 | [TestCase(10)] 11 | [TestCase(100)] 12 | [TestCase(128)] 13 | [TestCase(256)] 14 | public void GetSomeBucketNumbers(int bucketCount) 15 | { 16 | 10000.Times(() => Assert.That(ConnectionLocker.GetIntBucket(new object(), bucketCount), Is.GreaterThanOrEqualTo(0).And.LessThan(bucketCount))); 17 | } 18 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestBugWhenCustomizingExpiredMessagesCleanupInterval.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.SqlServer.Tests.Bugs; 9 | 10 | [TestFixture] 11 | public class TestBugWhenCustomizingExpiredMessagesCleanupInterval : FixtureBase 12 | { 13 | [Test] 14 | public async Task CanConfigureIt() 15 | { 16 | var activator = Using(new BuiltinHandlerActivator()); 17 | var options = new SqlServerTransportOptions(SqlTestHelper.ConnectionString); 18 | 19 | Configure.With(activator) 20 | .Transport(t => 21 | { 22 | t.UseSqlServer(options, TestConfig.GetName("whatever")) 23 | .SetExpiredMessagesCleanupInterval(TimeSpan.FromSeconds(5)); 24 | }) 25 | .Start(); 26 | 27 | await Task.Delay(TimeSpan.FromSeconds(15)); 28 | } 29 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestBugWhenSendingMessagesInParallel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Bus; 8 | using Rebus.Config; 9 | using Rebus.Logging; 10 | using Rebus.Tests.Contracts; 11 | using Rebus.Tests.Contracts.Extensions; 12 | 13 | #pragma warning disable 1998 14 | 15 | namespace Rebus.SqlServer.Tests.Bugs; 16 | 17 | [TestFixture] 18 | public class TestBugWhenSendingMessagesInParallel : FixtureBase 19 | { 20 | readonly string _subscriptionsTableName = "subscriptions" + TestConfig.Suffix; 21 | 22 | IBus _bus1; 23 | IBus _bus2; 24 | IBus _bus3; 25 | 26 | ConcurrentQueue _receivedMessages; 27 | 28 | protected override void SetUp() 29 | { 30 | _receivedMessages = new ConcurrentQueue(); 31 | 32 | _bus1 = CreateBus(TestConfig.GetName("bus1"), async str => { }); 33 | _bus2 = CreateBus(TestConfig.GetName("bus2"), async str => 34 | { 35 | _receivedMessages.Enqueue("bus2 got " + str); 36 | }); 37 | _bus3 = CreateBus(TestConfig.GetName("bus3"), async str => 38 | { 39 | _receivedMessages.Enqueue("bus3 got " + str); 40 | }); 41 | } 42 | 43 | IBus CreateBus(string inputQueueName, Func stringHandler) 44 | { 45 | var activator = Using(new BuiltinHandlerActivator()); 46 | 47 | activator.Handle(stringHandler); 48 | 49 | var bus = Configure.With(activator) 50 | .Logging(l => l.ColoredConsole(minLevel: LogLevel.Info)) 51 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(SqlTestHelper.ConnectionString), inputQueueName)) 52 | .Subscriptions(s => s.StoreInSqlServer(SqlTestHelper.ConnectionString, _subscriptionsTableName, isCentralized: true)) 53 | .Start(); 54 | 55 | return bus; 56 | } 57 | 58 | [Test] 59 | [Description("When using the SQL transport, publishing to two subscribers would hit a requirement from SQL Server to have MARS enabled on the connection")] 60 | [Repeat(5)] 61 | public async Task CheckRealisticScenarioWithSqlAllTheWay() 62 | { 63 | await Task.WhenAll( 64 | _bus2.Advanced.Topics.Subscribe(typeof(string).FullName), 65 | _bus3.Advanced.Topics.Subscribe(typeof(string).FullName) 66 | ); 67 | 68 | await _bus1.Advanced.Topics.Publish(typeof(string).FullName, "hej"); 69 | 70 | await _receivedMessages.WaitUntil(q => q.Count >= 2); 71 | 72 | await Task.Delay(200); 73 | 74 | var receivedStrings = _receivedMessages.OrderBy(s => s).ToArray(); 75 | 76 | Assert.That(receivedStrings, Is.EqualTo(new[] 77 | { 78 | "bus2 got hej", 79 | "bus3 got hej" 80 | })); 81 | } 82 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestBugWhereOutboxMessagesAreSentWhenSecondLevelRetryHandlerCompletes.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.Config.Outbox; 8 | using Rebus.Exceptions; 9 | using Rebus.Retry.Simple; 10 | using Rebus.Routing.TypeBased; 11 | using Rebus.Tests.Contracts; 12 | using Rebus.Tests.Contracts.Extensions; 13 | using Rebus.Transport.InMem; 14 | // ReSharper disable AccessToDisposedClosure 15 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 16 | 17 | namespace Rebus.SqlServer.Tests.Bugs; 18 | 19 | [TestFixture] 20 | [Description(@"When outbox and 2nd level retries are combined, it's important that outgoing messages from 1st level handler do not get sent 21 | when 1st level handler throws an exception. It used to be the case that when 1st level handler threw, but 2nd level handler SUCCEEDED, then 22 | outbox messages sent by 1st level handler would end up getting sent. This is no longer the case, as verified by this test.")] 23 | public class TestBugWhereOutboxMessagesAreSentWhenSecondLevelRetryHandlerCompletes : FixtureBase 24 | { 25 | string _outboxTable; 26 | InMemNetwork _network; 27 | 28 | protected override void SetUp() 29 | { 30 | _network = new InMemNetwork(); 31 | 32 | _outboxTable = "outbox-buggerino"; 33 | 34 | SqlTestHelper.DropTable(_outboxTable); 35 | 36 | Using(new DisposableCallback(() => SqlTestHelper.DropTable(_outboxTable))); 37 | } 38 | 39 | [Test] 40 | public async Task Reproduce() 41 | { 42 | _network.CreateQueue("destination"); 43 | _network.CreateQueue("final"); 44 | 45 | using var done = new ManualResetEvent(initialState: false); 46 | using var activator = new BuiltinHandlerActivator(); 47 | 48 | activator.Handle(async (bus, _) => 49 | { 50 | await bus.Send(new MessageForDestination()); 51 | throw new FailFastException("💀"); 52 | }); 53 | 54 | activator.Handle>(async (bus, _) => 55 | { 56 | await bus.Send(new FinalMessage()); 57 | done.Set(); 58 | }); 59 | 60 | Configure.With(activator) 61 | .Transport(t => t.UseInMemoryTransport(_network, "whatever")) 62 | .Outbox(o => o.StoreInSqlServer(SqlTestHelper.ConnectionString, _outboxTable)) 63 | .Routing(r => r.TypeBased() 64 | .Map("destination") 65 | .Map("final")) 66 | .Options(o => o.RetryStrategy(secondLevelRetriesEnabled: true)) 67 | .Start(); 68 | 69 | await activator.Bus.SendLocal(new Initiator()); 70 | 71 | done.WaitOrDie(TimeSpan.FromSeconds(50)); 72 | 73 | await Task.Delay(TimeSpan.FromSeconds(2)); 74 | 75 | var destinationQueueMessageCount = _network.Count("destination"); 76 | var finalQueueMessageCount = _network.Count("final"); 77 | 78 | Console.WriteLine($@"Queue counts: 79 | 80 | 'destination': {destinationQueueMessageCount} 81 | 'final': {finalQueueMessageCount} 82 | "); 83 | 84 | Assert.That(destinationQueueMessageCount, Is.Zero); 85 | Assert.That(finalQueueMessageCount, Is.EqualTo(1)); 86 | } 87 | 88 | record Initiator; 89 | 90 | record MessageForDestination; 91 | 92 | record FinalMessage; 93 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestConcurrencyAndSnapshotIsolation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | using System.Text; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using NUnit.Framework; 7 | using Rebus.Config; 8 | using Rebus.Logging; 9 | using Rebus.Messages; 10 | using Rebus.SqlServer.Transport; 11 | using Rebus.Tests.Contracts; 12 | using Rebus.Threading.TaskParallelLibrary; 13 | using Rebus.Time; 14 | using Rebus.Transport; 15 | 16 | namespace Rebus.SqlServer.Tests.Bugs; 17 | 18 | [TestFixture] 19 | public class TestConcurrencyAndSnapshotIsolation : FixtureBase 20 | { 21 | string _queueName; 22 | 23 | protected override void SetUp() 24 | { 25 | SqlTestHelper.Execute($"alter database [{SqlTestHelper.DatabaseName}] set allow_snapshot_isolation on"); 26 | 27 | Using(new DisposableCallback(() => SqlTestHelper.Execute($"alter database [{SqlTestHelper.DatabaseName}] set allow_snapshot_isolation off"))); 28 | 29 | _queueName = TestConfig.GetName("test-queue"); 30 | 31 | SqlTestHelper.DropTable(_queueName); 32 | 33 | var transport = GetTransport(SqlTestHelper.ConnectionString, IsolationLevel.ReadCommitted); 34 | 35 | Using(transport); 36 | 37 | transport.EnsureTableIsCreated(); 38 | } 39 | 40 | [Test] 41 | public async Task CannotReceiveSameMessageTwiceEvenWhenSnapahotIsolationIsOn() 42 | { 43 | const string sentText = "HEJ MED DIG MIN VEN!!!!!11"; 44 | var transportMessage = new TransportMessage(new Dictionary(), Encoding.UTF8.GetBytes(sentText)); 45 | 46 | var transport1 = GetTransport(";MultipleActiveResultSets=True;" + SqlTestHelper.ConnectionString, IsolationLevel.Snapshot); 47 | var transport2 = GetTransport(SqlTestHelper.ConnectionString, IsolationLevel.Snapshot); 48 | 49 | using (var scope = new RebusTransactionScope()) 50 | { 51 | await transport1.Send(_queueName, transportMessage, scope.TransactionContext); 52 | await scope.CompleteAsync(); 53 | } 54 | 55 | using var scope1 = new RebusTransactionScope(); 56 | using var scope2 = new RebusTransactionScope(); 57 | var message1 = await transport1.Receive(scope1.TransactionContext, CancellationToken.None); 58 | var message2 = await transport2.Receive(scope2.TransactionContext, CancellationToken.None); 59 | 60 | Assert.That(message1, Is.Not.Null); 61 | Assert.That(Encoding.UTF8.GetString(message1.Body), Is.EqualTo(sentText)); 62 | 63 | Assert.That(message2, Is.Null, 64 | "Expected the second message to be null, because we should NOT be able to accidentally peek at the same message as another ongoing transaction"); 65 | 66 | await scope2.CompleteAsync(); 67 | await scope1.CompleteAsync(); 68 | } 69 | 70 | SqlServerTransport GetTransport(string connectionString, IsolationLevel isolationLevel) 71 | { 72 | var rebusTime = new DefaultRebusTime(); 73 | var rebusLoggerFactory = new ConsoleLoggerFactory(false); 74 | 75 | var connectionProvider = new DbConnectionProvider(connectionString, rebusLoggerFactory) 76 | { 77 | IsolationLevel = isolationLevel 78 | }; 79 | 80 | var taskFactory = new TplAsyncTaskFactory(rebusLoggerFactory); 81 | 82 | var transport = new SqlServerTransport( 83 | connectionProvider: connectionProvider, 84 | inputQueueName: _queueName, 85 | rebusLoggerFactory: rebusLoggerFactory, 86 | asyncTaskFactory: taskFactory, 87 | rebusTime: rebusTime, 88 | options: new SqlServerTransportOptions(connectionProvider) 89 | ); 90 | 91 | Using(transport); 92 | 93 | transport.Initialize(); 94 | 95 | return transport; 96 | } 97 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestErrorMessageWhenConnectionStringHasErrors.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Rebus.Activation; 4 | using Rebus.Config; 5 | using Rebus.Config.Outbox; 6 | using Rebus.Injection; 7 | using Rebus.Tests.Contracts; 8 | using Rebus.Transport.InMem; 9 | 10 | namespace Rebus.SqlServer.Tests.Bugs; 11 | 12 | [TestFixture] 13 | [Description("Tried (without success) to replicate missing inner exception when using a malformed connection string")] 14 | public class TestErrorMessageWhenConnectionStringHasErrors : FixtureBase 15 | { 16 | [Test] 17 | public void FehlermeldungIstGeil_Transport() 18 | { 19 | using var activator = new BuiltinHandlerActivator(); 20 | 21 | var exception = Assert.Throws(() => Configure.With(activator) 22 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions("server=.; inital catalog=whatever"), "msgs")) 23 | .Start()); 24 | 25 | Console.WriteLine(exception); 26 | 27 | Assert.That(exception.ToString(), Contains.Substring("inital catalog")); 28 | } 29 | 30 | [Test] 31 | public void FehlermeldungIstGeil_Subscriptions() 32 | { 33 | using var activator = new BuiltinHandlerActivator(); 34 | 35 | var exception = Assert.Throws(() => Configure.With(activator) 36 | .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "doesn't matter", registerSubscriptionStorage: false)) 37 | .Subscriptions(s => s.StoreInSqlServer("server=.; inital catalog=whatever", "subs")) 38 | .Start()); 39 | 40 | Console.WriteLine(exception); 41 | 42 | Assert.That(exception.ToString(), Contains.Substring("inital catalog")); 43 | } 44 | 45 | [Test] 46 | public void FehlermeldungIstGeil_Sagas() 47 | { 48 | using var activator = new BuiltinHandlerActivator(); 49 | 50 | var exception = Assert.Throws(() => Configure.With(activator) 51 | .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "doesn't matter")) 52 | .Sagas(s => s.StoreInSqlServer("server=.; inital catalog=whatever", "sagas", "saga_index")) 53 | .Start()); 54 | 55 | Console.WriteLine(exception); 56 | 57 | Assert.That(exception.ToString(), Contains.Substring("inital catalog")); 58 | } 59 | 60 | [Test] 61 | public void FehlermeldungIstGeil_Timeouts() 62 | { 63 | using var activator = new BuiltinHandlerActivator(); 64 | 65 | var exception = Assert.Throws(() => Configure.With(activator) 66 | .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "doesn't matter")) 67 | .Timeouts(s => s.StoreInSqlServer("server=.; inital catalog=whatever", "timeouts")) 68 | .Start()); 69 | 70 | Console.WriteLine(exception); 71 | 72 | Assert.That(exception.ToString(), Contains.Substring("inital catalog")); 73 | } 74 | 75 | [Test] 76 | public void FehlermeldungIstGeil_Outbox() 77 | { 78 | using var activator = new BuiltinHandlerActivator(); 79 | 80 | var exception = Assert.Throws(() => Configure.With(activator) 81 | .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "doesn't matter")) 82 | .Outbox(s => s.StoreInSqlServer("server=.; inital catalog=whatever", "outbox")) 83 | .Start()); 84 | 85 | Console.WriteLine(exception); 86 | 87 | Assert.That(exception.ToString(), Contains.Substring("inital catalog")); 88 | } 89 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestErrorMessageWhenUsingSqlTransportAndRegisteringTimeoutManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Rebus.Activation; 4 | using Rebus.Config; 5 | 6 | namespace Rebus.SqlServer.Tests.Bugs; 7 | 8 | [TestFixture] 9 | public class TestErrorMessageWhenUsingSqlTransportAndRegisteringTimeoutManager 10 | { 11 | [Test] 12 | public void PrintException() 13 | { 14 | try 15 | { 16 | using var activator = new BuiltinHandlerActivator(); 17 | 18 | Configure.With(activator) 19 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(SqlTestHelper.ConnectionString), "whatever")) 20 | .Timeouts(t => t.StoreInSqlServer(SqlTestHelper.ConnectionString, "timeouts")) 21 | .Start(); 22 | } 23 | catch (Exception exception) 24 | { 25 | Console.WriteLine(exception); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestLeaseBasedTransportAndConcurrency.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Config; 8 | using Rebus.Logging; 9 | using Rebus.SqlServer.Tests.Extensions; 10 | using Rebus.Tests.Contracts; 11 | 12 | #pragma warning disable CS1998 13 | 14 | namespace Rebus.SqlServer.Tests.Bugs; 15 | 16 | [TestFixture] 17 | public class TestLeaseBasedTransportAndConcurrency : FixtureBase 18 | { 19 | [TestCase(2, 2, 10)] 20 | [TestCase(5, 10, 100)] 21 | [TestCase(5, 10, 1000, 10)] 22 | [TestCase(5, 10, 10000, 30, Explicit = true)] 23 | [Repeat(10)] 24 | [Description(@"Tried (without success) to reproduce a situation where the same message seemed to be received twice, allegedly because of SQL Server failing to properly handling the exclusive lock&update required to grab a lease on the message. 25 | 26 | It did not look like this was the case though, as everything seemed to work just like it should.")] 27 | public async Task TryToReproduceTheIssue(int numberOfBusInstances, int parallelismPerInstance, int messageCount, int timeoutSeconds = 5) 28 | { 29 | var inputQueueName = Guid.NewGuid().ToString("N"); 30 | 31 | Using(new DisposableCallback(() => SqlTestHelper.DropTable(inputQueueName))); 32 | 33 | var receiveCountsByMessageId = new ConcurrentDictionary(); 34 | 35 | var instances = Enumerable 36 | .Range(0, numberOfBusInstances) 37 | .Select(_ => 38 | { 39 | var activator = Using(new BuiltinHandlerActivator()); 40 | 41 | activator.Handle(async msg => receiveCountsByMessageId.AddOrUpdate(msg.Id, 1, (_, count) => count + 1)); 42 | 43 | Configure.With(activator) 44 | .Logging(l => l.Console(minLevel: LogLevel.Warn)) 45 | .Transport(t => t.UseSqlServerInLeaseMode(new SqlServerLeaseTransportOptions(SqlTestHelper.ConnectionString), inputQueueName)) 46 | .Options(o => 47 | { 48 | o.SetNumberOfWorkers(0); //< start out with passive instances 49 | o.SetMaxParallelism(parallelismPerInstance); 50 | }) 51 | .Start(); 52 | 53 | return activator.Bus; 54 | }) 55 | .ToList(); 56 | 57 | // optimistic attempt at disposing in parallel, because it's quicker 58 | Using(new DisposableCallback(() => Parallel.ForEach(instances, i => i.Dispose()))); ; 59 | 60 | var messages = Enumerable 61 | .Range(0, messageCount) 62 | .Select(n => new MessageWithId($"MESSAGE NUMBER {n}")); 63 | 64 | var sender = instances.First(); 65 | 66 | await Parallel.ForEachAsync(messages, async (msg, _) => await sender.SendLocal(msg)); 67 | 68 | // start all the workers 69 | Parallel.ForEach(instances, bus => bus.Advanced.Workers.SetNumberOfWorkers(parallelismPerInstance)); 70 | 71 | await receiveCountsByMessageId.WaitUntil(c => c.Count == messageCount, timeoutSeconds: timeoutSeconds); 72 | 73 | Assert.That(receiveCountsByMessageId.All(c => c.Value == 1), Is.True, $@"One or more messages were received more than once: 74 | 75 | {string.Join(Environment.NewLine, receiveCountsByMessageId.Where(c => c.Value != 1).Select(kvp => $" {kvp.Key}: {kvp.Value}"))}"); 76 | } 77 | 78 | record MessageWithId(string Id); 79 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestNativeDeferToSomeoneElse.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.Messages; 9 | using Rebus.Routing.TypeBased; 10 | using Rebus.Tests.Contracts; 11 | using Rebus.Tests.Contracts.Extensions; 12 | // ReSharper disable AccessToDisposedClosure 13 | 14 | #pragma warning disable 1998 15 | 16 | namespace Rebus.SqlServer.Tests.Bugs; 17 | 18 | [TestFixture] 19 | public class TestNativeDeferToSomeoneElse : FixtureBase 20 | { 21 | static readonly string ConnectionString = SqlTestHelper.ConnectionString; 22 | 23 | [Test] 24 | public async Task CanDeferWithImplicitRouting() 25 | { 26 | using var receiver = new BuiltinHandlerActivator(); 27 | using var gotTheString = new ManualResetEvent(false); 28 | 29 | receiver.Handle(async _ => gotTheString.Set()); 30 | 31 | Configure.With(receiver) 32 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(ConnectionString), "receiver")) 33 | .Start(); 34 | 35 | using var sender = Configure.With(new BuiltinHandlerActivator()) 36 | .Transport(x => x.UseSqlServerAsOneWayClient(new SqlServerTransportOptions(ConnectionString))) 37 | .Routing(r => r.TypeBased().Map("receiver")) 38 | .Start(); 39 | 40 | await sender.Defer(TimeSpan.FromSeconds(0.2), "HEEELOOOOOO"); 41 | 42 | gotTheString.WaitOrDie(TimeSpan.FromSeconds(5)); 43 | } 44 | 45 | [Test] 46 | public async Task CanDeferWithExplicitRouting_AdvancedApi() 47 | { 48 | using var receiver = new BuiltinHandlerActivator(); 49 | using var gotTheString = new ManualResetEvent(false); 50 | 51 | receiver.Handle(async _ => gotTheString.Set()); 52 | 53 | Configure.With(receiver) 54 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(ConnectionString), "receiver")) 55 | .Start(); 56 | 57 | using var sender = Configure.With(new BuiltinHandlerActivator()) 58 | .Transport(x => x.UseSqlServerAsOneWayClient(new SqlServerTransportOptions(ConnectionString))) 59 | .Routing(r => r.TypeBased().Map("doesn't exist")) 60 | .Start(); 61 | 62 | await sender.Advanced.Routing.Defer("receiver", TimeSpan.FromSeconds(0.2), "HEEELOOOOOO"); 63 | 64 | gotTheString.WaitOrDie(TimeSpan.FromSeconds(5)); 65 | } 66 | 67 | [Test] 68 | public async Task CanDeferWithExplicitRouting_UsingHeader() 69 | { 70 | using var receiver = new BuiltinHandlerActivator(); 71 | using var gotTheString = new ManualResetEvent(false); 72 | 73 | receiver.Handle(async _ => gotTheString.Set()); 74 | 75 | Configure.With(receiver) 76 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(ConnectionString), "receiver")) 77 | .Start(); 78 | 79 | using var sender = Configure.With(new BuiltinHandlerActivator()) 80 | .Transport(x => x.UseSqlServerAsOneWayClient(new SqlServerTransportOptions(ConnectionString))) 81 | .Routing(r => r.TypeBased().Map("sender")) //< this one is not supposed to receive the message 82 | .Start(); 83 | 84 | var headers = new Dictionary { [Headers.DeferredRecipient] = "receiver" }; 85 | 86 | await sender.Defer(TimeSpan.FromSeconds(0.2), "HEEELOOOOOO", headers); 87 | 88 | gotTheString.WaitOrDie(TimeSpan.FromSeconds(5)); 89 | } 90 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Bugs/TestSqlTransportAndDedicatedTimeoutManager.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 | using Rebus.Timeouts; 10 | // ReSharper disable AccessToDisposedClosure 11 | #pragma warning disable 1998 12 | 13 | namespace Rebus.SqlServer.Tests.Bugs; 14 | 15 | [TestFixture] 16 | public class TestSqlTransportAndDedicatedTimeoutManager : FixtureBase 17 | { 18 | protected override void SetUp() 19 | { 20 | base.SetUp(); 21 | 22 | SqlTestHelper.DropAllTables(); 23 | 24 | Using(new DisposableCallback(SqlTestHelper.DropAllTables)); 25 | } 26 | 27 | [Test] 28 | public async Task ItWorks() 29 | { 30 | using var gotTheString = new ManualResetEvent(initialState: false); 31 | 32 | var connectionString = SqlTestHelper.ConnectionString; 33 | 34 | using var timeoutManager = new BuiltinHandlerActivator(); 35 | 36 | Configure.With(timeoutManager) 37 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(connectionString), "TimeoutManager").DisableNativeTimeoutManager()) 38 | .Timeouts(t => t.StoreInSqlServer(connectionString, "Timeouts")) 39 | .Start(); 40 | 41 | using var ordinaryEndpoint = new BuiltinHandlerActivator(); 42 | 43 | ordinaryEndpoint.Handle(async _ => gotTheString.Set()); 44 | 45 | Configure.With(ordinaryEndpoint) 46 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(connectionString), "OrdinaryEndpoint").DisableNativeTimeoutManager()) 47 | .Timeouts(t => t.UseExternalTimeoutManager("TimeoutManager")) 48 | .Start(); 49 | 50 | await ordinaryEndpoint.Bus.DeferLocal(TimeSpan.FromSeconds(2), "HEJ MED DIG MIN VEN 🤠"); 51 | 52 | gotTheString.WaitOrDie(timeout: TimeSpan.FromSeconds(5), errorMessage: "Did not receive the expected string message within 5 s timeout"); 53 | } 54 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Categories.cs: -------------------------------------------------------------------------------- 1 | namespace Rebus.SqlServer.Tests; 2 | 3 | public class Categories 4 | { 5 | public const string SqlServer = "sqlserver"; 6 | public const string Msmq = "msmq"; 7 | public const string Filesystem = "filesystem"; 8 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/DataBus/SqlServerDataBusStorageFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.DataBus; 3 | using Rebus.Logging; 4 | using Rebus.SqlServer.DataBus; 5 | using Rebus.Tests.Contracts.DataBus; 6 | 7 | namespace Rebus.SqlServer.Tests.DataBus; 8 | 9 | public class SqlServerDataBusStorageFactory : IDataBusStorageFactory 10 | { 11 | readonly FakeRebusTime _fakeRebusTime = new FakeRebusTime(); 12 | 13 | public SqlServerDataBusStorageFactory() 14 | { 15 | SqlTestHelper.DropTable("databus"); 16 | } 17 | 18 | public IDataBusStorage Create() 19 | { 20 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 21 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 22 | var sqlServerDataBusStorage = new SqlServerDataBusStorage(connectionProvider, "databus", true, consoleLoggerFactory, _fakeRebusTime, 240); 23 | sqlServerDataBusStorage.Initialize(); 24 | return sqlServerDataBusStorage; 25 | } 26 | 27 | public void CleanUp() 28 | { 29 | SqlTestHelper.DropTable("databus"); 30 | } 31 | 32 | public void FakeIt(DateTimeOffset fakeTime) 33 | { 34 | _fakeRebusTime.SetNow(fakeTime); 35 | } 36 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/DataBus/SqlServerDataBusStorageTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Tests.Contracts.DataBus; 3 | 4 | namespace Rebus.SqlServer.Tests.DataBus; 5 | 6 | [TestFixture] 7 | public class SqlServerDataBusStorageTest : GeneralDataBusStorageTests { } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/DataBus/TestSqlServerDataBusLazyRead.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.DataBus; 7 | using Rebus.Tests.Contracts; 8 | 9 | namespace Rebus.SqlServer.Tests.DataBus; 10 | 11 | [TestFixture] 12 | public class TestSqlServerDataBusLazyRead : FixtureBase 13 | { 14 | IDataBusStorage _storage; 15 | SqlServerDataBusStorageFactory _factory; 16 | 17 | protected override void SetUp() 18 | { 19 | _factory = new SqlServerDataBusStorageFactory(); 20 | _storage = _factory.Create(); 21 | } 22 | 23 | protected override void TearDown() 24 | { 25 | _factory.CleanUp(); 26 | } 27 | 28 | [TestCase(1024*1024*100)] 29 | public async Task ReadingIsLazy(int byteCount) 30 | { 31 | const string dataId = "known id"; 32 | 33 | Console.WriteLine($"Generating {byteCount/(double)(1024*1024):0.00} MB of data..."); 34 | 35 | var data = GenerateData(byteCount); 36 | 37 | Console.WriteLine("Saving data..."); 38 | 39 | await _storage.Save(dataId, new MemoryStream(data)); 40 | 41 | Console.WriteLine("Reading data..."); 42 | 43 | var stopwatch = Stopwatch.StartNew(); 44 | await using var source = await _storage.Read(dataId); 45 | using var destination = new MemoryStream(); 46 | var elapsedWhenStreamIsOpen = stopwatch.Elapsed; 47 | 48 | Console.WriteLine($"Opening stream took {elapsedWhenStreamIsOpen.TotalSeconds:0.00} s"); 49 | 50 | await source.CopyToAsync(destination); 51 | 52 | var elapsedWhenStreamHasBeenRead = stopwatch.Elapsed; 53 | 54 | Console.WriteLine($"Entire operation took {elapsedWhenStreamHasBeenRead.TotalSeconds:0.00} s"); 55 | 56 | var fraction = elapsedWhenStreamHasBeenRead.TotalSeconds / 10; 57 | Assert.That(elapsedWhenStreamIsOpen.TotalSeconds, Is.LessThan(fraction), 58 | "Expected time to open stream to be less than 1/10 of the time it takes to read the entire stream"); 59 | } 60 | 61 | static byte[] GenerateData(int byteCount) 62 | { 63 | var buffer = new byte[byteCount]; 64 | new Random(DateTime.Now.GetHashCode()).NextBytes(buffer); 65 | return buffer; 66 | } 67 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/DataBus/TestSqlServerDataBusStorage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using NUnit.Framework; 9 | using Rebus.Logging; 10 | using Rebus.SqlServer.DataBus; 11 | using Rebus.Tests.Contracts; 12 | using Rebus.Tests.Contracts.Extensions; 13 | using Rebus.Time; 14 | 15 | namespace Rebus.SqlServer.Tests.DataBus; 16 | 17 | [TestFixture] 18 | public class TestSqlServerDataBusStorage : FixtureBase 19 | { 20 | SqlServerDataBusStorage _storage; 21 | 22 | protected override void SetUp() 23 | { 24 | var rebusTime = new DefaultRebusTime(); 25 | var loggerFactory = new ConsoleLoggerFactory(false); 26 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, loggerFactory); 27 | 28 | var tableName = TestConfig.GetName("data"); 29 | 30 | SqlTestHelper.DropTable(tableName); 31 | 32 | _storage = new SqlServerDataBusStorage(connectionProvider, tableName, true, loggerFactory, rebusTime, 240); 33 | _storage.Initialize(); 34 | } 35 | 36 | [Test] 37 | public async Task CanReadDataInParallel() 38 | { 39 | var longString = string.Concat(Enumerable.Repeat(@" 40 | 41 | Let me explain something to you. Um, I am not ""Mr.Lebowski"". 42 | 43 | You're Mr. Lebowski. I'm the Dude. 44 | 45 | So that's what you call me. 46 | 47 | You know, that or, uh, His Dudeness, or uh, Duder, or El Duderino if you're not into the whole brevity thing. 48 | 49 | ", 100)); 50 | 51 | const string dataId = "known-id"; 52 | 53 | using (var source = new MemoryStream(Encoding.UTF8.GetBytes(longString))) 54 | { 55 | await _storage.Save(dataId, source); 56 | } 57 | 58 | var caughtExceptions = new ConcurrentQueue(); 59 | 60 | Console.WriteLine("Reading the data many times in parallel"); 61 | var threads = Enumerable.Range(0, 10) 62 | .Select(i => 63 | { 64 | var thread = new Thread(() => 65 | { 66 | 100.Times(() => 67 | { 68 | Console.Write("."); 69 | try 70 | { 71 | using var source = _storage.Read(dataId).Result; 72 | using var destination = new MemoryStream(); 73 | source.CopyTo(destination); 74 | } 75 | catch (Exception exception) 76 | { 77 | caughtExceptions.Enqueue(exception); 78 | } 79 | }); 80 | }); 81 | 82 | return thread; 83 | }) 84 | .ToList(); 85 | 86 | Console.WriteLine("Starting threads"); 87 | threads.ForEach(t => t.Start()); 88 | 89 | Console.WriteLine("Waiting for them to finish"); 90 | threads.ForEach(t => t.Join()); 91 | 92 | Console.WriteLine("Finished :)"); 93 | 94 | if (caughtExceptions.Count > 0) 95 | { 96 | Assert.Fail($@"Caught {caughtExceptions.Count} exceptions - here's the first 5: 97 | 98 | {string.Join(Environment.NewLine + Environment.NewLine, caughtExceptions.Take(5))}"); 99 | } 100 | 101 | } 102 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/DisposableCallback.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Rebus.SqlServer.Tests; 4 | 5 | class DisposableCallback : IDisposable 6 | { 7 | readonly Action _disposeAction; 8 | 9 | public DisposableCallback(Action disposeAction) => _disposeAction = disposeAction; 10 | 11 | public void Dispose() => _disposeAction(); 12 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Extensions/ConnectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.Data.SqlClient; 5 | 6 | namespace Rebus.SqlServer.Tests.Extensions; 7 | 8 | static class ConnectionExtensions 9 | { 10 | public static IEnumerable Query(this SqlConnection connection, string query) where T : class 11 | { 12 | using var command = connection.CreateCommand(); 13 | command.CommandText = query; 14 | 15 | var properties = typeof(T).GetProperties().Select(p => p.Name).ToArray(); 16 | 17 | using var reader = command.ExecuteReader(); 18 | 19 | while (reader.Read()) 20 | { 21 | if (typeof(T) == typeof(string)) 22 | { 23 | yield return reader.GetString(0) as T; 24 | continue; 25 | } 26 | 27 | var instance = Activator.CreateInstance(typeof(T)); 28 | 29 | foreach (var name in properties) 30 | { 31 | var ordinal = reader.GetOrdinal(name); 32 | var value = reader.GetValue(ordinal); 33 | 34 | instance.GetType().GetProperty(name).SetValue(instance, value); 35 | } 36 | 37 | yield return (T)instance; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Extensions/TestExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Rebus.SqlServer.Tests.Extensions; 7 | 8 | static class TestExtensions 9 | { 10 | public static async Task WaitUntil(this T subject, Expression> successExpression, Expression> failureExpression = null, int timeoutSeconds = 5) 11 | { 12 | if (subject == null) throw new ArgumentNullException(nameof(subject)); 13 | if (successExpression == null) throw new ArgumentNullException(nameof(successExpression)); 14 | 15 | var success = successExpression.Compile(); 16 | var failure = failureExpression?.Compile() ?? (_ => false); 17 | 18 | using var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)); 19 | 20 | var cancellationToken = cancellationTokenSource.Token; 21 | 22 | try 23 | { 24 | while (!cancellationToken.IsCancellationRequested) 25 | { 26 | if (failure(subject)) throw new ApplicationException($@"The success expression 27 | 28 | {successExpression} 29 | 30 | was not completed before detecting a failure via 31 | 32 | {failureExpression}"); 33 | 34 | if (success(subject) && !failure(subject)) return; 35 | 36 | if (failure(subject)) throw new ApplicationException($@"The success expression 37 | 38 | {successExpression} 39 | 40 | was not completed before detecting a failure via 41 | 42 | {failureExpression}"); 43 | 44 | await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); 45 | } 46 | } 47 | catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) 48 | { 49 | throw new TaskCanceledException($@"The success expression 50 | 51 | {successExpression} 52 | 53 | was not completed within {timeoutSeconds} s timeout"); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Extensions/TransportExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | using Rebus.Messages; 4 | using Rebus.SqlServer.Transport; 5 | using Rebus.Transport; 6 | 7 | namespace Rebus.SqlServer.Tests.Extensions; 8 | 9 | static class TransportExtensions 10 | { 11 | public static IEnumerable GetMessages(this SqlServerTransport transport) 12 | { 13 | var messages = new List(); 14 | 15 | AsyncHelpers.RunSync(async () => 16 | { 17 | while (true) 18 | { 19 | using (var scope = new RebusTransactionScope()) 20 | { 21 | var transportMessage = await transport.Receive(scope.TransactionContext, CancellationToken.None); 22 | if (transportMessage == null) break; 23 | 24 | messages.Add(transportMessage); 25 | 26 | await scope.CompleteAsync(); 27 | } 28 | } 29 | }); 30 | 31 | return messages; 32 | } 33 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/FakeRebusTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Time; 3 | 4 | namespace Rebus.SqlServer.Tests; 5 | 6 | class FakeRebusTime : IRebusTime 7 | { 8 | Func _nowFactory = () => DateTimeOffset.Now; 9 | 10 | public DateTimeOffset Now => _nowFactory(); 11 | 12 | public void SetNow(DateTimeOffset fakeTime) => _nowFactory = () => fakeTime; 13 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Integration/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.Config; 7 | using Rebus.Messages; 8 | using Rebus.Routing.TypeBased; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Extensions; 11 | // ReSharper disable AccessToDisposedClosure 12 | 13 | #pragma warning disable 1998 14 | 15 | namespace Rebus.SqlServer.Tests.Integration; 16 | 17 | [TestFixture, Category(Categories.SqlServer)] 18 | public class NativeDeferTest : FixtureBase 19 | { 20 | static readonly string QueueName = TestConfig.GetName("input"); 21 | BuiltinHandlerActivator _activator; 22 | IBusStarter _starter; 23 | 24 | protected override void SetUp() 25 | { 26 | SqlTestHelper.DropTable("Messages"); 27 | 28 | _activator = new BuiltinHandlerActivator(); 29 | 30 | Using(_activator); 31 | 32 | _starter = Configure.With(_activator) 33 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(SqlTestHelper.ConnectionString), QueueName)) 34 | .Routing(r => r.TypeBased().Map(QueueName)) 35 | .Options(o => 36 | { 37 | o.LogPipeline(); 38 | }) 39 | .Create(); 40 | } 41 | 42 | [Test] 43 | public async Task UsesNativeDeferralMechanism() 44 | { 45 | using var done = new ManualResetEvent(false); 46 | 47 | var receiveTime = DateTimeOffset.MinValue; 48 | var hadDeferredUntilHeader = false; 49 | 50 | _activator.Handle(async (bus, context, message) => 51 | { 52 | receiveTime = DateTimeOffset.Now; 53 | 54 | hadDeferredUntilHeader = context.TransportMessage.Headers.ContainsKey(Headers.DeferredUntil); 55 | 56 | done.Set(); 57 | }); 58 | 59 | var sendTime = DateTimeOffset.Now; 60 | 61 | var bus = _starter.Start(); 62 | await bus.Defer(TimeSpan.FromSeconds(5), new TimedMessage { Time = sendTime }); 63 | 64 | done.WaitOrDie(TimeSpan.FromSeconds(8), "Did not receive 5s-deferred message within 8 seconds of waiting...."); 65 | 66 | var delay = receiveTime - sendTime; 67 | 68 | Console.WriteLine("Message was delayed {0}", delay); 69 | 70 | Assert.That(delay, Is.GreaterThan(TimeSpan.FromSeconds(4)), "The message not delayed ~5 seconds as expected!"); 71 | Assert.That(delay, Is.LessThan(TimeSpan.FromSeconds(8)), "The message not delayed ~5 seconds as expected!"); 72 | 73 | Assert.That(hadDeferredUntilHeader, Is.False, "Received message still had the '{0}' header - we must remove that", Headers.DeferredUntil); 74 | } 75 | 76 | class TimedMessage 77 | { 78 | public DateTimeOffset Time { get; set; } 79 | } 80 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Integration/TestConnectionCallback.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.Logging; 8 | using Rebus.Tests.Contracts; 9 | using Rebus.Tests.Contracts.Extensions; 10 | 11 | #pragma warning disable 1998 12 | 13 | namespace Rebus.SqlServer.Tests.Integration; 14 | 15 | [TestFixture] 16 | public class TestConnectionCallback : FixtureBase 17 | { 18 | [Test] 19 | public async Task SeeIfItWorks() 20 | { 21 | var callBackWasCalledAlright = Using(new ManualResetEvent(initialState: false)); 22 | var activator = Using(new BuiltinHandlerActivator()); 23 | 24 | Configure.With(activator) 25 | .Transport(t => 26 | { 27 | var options = new SqlServerTransportOptions(c => 28 | { 29 | var connectionString = SqlTestHelper.ConnectionString; 30 | var loggerFactory = c.Get(); 31 | 32 | var connectionProvider = new DbConnectionProvider(connectionString, loggerFactory) 33 | { 34 | SqlConnectionOpening = async connection => callBackWasCalledAlright.Set() 35 | }; 36 | 37 | return connectionProvider; 38 | }); 39 | t.UseSqlServer(options, "test-queue"); 40 | }) 41 | .Start(); 42 | 43 | callBackWasCalledAlright.WaitOrDie( 44 | timeout: TimeSpan.FromSeconds(3), 45 | errorMessage: $"The '{nameof(DbConnectionProvider.SqlConnectionOpening)}' callback was not invoked as expected" 46 | ); 47 | } 48 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Integration/TestSqlAllTheWay.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 | // ReSharper disable AccessToDisposedClosure 10 | 11 | #pragma warning disable 1998 12 | 13 | namespace Rebus.SqlServer.Tests.Integration; 14 | 15 | [TestFixture, Category(Categories.SqlServer)] 16 | public class TestSqlAllTheWay : FixtureBase 17 | { 18 | static readonly string ConnectionString = SqlTestHelper.ConnectionString; 19 | 20 | BuiltinHandlerActivator _activator; 21 | IBusStarter _starter; 22 | 23 | protected override void SetUp() 24 | { 25 | DropTables(); 26 | 27 | _activator = new BuiltinHandlerActivator(); 28 | 29 | Using(_activator); 30 | 31 | _starter = Configure.With(_activator) 32 | .Transport(x => x.UseSqlServer(new SqlServerTransportOptions(ConnectionString), "test.input")) 33 | .Sagas(x => x.StoreInSqlServer(ConnectionString, "Sagas", "SagaIndex")) 34 | .Options(x => 35 | { 36 | x.SetNumberOfWorkers(1); 37 | x.SetMaxParallelism(1); 38 | }) 39 | .Create(); 40 | } 41 | 42 | protected override void TearDown() 43 | { 44 | DropTables(); 45 | } 46 | 47 | static void DropTables() 48 | { 49 | SqlTestHelper.DropTable("RebusMessages"); 50 | SqlTestHelper.DropTable("SagaIndex"); 51 | SqlTestHelper.DropTable("Sagas"); 52 | } 53 | 54 | [Test] 55 | public async Task SendAndReceiveOneSingleMessage() 56 | { 57 | using var gotTheMessage = new ManualResetEvent(false); 58 | 59 | var receivedMessageCount = 0; 60 | 61 | _activator.Handle(async message => 62 | { 63 | Interlocked.Increment(ref receivedMessageCount); 64 | Console.WriteLine("w00000t! Got message: {0}", message); 65 | gotTheMessage.Set(); 66 | }); 67 | 68 | var bus = _starter.Start(); 69 | await bus.SendLocal("hej med dig min ven!"); 70 | 71 | gotTheMessage.WaitOrDie(TimeSpan.FromSeconds(10)); 72 | 73 | await Task.Delay(500); 74 | 75 | Assert.That(receivedMessageCount, Is.EqualTo(1)); 76 | } 77 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Math/TestMathUtil.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Rebus.SqlServer.Tests.Math; 4 | 5 | [TestFixture] 6 | public class TestMathUtil 7 | { 8 | [TestCase(0, 0)] 9 | [TestCase(1, 1)] 10 | [TestCase(2, 2)] 11 | [TestCase(3, 4)] 12 | [TestCase(4, 4)] 13 | [TestCase(5, 8)] 14 | [TestCase(6, 8)] 15 | [TestCase(7, 8)] 16 | [TestCase(8, 8)] 17 | [TestCase(9, 16)] 18 | [TestCase(10, 16)] 19 | [TestCase(15, 16)] 20 | [TestCase(16, 16)] 21 | [TestCase(17, 32)] 22 | [TestCase(31, 32)] 23 | [TestCase(32, 32)] 24 | [TestCase(33, 64)] 25 | [TestCase(63, 64)] 26 | [TestCase(64, 64)] 27 | [TestCase(65, 128)] 28 | [TestCase(127, 128)] 29 | [TestCase(128, 128)] 30 | [TestCase(129, 256)] 31 | [TestCase(255, 256)] 32 | [TestCase(256, 256)] 33 | [TestCase(257, 512)] 34 | public void CanGetNextPowerOfTwo_Zero(int input, int expectedOutput) 35 | { 36 | var actualOutput = MathUtil.GetNextPowerOfTwo(input); 37 | 38 | Assert.That(actualOutput, Is.EqualTo(expectedOutput)); 39 | Assert.That(actualOutput, Is.GreaterThanOrEqualTo(input), 40 | "Just to be sure that, if we choose to use this function to come up with a sensible, non-query plan cache-polluting length to pass as SQL parameter length, we never end up truncating anything"); 41 | } 42 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Outbox/FlakySenderTransportDecorator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Rebus.Messages; 5 | using Rebus.Transport; 6 | 7 | namespace Rebus.SqlServer.Tests.Outbox; 8 | 9 | class FlakySenderTransportDecorator : ITransport 10 | { 11 | readonly ITransport _transport; 12 | readonly FlakySenderTransportDecoratorSettings _flakySenderTransportDecoratorSettings; 13 | 14 | public FlakySenderTransportDecorator(ITransport transport, 15 | FlakySenderTransportDecoratorSettings flakySenderTransportDecoratorSettings) 16 | { 17 | _transport = transport; 18 | _flakySenderTransportDecoratorSettings = flakySenderTransportDecoratorSettings; 19 | } 20 | 21 | public void CreateQueue(string address) => _transport.CreateQueue(address); 22 | 23 | public Task Send(string destinationAddress, TransportMessage message, ITransactionContext context) 24 | { 25 | if (Random.Shared.NextDouble() > _flakySenderTransportDecoratorSettings.SuccessRate) 26 | { 27 | throw new RandomUnluckyException(); 28 | } 29 | 30 | return _transport.Send(destinationAddress, message, context); 31 | } 32 | 33 | public Task Receive(ITransactionContext context, CancellationToken cancellationToken) 34 | { 35 | return _transport.Receive(context, cancellationToken); 36 | } 37 | 38 | public string Address => _transport.Address; 39 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Outbox/FlakySenderTransportDecoratorSettings.cs: -------------------------------------------------------------------------------- 1 | namespace Rebus.SqlServer.Tests.Outbox; 2 | 3 | class FlakySenderTransportDecoratorSettings 4 | { 5 | public double SuccessRate { get; set; } = 1; 6 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Outbox/RandomUnluckyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Rebus.SqlServer.Tests.Outbox; 4 | 5 | class RandomUnluckyException : ApplicationException 6 | { 7 | public RandomUnluckyException() : base("You were unfortunate") 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Outbox/TestIdGenerationAssumptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using NUnit.Framework; 5 | 6 | namespace Rebus.SqlServer.Tests.Outbox; 7 | 8 | [TestFixture] 9 | public class TestIdGenerationAssumptions 10 | { 11 | [TestCase(4)] 12 | [TestCase(5)] 13 | [TestCase(6)] 14 | [TestCase(7)] 15 | [TestCase(8)] 16 | [TestCase(16)] 17 | [Repeat(5)] 18 | public void CheckGuidPrefix(int length) => RunTest(() => Guid.NewGuid().ToString("N").Substring(0, length)); 19 | 20 | [Test] 21 | [Repeat(5)] 22 | public void CheckTimestampHashCode() => RunTest(() => DateTime.Now.GetHashCode().ToString(CultureInfo.InvariantCulture)); 23 | 24 | [Test] 25 | public void CheckLengthOfIntegers() 26 | { 27 | Console.WriteLine(int.MaxValue); 28 | Console.WriteLine(int.MaxValue.ToString().Length); 29 | } 30 | 31 | static void RunTest(Func getNextId) 32 | { 33 | var ids = new HashSet(); 34 | 35 | while (true) 36 | { 37 | var id = getNextId(); 38 | 39 | if (ids.Contains(id)) 40 | { 41 | Console.WriteLine($"The ID {id} already exists - found {ids.Count} unique ids"); 42 | return; 43 | } 44 | 45 | ids.Add(id); 46 | 47 | if (ids.Count == 1000 * 1000) 48 | { 49 | Console.WriteLine("OK that was 1M - quitting"); 50 | return; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Outbox/TestMathExtensions.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Rebus.SqlServer.Tests.Outbox; 4 | 5 | [TestFixture] 6 | public class TestMathExtensions 7 | { 8 | [TestCase(1, 1)] 9 | [TestCase(2, 2)] 10 | [TestCase(3, 4)] 11 | [TestCase(4, 4)] 12 | [TestCase(5, 8)] 13 | [TestCase(10, 16)] 14 | [TestCase(17, 32)] 15 | [TestCase(789, 1024)] 16 | public void CanRoundUpToNextPowerOfTwo(int input, int expectedOutput) => Assert.That(input.RoundUpToNextPowerOfTwo(), Is.EqualTo(expectedOutput)); 17 | } 18 | -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Rebus.SqlServer.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | 13 5 | net8.0 6 | mookid8000 7 | https://raw.githubusercontent.com/rebus-org/Rebus/master/LICENSE.md 8 | https://rebus.fm/what-is-rebus/ 9 | Copyright Rebus FM ApS 2012 10 | rebus queue messaging service bus sql-server sqlserver mssql sql 11 | https://github.com/mookid8000/Rebus/raw/master/artwork/little_rebusbus2_copy-200x200.png 12 | https://github.com/rebus-org/Rebus 13 | git 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Sagas/SqlServerSagaSnapshotStorageTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Tests.Contracts.Sagas; 3 | 4 | namespace Rebus.SqlServer.Tests.Sagas; 5 | 6 | [TestFixture] 7 | public class SqlServerSagaSnapshotStorageTest : SagaSnapshotStorageTest 8 | { 9 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Sagas/SqlServerSagaStorageFactory.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Logging; 3 | using Rebus.Sagas; 4 | using Rebus.SqlServer.Sagas; 5 | using Rebus.SqlServer.Sagas.Serialization; 6 | using Rebus.Tests.Contracts.Sagas; 7 | 8 | namespace Rebus.SqlServer.Tests.Sagas; 9 | 10 | [TestFixture, Category(Categories.SqlServer)] 11 | public class SqlServerSagaStorageBasicLoadAndSaveAndFindOperations : BasicLoadAndSaveAndFindOperations { } 12 | 13 | [TestFixture, Category(Categories.SqlServer)] 14 | public class SqlServerSagaStorageConcurrencyHandling : ConcurrencyHandling { } 15 | 16 | [TestFixture, Category(Categories.SqlServer)] 17 | public class SqlServerSagaStorageSagaIntegrationTests : SagaIntegrationTests { } 18 | 19 | public class SqlServerSagaStorageFactory : ISagaStorageFactory 20 | { 21 | const string IndexTableName = "RebusSagaIndex"; 22 | const string DataTableName = "RebusSagaData"; 23 | 24 | public SqlServerSagaStorageFactory() 25 | { 26 | CleanUp(); 27 | } 28 | 29 | public ISagaStorage GetSagaStorage() 30 | { 31 | var consoleLoggerFactory = new ConsoleLoggerFactory(true); 32 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 33 | var sagaTypeNamingStrategy = new LegacySagaTypeNamingStrategy(); 34 | var serializer = new DefaultSagaSerializer(); 35 | var storage = new SqlServerSagaStorage(connectionProvider, DataTableName, IndexTableName, consoleLoggerFactory, sagaTypeNamingStrategy, serializer); 36 | 37 | storage.EnsureTablesAreCreated(); 38 | 39 | return storage; 40 | } 41 | 42 | public void CleanUp() 43 | { 44 | SqlTestHelper.DropTable(IndexTableName); 45 | SqlTestHelper.DropTable(DataTableName); 46 | } 47 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Sagas/SqlServerSnapshotStorageFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Rebus.Auditing.Sagas; 4 | using Rebus.Logging; 5 | using Rebus.Sagas; 6 | using Rebus.Serialization; 7 | using Rebus.SqlServer.Sagas; 8 | using Rebus.Tests.Contracts.Sagas; 9 | 10 | namespace Rebus.SqlServer.Tests.Sagas; 11 | 12 | public class SqlServerSnapshotStorageFactory : ISagaSnapshotStorageFactory 13 | { 14 | const string TableName = "SagaSnapshots"; 15 | 16 | public SqlServerSnapshotStorageFactory() => SqlTestHelper.DropTable(TableName); 17 | 18 | public ISagaSnapshotStorage Create() 19 | { 20 | var consoleLoggerFactory = new ConsoleLoggerFactory(true); 21 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 22 | 23 | var snapperino = new SqlServerSagaSnapshotStorage(connectionProvider, TableName, consoleLoggerFactory); 24 | 25 | snapperino.EnsureTableIsCreated(); 26 | 27 | return snapperino; 28 | } 29 | 30 | public IEnumerable GetAllSnapshots() 31 | { 32 | return LoadStoredCopies(new DbConnectionProvider(SqlTestHelper.ConnectionString, new ConsoleLoggerFactory(true)), TableName).Result; 33 | } 34 | 35 | static async Task> LoadStoredCopies(DbConnectionProvider connectionProvider, string tableName) 36 | { 37 | var storedCopies = new List(); 38 | 39 | using var connection = await connectionProvider.GetConnection(); 40 | 41 | await using var command = connection.CreateCommand(); 42 | command.CommandText = $@"SELECT * FROM [{tableName}]"; 43 | 44 | await using (var reader = await command.ExecuteReaderAsync()) 45 | { 46 | while (await reader.ReadAsync()) 47 | { 48 | var sagaData = (ISagaData)new ObjectSerializer().DeserializeFromString((string)reader["data"]); 49 | var metadata = new HeaderSerializer().DeserializeFromString((string)reader["metadata"]); 50 | 51 | storedCopies.Add(new SagaDataSnapshot { SagaData = sagaData, Metadata = metadata }); 52 | } 53 | } 54 | 55 | await connection.Complete(); 56 | 57 | return storedCopies; 58 | } 59 | 60 | public void Dispose() => SqlTestHelper.DropTable(TableName); 61 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Sagas/TestSagaCorrelationSql.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Tests.Contracts.Sagas; 3 | 4 | namespace Rebus.SqlServer.Tests.Sagas; 5 | 6 | [TestFixture] 7 | public class TestSagaCorrelationSql : TestSagaCorrelation { } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Sagas/TestSqlServerSagaStoragePerformance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Logging; 7 | using Rebus.Sagas; 8 | using Rebus.SqlServer.Sagas; 9 | using Rebus.SqlServer.Sagas.Serialization; 10 | using Rebus.Tests.Contracts; 11 | 12 | namespace Rebus.SqlServer.Tests.Sagas; 13 | 14 | [TestFixture] 15 | public class TestSqlServerSagaStoragePerformance : FixtureBase 16 | { 17 | SqlServerSagaStorage _storage; 18 | 19 | protected override void SetUp() 20 | { 21 | var loggerFactory = new ConsoleLoggerFactory(false); 22 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, loggerFactory); 23 | var sagaTypeNamingStrategy = new LegacySagaTypeNamingStrategy(); 24 | var serializer = new DefaultSagaSerializer(); 25 | 26 | var dataTableName = TestConfig.GetName("sagas"); 27 | var indexTableName = TestConfig.GetName("sagaindex"); 28 | 29 | SqlTestHelper.DropTable(indexTableName); 30 | SqlTestHelper.DropTable(dataTableName); 31 | 32 | _storage = new SqlServerSagaStorage(connectionProvider, dataTableName, indexTableName, loggerFactory, sagaTypeNamingStrategy, serializer); 33 | 34 | _storage.EnsureTablesAreCreated(); 35 | } 36 | 37 | [Test] 38 | public async Task TimeToInsertBigSaga() 39 | { 40 | var sagaData = GetSagaData(); 41 | 42 | var elapsed = await TakeTime(async () => 43 | { 44 | await _storage.Insert(sagaData, Enumerable.Empty()); 45 | }); 46 | 47 | Console.WriteLine($"Inserting saga data with {sagaData.BigString.Length} chars took {elapsed.TotalSeconds:0.0} s"); 48 | } 49 | 50 | [Test] 51 | public async Task TimeToLoadBigSaga() 52 | { 53 | var sagaData = GetSagaData(); 54 | 55 | await _storage.Insert(sagaData, Enumerable.Empty()); 56 | 57 | var elapsed = await TakeTime(async () => 58 | { 59 | var loadedData = await _storage.Find(typeof(BigStringSagaData), "Id", sagaData.Id.ToString()); 60 | 61 | Console.WriteLine(loadedData.Id.ToString()); 62 | }); 63 | 64 | Console.WriteLine($"Loading saga data with {sagaData.BigString.Length} chars took {elapsed.TotalSeconds:0.00} s"); 65 | } 66 | 67 | [Test] 68 | public async Task TimeToUpdateBigSaga() 69 | { 70 | var sagaData = GetSagaData(); 71 | 72 | await _storage.Insert(sagaData, Enumerable.Empty()); 73 | 74 | var elapsed = await TakeTime(async () => 75 | { 76 | await _storage.Update(sagaData, Enumerable.Empty()); 77 | }); 78 | 79 | Console.WriteLine($"Updating saga data with {sagaData.BigString.Length} chars took {elapsed.TotalSeconds:0.0} s"); 80 | } 81 | 82 | async Task TakeTime(Func asyncAction) 83 | { 84 | var stopwatch = Stopwatch.StartNew(); 85 | await asyncAction(); 86 | return stopwatch.Elapsed; 87 | } 88 | 89 | static BigStringSagaData GetSagaData() 90 | { 91 | return new BigStringSagaData 92 | { 93 | Id = Guid.NewGuid(), 94 | Revision = 0, 95 | BigString = string.Join(Environment.NewLine, Enumerable.Repeat("this is just a line of text", 100000)) 96 | }; 97 | } 98 | 99 | class BigStringSagaData : ISagaData 100 | { 101 | public Guid Id { get; set; } 102 | public int Revision { get; set; } 103 | public string BigString { get; set; } 104 | } 105 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Subscriptions/SqlServerSubscriptionStorageBasicSubscriptionOperations.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Tests.Contracts.Subscriptions; 3 | 4 | namespace Rebus.SqlServer.Tests.Subscriptions; 5 | 6 | [TestFixture, Category(Categories.SqlServer)] 7 | public class SqlServerSubscriptionStorageBasicSubscriptionOperations : BasicSubscriptionOperations 8 | { 9 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Subscriptions/SqlServerSubscriptionStorageFactory.cs: -------------------------------------------------------------------------------- 1 | using Rebus.Logging; 2 | using Rebus.SqlServer.Subscriptions; 3 | using Rebus.Subscriptions; 4 | using Rebus.Tests.Contracts.Subscriptions; 5 | 6 | namespace Rebus.SqlServer.Tests.Subscriptions; 7 | 8 | public class SqlServerSubscriptionStorageFactory : ISubscriptionStorageFactory 9 | { 10 | const string TableName = "RebusSubscriptions"; 11 | 12 | public ISubscriptionStorage Create() 13 | { 14 | var consoleLoggerFactory = new ConsoleLoggerFactory(true); 15 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 16 | var storage = new SqlServerSubscriptionStorage(connectionProvider, TableName, true, consoleLoggerFactory); 17 | 18 | storage.EnsureTableIsCreated(); 19 | 20 | return storage; 21 | } 22 | 23 | public void Cleanup() 24 | { 25 | SqlTestHelper.DropTable(TableName); 26 | } 27 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Timeouts/SqlServerBasicStoreAndRetrieveOperations.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.Tests.Contracts.Timeouts; 3 | 4 | namespace Rebus.SqlServer.Tests.Timeouts; 5 | 6 | [TestFixture, Category(Categories.SqlServer)] 7 | public class SqlServerBasicStoreAndRetrieveOperations : BasicStoreAndRetrieveOperations 8 | { 9 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Timeouts/SqlServerTimeoutManagerFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Logging; 3 | using Rebus.SqlServer.Timeouts; 4 | using Rebus.Tests.Contracts.Timeouts; 5 | using Rebus.Timeouts; 6 | 7 | namespace Rebus.SqlServer.Tests.Timeouts; 8 | 9 | public class SqlServerTimeoutManagerFactory : ITimeoutManagerFactory 10 | { 11 | const string TableName = "RebusTimeouts"; 12 | 13 | readonly FakeRebusTime _fakeRebusTime = new FakeRebusTime(); 14 | 15 | public ITimeoutManager Create() 16 | { 17 | var consoleLoggerFactory = new ConsoleLoggerFactory(true); 18 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 19 | var timeoutManager = new SqlServerTimeoutManager(connectionProvider, TableName, consoleLoggerFactory, _fakeRebusTime); 20 | 21 | timeoutManager.EnsureTableIsCreated(); 22 | 23 | return timeoutManager; 24 | } 25 | 26 | public void Cleanup() 27 | { 28 | SqlTestHelper.DropTable(TableName); 29 | } 30 | 31 | public string GetDebugInfo() 32 | { 33 | return "could not provide debug info for this particular timeout manager.... implement if needed :)"; 34 | } 35 | 36 | public void FakeIt(DateTimeOffset fakeTime) 37 | { 38 | _fakeRebusTime.SetNow(fakeTime); 39 | } 40 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/Factories/SqlLeaseTransportFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Rebus.Config; 4 | using Rebus.Logging; 5 | using Rebus.SqlServer.Transport; 6 | using Rebus.Tests.Contracts; 7 | using Rebus.Tests.Contracts.Transports; 8 | using Rebus.Threading.TaskParallelLibrary; 9 | using Rebus.Time; 10 | using Rebus.Transport; 11 | 12 | namespace Rebus.SqlServer.Tests.Transport.Contract.Factories; 13 | 14 | public class SqlLeaseTransportFactory : ITransportFactory 15 | { 16 | readonly HashSet _tablesToDrop = new HashSet(); 17 | readonly List _disposables = new List(); 18 | 19 | public SqlLeaseTransportFactory() 20 | { 21 | SqlTestHelper.DropAllTables(); 22 | } 23 | 24 | public ITransport CreateOneWayClient() 25 | { 26 | var tableName = ("RebusMessages_" + TestConfig.Suffix).TrimEnd('_'); 27 | 28 | SqlTestHelper.DropTable(tableName); 29 | 30 | _tablesToDrop.Add(tableName); 31 | 32 | var rebusTime = new DefaultRebusTime(); 33 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 34 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 35 | var asyncTaskFactory = new TplAsyncTaskFactory(consoleLoggerFactory); 36 | var transport = new SqlServerLeaseTransport(connectionProvider, null, consoleLoggerFactory, 37 | asyncTaskFactory, rebusTime, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(2), () => Environment.MachineName, new SqlServerLeaseTransportOptions(connectionProvider)); 38 | 39 | _disposables.Add(transport); 40 | 41 | transport.Initialize(); 42 | 43 | return transport; 44 | } 45 | 46 | public ITransport Create(string inputQueueAddress) 47 | { 48 | var tableName = ("RebusMessages_" + TestConfig.Suffix).TrimEnd('_'); 49 | 50 | SqlTestHelper.DropTable(tableName); 51 | 52 | _tablesToDrop.Add(tableName); 53 | 54 | var rebusTime = new DefaultRebusTime(); 55 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 56 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 57 | var asyncTaskFactory = new TplAsyncTaskFactory(consoleLoggerFactory); 58 | var transport = new SqlServerLeaseTransport(connectionProvider, inputQueueAddress, consoleLoggerFactory, 59 | asyncTaskFactory, rebusTime, TimeSpan.FromMinutes(1), TimeSpan.FromSeconds(2), () => Environment.MachineName, new SqlServerLeaseTransportOptions(connectionProvider)); 60 | 61 | _disposables.Add(transport); 62 | 63 | transport.EnsureTableIsCreated(); 64 | transport.Initialize(); 65 | 66 | return transport; 67 | } 68 | 69 | public void CleanUp() 70 | { 71 | _disposables.ForEach(d => d.Dispose()); 72 | _disposables.Clear(); 73 | 74 | foreach (var table in _tablesToDrop) 75 | { 76 | SqlTestHelper.DropTable(table); 77 | } 78 | 79 | _tablesToDrop.Clear(); 80 | } 81 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/Factories/SqlServerBusFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Rebus.Activation; 5 | using Rebus.Bus; 6 | using Rebus.Config; 7 | using Rebus.Tests.Contracts; 8 | using Rebus.Tests.Contracts.Transports; 9 | 10 | namespace Rebus.SqlServer.Tests.Transport.Contract.Factories; 11 | 12 | public class SqlServerBusFactory : IBusFactory 13 | { 14 | readonly List _stuffToDispose = new List(); 15 | 16 | public SqlServerBusFactory() 17 | { 18 | SqlTestHelper.DropAllTables(); 19 | } 20 | 21 | public IBus GetBus(string inputQueueAddress, Func handler) 22 | { 23 | var builtinHandlerActivator = new BuiltinHandlerActivator(); 24 | 25 | builtinHandlerActivator.Handle(handler); 26 | 27 | var tableName = "messages" + TestConfig.Suffix; 28 | 29 | SqlTestHelper.DropTable(tableName); 30 | 31 | var bus = Configure.With(builtinHandlerActivator) 32 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(SqlTestHelper.ConnectionString), inputQueueAddress)) 33 | .Options(o => 34 | { 35 | o.SetNumberOfWorkers(10); 36 | o.SetMaxParallelism(10); 37 | }) 38 | .Start(); 39 | 40 | _stuffToDispose.Add(bus); 41 | 42 | return bus; 43 | } 44 | 45 | public void Cleanup() 46 | { 47 | _stuffToDispose.ForEach(d => d.Dispose()); 48 | _stuffToDispose.Clear(); 49 | } 50 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/Factories/SqlServerLeaseBusFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Rebus.Activation; 5 | using Rebus.Bus; 6 | using Rebus.Config; 7 | using Rebus.Tests.Contracts; 8 | using Rebus.Tests.Contracts.Transports; 9 | 10 | namespace Rebus.SqlServer.Tests.Transport.Contract.Factories; 11 | 12 | public class SqlServerLeaseBusFactory : IBusFactory 13 | { 14 | readonly List _stuffToDispose = new List(); 15 | 16 | public SqlServerLeaseBusFactory() 17 | { 18 | SqlTestHelper.DropAllTables(); 19 | } 20 | 21 | public IBus GetBus(string inputQueueAddress, Func handler) 22 | { 23 | var builtinHandlerActivator = new BuiltinHandlerActivator(); 24 | 25 | builtinHandlerActivator.Handle(handler); 26 | 27 | var tableName = "messages" + TestConfig.Suffix; 28 | 29 | SqlTestHelper.DropTable(tableName); 30 | 31 | var bus = Configure.With(builtinHandlerActivator) 32 | .Transport(t => t.UseSqlServerInLeaseMode(new SqlServerLeaseTransportOptions(SqlTestHelper.ConnectionString), inputQueueAddress)) 33 | .Options(o => 34 | { 35 | o.SetNumberOfWorkers(10); 36 | o.SetMaxParallelism(10); 37 | }) 38 | .Start(); 39 | 40 | _stuffToDispose.Add(bus); 41 | 42 | return bus; 43 | } 44 | 45 | public void Cleanup() 46 | { 47 | _stuffToDispose.ForEach(d => d.Dispose()); 48 | _stuffToDispose.Clear(); 49 | } 50 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/Factories/SqlTransportFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Rebus.Config; 4 | using Rebus.Logging; 5 | using Rebus.SqlServer.Transport; 6 | using Rebus.Tests.Contracts; 7 | using Rebus.Tests.Contracts.Transports; 8 | using Rebus.Threading.TaskParallelLibrary; 9 | using Rebus.Time; 10 | using Rebus.Transport; 11 | 12 | namespace Rebus.SqlServer.Tests.Transport.Contract.Factories; 13 | 14 | public class SqlTransportFactory : ITransportFactory 15 | { 16 | readonly HashSet _tablesToDrop = new HashSet(); 17 | readonly List _disposables = new List(); 18 | 19 | public SqlTransportFactory() 20 | { 21 | SqlTestHelper.DropAllTables(); 22 | } 23 | 24 | public ITransport CreateOneWayClient() 25 | { 26 | var rebusTime = new DefaultRebusTime(); 27 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 28 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 29 | var asyncTaskFactory = new TplAsyncTaskFactory(consoleLoggerFactory); 30 | var transport = new SqlServerTransport(connectionProvider, null, consoleLoggerFactory, asyncTaskFactory, rebusTime, new SqlServerTransportOptions(connectionProvider)); 31 | 32 | _disposables.Add(transport); 33 | 34 | transport.Initialize(); 35 | 36 | return transport; 37 | } 38 | 39 | public ITransport Create(string inputQueueAddress) 40 | { 41 | var tableName = ("RebusMessages_" + TestConfig.Suffix).TrimEnd('_'); 42 | 43 | SqlTestHelper.DropTable(tableName); 44 | 45 | _tablesToDrop.Add(tableName); 46 | 47 | var rebusTime = new DefaultRebusTime(); 48 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 49 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 50 | var asyncTaskFactory = new TplAsyncTaskFactory(consoleLoggerFactory); 51 | var transport = new SqlServerTransport(connectionProvider, inputQueueAddress, consoleLoggerFactory, asyncTaskFactory, rebusTime, new SqlServerTransportOptions(connectionProvider)); 52 | 53 | _disposables.Add(transport); 54 | 55 | transport.EnsureTableIsCreated(); 56 | transport.Initialize(); 57 | 58 | return transport; 59 | } 60 | 61 | public void CleanUp() 62 | { 63 | _disposables.ForEach(d => d.Dispose()); 64 | _disposables.Clear(); 65 | 66 | foreach (var table in _tablesToDrop) 67 | { 68 | SqlTestHelper.DropTable(table); 69 | } 70 | 71 | _tablesToDrop.Clear(); 72 | } 73 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/SqlServerLeaseTestManyMessages.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.SqlServer.Tests.Transport.Contract.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.SqlServer.Tests.Transport.Contract; 6 | 7 | [TestFixture] 8 | public class SqlServerLeaseTestManyMessages : TestManyMessages { } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/SqlServerLeaseTransportBasicSendReceive.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.SqlServer.Tests.Transport.Contract.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.SqlServer.Tests.Transport.Contract; 6 | 7 | [TestFixture, Category(Categories.SqlServer)] 8 | public class SqlServerLeaseTransportBasicSendReceive : BasicSendReceive 9 | { 10 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/SqlServerLeaseTransportMessageExpiration.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.SqlServer.Tests.Transport.Contract.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.SqlServer.Tests.Transport.Contract; 6 | 7 | [TestFixture, Category(Categories.SqlServer)] 8 | public class SqlServerLeaseTransportMessageExpiration : MessageExpiration { } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/SqlServerTestManyMessages.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.SqlServer.Tests.Transport.Contract.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.SqlServer.Tests.Transport.Contract; 6 | 7 | [TestFixture] 8 | public class SqlServerTestManyMessages : TestManyMessages { } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/SqlServerTransportBasicSendReceive.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.SqlServer.Tests.Transport.Contract.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.SqlServer.Tests.Transport.Contract; 6 | 7 | [TestFixture, Category(Categories.SqlServer)] 8 | public class SqlServerTransportBasicSendReceive : BasicSendReceive 9 | { 10 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/Contract/SqlServerTransportMessageExpiration.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Rebus.SqlServer.Tests.Transport.Contract.Factories; 3 | using Rebus.Tests.Contracts.Transports; 4 | 5 | namespace Rebus.SqlServer.Tests.Transport.Contract; 6 | 7 | [TestFixture, Category(Categories.SqlServer)] 8 | public class SqlServerTransportMessageExpiration : MessageExpiration { } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/TestDbConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Transactions; 4 | using NUnit.Framework; 5 | using Rebus.Logging; 6 | 7 | namespace Rebus.SqlServer.Tests.Transport; 8 | 9 | [TestFixture, Category(Categories.SqlServer)] 10 | public class TestDbConnectionProvider 11 | { 12 | [Test, Ignore("assumes existence of a bimse table")] 13 | public async Task CanDoWorkInTransaction() 14 | { 15 | var provizzle = new DbConnectionProvider(SqlTestHelper.ConnectionString, new ConsoleLoggerFactory(true)); 16 | 17 | using var dbConnection = await provizzle.GetConnection(); 18 | await using var cmd = dbConnection.CreateCommand(); 19 | cmd.CommandText = "insert into bimse (text) values ('hej med dig')"; 20 | 21 | await cmd.ExecuteNonQueryAsync(); 22 | 23 | //await dbConnection.Complete(); 24 | } 25 | [Test, Ignore("assumes existence of a bimse table")] 26 | public async Task CanDoWorkInAmbientTransaction() 27 | { 28 | using var tx = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions 29 | { 30 | IsolationLevel = IsolationLevel.ReadCommitted, 31 | Timeout = TimeSpan.FromSeconds(60) 32 | }); 33 | var provizzle = new DbConnectionProvider(SqlTestHelper.ConnectionString, new ConsoleLoggerFactory(true), 34 | enlistInAmbientTransaction: true); 35 | 36 | using var dbConnection = await provizzle.GetConnection(); 37 | await using var cmd = dbConnection.CreateCommand(); 38 | cmd.CommandText = "insert into bimse (text) values ('Nogen fjellaper liger 2PC')"; 39 | 40 | await cmd.ExecuteNonQueryAsync(); 41 | // tx.Complete(); 42 | } 43 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/TestMessagePriority.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Bus; 8 | using Rebus.Config; 9 | using Rebus.Routing.TypeBased; 10 | using Rebus.SqlServer.Transport; 11 | using Rebus.Tests.Contracts; 12 | using Rebus.Tests.Contracts.Extensions; 13 | using Rebus.Tests.Contracts.Utilities; 14 | // ReSharper disable AccessToDisposedClosure 15 | #pragma warning disable 1998 16 | 17 | namespace Rebus.SqlServer.Tests.Transport; 18 | 19 | [TestFixture] 20 | public class TestMessagePriority : FixtureBase 21 | { 22 | protected override void SetUp() => SqlTestHelper.DropAllTables(); 23 | 24 | [Test] 25 | public async Task ReceivedMessagesByPriority_HigherIsMoreImportant_Normal() => await RunTest("normal", 20); 26 | 27 | [Test] 28 | public async Task ReceivedMessagesByPriority_HigherIsMoreImportant_LeaseBased() => await RunTest("lease-based", 20); 29 | 30 | async Task RunTest(string type, int messageCount) 31 | { 32 | using var counter = new SharedCounter(messageCount); 33 | var receivedMessagePriorities = new List(); 34 | var server = new BuiltinHandlerActivator(); 35 | 36 | server.Handle(async str => 37 | { 38 | Console.WriteLine($"Received message: {str}"); 39 | var parts = str.Split(' '); 40 | var priority = int.Parse(parts[1]); 41 | receivedMessagePriorities.Add(priority); 42 | counter.Decrement(); 43 | }); 44 | 45 | var serverBus = Configure.With(Using(server)) 46 | .Transport(t => 47 | { 48 | if (type == "normal") 49 | { 50 | t.UseSqlServer(new SqlServerTransportOptions(SqlTestHelper.ConnectionString), "server"); 51 | } 52 | else 53 | { 54 | t.UseSqlServerInLeaseMode(new SqlServerLeaseTransportOptions(SqlTestHelper.ConnectionString), "server"); 55 | } 56 | }) 57 | .Options(o => 58 | { 59 | o.SetNumberOfWorkers(0); 60 | o.SetMaxParallelism(1); 61 | }) 62 | .Start(); 63 | 64 | var clientBus = Configure.With(Using(new BuiltinHandlerActivator())) 65 | .Transport(t => 66 | { 67 | if (type == "normal") 68 | { 69 | t.UseSqlServerAsOneWayClient(new SqlServerTransportOptions(SqlTestHelper.ConnectionString)); 70 | } 71 | else 72 | { 73 | t.UseSqlServerInLeaseModeAsOneWayClient(new SqlServerLeaseTransportOptions(SqlTestHelper.ConnectionString)); 74 | } 75 | }) 76 | .Routing(t => t.TypeBased().Map("server")) 77 | .Start(); 78 | 79 | await Task.WhenAll(Enumerable.Range(0, messageCount) 80 | .InRandomOrder() 81 | .Select(priority => SendPriMsg(clientBus, priority))); 82 | 83 | serverBus.Advanced.Workers.SetNumberOfWorkers(1); 84 | 85 | counter.WaitForResetEvent(); 86 | 87 | await Task.Delay(TimeSpan.FromSeconds(1)); 88 | 89 | Assert.That(receivedMessagePriorities.Count, Is.EqualTo(messageCount)); 90 | Assert.That(receivedMessagePriorities.ToArray(), Is.EqualTo(Enumerable.Range(0, messageCount).Reverse().ToArray())); 91 | } 92 | 93 | static Task SendPriMsg(IBus clientBus, int priority) => clientBus.Send($"prioritet {priority}", new Dictionary 94 | { 95 | {SqlServerTransport.MessagePriorityHeaderKey, priority.ToString()} 96 | }); 97 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/TestSqlServerTransportAutoDelete.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using NUnit.Framework; 4 | using Rebus.Activation; 5 | using Rebus.Config; 6 | using Rebus.Logging; 7 | using Rebus.Tests.Contracts; 8 | 9 | namespace Rebus.SqlServer.Tests.Transport; 10 | 11 | [TestFixture, Category(Categories.SqlServer)] 12 | public class TestSqlServerTransportAutoDelete : FixtureBase 13 | { 14 | protected override void SetUp() 15 | { 16 | SqlTestHelper.DropAllTables(); 17 | } 18 | 19 | [Test] 20 | public async Task Dispose_WhenAutoDeleteQueueEnabled_DropsInputQueue() 21 | { 22 | var consoleLoggerFactory = new ConsoleLoggerFactory(false); 23 | var connectionProvider = new DbConnectionProvider(SqlTestHelper.ConnectionString, consoleLoggerFactory); 24 | 25 | const string queueName = "input"; 26 | 27 | var options = new SqlServerTransportOptions(SqlTestHelper.ConnectionString); 28 | 29 | var activator = Using(new BuiltinHandlerActivator()); 30 | Configure.With(activator) 31 | .Logging(l => l.Use(consoleLoggerFactory)) 32 | .Transport(t => t.UseSqlServer(options, queueName).SetAutoDeleteQueue(true)) 33 | .Start(); 34 | 35 | using (var connection = await connectionProvider.GetConnection()) 36 | { 37 | Assert.That(connection.GetTableNames().Contains(TableName.Parse(queueName)), Is.True); 38 | } 39 | 40 | CleanUpDisposables(); 41 | 42 | using (var connection = await connectionProvider.GetConnection()) 43 | { 44 | Assert.That(connection.GetTableNames().Contains(TableName.Parse(queueName)), Is.False); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/TestSqlServerTransportCleanup.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.Logging; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Extensions; 11 | using Rebus.Tests.Contracts.Utilities; 12 | // ReSharper disable ArgumentsStyleLiteral 13 | // ReSharper disable AccessToDisposedClosure 14 | 15 | namespace Rebus.SqlServer.Tests.Transport; 16 | 17 | [TestFixture] 18 | public class TestSqlServerTransportCleanup : FixtureBase 19 | { 20 | BuiltinHandlerActivator _activator; 21 | ListLoggerFactory _loggerFactory; 22 | IBusStarter _starter; 23 | 24 | protected override void SetUp() 25 | { 26 | var queueName = TestConfig.GetName("connection_timeout"); 27 | 28 | _activator = new BuiltinHandlerActivator(); 29 | 30 | Using(_activator); 31 | 32 | _loggerFactory = new ListLoggerFactory(outputToConsole: true); 33 | 34 | _starter = Configure.With(_activator) 35 | .Logging(l => l.Use(_loggerFactory)) 36 | .Transport(t => t.UseSqlServer(new SqlServerTransportOptions(SqlTestHelper.ConnectionString), queueName)) 37 | .Create(); 38 | } 39 | 40 | [Test] 41 | public async Task DoesNotBarfInTheBackground() 42 | { 43 | using var doneHandlingMessage = new ManualResetEvent(false); 44 | 45 | _activator.Handle(async str => 46 | { 47 | for (var count = 0; count < 5; count++) 48 | { 49 | Console.WriteLine("waiting..."); 50 | await Task.Delay(TimeSpan.FromSeconds(20)); 51 | } 52 | 53 | Console.WriteLine("done waiting!"); 54 | 55 | doneHandlingMessage.Set(); 56 | }); 57 | 58 | var bus = _starter.Start(); 59 | 60 | await bus.SendLocal("hej med dig min ven!"); 61 | 62 | doneHandlingMessage.WaitOrDie(TimeSpan.FromMinutes(2)); 63 | 64 | var logLinesAboveInformation = _loggerFactory 65 | .Where(l => l.Level >= LogLevel.Warn) 66 | .ToList(); 67 | 68 | Assert.That(!logLinesAboveInformation.Any(), "Expected no warnings - got this: {0}", string.Join(Environment.NewLine, logLinesAboveInformation)); 69 | } 70 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.Tests/Transport/TestSqlTransportReceivePerformance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Rebus.Activation; 7 | using Rebus.Config; 8 | using Rebus.Logging; 9 | using Rebus.Tests.Contracts; 10 | using Rebus.Tests.Contracts.Utilities; 11 | // ReSharper disable ArgumentsStyleOther 12 | 13 | #pragma warning disable 1998 14 | 15 | namespace Rebus.SqlServer.Tests.Transport; 16 | 17 | [TestFixture, Category(Categories.SqlServer)] 18 | public class TestSqlTransportReceivePerformance : FixtureBase 19 | { 20 | const string QueueName = "perftest"; 21 | 22 | static readonly string TableName = TestConfig.GetName("perftest"); 23 | 24 | protected override void SetUp() 25 | { 26 | SqlTestHelper.DropTable(TableName); 27 | } 28 | 29 | /* 30 | * 31 | Can be used as a simple indicator of the receive performance of the SQL transport. 32 | 33 | Before PR 55: 34 | *** Using LEASE-BASED SQL transport *** 35 | 1000 messages received in 6,5 s - that's 152,7 msg/s 36 | 37 | *** Using LEASE-BASED SQL transport *** 38 | 10000 messages received in 56,3 s - that's 177,7 msg/s 39 | 40 | After PR 55: 41 | *** Using LEASE-BASED SQL transport *** 42 | 1000 messages received in 2,0 s - that's 499,4 msg/s 43 | 44 | *** Using LEASE-BASED SQL transport *** 45 | 10000 messages received in 41,2 s - that's 242,7 msg/s 46 | * 47 | */ 48 | [TestCase(1000, true)] 49 | [TestCase(1000, false)] 50 | [TestCase(10000, true)] 51 | [TestCase(10000, false)] 52 | public async Task CheckReceivePerformance(int messageCount, bool useLeaseBasedTransport) 53 | { 54 | var adapter = Using(new BuiltinHandlerActivator()); 55 | 56 | Configure.With(adapter) 57 | .Logging(l => l.ColoredConsole(LogLevel.Warn)) 58 | .Transport(t => 59 | { 60 | if (useLeaseBasedTransport) 61 | { 62 | Console.WriteLine("*** Using LEASE-BASED SQL transport ***"); 63 | t.UseSqlServerInLeaseMode(new SqlServerLeaseTransportOptions(SqlTestHelper.ConnectionString), QueueName); 64 | } 65 | else 66 | { 67 | Console.WriteLine("*** Using NORMAL SQL transport ***"); 68 | t.UseSqlServer(new SqlServerTransportOptions(SqlTestHelper.ConnectionString), QueueName); 69 | } 70 | }) 71 | .Options(o => 72 | { 73 | o.SetNumberOfWorkers(0); 74 | o.SetMaxParallelism(20); 75 | }) 76 | .Start(); 77 | 78 | Console.WriteLine($"Sending {messageCount} messages..."); 79 | 80 | await Task.WhenAll(Enumerable.Range(0, messageCount) 81 | .Select(i => adapter.Bus.SendLocal($"THIS IS MESSAGE {i}"))); 82 | 83 | var counter = Using(new SharedCounter(messageCount)); 84 | 85 | adapter.Handle(async _ => counter.Decrement()); 86 | 87 | Console.WriteLine("Waiting for messages to be received..."); 88 | 89 | var stopwtach = Stopwatch.StartNew(); 90 | 91 | adapter.Bus.Advanced.Workers.SetNumberOfWorkers(3); 92 | 93 | counter.WaitForResetEvent(timeoutSeconds: messageCount / 100 + 5); 94 | 95 | var elapsedSeconds = stopwtach.Elapsed.TotalSeconds; 96 | 97 | Console.WriteLine($"{messageCount} messages received in {elapsedSeconds:0.0} s - that's {messageCount / elapsedSeconds:0.0} msg/s"); 98 | } 99 | } -------------------------------------------------------------------------------- /Rebus.SqlServer.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.0.31717.71 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 | CHANGELOG.md = CHANGELOG.md 8 | CONTRIBUTING.md = CONTRIBUTING.md 9 | LICENSE.md = LICENSE.md 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebus.SqlServer", "Rebus.SqlServer\Rebus.SqlServer.csproj", "{FE4FDE82-15A0-4C95-B640-779E8F72E870}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rebus.SqlServer.Tests", "Rebus.SqlServer.Tests\Rebus.SqlServer.Tests.csproj", "{7EFCAF69-991E-4991-A03B-077BE4B9952A}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8B2F7563-E5BE-40CE-B5C5-1882C67230DE}" 18 | ProjectSection(SolutionItems) = preProject 19 | .editorconfig = .editorconfig 20 | appveyor.yml = appveyor.yml 21 | EndProjectSection 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RebusOutboxWebApp", "RebusOutboxWebApp\RebusOutboxWebApp.csproj", "{50748090-22C4-4D3B-BB3B-42863FDC41EA}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RebusOutboxWebAppEfCore", "RebusOutboxWebAppEfCore\RebusOutboxWebAppEfCore.csproj", "{D4F57C7F-1AEC-41FC-A352-EC23A6A840CB}" 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Release|Any CPU = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {FE4FDE82-15A0-4C95-B640-779E8F72E870}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {FE4FDE82-15A0-4C95-B640-779E8F72E870}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {FE4FDE82-15A0-4C95-B640-779E8F72E870}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {FE4FDE82-15A0-4C95-B640-779E8F72E870}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {7EFCAF69-991E-4991-A03B-077BE4B9952A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {7EFCAF69-991E-4991-A03B-077BE4B9952A}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {7EFCAF69-991E-4991-A03B-077BE4B9952A}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {7EFCAF69-991E-4991-A03B-077BE4B9952A}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {50748090-22C4-4D3B-BB3B-42863FDC41EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {50748090-22C4-4D3B-BB3B-42863FDC41EA}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {50748090-22C4-4D3B-BB3B-42863FDC41EA}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {50748090-22C4-4D3B-BB3B-42863FDC41EA}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {D4F57C7F-1AEC-41FC-A352-EC23A6A840CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {D4F57C7F-1AEC-41FC-A352-EC23A6A840CB}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {D4F57C7F-1AEC-41FC-A352-EC23A6A840CB}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {D4F57C7F-1AEC-41FC-A352-EC23A6A840CB}.Release|Any CPU.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(ExtensibilityGlobals) = postSolution 54 | SolutionGuid = {9EE5BF36-7A6F-49D1-8C96-840BF29144FC} 55 | EndGlobalSection 56 | EndGlobal 57 | -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/Outbox/OutboxConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.SqlClient; 3 | using Rebus.SqlServer.Outbox; 4 | 5 | namespace Rebus.Config.Outbox; 6 | 7 | class OutboxConnectionProvider : IOutboxConnectionProvider 8 | { 9 | readonly string _connectionString; 10 | 11 | public OutboxConnectionProvider(string connectionString) 12 | { 13 | _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); 14 | } 15 | 16 | public OutboxConnection GetDbConnection() 17 | { 18 | var connection = new SqlConnection(_connectionString); 19 | 20 | try 21 | { 22 | connection.Open(); 23 | 24 | var transaction = connection.BeginTransaction(); 25 | 26 | return new OutboxConnection(connection, transaction); 27 | } 28 | catch 29 | { 30 | connection.Dispose(); 31 | throw; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/Outbox/SqlServerOutboxConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Logging; 3 | using Rebus.Pipeline; 4 | using Rebus.Retry.Simple; 5 | using Rebus.SqlServer.Outbox; 6 | using Rebus.Threading; 7 | using Rebus.Transport; 8 | 9 | namespace Rebus.Config.Outbox; 10 | 11 | /// 12 | /// Configuration extensions for the experimental outbox support 13 | /// 14 | public static class SqlServerOutboxConfigurationExtensions 15 | { 16 | /// 17 | /// Configures Rebus to use an outbox. 18 | /// This will store a (message ID, source queue) tuple for all processed messages, and under this tuple any messages sent/published will 19 | /// also be stored, thus enabling truly idempotent message processing. 20 | /// 21 | public static RebusConfigurer Outbox(this RebusConfigurer configurer, Action> configure) 22 | { 23 | if (configurer == null) throw new ArgumentNullException(nameof(configurer)); 24 | if (configure == null) throw new ArgumentNullException(nameof(configure)); 25 | 26 | configurer.Options(o => 27 | { 28 | configure(StandardConfigurer.GetConfigurerFrom(o)); 29 | 30 | // if no outbox storage was registered, no further calls must have been made... that's ok, so we just bail out here 31 | if (!o.Has()) return; 32 | 33 | o.Decorate(c => new OutboxClientTransportDecorator(c.Get(), c.Get())); 34 | 35 | o.Register(c => 36 | { 37 | var asyncTaskFactory = c.Get(); 38 | var rebusLoggerFactory = c.Get(); 39 | var outboxStorage = c.Get(); 40 | var transport = c.Get(); 41 | return new OutboxForwarder(asyncTaskFactory, rebusLoggerFactory, outboxStorage, transport); 42 | }); 43 | 44 | o.Decorate(c => 45 | { 46 | _ = c.Get(); 47 | return c.Get(); 48 | }); 49 | 50 | o.Decorate(c => 51 | { 52 | var pipeline = c.Get(); 53 | var outboxConnectionProvider = c.Get(); 54 | var step = new OutboxIncomingStep(outboxConnectionProvider); 55 | return new PipelineStepInjector(pipeline) 56 | .OnReceive(step, PipelineRelativePosition.After, typeof(DefaultRetryStep)); 57 | }); 58 | }); 59 | 60 | return configurer; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerDataBusOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Injection; 3 | using Rebus.Logging; 4 | using Rebus.SqlServer; 5 | using Rebus.SqlServer.DataBus; 6 | 7 | namespace Rebus.Config; 8 | 9 | /// 10 | /// Describes options used to configure 11 | /// 12 | public class SqlServerDataBusOptions : SqlServerOptions 13 | { 14 | /// 15 | /// Creates the options with the given cnnection provider factory 16 | /// 17 | public SqlServerDataBusOptions(Func connectionProviderFactory) 18 | { 19 | ConnectionProviderFactory = connectionProviderFactory ?? throw new ArgumentNullException(nameof(connectionProviderFactory)); 20 | } 21 | 22 | /// 23 | /// Creates the options with the given 24 | /// 25 | public SqlServerDataBusOptions(IDbConnectionProvider connectionProvider) 26 | { 27 | if (connectionProvider == null) throw new ArgumentNullException(nameof(connectionProvider)); 28 | 29 | ConnectionProviderFactory = context => connectionProvider; 30 | } 31 | 32 | /// 33 | /// Creates an instance of the options via 34 | /// 35 | public SqlServerDataBusOptions(string connectionString, bool enlistInAmbientTransactions = false) 36 | { 37 | ConnectionProviderFactory = context => new DbConnectionProvider(connectionString, context.Get(), enlistInAmbientTransactions); 38 | } 39 | 40 | /// 41 | /// Configures the commanbd timeout 42 | /// 43 | public TimeSpan CommandTimeout { get; set; } = TimeSpan.FromSeconds(240); 44 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerLeaseTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Rebus.SqlServer; 4 | using Rebus.SqlServer.Transport; 5 | 6 | namespace Rebus.Config; 7 | 8 | /// 9 | /// Extends with options specific to the 10 | /// 11 | public class SqlServerLeaseTransportOptions : SqlServerTransportOptions 12 | { 13 | /// 14 | /// Create an instance of the transport with a pre-created 15 | /// 16 | public SqlServerLeaseTransportOptions(IDbConnectionProvider connectionProvider) : base(connectionProvider) 17 | { 18 | } 19 | 20 | /// 21 | /// Creates an instance of the transport connecting via 22 | /// 23 | public SqlServerLeaseTransportOptions(string connectionString, bool enlistInAmbientTransaction = false) : base(connectionString, enlistInAmbientTransaction) 24 | { 25 | } 26 | 27 | /// 28 | /// Creates an instance of the transport with utilising an factory 29 | /// 30 | public SqlServerLeaseTransportOptions(Func> connectionFactory) : base(connectionFactory) 31 | { 32 | } 33 | 34 | /// 35 | /// If null will default to . Specifies how long a worker will request to keep a message. Higher values require less database communication but increase latency of a message being processed if a worker dies 36 | /// 37 | public TimeSpan? LeaseInterval { get; internal set; } 38 | 39 | /// 40 | /// If null will default to . Specifies how long a worker will request to keep a message. Higher values require less database communication but increase latency of a message being processed if a worker dies 41 | /// 42 | public TimeSpan? LeaseTolerance { get; internal set; } 43 | 44 | /// 45 | /// If true then workers will automatically renew the lease they have acquired whilst they're still processing the message. This will occur in accordance with 46 | /// 47 | public bool AutomaticallyRenewLeases { get; internal set; } 48 | 49 | /// 50 | /// If null defaults to . Specifies how frequently a lease will be renewed whilst the worker is processing a message. Lower values decrease the chance of other workers processing the same message but increase DB communication. A value 50% of should be appropriate 51 | /// 52 | public TimeSpan? LeaseAutoRenewInterval { get; internal set; } 53 | 54 | /// 55 | /// If non-null a factory which returns a string identifying this worker when it leases a message. If null> the current machine name is used 56 | /// 57 | public Func LeasedByFactory { get; internal set; } 58 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Injection; 3 | using Rebus.SqlServer; 4 | 5 | namespace Rebus.Config; 6 | 7 | /// 8 | /// Options base class 9 | /// 10 | public abstract class SqlServerOptions 11 | { 12 | /// 13 | /// Connection provider used to create connections for the transport 14 | /// 15 | public Func ConnectionProviderFactory { get; protected set; } 16 | 17 | /// 18 | /// If false tables will not be created and must be created outside of Rebus 19 | /// 20 | public bool EnsureTablesAreCreated { get; internal set; } = true; 21 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerSagaSnapshotStorageOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Injection; 3 | using Rebus.Logging; 4 | using Rebus.SqlServer; 5 | using Rebus.SqlServer.Sagas; 6 | 7 | namespace Rebus.Config; 8 | 9 | /// 10 | /// Describes options used to configure 11 | /// 12 | public class SqlServerSagaSnapshotStorageOptions : SqlServerOptions 13 | { 14 | /// 15 | /// Creates the options with the given cnnection provider factory 16 | /// 17 | public SqlServerSagaSnapshotStorageOptions(Func connectionProviderFactory) 18 | { 19 | ConnectionProviderFactory = connectionProviderFactory ?? throw new ArgumentNullException(nameof(connectionProviderFactory)); 20 | } 21 | 22 | /// 23 | /// Creates the options with the given 24 | /// 25 | public SqlServerSagaSnapshotStorageOptions(IDbConnectionProvider connectionProvider) 26 | { 27 | if (connectionProvider == null) throw new ArgumentNullException(nameof(connectionProvider)); 28 | 29 | ConnectionProviderFactory = context => connectionProvider; 30 | } 31 | 32 | /// 33 | /// Creates an instance of the options via 34 | /// 35 | public SqlServerSagaSnapshotStorageOptions(string connectionString, bool enlistInAmbientTransactions = false) 36 | { 37 | if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); 38 | 39 | ConnectionProviderFactory = context => new DbConnectionProvider(connectionString, context.Get(), enlistInAmbientTransactions); 40 | } 41 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerSagaSnapshotsConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Rebus.Auditing.Sagas; 4 | using Rebus.Logging; 5 | using Rebus.SqlServer; 6 | using Rebus.SqlServer.Sagas; 7 | 8 | namespace Rebus.Config; 9 | 10 | /// 11 | /// Configuration extensions for saga snapshots 12 | /// 13 | public static class SqlServerSagaSnapshotsConfigurationExtensions 14 | { 15 | /// 16 | /// Configures Rebus to store saga snapshots in SQL Server 17 | /// 18 | public static void StoreInSqlServer(this StandardConfigurer configurer, 19 | string connectionString, string tableName, bool automaticallyCreateTables = true, bool enlistInAmbientTransaction = false) 20 | { 21 | if (configurer == null) throw new ArgumentNullException(nameof(configurer)); 22 | if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); 23 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 24 | 25 | configurer.Register(c => 26 | { 27 | var rebusLoggerFactory = c.Get(); 28 | var connectionProvider = new DbConnectionProvider(connectionString, rebusLoggerFactory, enlistInAmbientTransaction); 29 | var snapshotStorage = new SqlServerSagaSnapshotStorage(connectionProvider, tableName, rebusLoggerFactory); 30 | 31 | if (automaticallyCreateTables) 32 | { 33 | snapshotStorage.EnsureTableIsCreated(); 34 | } 35 | 36 | return snapshotStorage; 37 | }); 38 | } 39 | 40 | /// 41 | /// Configures Rebus to store saga snapshots in SQL Server 42 | /// 43 | public static void StoreInSqlServer(this StandardConfigurer configurer, 44 | Func> connectionFactory, string tableName, bool automaticallyCreateTables = true) 45 | { 46 | if (configurer == null) throw new ArgumentNullException(nameof(configurer)); 47 | if (connectionFactory == null) throw new ArgumentNullException(nameof(connectionFactory)); 48 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 49 | 50 | configurer.Register(c => 51 | { 52 | var rebusLoggerFactory = c.Get(); 53 | var connectionProvider = new DbConnectionFactoryProvider(connectionFactory); 54 | var snapshotStorage = new SqlServerSagaSnapshotStorage(connectionProvider, tableName, rebusLoggerFactory); 55 | 56 | if (automaticallyCreateTables) 57 | { 58 | snapshotStorage.EnsureTableIsCreated(); 59 | } 60 | 61 | return snapshotStorage; 62 | }); 63 | } 64 | 65 | /// 66 | /// Configures Rebus to store saga snapshots in SQL Server 67 | /// 68 | public static void StoreInSqlServer(this StandardConfigurer configurer, SqlServerSagaSnapshotStorageOptions options, string tableName) 69 | { 70 | if (configurer == null) throw new ArgumentNullException(nameof(configurer)); 71 | if (options == null) throw new ArgumentNullException(nameof(options)); 72 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 73 | 74 | configurer.Register(c => 75 | { 76 | var rebusLoggerFactory = c.Get(); 77 | var connectionProvider = options.ConnectionProviderFactory(c); 78 | var snapshotStorage = new SqlServerSagaSnapshotStorage(connectionProvider, tableName, rebusLoggerFactory); 79 | 80 | if (options.EnsureTablesAreCreated) 81 | { 82 | snapshotStorage.EnsureTableIsCreated(); 83 | } 84 | 85 | return snapshotStorage; 86 | }); 87 | } 88 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerSagaStorageOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Injection; 3 | using Rebus.Logging; 4 | using Rebus.SqlServer; 5 | using Rebus.SqlServer.Sagas; 6 | 7 | namespace Rebus.Config; 8 | 9 | /// 10 | /// Describes options used to configure 11 | /// 12 | public class SqlServerSagaStorageOptions : SqlServerOptions 13 | { 14 | /// 15 | /// Creates the options with the given cnnection provider factory 16 | /// 17 | public SqlServerSagaStorageOptions(Func connectionProviderFactory) 18 | { 19 | ConnectionProviderFactory = connectionProviderFactory ?? throw new ArgumentNullException(nameof(connectionProviderFactory)); 20 | } 21 | 22 | /// 23 | /// Creates the options with the given 24 | /// 25 | public SqlServerSagaStorageOptions(IDbConnectionProvider connectionProvider) 26 | { 27 | if (connectionProvider == null) throw new ArgumentNullException(nameof(connectionProvider)); 28 | 29 | ConnectionProviderFactory = context => connectionProvider; 30 | } 31 | 32 | /// 33 | /// Creates an instance of the options via 34 | /// 35 | public SqlServerSagaStorageOptions(string connectionString, bool enlistInAmbientTransactions = false) 36 | { 37 | if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); 38 | 39 | ConnectionProviderFactory = context => new DbConnectionProvider(connectionString, context.Get(), enlistInAmbientTransactions); 40 | } 41 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerSubscriptionsConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Rebus.Logging; 4 | using Rebus.SqlServer; 5 | using Rebus.SqlServer.Subscriptions; 6 | using Rebus.Subscriptions; 7 | 8 | namespace Rebus.Config; 9 | 10 | /// 11 | /// Configuration extensions for subscriptions 12 | /// 13 | public static class SqlServerSubscriptionsConfigurationExtensions 14 | { 15 | /// 16 | /// Configures Rebus to use SQL Server to store subscriptions. Use = true to indicate whether it's OK to short-circuit 17 | /// subscribing and unsubscribing by manipulating the subscription directly from the subscriber or just let it default to false to preserve the 18 | /// default behavior. 19 | /// 20 | public static void StoreInSqlServer(this StandardConfigurer configurer, 21 | string connectionString, string tableName, bool isCentralized = false, bool automaticallyCreateTables = true, bool enlistInAmbientTransaction = false) 22 | { 23 | if (configurer == null) throw new ArgumentNullException(nameof(configurer)); 24 | if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); 25 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 26 | 27 | configurer.Register(c => 28 | { 29 | var rebusLoggerFactory = c.Get(); 30 | var connectionProvider = new DbConnectionProvider(connectionString, rebusLoggerFactory, enlistInAmbientTransaction); 31 | var subscriptionStorage = new SqlServerSubscriptionStorage(connectionProvider, tableName, isCentralized, rebusLoggerFactory); 32 | 33 | if (automaticallyCreateTables) 34 | { 35 | subscriptionStorage.EnsureTableIsCreated(); 36 | } 37 | 38 | return subscriptionStorage; 39 | }); 40 | } 41 | 42 | /// 43 | /// Configures Rebus to use SQL Server to store subscriptions. Use = true to indicate whether it's OK to short-circuit 44 | /// subscribing and unsubscribing by manipulating the subscription directly from the subscriber or just let it default to false to preserve the 45 | /// default behavior. 46 | /// 47 | public static void StoreInSqlServer(this StandardConfigurer configurer, 48 | Func> connectionFactory, string tableName, bool isCentralized = false, bool automaticallyCreateTables = true) 49 | { 50 | if (configurer == null) throw new ArgumentNullException(nameof(configurer)); 51 | if (connectionFactory == null) throw new ArgumentNullException(nameof(connectionFactory)); 52 | if (tableName == null) throw new ArgumentNullException(nameof(tableName)); 53 | 54 | configurer.Register(c => 55 | { 56 | var rebusLoggerFactory = c.Get(); 57 | var connectionProvider = new DbConnectionFactoryProvider(connectionFactory); 58 | var subscriptionStorage = new SqlServerSubscriptionStorage(connectionProvider, tableName, isCentralized, rebusLoggerFactory); 59 | 60 | if (automaticallyCreateTables) 61 | { 62 | subscriptionStorage.EnsureTableIsCreated(); 63 | } 64 | 65 | return subscriptionStorage; 66 | }); 67 | } 68 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerTimeoutManagerOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Injection; 3 | using Rebus.Logging; 4 | using Rebus.SqlServer; 5 | using Rebus.SqlServer.Timeouts; 6 | 7 | namespace Rebus.Config; 8 | 9 | /// 10 | /// Describes options used to configure 11 | /// 12 | public class SqlServerTimeoutManagerOptions : SqlServerOptions 13 | { 14 | /// 15 | /// Creates the options with the given cnnection provider factory 16 | /// 17 | public SqlServerTimeoutManagerOptions(Func connectionProviderFactory) 18 | { 19 | ConnectionProviderFactory = connectionProviderFactory ?? throw new ArgumentNullException(nameof(connectionProviderFactory)); 20 | } 21 | 22 | /// 23 | /// Creates the options with the given 24 | /// 25 | public SqlServerTimeoutManagerOptions(IDbConnectionProvider connectionProvider) 26 | { 27 | if (connectionProvider == null) throw new ArgumentNullException(nameof(connectionProvider)); 28 | 29 | ConnectionProviderFactory = context => connectionProvider; 30 | } 31 | 32 | /// 33 | /// Creates an instance of the options via 34 | /// 35 | public SqlServerTimeoutManagerOptions(string connectionString, bool enlistInAmbientTransactions = false) 36 | { 37 | if (connectionString == null) throw new ArgumentNullException(nameof(connectionString)); 38 | 39 | ConnectionProviderFactory = context => new DbConnectionProvider(connectionString, context.Get(), enlistInAmbientTransactions); 40 | } 41 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Config/SqlServerTransportOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Rebus.Injection; 4 | using Rebus.Logging; 5 | using Rebus.SqlServer; 6 | using Rebus.SqlServer.Transport; 7 | 8 | namespace Rebus.Config; 9 | 10 | /// 11 | /// Describes options used to configure the 12 | /// 13 | public class SqlServerTransportOptions : SqlServerOptions 14 | { 15 | /// 16 | /// Create an instance of the transport with a pre-created 17 | /// 18 | public SqlServerTransportOptions(IDbConnectionProvider connectionProvider) 19 | { 20 | ConnectionProviderFactory = (resolutionContext) => connectionProvider; 21 | } 22 | 23 | /// 24 | /// Create an instance of the transport with a that can use the to look up things 25 | /// 26 | public SqlServerTransportOptions(Func connectionProviderFactory) 27 | { 28 | ConnectionProviderFactory = connectionProviderFactory ?? throw new ArgumentNullException(nameof(connectionProviderFactory)); 29 | } 30 | 31 | /// 32 | /// Creates an instance of the transport connecting via 33 | /// 34 | public SqlServerTransportOptions(string connectionString, bool enlistInAmbientTransaction = false) 35 | { 36 | ConnectionProviderFactory = resolutionContext => new DbConnectionProvider(connectionString, resolutionContext.Get(), enlistInAmbientTransaction); 37 | } 38 | 39 | /// 40 | /// Creates an instance of the transport with utilising an factory 41 | /// 42 | public SqlServerTransportOptions(Func> connectionFactory) 43 | { 44 | ConnectionProviderFactory = resolutionContext => new DbConnectionFactoryProvider(connectionFactory); 45 | } 46 | 47 | /// 48 | /// Disables the SQL transport's built-in ability to delay message delivery. This can be done if ther requirements for delayed messages 49 | /// exceeds what is convenient, as delayed messages will be sitting in the recipient's table until it is time to be consumed. 50 | /// 51 | public SqlServerTransportOptions DisableNativeTimeoutManager() 52 | { 53 | NativeTimeoutManagerDisabled = true; 54 | return this; 55 | } 56 | 57 | /// 58 | /// Name of the input queue to process. If null or whitespace the transport will be configured in one way mode (send only) 59 | /// 60 | public string InputQueueName { get; internal set; } 61 | 62 | /// 63 | /// If true, the input queue table will be automatically dropped on transport disposal 64 | /// 65 | public bool AutoDeleteQueue { get; internal set; } = false; 66 | 67 | /// 68 | /// If true the transport is configured in one way mode 69 | /// 70 | internal bool IsOneWayClient => InputQueueName == null; 71 | 72 | /// 73 | /// Gets the delay between executions of the background cleanup task 74 | /// 75 | internal TimeSpan? ExpiredMessagesCleanupInterval { get; set; } 76 | 77 | internal bool NativeTimeoutManagerDisabled { get; set; } 78 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/Rebus.SqlServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Library 4 | netstandard2.0 5 | 13 6 | Rebus 7 | Rebus.SqlServer 8 | mookid8000 9 | MIT 10 | https://rebus.fm/what-is-rebus/ 11 | Microsoft SQL Server-based persistence for Rebus 12 | Copyright Rebus FM ApS 2012 13 | rebus queue messaging service bus sql-server sqlserver mssql sql 14 | little_rebusbus2_copy-500x500.png 15 | https://github.com/rebus-org/Rebus.SqlServer 16 | git 17 | True 18 | README.md 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | True 27 | 28 | 29 | 30 | True 31 | \ 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/ConnectionLocker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace Rebus.SqlServer; 7 | 8 | class ConnectionLocker(int buckets) : IDisposable 9 | { 10 | public static readonly ConnectionLocker Instance = new(buckets: 256); 11 | 12 | readonly ConcurrentDictionary _semaphores = new(); 13 | 14 | public async ValueTask GetLockAsync(IDbConnection connection) 15 | { 16 | using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); 17 | 18 | var semaphore = GetSemaphore(connection); 19 | await semaphore.WaitAsync(timeout.Token); 20 | 21 | return new SemaphoreReleaser(semaphore); 22 | } 23 | 24 | public IDisposable GetLock(IDbConnection connection) 25 | { 26 | using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(30)); 27 | 28 | var semaphore = GetSemaphore(connection); 29 | semaphore.Wait(timeout.Token); 30 | 31 | return new SemaphoreReleaser(semaphore); 32 | } 33 | 34 | SemaphoreSlim GetSemaphore(IDbConnection connection) 35 | { 36 | var bucket = GetIntBucket(connection, buckets); 37 | var semaphore = _semaphores.GetOrAdd(bucket, _ => new SemaphoreSlim(initialCount: 1)); 38 | return semaphore; 39 | } 40 | 41 | internal static int GetIntBucket(object obj, int bucketCount) 42 | { 43 | return (int)((uint)obj.GetHashCode() % bucketCount); 44 | } 45 | 46 | readonly struct SemaphoreReleaser(SemaphoreSlim semaphore) : IDisposable 47 | { 48 | public void Dispose() => semaphore.Release(); 49 | } 50 | 51 | public void Dispose() 52 | { 53 | foreach (var semaphore in _semaphores.Values) 54 | { 55 | semaphore.Dispose(); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/DataBus/StreamWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace Rebus.SqlServer.DataBus; 7 | 8 | /// 9 | /// Wraps a stream and an action, calling the action when the stream is disposed 10 | /// 11 | class StreamWrapper : Stream 12 | { 13 | readonly Stream _innerStream; 14 | readonly IDisposable[] _disposables; 15 | 16 | public StreamWrapper(Stream innerStream, IEnumerable disposables) 17 | { 18 | _innerStream = innerStream ?? throw new ArgumentNullException(nameof(innerStream)); 19 | if (disposables == null) throw new ArgumentNullException(nameof(disposables)); 20 | _disposables = disposables.ToArray(); 21 | } 22 | 23 | public override void Flush() 24 | { 25 | _innerStream.Flush(); 26 | } 27 | 28 | public override long Seek(long offset, SeekOrigin origin) 29 | { 30 | return _innerStream.Seek(offset, origin); 31 | } 32 | 33 | public override void SetLength(long value) 34 | { 35 | _innerStream.SetLength(value); 36 | } 37 | 38 | public override int Read(byte[] buffer, int offset, int count) 39 | { 40 | return _innerStream.Read(buffer, offset, count); 41 | } 42 | 43 | public override void Write(byte[] buffer, int offset, int count) 44 | { 45 | _innerStream.Write(buffer, offset, count); 46 | } 47 | 48 | public override bool CanRead => _innerStream.CanRead; 49 | public override bool CanSeek => _innerStream.CanSeek; 50 | public override bool CanWrite => _innerStream.CanWrite; 51 | public override long Length => _innerStream.Length; 52 | 53 | public override long Position 54 | { 55 | get { return _innerStream.Position; } 56 | set { _innerStream.Position = value; } 57 | } 58 | 59 | protected override void Dispose(bool disposing) 60 | { 61 | foreach (var disposable in _disposables) 62 | { 63 | disposable.Dispose(); 64 | } 65 | base.Dispose(disposing); 66 | } 67 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/DbConnectionFactoryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | #pragma warning disable 1998 5 | 6 | namespace Rebus.SqlServer; 7 | 8 | /// 9 | /// Implementation of that uses an async function to retrieve the 10 | /// 11 | public class DbConnectionFactoryProvider : IDbConnectionProvider 12 | { 13 | readonly Func> _connectionFactory; 14 | 15 | /// 16 | /// Creates the connection provider to use the specified to create s 17 | /// 18 | public DbConnectionFactoryProvider(Func> connectionFactory) => _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); 19 | 20 | /// 21 | /// Gets a nice ready-to-use database connection with an open transaction 22 | /// 23 | public async Task GetConnection() => await _connectionFactory().ConfigureAwait(false); 24 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/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.SqlServer; 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 SQL Server as the transport layer in, which" + 22 | " case the native support for a specific visibility time is used..."; 23 | 24 | throw new InvalidOperationException(message); 25 | } 26 | 27 | public async Task GetDueMessages() 28 | { 29 | return DueMessagesResult.Empty; 30 | } 31 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/IDbConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Threading.Tasks; 5 | using Microsoft.Data.SqlClient; 6 | 7 | namespace Rebus.SqlServer; 8 | 9 | /// 10 | /// Wrapper of that allows for easily changing how transactions are handled, and possibly how instances 11 | /// are reused by various services 12 | /// 13 | public interface IDbConnection : IDisposable 14 | { 15 | /// 16 | /// Creates a ready to used 17 | /// 18 | SqlCommand CreateCommand(); 19 | 20 | /// 21 | /// Gets the names of all the tables in the current database for the current schema 22 | /// 23 | IEnumerable GetTableNames(); 24 | 25 | /// 26 | /// Marks that all work has been successfully done and the may have its transaction committed or whatever is natural to do at this time 27 | /// 28 | Task Complete(); 29 | 30 | /// 31 | /// Gets information about the columns in the table given by [].[] 32 | /// 33 | IEnumerable GetColumns(string schema, string dataTableName); 34 | } 35 | 36 | /// 37 | /// Represents a SQL Server column 38 | /// 39 | public class DbColumn 40 | { 41 | /// 42 | /// Gets the name of the column 43 | /// 44 | public string Name { get; } 45 | 46 | /// 47 | /// Gets the SQL datatype of the column 48 | /// 49 | public SqlDbType Type { get; } 50 | 51 | /// 52 | /// Creates the column 53 | /// 54 | public DbColumn(string name, SqlDbType type) 55 | { 56 | Name = name; 57 | Type = type; 58 | } 59 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/IDbConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.Data.SqlClient; 3 | 4 | namespace Rebus.SqlServer; 5 | 6 | /// 7 | /// SQL Server database connection provider that allows for easily changing how the current is obtained, 8 | /// possibly also changing how transactions are handled 9 | /// 10 | public interface IDbConnectionProvider 11 | { 12 | /// 13 | /// Gets a wrapper with the current inside 14 | /// 15 | Task GetConnection(); 16 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("Rebus.SqlServer.Tests")] 4 | -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/IsExternalInit.cs: -------------------------------------------------------------------------------- 1 | // ReSharper disable CheckNamespace 2 | // ReSharper disable UnusedMember.Global 3 | #pragma warning disable CS1591 4 | namespace System.Runtime.CompilerServices; 5 | 6 | internal class IsExternalInit 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/MathExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | // ReSharper disable ArgumentsStyleLiteral 4 | 5 | namespace Rebus.SqlServer; 6 | 7 | static class MathExtensions 8 | { 9 | public static int RoundUpToNextPowerOfTwo(this int number) => 1 << (int)Math.Ceiling(Math.Log(number, newBase: 2)); 10 | } 11 | -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/MathUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Rebus.SqlServer; 4 | 5 | static class MathUtil 6 | { 7 | public static int GetNextPowerOfTwo(int input) 8 | { 9 | if (input < 0) throw new ArgumentOutOfRangeException(nameof(input), input, "Please pass a non-negative number to this function"); 10 | 11 | if (input == 0) return 0; 12 | 13 | var log = Math.Log(input, 2); 14 | var logRoundedUp = Math.Ceiling(log); 15 | return (int) Math.Pow(2, logRoundedUp); 16 | } 17 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Outbox/Archive/OutboxTransportDecorator.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using System.Collections.Concurrent; 3 | //using System.Linq; 4 | //using System.Threading; 5 | //using System.Threading.Tasks; 6 | //using Rebus.Bus; 7 | //using Rebus.Logging; 8 | //using Rebus.Messages; 9 | //using Rebus.Pipeline; 10 | //using Rebus.Threading; 11 | //using Rebus.Transport; 12 | //#pragma warning disable CS1998 13 | 14 | //namespace Rebus.SqlServer.Outbox; 15 | 16 | //class OutboxTransportDecorator : ITransport, IInitializable, IDisposable 17 | //{ 18 | // readonly OutboxForwarder _forwarder; 19 | // readonly IOutboxStorage _outboxStorage; 20 | // readonly ITransport _transport; 21 | 22 | // public OutboxTransportDecorator(IRebusLoggerFactory rebusLoggerFactory, ITransport transport, IOutboxStorage outboxStorage, IAsyncTaskFactory asyncTaskFactory) 23 | // { 24 | // _forwarder = new OutboxForwarder(asyncTaskFactory, rebusLoggerFactory, outboxStorage, transport); 25 | // _transport = transport; 26 | // _outboxStorage = outboxStorage; 27 | // } 28 | 29 | // public void Initialize() 30 | // { 31 | // _forwarder.Start(); 32 | // } 33 | 34 | // public void CreateQueue(string address) => _transport.CreateQueue(address); 35 | 36 | // public async Task Send(string destinationAddress, TransportMessage message, ITransactionContext context) 37 | // { 38 | // ConcurrentQueue SendOutgoingMessages() 39 | // { 40 | // var queue = new ConcurrentQueue(); 41 | // var correlationId = Guid.NewGuid().ToString("N").Substring(0, 16); 42 | 43 | // // if we're currently handling a message, we get information about it here 44 | // if (context.Items.TryGetValue("stepContext", out var result) && result is IncomingStepContext stepContext) 45 | // { 46 | // var messageId = stepContext.Load().GetMessageId(); 47 | // var sourceQueue = _transport.Address; 48 | 49 | // async Task CommitAction(ITransactionContext _) 50 | // { 51 | // if (!queue.Any()) return; //< don't do anything if no outgoing messages were sent 52 | 53 | // await _outboxStorage.Save(outgoingMessages: queue, messageId: messageId, sourceQueue: sourceQueue, correlationId); 54 | // } 55 | 56 | // context.OnCommitted(CommitAction); 57 | // } 58 | // else 59 | // { 60 | // async Task CommitAction(ITransactionContext _) 61 | // { 62 | // if (!queue.Any()) return; //< don't do anything if no outgoing messages were sent 63 | 64 | // await _outboxStorage.Save(outgoingMessages: queue, correlationId: correlationId); 65 | // } 66 | 67 | // context.OnCommitted(CommitAction); 68 | // } 69 | 70 | // // here, we intentionally kick off an async task to try to eager-send the messages that were just sent 71 | // context.OnCompleted(async _ => _forwarder.TryEagerSend(queue, correlationId)); 72 | 73 | // return queue; 74 | // } 75 | 76 | // var outgoingMessages = context.GetOrAdd("rebus-outbox-messages", SendOutgoingMessages); 77 | 78 | // outgoingMessages.Enqueue(new AbstractRebusTransport.OutgoingMessage(message, destinationAddress)); 79 | // } 80 | 81 | // public Task Receive(ITransactionContext context, CancellationToken cancellationToken) => _transport.Receive(context, cancellationToken); 82 | 83 | // public string Address => _transport.Address; 84 | 85 | // public void Dispose() 86 | // { 87 | // _forwarder.Dispose(); 88 | // } 89 | //} 90 | -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Outbox/IOutboxConnectionProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Rebus.SqlServer.Outbox; 2 | 3 | interface IOutboxConnectionProvider 4 | { 5 | OutboxConnection GetDbConnection(); 6 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Outbox/IOutboxStorage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Rebus.Transport; 4 | 5 | namespace Rebus.SqlServer.Outbox; 6 | 7 | /// 8 | /// Outbox abstraction that enables truly idempotent message processing and store-and-forward for outgoing messages 9 | /// 10 | public interface IOutboxStorage 11 | { 12 | /// 13 | /// Stores the given as being the result of processing message with ID 14 | /// in the queue of this particular endpoint. If is an empty sequence, a note is made of the fact 15 | /// that the message with ID has been processed. 16 | /// 17 | Task Save(IEnumerable outgoingMessages, string messageId = null, string sourceQueue = null, string correlationId = null); 18 | 19 | /// 20 | /// Stores the given using the given . 21 | /// 22 | Task Save(IEnumerable outgoingMessages, IDbConnection dbConnection); 23 | 24 | /// 25 | /// Gets the next message batch to be sent, possibly filtered by the given . MIGHT return messages from other send operations in the rare 26 | /// case where there is a colission between correlation IDs. Returns from 0 to messages in the batch. 27 | /// 28 | Task GetNextMessageBatch(string correlationId = null, int maxMessageBatchSize = 100); 29 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Outbox/OutboxConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Data.SqlClient; 3 | // ReSharper disable ArgumentsStyleLiteral 4 | 5 | namespace Rebus.SqlServer.Outbox; 6 | 7 | /// 8 | /// Holds an open 9 | /// 10 | public class OutboxConnection 11 | { 12 | /// 13 | /// Gets the connection 14 | /// 15 | public SqlConnection Connection { get; } 16 | 17 | /// 18 | /// Gets the current transaction 19 | /// 20 | public SqlTransaction Transaction { get; } 21 | 22 | internal OutboxConnection(SqlConnection connection, SqlTransaction transaction) 23 | { 24 | Connection = connection ?? throw new ArgumentNullException(nameof(connection)); 25 | Transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Outbox/OutboxIncomingStep.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Rebus.Config.Outbox; 4 | using Rebus.Pipeline; 5 | using Rebus.Transport; 6 | #pragma warning disable CS1998 7 | 8 | namespace Rebus.SqlServer.Outbox; 9 | 10 | class OutboxIncomingStep : IIncomingStep 11 | { 12 | readonly IOutboxConnectionProvider _outboxConnectionProvider; 13 | 14 | public OutboxIncomingStep(IOutboxConnectionProvider outboxConnectionProvider) 15 | { 16 | _outboxConnectionProvider = outboxConnectionProvider ?? throw new ArgumentNullException(nameof(outboxConnectionProvider)); 17 | } 18 | 19 | public async Task Process(IncomingStepContext context, Func next) 20 | { 21 | var outboxConnection = _outboxConnectionProvider.GetDbConnection(); 22 | var transactionContext = context.Load(); 23 | 24 | transactionContext.Items[OutboxExtensions.CurrentOutboxConnectionKey] = outboxConnection; 25 | 26 | transactionContext.OnCommit(async _ => 27 | { 28 | var tx = transactionContext; 29 | 30 | outboxConnection.Transaction.Commit(); 31 | }); 32 | 33 | transactionContext.OnDisposed(_ => 34 | { 35 | outboxConnection.Transaction.Dispose(); 36 | outboxConnection.Connection.Dispose(); 37 | }); 38 | 39 | await next(); 40 | } 41 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Outbox/OutboxMessage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Rebus.Messages; 3 | 4 | namespace Rebus.SqlServer.Outbox; 5 | 6 | /// 7 | /// Represents one single message to be delivered to the transport 8 | /// 9 | public record OutboxMessage(long Id, string DestinationAddress, Dictionary Headers, byte[] Body) 10 | { 11 | /// 12 | /// Gets the and wrapped in a 13 | /// 14 | public TransportMessage ToTransportMessage() => new(Headers, Body); 15 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Outbox/OutboxMessageBatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Rebus.SqlServer.Outbox; 8 | 9 | /// 10 | /// Wraps a batch of s along with a function that "completes" the batch (i.e. ensures that it will not be handled again -... e.g. by deleting it, or marking it as completed) 11 | /// 12 | public class OutboxMessageBatch : IDisposable, IReadOnlyList 13 | { 14 | /// 15 | /// Gets an empty outbox message batch that doesn't complete anything and only performs some kind of cleanup when done 16 | /// 17 | public static OutboxMessageBatch Empty(Action disposeFunction) => new(() => Task.CompletedTask, Array.Empty(), disposeFunction); 18 | 19 | readonly IReadOnlyList _messages; 20 | readonly Func _completionFunction; 21 | readonly Action _disposeFunction; 22 | 23 | /// 24 | /// Creates the batch 25 | /// 26 | public OutboxMessageBatch(Func completionFunction, IEnumerable messages, Action disposeFunction) 27 | { 28 | _messages = messages.ToList(); 29 | _completionFunction = completionFunction ?? throw new ArgumentNullException(nameof(completionFunction)); 30 | _disposeFunction = disposeFunction; 31 | } 32 | 33 | /// 34 | /// Marks the message batch as properly handled 35 | /// 36 | public async Task Complete() => await _completionFunction(); 37 | 38 | /// 39 | /// Performs any cleanup actions necessary 40 | /// 41 | public void Dispose() => _disposeFunction(); 42 | 43 | /// 44 | /// Gets how many 45 | /// 46 | public int Count => _messages.Count; 47 | 48 | /// 49 | /// Gets by index 50 | /// 51 | public OutboxMessage this[int index] => _messages[index]; 52 | 53 | /// 54 | /// Gets an enumerator for the wrapped sequence of s 55 | /// 56 | public IEnumerator GetEnumerator() => _messages.GetEnumerator(); 57 | 58 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 59 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Ponder.cs: -------------------------------------------------------------------------------- 1 | namespace Rebus.SqlServer; 2 | 3 | class Reflect 4 | { 5 | public static object Value(object obj, string path) 6 | { 7 | var dots = path.Split('.'); 8 | 9 | foreach(var dot in dots) 10 | { 11 | var propertyInfo = obj.GetType().GetProperty(dot); 12 | if (propertyInfo == null) return null; 13 | obj = propertyInfo.GetValue(obj, new object[0]); 14 | if (obj == null) break; 15 | } 16 | 17 | return obj; 18 | } 19 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Retrier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Rebus.SqlServer; 8 | 9 | /// 10 | /// Mini-Polly 🙂 11 | /// 12 | class Retrier 13 | { 14 | readonly List _delays; 15 | 16 | public Retrier(IEnumerable delays) 17 | { 18 | if (delays == null) 19 | { 20 | throw new ArgumentNullException(nameof(delays)); 21 | } 22 | 23 | _delays = delays.ToList(); 24 | } 25 | 26 | public async Task ExecuteAsync(Func execute, CancellationToken cancellationToken = default) 27 | { 28 | for (var index = 0; index <= _delays.Count; index++) 29 | { 30 | try 31 | { 32 | await execute(); 33 | return; 34 | } 35 | catch (Exception exception) 36 | { 37 | if (index == _delays.Count) 38 | { 39 | throw; 40 | } 41 | 42 | var delay = _delays[index]; 43 | 44 | try 45 | { 46 | await Task.Delay(delay, cancellationToken); 47 | } 48 | catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) 49 | { 50 | return; 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Sagas/CachedSagaTypeNamingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | 4 | namespace Rebus.SqlServer.Sagas; 5 | 6 | /// 7 | /// Decorator for that caches the results in-case calculations are expensive 8 | /// 9 | public class CachedSagaTypeNamingStrategy : ISagaTypeNamingStrategy 10 | { 11 | private readonly ConcurrentDictionary _sagaTypeCache = new ConcurrentDictionary(); 12 | private readonly ISagaTypeNamingStrategy _innerSagaTypeNamingStrategy; 13 | 14 | /// 15 | /// Constructs a new instance which will defer actual naming to 16 | /// 17 | public CachedSagaTypeNamingStrategy(ISagaTypeNamingStrategy innerSagaTypeNamingStrategy) 18 | { 19 | _innerSagaTypeNamingStrategy = innerSagaTypeNamingStrategy; 20 | } 21 | 22 | /// 23 | public string GetSagaTypeName(Type sagaDataType, int maximumLength) 24 | { 25 | return _sagaTypeCache.GetOrAdd(sagaDataType, t => _innerSagaTypeNamingStrategy.GetSagaTypeName(t, maximumLength)); 26 | } 27 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Sagas/HumanReadableHashedSagaTypeNamingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Extensions; 3 | 4 | namespace Rebus.SqlServer.Sagas; 5 | 6 | /// 7 | /// Returns a string that uses a portion of the actual saga type name and appends the hash of the simple assembly qualified name of the hash. This is a balance between uniqueness and readable for diagnostic purposes 8 | /// 9 | public class HumanReadableHashedSagaTypeNamingStrategy : Sha512SagaTypeNamingStrategy 10 | { 11 | /// 12 | /// The number of bytes to leave as human readable if no value is specified 13 | /// 14 | public const int DefaultHumanReadableBytes = 10; 15 | 16 | private readonly int _numberOfHumanReadableBytes; 17 | 18 | /// 19 | /// Default constructor configured to use number of human readable bytes 20 | /// 21 | public HumanReadableHashedSagaTypeNamingStrategy() : this(DefaultHumanReadableBytes) 22 | { 23 | } 24 | 25 | /// 26 | /// Constructs a new instance which uses of human readable bytes in the output 27 | /// 28 | public HumanReadableHashedSagaTypeNamingStrategy(int numberOfHumanReadableBytes) 29 | { 30 | _numberOfHumanReadableBytes = numberOfHumanReadableBytes; 31 | } 32 | 33 | /// 34 | public override string GetSagaTypeName(Type sagaDataType, int maximumLength) 35 | { 36 | if (_numberOfHumanReadableBytes > maximumLength) 37 | { 38 | throw new ArgumentOutOfRangeException(nameof(maximumLength), $"Cannot generate a name of {maximumLength} bytes as {nameof(HumanReadableHashedSagaTypeNamingStrategy)} is configured to use at {_numberOfHumanReadableBytes} human readable bytes"); 39 | } 40 | 41 | return GenerateHumanReadableHash(sagaDataType.Name, sagaDataType, maximumLength); 42 | } 43 | 44 | /// 45 | /// Generates a human readable hash that uses portions of and the hash of 46 | /// 47 | protected virtual string GenerateHumanReadableHash(string humanReadableName, Type sagaDataType, int maximumLength) 48 | { 49 | var humanReadablePortion = humanReadableName.Substring(0, Math.Min(_numberOfHumanReadableBytes, humanReadableName.Length)); 50 | var simpleName = sagaDataType.GetSimpleAssemblyQualifiedName(); 51 | var hash = GetBase64EncodedPartialHash(simpleName, maximumLength - humanReadablePortion.Length, System.Text.Encoding.UTF8); 52 | 53 | return $"{humanReadablePortion}{hash}"; 54 | } 55 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Sagas/ISagaTypeNamingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Rebus.SqlServer.Sagas; 4 | 5 | /// 6 | /// A contract for generating a type name for a saga as it'll be stored in the database 7 | /// 8 | public interface ISagaTypeNamingStrategy 9 | { 10 | /// 11 | /// Generate a saga type name. Implementations should be pure/stable; for a given input the output should always be the same as it's used to find the saga in the database 12 | /// 13 | /// Type a name is to be generated for 14 | /// Maximum allowed length of the result. If the return is longer than this an exception will be thrown 15 | /// A string representation of 16 | string GetSagaTypeName(Type sagaDataType, int maximumLength); 17 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Sagas/LegacySagaTypeNamingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Rebus.SqlServer.Sagas; 4 | 5 | /// 6 | /// Implementation of which uses legacy type naming; simply returning the name of the class 7 | /// 8 | public class LegacySagaTypeNamingStrategy : ISagaTypeNamingStrategy 9 | { 10 | /// 11 | public string GetSagaTypeName(Type sagaDataType, int maximumLength) 12 | { 13 | return sagaDataType.Name; 14 | } 15 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Sagas/Serialization/DefaultSagaSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Sagas; 3 | using Rebus.Serialization; 4 | 5 | namespace Rebus.SqlServer.Sagas.Serialization; 6 | 7 | /// 8 | /// The default serializer for serializing sql saga data, 9 | /// Implement to make your own custom serializer and register it using the UseSagaSerializer extension method. 10 | /// 11 | /// 12 | public class DefaultSagaSerializer : ISagaSerializer 13 | { 14 | readonly ObjectSerializer _objectSerializer = new(); 15 | 16 | /// 17 | /// Serializes the given ISagaData object into a string 18 | /// 19 | /// 20 | /// 21 | public string SerializeToString(ISagaData obj) 22 | { 23 | return _objectSerializer.SerializeToString(obj); 24 | } 25 | 26 | /// 27 | /// Deserializes the given string and type into a ISagaData object 28 | /// 29 | /// 30 | /// 31 | /// 32 | public ISagaData DeserializeFromString(Type type, string str) 33 | { 34 | var sagaData = _objectSerializer.DeserializeFromString(str) as ISagaData; 35 | 36 | if (!type.IsInstanceOfType(sagaData)) 37 | { 38 | return null; 39 | } 40 | 41 | return sagaData; 42 | } 43 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Sagas/Serialization/ISagaSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Rebus.Sagas; 3 | 4 | namespace Rebus.SqlServer.Sagas.Serialization; 5 | 6 | /// 7 | /// Serializer used to serialize and deserialize saga data 8 | /// 9 | public interface ISagaSerializer 10 | { 11 | /// 12 | /// Serializes the given ISagaData object into a string 13 | /// 14 | string SerializeToString(ISagaData obj); 15 | 16 | /// 17 | /// Deserializes the given string and type into a ISagaData object 18 | /// 19 | ISagaData DeserializeFromString(Type type, string str); 20 | } -------------------------------------------------------------------------------- /Rebus.SqlServer/SqlServer/Sagas/Sha512SagaTypeNamingStrategy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | using Rebus.Extensions; 5 | 6 | namespace Rebus.SqlServer.Sagas; 7 | 8 | /// 9 | /// Implementation of that uses as many bytes as it can from an SHA2-512 hash to generat ea name 10 | /// 11 | /// Note: SHA-2 is seemingly safe to truncate Reference 12 | public class Sha512SagaTypeNamingStrategy : ISagaTypeNamingStrategy 13 | { 14 | /// 15 | public virtual string GetSagaTypeName(Type sagaDataType, int maximumLength) 16 | { 17 | return GetBase64EncodedPartialHash(sagaDataType.GetSimpleAssemblyQualifiedName(), maximumLength, Encoding.UTF8); 18 | } 19 | 20 | /// 21 | /// Returns a string of up to characters long that represent the Base64 encoded SHA2-512 hash of . The string will be encoded using first 22 | /// 23 | protected string GetBase64EncodedPartialHash(string inputString, int numberOfBytes, Encoding encoding) 24 | { 25 | using (SHA512Managed sha = new SHA512Managed()) 26 | { 27 | var encodedBytes = encoding.GetBytes(inputString); 28 | var hashedBytes = sha.ComputeHash(encodedBytes, 0, encodedBytes.Length); 29 | var bytesToUseFromHash = GetMaximumBase64EncodedBytesThatFit(Math.Min(numberOfBytes, hashedBytes.Length)); 30 | 31 | return Convert.ToBase64String(hashedBytes, 0, bytesToUseFromHash); 32 | } 33 | } 34 | 35 | /// 36 | /// Returns the number of bytes of bytes that can be encoded in Base64 and still fit in 37 | /// 38 | private static int GetMaximumBase64EncodedBytesThatFit(int maximumSize) => (int)Math.Floor(0.75f * maximumSize); 39 | } -------------------------------------------------------------------------------- /RebusOutboxWebApp/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using RebusOutboxWebApp.Models; 4 | using System.Diagnostics; 5 | using System.Threading.Tasks; 6 | using Rebus.Bus; 7 | using RebusOutboxWebApp.Messages; 8 | 9 | namespace RebusOutboxWebApp.Controllers 10 | { 11 | public class HomeController : Controller 12 | { 13 | private readonly ILogger _logger; 14 | private readonly IBus _bus; 15 | 16 | public HomeController(ILogger logger, IBus bus) 17 | { 18 | _logger = logger; 19 | _bus = bus; 20 | } 21 | 22 | public IActionResult Index() 23 | { 24 | return View(); 25 | } 26 | 27 | public IActionResult Privacy() 28 | { 29 | return View(); 30 | } 31 | 32 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 33 | public IActionResult Error() 34 | { 35 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 36 | } 37 | 38 | [HttpPost] 39 | [Route("send-message")] 40 | public async Task SendMessage([FromBody] SendMessageForm form) 41 | { 42 | await _bus.Send(new SendMessageCommand(form.Message)); 43 | 44 | return Ok(); 45 | } 46 | 47 | public record SendMessageForm(string Message); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Extensions/RebusOutboxMiddleware.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Data.SqlClient; 3 | using Rebus.Config.Outbox; 4 | using Rebus.Transport; 5 | // ReSharper disable UnusedParameter.Local 6 | 7 | namespace RebusOutboxWebApp.Extensions; 8 | 9 | static class RebusOutboxMiddleware 10 | { 11 | public static void UseRebusOutbox(this IApplicationBuilder app, string connectionString) 12 | { 13 | app.Use(async (context, next) => 14 | { 15 | // if you've set up DI resolution to fetch the current connection/transaction from a unit of work, you can do something like this: 16 | //var serviceProvider = context.RequestServices; 17 | //var connection = serviceProvider.GetRequiredService(); 18 | //var transaction = serviceProvider.GetRequiredService(); 19 | 20 | // but in this case we'll work with a simulated unit of work here: 21 | await using var connection = new SqlConnection(connectionString); 22 | await connection.OpenAsync(); 23 | 24 | await using var transaction = connection.BeginTransaction(); 25 | 26 | using var scope = new RebusTransactionScope(); 27 | 28 | scope.UseOutbox(connection, transaction); 29 | 30 | // process the web request 31 | await next(); 32 | 33 | // insert outgoing messages 34 | await scope.CompleteAsync(); 35 | 36 | // commit! 37 | await transaction.CommitAsync(); 38 | }); 39 | } 40 | } -------------------------------------------------------------------------------- /RebusOutboxWebApp/Handlers/SendMessageCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.Extensions.Logging; 3 | using Rebus.Handlers; 4 | using RebusOutboxWebApp.Messages; 5 | #pragma warning disable CS1998 6 | 7 | namespace RebusOutboxWebApp.Handlers 8 | { 9 | public class SendMessageCommandHandler : IHandleMessages 10 | { 11 | private readonly ILogger _logger; 12 | 13 | public SendMessageCommandHandler(ILogger logger) 14 | { 15 | _logger = logger; 16 | } 17 | 18 | public async Task Handle(SendMessageCommand message) 19 | { 20 | _logger.LogInformation("Handling message {text}", message.Message); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Messages/SendMessageCommand.cs: -------------------------------------------------------------------------------- 1 | namespace RebusOutboxWebApp.Messages 2 | { 3 | public record SendMessageCommand(string Message); 4 | } 5 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace RebusOutboxWebApp.Models 2 | { 3 | public class ErrorViewModel 4 | { 5 | public string RequestId { get; set; } 6 | 7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace RebusOutboxWebApp 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:47993", 7 | "sslPort": 44372 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "RebusOutboxWebApp": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/RebusOutboxWebApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using Rebus.Config; 7 | using Rebus.Config.Outbox; 8 | using Rebus.Routing.TypeBased; 9 | using Rebus.Transport.InMem; 10 | using RebusOutboxWebApp.Extensions; 11 | using RebusOutboxWebApp.Handlers; 12 | using RebusOutboxWebApp.Messages; 13 | 14 | namespace RebusOutboxWebApp 15 | { 16 | public class Startup 17 | { 18 | const string OutboxConnectionString = "server=.; database=rebusoutboxwebapp; trusted_connection=true; encrypt=false"; 19 | const string OutboxTableName = "Outbox"; 20 | 21 | public Startup(IConfiguration configuration) 22 | { 23 | Configuration = configuration; 24 | } 25 | 26 | public IConfiguration Configuration { get; } 27 | 28 | // This method gets called by the runtime. Use this method to add services to the container. 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | services.AddControllersWithViews(); 32 | 33 | services.AddRebus( 34 | (configure, _) => configure 35 | .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "outbox-test")) 36 | .Outbox(o => o.StoreInSqlServer(OutboxConnectionString, OutboxTableName)) 37 | .Routing(r => r.TypeBased().Map("outbox-test")) 38 | ); 39 | 40 | services.AddRebusHandler(); 41 | } 42 | 43 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 44 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 45 | { 46 | if (env.IsDevelopment()) 47 | { 48 | app.UseDeveloperExceptionPage(); 49 | } 50 | else 51 | { 52 | app.UseExceptionHandler("/Home/Error"); 53 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 54 | app.UseHsts(); 55 | } 56 | app.UseHttpsRedirection(); 57 | app.UseStaticFiles(); 58 | 59 | app.UseRouting(); 60 | 61 | app.UseAuthorization(); 62 | 63 | app.UseRebusOutbox(OutboxConnectionString); 64 | 65 | app.UseEndpoints(endpoints => 66 | { 67 | endpoints.MapControllerRoute( 68 | name: "default", 69 | pattern: "{controller=Home}/{action=Index}/{id?}"); 70 | }); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 |
6 |

Welcome

7 |

Learn about building Web apps with ASP.NET Core.

8 |

...or type some text and click this button for fun:

9 |

10 | 11 | 12 |

13 |

14 | 15 |

16 |
17 | 18 | @section Scripts 19 | { 20 | 37 | } 38 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

Use this page to detail your site's privacy policy.

7 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

26 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - RebusOutboxWebApp 7 | 8 | 9 | 10 | 11 |
12 | 31 |
32 |
33 |
34 | @RenderBody() 35 |
36 |
37 | 38 |
39 |
40 | © 2021 - RebusOutboxWebApp - Privacy 41 |
42 |
43 | 44 | 45 | 46 | @await RenderSectionAsync("Scripts", required: false) 47 | 48 | 49 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using RebusOutboxWebApp 2 | @using RebusOutboxWebApp.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | /* Provide sufficient contrast against white background */ 11 | a { 12 | color: #0366d6; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 22 | color: #fff; 23 | background-color: #1b6ec2; 24 | border-color: #1861ac; 25 | } 26 | 27 | /* Sticky footer styles 28 | -------------------------------------------------- */ 29 | html { 30 | font-size: 14px; 31 | } 32 | @media (min-width: 768px) { 33 | html { 34 | font-size: 16px; 35 | } 36 | } 37 | 38 | .border-top { 39 | border-top: 1px solid #e5e5e5; 40 | } 41 | .border-bottom { 42 | border-bottom: 1px solid #e5e5e5; 43 | } 44 | 45 | .box-shadow { 46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 47 | } 48 | 49 | button.accept-policy { 50 | font-size: 1rem; 51 | line-height: inherit; 52 | } 53 | 54 | /* Sticky footer styles 55 | -------------------------------------------------- */ 56 | html { 57 | position: relative; 58 | min-height: 100%; 59 | } 60 | 61 | body { 62 | /* Margin bottom by footer height */ 63 | margin-bottom: 60px; 64 | } 65 | .footer { 66 | position: absolute; 67 | bottom: 0; 68 | width: 100%; 69 | white-space: nowrap; 70 | line-height: 60px; /* Vertically center the text there */ 71 | } 72 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.SqlServer/c6a2f04d91f5d247f694a93ee812fb958c0f62e1/RebusOutboxWebApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /RebusOutboxWebApp/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2018 Twitter, Inc. 4 | Copyright (c) 2011-2018 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /RebusOutboxWebApp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /RebusOutboxWebApp/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Logging; 6 | using Rebus.Bus; 7 | using RebusOutboxWebAppEfCore.Entities; 8 | using RebusOutboxWebAppEfCore.Messages; 9 | using RebusOutboxWebAppEfCore.Models; 10 | 11 | namespace RebusOutboxWebAppEfCore.Controllers 12 | { 13 | public class HomeController : Controller 14 | { 15 | private readonly ILogger _logger; 16 | private readonly WebAppDbContext _context; 17 | private readonly IBus _bus; 18 | 19 | public HomeController(ILogger logger, IBus bus, WebAppDbContext context) 20 | { 21 | _logger = logger; 22 | _bus = bus; 23 | _context = context; 24 | } 25 | 26 | public IActionResult Index() 27 | { 28 | return View(); 29 | } 30 | 31 | public IActionResult Privacy() 32 | { 33 | return View(); 34 | } 35 | 36 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 37 | public IActionResult Error() 38 | { 39 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 40 | } 41 | 42 | [HttpPost] 43 | [Route("send-message")] 44 | public async Task SendMessage([FromBody] SendMessageForm form) 45 | { 46 | var postedMessage = PostedMessage.New(form.Message); 47 | 48 | await _context.PostedMessages.AddAsync(postedMessage); 49 | 50 | try 51 | { 52 | await _context.SaveChangesAsync(); 53 | } 54 | catch (Exception exception) 55 | { 56 | var details = exception.ToString(); 57 | Console.WriteLine(details); 58 | } 59 | 60 | await _bus.Send(new ProcessMessageCommand(postedMessage.Id)); 61 | 62 | return Ok(); 63 | } 64 | 65 | public record SendMessageForm(string Message); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Entities/PostedMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | // ReSharper disable AutoPropertyCanBeMadeGetOnly.Local 3 | // ReSharper disable UnusedAutoPropertyAccessor.Local 4 | 5 | namespace RebusOutboxWebAppEfCore.Entities 6 | { 7 | /* 8 | 9 | CREATE TABLE [dbo].[PostedMessages]( 10 | [Id] [int] IDENTITY(1,1) NOT NULL, 11 | [Text] [nvarchar](max) NOT NULL, 12 | [Time] [datetimeoffset](3) NOT NULL, 13 | [State] [int] NOT NULL, 14 | PRIMARY KEY CLUSTERED 15 | ( 16 | [Id] ASC 17 | ) 18 | ) 19 | 20 | */ 21 | public class PostedMessage 22 | { 23 | public static PostedMessage New(string text) => new(text, DateTimeOffset.Now, PostedMessageState.Enqueued); 24 | 25 | public int Id { get; private set; } 26 | 27 | public string Text { get; private set; } 28 | 29 | public DateTimeOffset Time { get; private set; } 30 | 31 | public PostedMessageState State { get; private set; } 32 | 33 | public PostedMessage(string text, DateTimeOffset time, PostedMessageState state) 34 | { 35 | Text = text; 36 | State = state; 37 | Time = time; 38 | } 39 | 40 | public void ChangeState(PostedMessageState state) 41 | { 42 | State = state; 43 | } 44 | 45 | public enum PostedMessageState 46 | { 47 | Enqueued, 48 | Finished 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Entities/WebAppDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace RebusOutboxWebAppEfCore.Entities 4 | { 5 | public class WebAppDbContext : DbContext 6 | { 7 | public WebAppDbContext(DbContextOptions options) : base(options) 8 | { 9 | } 10 | 11 | public DbSet PostedMessages { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Handlers/ProcessMessageCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Logging; 4 | using Rebus.Handlers; 5 | using RebusOutboxWebAppEfCore.Entities; 6 | using RebusOutboxWebAppEfCore.Messages; 7 | 8 | namespace RebusOutboxWebAppEfCore.Handlers 9 | { 10 | public class ProcessMessageCommandHandler : IHandleMessages 11 | { 12 | private readonly ILogger _logger; 13 | private readonly WebAppDbContext _context; 14 | 15 | public ProcessMessageCommandHandler(ILogger logger, WebAppDbContext context) 16 | { 17 | _logger = logger; 18 | _context = context; 19 | } 20 | 21 | public async Task Handle(ProcessMessageCommand message) 22 | { 23 | var id = message.MessageId; 24 | 25 | var postedMessage = await _context.PostedMessages.FindAsync(id) 26 | ?? throw new ArgumentException($"Could not find message with ID '{id}'"); 27 | 28 | postedMessage.ChangeState(PostedMessage.PostedMessageState.Finished); 29 | 30 | _logger.LogInformation("Successfully handled posted message with ID {postedMessageId}", id); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Messages/ProcessMessageCommand.cs: -------------------------------------------------------------------------------- 1 | namespace RebusOutboxWebAppEfCore.Messages 2 | { 3 | public record ProcessMessageCommand(int MessageId); 4 | } 5 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace RebusOutboxWebAppEfCore.Models 2 | { 3 | public class ErrorViewModel 4 | { 5 | public string RequestId { get; set; } 6 | 7 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace RebusOutboxWebAppEfCore 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:50291", 7 | "sslPort": 44375 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "RebusOutboxWebAppEfCore": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": "true", 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/RebusOutboxWebAppEfCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 |
6 |

Welcome

7 |

Learn about building Web apps with ASP.NET Core.

8 |

...or type some text and click this button for fun:

9 |

10 | 11 | 12 |

13 |

14 | 15 |

16 |
17 | 18 | @section Scripts 19 | { 20 | 37 | } -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

Use this page to detail your site's privacy policy.

7 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

26 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - RebusOutboxWebAppEfCore 7 | 8 | 9 | 10 | 11 |
12 | 31 |
32 |
33 |
34 | @RenderBody() 35 |
36 |
37 | 38 |
39 |
40 | © 2021 - RebusOutboxWebAppEfCore - Privacy 41 |
42 |
43 | 44 | 45 | 46 | @await RenderSectionAsync("Scripts", required: false) 47 | 48 | 49 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using RebusOutboxWebAppEfCore 2 | @using RebusOutboxWebAppEfCore.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "RebusDatabase": "server=.; database=rebusoutboxwebapp; trusted_connection=true; encrypt=false" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft": "Warning", 9 | "Microsoft.Hosting.Lifetime": "Information" 10 | } 11 | }, 12 | "AllowedHosts": "*" 13 | } 14 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | /* Provide sufficient contrast against white background */ 11 | a { 12 | color: #0366d6; 13 | } 14 | 15 | .btn-primary { 16 | color: #fff; 17 | background-color: #1b6ec2; 18 | border-color: #1861ac; 19 | } 20 | 21 | .nav-pills .nav-link.active, .nav-pills .show > .nav-link { 22 | color: #fff; 23 | background-color: #1b6ec2; 24 | border-color: #1861ac; 25 | } 26 | 27 | /* Sticky footer styles 28 | -------------------------------------------------- */ 29 | html { 30 | font-size: 14px; 31 | } 32 | @media (min-width: 768px) { 33 | html { 34 | font-size: 16px; 35 | } 36 | } 37 | 38 | .border-top { 39 | border-top: 1px solid #e5e5e5; 40 | } 41 | .border-bottom { 42 | border-bottom: 1px solid #e5e5e5; 43 | } 44 | 45 | .box-shadow { 46 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 47 | } 48 | 49 | button.accept-policy { 50 | font-size: 1rem; 51 | line-height: inherit; 52 | } 53 | 54 | /* Sticky footer styles 55 | -------------------------------------------------- */ 56 | html { 57 | position: relative; 58 | min-height: 100%; 59 | } 60 | 61 | body { 62 | /* Margin bottom by footer height */ 63 | margin-bottom: 60px; 64 | } 65 | .footer { 66 | position: absolute; 67 | bottom: 0; 68 | width: 100%; 69 | white-space: nowrap; 70 | line-height: 60px; /* Vertically center the text there */ 71 | } 72 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.SqlServer/c6a2f04d91f5d247f694a93ee812fb958c0f62e1/RebusOutboxWebAppEfCore/wwwroot/favicon.ico -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2018 Twitter, Inc. 4 | Copyright (c) 2011-2018 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors 4 | * Copyright 2011-2019 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /RebusOutboxWebAppEfCore/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | services: 4 | - mssql2017 5 | 6 | shallow_clone: true 7 | 8 | cache: 9 | - packages -> **\packages.config 10 | - '%LocalAppData%\NuGet\Cache' 11 | 12 | before_build: 13 | - appveyor-retry dotnet restore -v Minimal 14 | 15 | build_script: 16 | - dotnet build Rebus.SqlServer -c Release --no-restore 17 | 18 | test_script: 19 | - dotnet test Rebus.SqlServer.Tests -c Release --no-restore 20 | -------------------------------------------------------------------------------- /artwork/little_rebusbus2_copy-200x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.SqlServer/c6a2f04d91f5d247f694a93ee812fb958c0f62e1/artwork/little_rebusbus2_copy-200x200.png -------------------------------------------------------------------------------- /artwork/little_rebusbus2_copy-500x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.SqlServer/c6a2f04d91f5d247f694a93ee812fb958c0f62e1/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 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 | 39 | 40 | 41 | 42 | 43 | goto exit_success 44 | :exit_fail 45 | exit /b 1 46 | :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 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.SqlServer/c6a2f04d91f5d247f694a93ee812fb958c0f62e1/tools/NuGet/nuget.exe -------------------------------------------------------------------------------- /tools/aversion/Aversion.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rebus-org/Rebus.SqlServer/c6a2f04d91f5d247f694a93ee812fb958c0f62e1/tools/aversion/Aversion.exe --------------------------------------------------------------------------------