diff --git a/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_failed_message.cs b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_failed_message.cs new file mode 100644 index 0000000000..c7404251bb --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_failed_message.cs @@ -0,0 +1,83 @@ +namespace NServiceBus.AcceptanceTests.UnitOfWork +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.UnitOfWork; + using NUnit.Framework; + + public class When_using_custom_unit_of_work_with_failed_message : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_execute_uow_and_provide_exception_details() + { + var context = await Scenario.Define() + .WithEndpoint(g => + { + g.DoNotFailOnErrorMessages(); + g.When(b => b.SendLocal(new MyMessage())); + }) + .Done(c => c.BeginCalled && c.EndCalled) + .Run(); + + Assert.True(context.BeginCalled, "Unit of work should have been executed"); + Assert.True(context.EndCalled, "Unit of work should have been executed"); + Assert.That(context.EndException, Is.InstanceOf().And.Message.Contain("Something went wrong"), "Exception was not provided but should have been"); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public bool BeginCalled { get; set; } + public bool EndCalled { get; set; } + public Exception EndException { get; set; } + } + + public class EndpointWithCustomUnitOfWork : EndpointConfigurationBuilder + { + public EndpointWithCustomUnitOfWork() + { + EndpointSetup((c, r) => + { + c.RegisterComponents(container => container.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + }); + } + + class CustomUnitOfWork : IManageUnitsOfWork + { + public CustomUnitOfWork(Context testContext) + { + this.testContext = testContext; + } + + public Task Begin() + { + testContext.BeginCalled = true; + return Task.FromResult(0); + } + + public Task End(Exception ex = null) + { + testContext.EndCalled = true; + testContext.EndException = ex; + return Task.FromResult(0); + } + + Context testContext; + } + + class MyMessageHandler : IHandleMessages + { + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + throw new SimulatedException("Something went wrong"); + } + } + } + + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_successful_message.cs b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_successful_message.cs new file mode 100644 index 0000000000..7a74c8e2c6 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_successful_message.cs @@ -0,0 +1,88 @@ +namespace NServiceBus.AcceptanceTests.UnitOfWork +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.UnitOfWork; + using NUnit.Framework; + + public class When_using_custom_unit_of_work_with_successful_message : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_execute_uow() + { + var context = await Scenario.Define() + .WithEndpoint(g => g.When(b => b.SendLocal(new MyMessage()))) + .Done(c => c.BeginCalled && c.EndCalled) + .Run(); + + Assert.True(context.BeginCalled, "Unit of work should have been executed"); + Assert.True(context.EndCalled, "Unit of work should have been executed"); + Assert.IsNull(context.EndException, "Exception was provided to unit of work but should not have been"); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public bool BeginCalled { get; set; } + public bool EndCalled { get; set; } + public Exception EndException { get; set; } + } + + public class EndpointWithCustomUnitOfWork : EndpointConfigurationBuilder + { + public EndpointWithCustomUnitOfWork() + { + EndpointSetup((c, r) => + { + c.RegisterComponents(container => container.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + }); + } + + class CustomUnitOfWork : IManageUnitsOfWork + { + public CustomUnitOfWork(Context testContext) + { + this.testContext = testContext; + } + + public Task Begin() + { + testContext.BeginCalled = true; + return Task.FromResult(0); + } + + public Task End(Exception ex = null) + { + testContext.EndCalled = true; + testContext.EndException = ex; + return Task.FromResult(0); + } + + Context testContext; + } + + class MyMessageHandler : IHandleMessages + { + public MyMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + testContext.Done = true; + + return Task.FromResult(0); + } + + Context testContext; + } + } + + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_wrap_handlers_in_scope.cs b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_wrap_handlers_in_scope.cs new file mode 100644 index 0000000000..21f20cf7b1 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/UnitOfWork/When_using_custom_unit_of_work_with_wrap_handlers_in_scope.cs @@ -0,0 +1,85 @@ +namespace NServiceBus.AcceptanceTests.UnitOfWork +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using System.Transactions; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.UnitOfWork; + using NUnit.Framework; + + public class When_using_custom_unit_of_work_with_wrap_handlers_in_scope : NServiceBusAcceptanceTest + { + [Test] + public async Task Should_fail() + { + var context = await Scenario.Define() + .WithEndpoint(g => + { + g.DoNotFailOnErrorMessages(); + g.When(b => b.SendLocal(new MyMessage())); + }) + .Done(c => c.FailedMessages.Any()) + .Run(); + + Assert.False(context.ShouldNeverBeCalled, "Unit of work should have been executed"); + } + + public class Context : ScenarioContext + { + public bool Done { get; set; } + public bool ShouldNeverBeCalled { get; set; } + } + + public class EndpointWithCustomUnitOfWork : EndpointConfigurationBuilder + { + public EndpointWithCustomUnitOfWork() + { + EndpointSetup((c, r) => + { + c.UnitOfWork().WrapHandlersInATransactionScope(); + + c.RegisterComponents(container => container.ConfigureComponent(DependencyLifecycle.InstancePerCall)); + }); + } + + class CustomUnitOfWork : IManageUnitsOfWork + { + TransactionScope transactionScope; + public Task Begin() + { + // this only works because we are not using the async state machine + transactionScope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled); + return Task.FromResult(0); + } + + public Task End(Exception ex = null) + { + transactionScope.Complete(); + return Task.FromResult(0); + } + } + + class MyMessageHandler : IHandleMessages + { + readonly Context testContext; + + public MyMessageHandler(Context testContext) + { + this.testContext = testContext; + } + + public Task Handle(MyMessage message, IMessageHandlerContext context) + { + testContext.ShouldNeverBeCalled = true; + return Task.FromResult(0); + } + } + } + + public class MyMessage : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWork.cs b/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWork.cs index 207e083d95..bd35e48c5c 100644 --- a/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWork.cs +++ b/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWork.cs @@ -13,7 +13,7 @@ protected internal override void Setup(FeatureConfigurationContext context) } var transactionOptions = context.Settings.Get().TransactionOptions; - context.Pipeline.Register("HandlerTransactionScopeWrapper", new TransactionScopeUnitOfWorkBehavior(transactionOptions), "Makes sure that the handlers gets wrapped in a transaction scope"); + context.Pipeline.Register( new TransactionScopeUnitOfWorkBehavior.Registration(transactionOptions)); } public class Settings diff --git a/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWorkBehavior.cs b/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWorkBehavior.cs index 126b4fab1a..8281aaba07 100644 --- a/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWorkBehavior.cs +++ b/src/NServiceBus.Core/UnitOfWork/TransactionScopes/TransactionScopeUnitOfWorkBehavior.cs @@ -28,5 +28,16 @@ public async Task Invoke(IIncomingPhysicalMessageContext context, Func new TransactionScopeUnitOfWorkBehavior(transactionOptions)) + { + InsertAfter("ExecuteUnitOfWork"); + } + } } } \ No newline at end of file