diff --git a/AcceptanceTests/NServiceBus.AcceptanceTests/NServiceBus.AcceptanceTests.csproj b/AcceptanceTests/NServiceBus.AcceptanceTests/NServiceBus.AcceptanceTests.csproj index c7d2d69043..6d2b87e5b8 100644 --- a/AcceptanceTests/NServiceBus.AcceptanceTests/NServiceBus.AcceptanceTests.csproj +++ b/AcceptanceTests/NServiceBus.AcceptanceTests/NServiceBus.AcceptanceTests.csproj @@ -173,6 +173,7 @@ + diff --git a/AcceptanceTests/NServiceBus.AcceptanceTests/Sagas/When_message_has_a_saga_id.cs b/AcceptanceTests/NServiceBus.AcceptanceTests/Sagas/When_message_has_a_saga_id.cs new file mode 100644 index 0000000000..bc3ccb5a78 --- /dev/null +++ b/AcceptanceTests/NServiceBus.AcceptanceTests/Sagas/When_message_has_a_saga_id.cs @@ -0,0 +1,77 @@ +namespace NServiceBus.AcceptanceTests.Sagas +{ + using System; + using AcceptanceTesting; + using EndpointTemplates; + using Saga; + using NUnit.Framework; + + public class When_message_has_a_saga_id : NServiceBusAcceptanceTest + { + [Test] + public void Should_not_start_a_new_saga_if_not_found() + { + var context = Scenario.Define() + .WithEndpoint(b => b.Given(bus => + { + var message = new MessageWithSagaId(); + + bus.SetMessageHeader(message, Headers.SagaId, Guid.NewGuid().ToString()); + bus.SetMessageHeader(message, Headers.SagaType, typeof(MySaga).AssemblyQualifiedName); + + bus.SendLocal(message); + })) + .Done(c => c.NotFoundHandlerCalled) + .Run(TimeSpan.FromSeconds(15)); + + Assert.True(context.NotFoundHandlerCalled); + Assert.False(context.MessageHandlerCalled); + Assert.False(context.TimeoutHandlerCalled); + } + + class MySaga : Saga, IAmStartedByMessages, + IHandleTimeouts, + IHandleSagaNotFound + { + public Context Context { get; set; } + + public class SagaData : ContainSagaData + { + } + + public void Handle(MessageWithSagaId message) + { + Context.MessageHandlerCalled = true; + } + + public void Handle(object message) + { + Context.NotFoundHandlerCalled = true; + } + + public void Timeout(MessageWithSagaId state) + { + Context.TimeoutHandlerCalled = true; + } + } + + class Context : ScenarioContext + { + public bool NotFoundHandlerCalled { get; set; } + public bool MessageHandlerCalled { get; set; } + public bool TimeoutHandlerCalled { get; set; } + } + + public class SagaEndpoint : EndpointConfigurationBuilder + { + public SagaEndpoint() + { + EndpointSetup(); + } + } + + public class MessageWithSagaId : IMessage + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/SagaDispatcherFactory.cs b/src/NServiceBus.Core/Sagas/SagaDispatcherFactory.cs index 02b8a7449d..f73b2fd912 100644 --- a/src/NServiceBus.Core/Sagas/SagaDispatcherFactory.cs +++ b/src/NServiceBus.Core/Sagas/SagaDispatcherFactory.cs @@ -44,6 +44,9 @@ public IEnumerable GetDispatcher(Type messageHandlerType, IBuilder build if (sagaTypesHandled.Contains(sagaType)) continue; // don't create the same saga type twice for the same message + if (!IsAllowedToStartANewSaga(sagaType)) + continue; + sagaEntity = CreateNewSagaEntity(sagaType); sagaEntityIsPersistent = false; @@ -128,6 +131,25 @@ public IEnumerable GetDispatcher(Type messageHandlerType, IBuilder build }; } + bool IsAllowedToStartANewSaga(Type sagaInstanceType) + { + string sagaType; + + if (Bus.CurrentMessageContext.Headers.ContainsKey(Headers.SagaId) && + Bus.CurrentMessageContext.Headers.TryGetValue(Headers.SagaType, out sagaType)) + { + //we want to move away from the assembly fully qualified name since that will break if you move sagas + // between assemblies. We use the fullname instead which is enough to identify the saga + if (sagaType.StartsWith(sagaInstanceType.FullName)) + { + //so now we have a saga id for this saga and if we can't find it we shouldn't start a new one + return false; + } + } + + return true; + } + IContainSagaData CreateNewSagaEntity(Type sagaType) { var sagaEntityType = Features.Sagas.GetSagaEntityTypeForSagaType(sagaType);