Search code examples
servicebusrebus

Rebus Send in transactionscope


It was possible in previous (<=0.84.0) versions of Rebus to Send message in TransactionScope and it was sent only if scope is completed

using (var scope = new TransactionScope())
{
    var ctx = new AmbientTransactionContext();
    sender.Send(recipient.InputQueue, msg, ctx);

    scope.Complete();
}

Is it possible to achive the same behaviour in Rebus2


Solution

  • As you have correctly discovered, Rebus version >= 0.90.0 does not automatically enlist in ambient transactions.

    (UPDATE: as of 0.99.16, the desired behavior can be had - see the end of this answer for details on how)

    However this does not mean that Rebus cannot enlist in a transaction - it just uses its own ambient transaction mechanism (which does not depend on System.Transactions and will be available when Rebus is ported to .NET core).

    You can use Rebus' DefaultTransactionContext and "make it ambient" with this AmbientRebusTransactionContext:

    /// <summary>
    /// Rebus ambient transaction scope helper
    /// </summary>
    public class AmbientRebusTransactionContext : IDisposable
    {
        readonly DefaultTransactionContext _transactionContext = new DefaultTransactionContext();
    
        public AmbientRebusTransactionContext()
        {
            if (AmbientTransactionContext.Current != null)
            {
                throw new InvalidOperationException("Cannot start a Rebus transaction because one was already active!");
            }
    
            AmbientTransactionContext.Current = _transactionContext;
        }
    
        public Task Complete()
        {
            return _transactionContext.Complete();
        }
    
        public void Dispose()
        {
            AmbientTransactionContext.Current = null;
        }
    }
    

    which you can then use like this:

    using(var tx = new AmbientRebusTransactionContext())
    {
        await bus.Send(new Message());
    
        await tx.Complete();
    }
    

    or, if you're using it in a web application, I suggest you wrap it in an OWIN middleware like this:

    app.Use(async (context, next) =>
    {
        using (var transactionContext = new AmbientRebusTransactionContext())
        {
            await next();
    
            await transactionContext.Complete();
        }
    });
    

    UPDATE: Since Rebus 0.99.16, the following has been supported (via the Rebus.TransactionScope package):

    using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
    {
        scope.EnlistRebus(); //< enlist Rebus in ambient .NET tx
    
        await _bus.SendLocal("hallå i stuen!1");
    
        scope.Complete();
    }