Jørn Wildt Jørn Wildt - 26 days ago 14
ASP.NET (C#) Question

How to set Rebus transaction context for a web service

Given that I have a web/SOAP service, how do I setup and teardown a proper transaction context for Rebus (the messaging bus)? When Rebus is calling into a message handler this is not a problem since Rebus will setup the transaction context before calling the handler - but what about the opposite where a web service handler needs to send/publish a message via Rebus?

I am not interested in how to implement an HTTP module or similar - only the basics around Rebus: what is needed to prepare Rebus for sending a message?

The web service code has its own transaction going on when talking to the application database. I need to be able to setup Rebus when setting up the database transaction and comit/rollback Rebus when doing the same with the database.

I have a similar problem with standalone command line programs that needs to both interaction with a database and sending Rebus messages.

Answer

Rebus will automatically enlist send and publish operations in its own "ambient transaction context", which is accessed via the static(*) AmbientTransactionContext.Current property.

You could implement ITransactionContext yourself if you wanted to, but Rebus comes with DefaultTransactionContext in the box.

You use it like this:

using(var context = new DefaultTransactionContext())
{
    AmbientTransactionContext.Current = context;

    // send and publish things in here

    // complete the transaction
    await context.Complete();
}

which could easily be put e.g. in an OWIN middleware or something similar.


(*) The property is static, but the underlying value is bound to the current execution context (by using CallContext.LogicalGet/SetData), which means that you can think of it as thread-bound, with the nice property that it flows as expected to continuations.

In Rebus 2.0.2 it is possible to customize the accessors used to get/set the context by calling AmbientTransactionContext.SetAccessors(...) with an Action<ITransactionContext> and a Func<ITransactionContext>, e.g. like this:

AmbientTransactionContext.SetAccessors(
    context => {
        if (HttpContext.Current == null) {
            throw new InvalidOperationException("Can't set the transaction context when there is no HTTP context");
        }
        HttpContext.Current.Items["current-rbs-context"] = context
    },
    () => HttpContext.Current?.Items["current-rbs-context"] as ITransactionContext
);

which in this case makes it work in a way that flows properly even when using old school HTTP modules ;)

Comments