0
votes

Using NServiceBus (v6), is there a way to ensure that a property is set in the SagaData object before the Saga Handler for a message is fired?

Our environment is multi-tenant so I want to ensure that the correct CustomerId is used for db access etc.. and that developers don't forget to pull this value from the incoming message/message header.

For example given this saga data ...

public interface ICustomerSagaData : IContainSagaData
{
    Guid CustomerId { get; set; }
}

public class SomeProcessSagaData : ICustomerSagaData
{
    // IContainSagaData and other properties removed for brevity ...

    #region ICustomerSagaData properties

    public virtual Guid CustomerId { get; set; }

    #endregion
}

... and the following Saga ...

public class  SomeProcessSagaSaga :
    Saga<SomeProcessSagaData>,
    IAmStartedByMessages<StartProcess>
{
    public async Task Handle(StartProcess message, IMessageHandlerContext context)
    {
        // How do I ensure that Data.CustomerId is already set at this point?

    }

    // ConfigureHowToFindSaga etc ...
}

I initially tried inserting a behaviour into the pipeline e.g.

public class MyInvokeHandlerBehavior : Behavior<IInvokeHandlerContext>
{
    public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
    {
        // Ideally I'd like to set the CustomerId here before the 
        // Saga Handler is invoked but calls to ...     
        //      context.Extensions.TryGet(out activeSagaInstance);
        // return a null activeSagaInstance

        await next().ConfigureAwait(false);

        // This is the only point I can get the saga data object but
        //  as mentioned above the hander has already been invoked
        ActiveSagaInstance activeSagaInstance;
        if (context.Extensions.TryGet(out activeSagaInstance))
        {
            var instance = activeSagaInstance.Instance.Entity as ICustomerSagaData;
            if (instance != null)
            {
                Guid customerId;
                if (Guid.TryParse(context.Headers["CustomerId"), out customerId))
                {
                    instance.CustomerId = customerId; 
                }
            }
        }
    }
}

... but this only allows access to the SagaData instance after the handler has been fired.

2

2 Answers

1
votes

Late answer, but you need to make sure your behaviour executes after the SagaPersistenceBehavior.

In your IConfigureThisEndpoint implementation:

    public virtual void Customize(EndpointConfiguration configuration)
    {
        configuration.Pipeline.Register<Registration>();
    }

    public class Registration : RegisterStep
    {
        public Registration()
            : base(
                stepId: "AuditMutator",
                behavior: typeof(AuditMutator),
                description: "Sets up for auditing")
        {
            this.InsertAfterIfExists("InvokeSaga");
        }
    }
0
votes

So to answer your question directly Data.CustomerId is not going to be set when you handle StartProcess messages. You will need to set that with the id coming off of the message.

public async Task Handle(StartProcess message, IMessageHandlerContext context)
{
    Data.CustomerId = message.CustomerId;
}

That being said your sample above is missing a crucial piece which is the code for determining how a saga can be looked up for continuation of processing:

protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SomeProcessSagaData> mapper)
{
    mapper.ConfigureMapping<StartProcess>(message => message.CustomerId)
        .ToSaga(sagaData => sagaData.CustomerId);
}

Each time you send a message type that is handled by a saga you need to have the ConfigureHowToFindSaga() method configured so it can look up the previously started saga to continue processing with. So in essence you are going to start a new saga for every customerid you send with a StartProcess message. You can read more about it here: https://docs.particular.net/nservicebus/sagas/

So the real question now is do you really need to be using a saga at this point? The sample only seems to be handling one type of message so do you really need to be saving the state of CustomerId? The overhead of the saga isn't necessary in your sample and I believe a regular handler would be just fine based on the example above.