3

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

1 Answers1

2

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();
}
mookid8000
  • 18,258
  • 2
  • 39
  • 63
  • 1
    That approach is OK if i want to enlist in Rebus TransactionContext. But what about enlisting in .Net System.Transactions? It seems to me, its quiet common situation when you have bussiness logic executed in .net Transactionscope and whant to execute **bus.Send** as a part of this scope – Evgeniy Korniets Nov 29 '15 at 10:29
  • 1
    I went ahead and implemented what you requested - now you can call `scope.EnlistRebus();` after you create a transaction scope to enlist an ambient Rebus transaction in the scope - please remember the `TransactionScopeAsyncFlowOption.Enabled` option though! – mookid8000 Nov 30 '15 at 12:37
  • 1
    While waiting for your reply I've implemented my own wrapper that worked like old fashion `AmbientTransactionContext : IEnlistmentNotification, ITransactionContext` and new `AmbientTransactionBridge`. But out of the box support is *PERFECT*. Probably It's a time to update [github Wiki](https://github.com/rebus-org/Rebus/wiki/Transactions) :) – Evgeniy Korniets Nov 30 '15 at 14:11