public async Task Invoke(IInvokeHandlerContext context, Func <IInvokeHandlerContext, Task> next) { var isTimeoutMessage = IsTimeoutMessage(context.Headers); var isTimeoutHandler = context.MessageHandler.IsTimeoutHandler; if (isTimeoutHandler && !isTimeoutMessage) { return; } if (!isTimeoutHandler && isTimeoutMessage) { return; } RemoveSagaHeadersIfProcessingAEvent(context); if (!(context.MessageHandler.Instance is Saga saga)) { await next(context).ConfigureAwait(false); return; } var currentSagaMetadata = sagaMetadataCollection.Find(context.MessageHandler.Instance.GetType()); if (context.Headers.TryGetValue(Headers.SagaType, out var targetSagaTypeString) && context.Headers.TryGetValue(Headers.SagaId, out var targetSagaId)) { var targetSagaType = Type.GetType(targetSagaTypeString, false); if (targetSagaType == null) { logger.Warn($"Saga headers indicated that the message was intended for {targetSagaTypeString} but that type isn't available. Will fallback to query persister for a saga instance of type {currentSagaMetadata.SagaType.FullName} and saga id {targetSagaId} instead"); } else { if (!sagaMetadataCollection.TryFind(targetSagaType, out var targetSagaMetaData)) { logger.Warn($"Saga headers indicated that the message was intended for {targetSagaType.FullName} but no metadata was found for that saga type. Will fallback to query persister for a saga instance of type {currentSagaMetadata.SagaType.FullName} and saga id {targetSagaId} instead"); } else { if (targetSagaMetaData.SagaType != currentSagaMetadata.SagaType) { //Message was intended for a different saga so no need to continue with this invocation return; } } } } var sagaInstanceState = new ActiveSagaInstance(saga, currentSagaMetadata, () => DateTimeOffset.UtcNow); //so that other behaviors can access the saga context.Extensions.Set(sagaInstanceState); var loadedEntity = await TryLoadSagaEntity(currentSagaMetadata, context).ConfigureAwait(false); if (loadedEntity == null) { if (IsMessageAllowedToStartTheSaga(context, currentSagaMetadata)) { context.Extensions.Get <SagaInvocationResult>().SagaFound(); sagaInstanceState.AttachNewEntity(CreateNewSagaEntity(currentSagaMetadata, context)); } else { if (!context.Headers.ContainsKey(Headers.SagaId)) { var finderDefinition = GetSagaFinder(currentSagaMetadata, context); if (finderDefinition == null) { throw new Exception($"Message type {context.MessageBeingHandled.GetType().Name} is handled by saga {currentSagaMetadata.SagaType.Name}, but the saga does not contain a property mapping or custom saga finder to map the message to saga data. Consider adding a mapping in the saga's {nameof(Saga.ConfigureHowToFindSaga)} method."); } } sagaInstanceState.MarkAsNotFound(); //we don't invoke not found handlers for timeouts if (isTimeoutMessage) { context.Extensions.Get <SagaInvocationResult>().SagaFound(); logger.InfoFormat("No saga found for timeout message {0}, ignoring since the saga has been marked as complete before the timeout fired", context.MessageId); } else { context.Extensions.Get <SagaInvocationResult>().SagaNotFound(); } } } else { context.Extensions.Get <SagaInvocationResult>().SagaFound(); sagaInstanceState.AttachExistingEntity(loadedEntity); } await next(context).ConfigureAwait(false); if (sagaInstanceState.NotFound) { return; } if (saga.Completed) { if (!sagaInstanceState.IsNew) { await sagaPersister.Complete(saga.Entity, context.SynchronizedStorageSession, context.Extensions, context.CancellationToken).ConfigureAwait(false); } logger.DebugFormat("Saga: '{0}' with Id: '{1}' has completed.", sagaInstanceState.Metadata.Name, saga.Entity.Id); sagaInstanceState.Completed(); } else { sagaInstanceState.ValidateChanges(); if (sagaInstanceState.IsNew) { var sagaCorrelationProperty = SagaCorrelationProperty.None; if (sagaInstanceState.TryGetCorrelationProperty(out var correlationProperty)) { sagaCorrelationProperty = new SagaCorrelationProperty(correlationProperty.PropertyInfo.Name, correlationProperty.PropertyInfo.GetValue(sagaInstanceState.Instance.Entity)); } await sagaPersister.Save(saga.Entity, sagaCorrelationProperty, context.SynchronizedStorageSession, context.Extensions, context.CancellationToken).ConfigureAwait(false); } else { await sagaPersister.Update(saga.Entity, context.SynchronizedStorageSession, context.Extensions, context.CancellationToken).ConfigureAwait(false); } sagaInstanceState.Updated(); } }
public async Task Invoke(IInvokeHandlerContext context, Func <IInvokeHandlerContext, Task> next) { var isTimeoutMessage = IsTimeoutMessage(context.Headers); var isTimeoutHandler = context.MessageHandler.IsTimeoutHandler; if (isTimeoutHandler && !isTimeoutMessage) { return; } if (!isTimeoutHandler && isTimeoutMessage) { return; } currentContext = context; RemoveSagaHeadersIfProcessingAEvent(context); var saga = context.MessageHandler.Instance as Saga; if (saga == null) { await next(context).ConfigureAwait(false); return; } var sagaMetadata = sagaMetadataCollection.Find(context.MessageHandler.Instance.GetType()); var sagaInstanceState = new ActiveSagaInstance(saga, sagaMetadata, () => DateTime.UtcNow); //so that other behaviors can access the saga context.Extensions.Set(sagaInstanceState); var loadedEntity = await TryLoadSagaEntity(sagaMetadata, context).ConfigureAwait(false); if (loadedEntity == null) { //if this message are not allowed to start the saga if (IsMessageAllowedToStartTheSaga(context, sagaMetadata)) { context.Extensions.Get <SagaInvocationResult>().SagaFound(); sagaInstanceState.AttachNewEntity(CreateNewSagaEntity(sagaMetadata, context)); } else { if (!context.Headers.ContainsKey(Headers.SagaId)) { var finderDefinition = GetSagaFinder(sagaMetadata, context); if (finderDefinition == null) { throw new Exception($"Message type {context.MessageBeingHandled.GetType().Name} is handled by saga {sagaMetadata.SagaType.Name}, but the saga does not contain a property mapping or custom saga finder to map the message to saga data. Consider adding a mapping in the saga's {nameof(Saga.ConfigureHowToFindSaga)} method."); } } sagaInstanceState.MarkAsNotFound(); //we don't invoke not found handlers for timeouts if (isTimeoutMessage) { context.Extensions.Get <SagaInvocationResult>().SagaFound(); logger.InfoFormat("No saga found for timeout message {0}, ignoring since the saga has been marked as complete before the timeout fired", context.MessageId); } else { context.Extensions.Get <SagaInvocationResult>().SagaNotFound(); } } } else { context.Extensions.Get <SagaInvocationResult>().SagaFound(); sagaInstanceState.AttachExistingEntity(loadedEntity); } await next(context).ConfigureAwait(false); if (sagaInstanceState.NotFound) { return; } if (saga.Completed) { if (!sagaInstanceState.IsNew) { await sagaPersister.Complete(saga.Entity, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } if (saga.Entity.Id != Guid.Empty) { await timeoutCancellation.CancelDeferredMessages(saga.Entity.Id.ToString(), context).ConfigureAwait(false); } logger.DebugFormat("Saga: '{0}' with Id: '{1}' has completed.", sagaInstanceState.Metadata.Name, saga.Entity.Id); sagaInstanceState.Completed(); } else { sagaInstanceState.ValidateChanges(); if (sagaInstanceState.IsNew) { ActiveSagaInstance.CorrelationPropertyInfo correlationProperty; var sagaCorrelationProperty = SagaCorrelationProperty.None; if (sagaInstanceState.TryGetCorrelationProperty(out correlationProperty)) { sagaCorrelationProperty = new SagaCorrelationProperty(correlationProperty.PropertyInfo.Name, correlationProperty.Value); } await sagaPersister.Save(saga.Entity, sagaCorrelationProperty, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } else { await sagaPersister.Update(saga.Entity, context.SynchronizedStorageSession, context.Extensions).ConfigureAwait(false); } sagaInstanceState.Updated(); } }