0

What I am trying to achieve is my website raises a message and puts it on the bus, a service picks it up and writes to the database with auditing that automatically populates the AddedBy/UpdatedBy field of the row.

I do this by using the NServiceBus IMessageMutator component which writes the user ID to the message headers from Thread.CurrentPrincipal which comes from the logged in user in my ASP.Net application. In my service I use a IMessageModule to extract this header and bind this to the Thread.CurrentPrincipal. This works great and during my message handler I can see Thread.CurrentPrincipal.Identity.Name is correctly bound to the user ID that raised the message in the web application.

Utilising the IPreUpdateEventListener/IPreInsertEventListener of NHibernate I am setting the AddedBy/UpdatedBy of each entity before it is written to the DB. This works on the website perfectly but in my NServiceBus service the thread that the listener runs on is different to the thread that handler ran on, which means the CurrentPrincipal of the thread is no longer the ID bound in my IMessageModule.

I can see NHibernate is using a DistributedTransactionFactory in the call stack which I suspect is the cause of my issue. I don't want to lose transactionality such that if the commit failed the message is not re-tried or put on the error queue and if the removal of the message from the queue fails and the update does not roll back to the DB.

I have looked around the web and all the examples utilise the thread's CurrentPrincipal to bind the id of the user that modified the rows. What I am looking for is a way to either keep the NHibernate listener on the same thread as the message handler or pass the user ID to the listener so it can be bound on to the entity before it is written to the DB.

Here is my listener, I have omitted the Set method found in it

public class EntityPersistenceListener : IPreUpdateEventListener, IPreInsertEventListener
{
    public bool OnPreUpdate(PreUpdateEvent @event)
    {
        var audit = @event.Entity as EntityBase;
        if (audit == null)
            return false;

        var time = DateTimeFactory.GetDateTime();

        var name = Thread.CurrentPrincipal.Identity.Name;

        Set(@event.Persister, @event.State, "AddedDate", audit.AddedDate);
        Set(@event.Persister, @event.State, "AddedBy", audit.AddedBy);
        Set(@event.Persister, @event.State, "UpdatedDate", time);
        Set(@event.Persister, @event.State, "UpdatedBy", name);

        audit.AddedDate = audit.AddedDate;
        audit.AddedBy = audit.AddedBy;
        audit.UpdatedDate= time;
        audit.UpdatedBy = name;

        return false;            
    }
}

And here is the NServiceBus Message module that extracts the id and binds it to the current thread's identity

public class TenantAndInstanceInfoExtractor : IMessageModule
{
    private readonly IBus _bus;

    public TenantAndInstanceInfoExtractor(IBus bus)
    {
        _bus = bus;
    }

    public void HandleBeginMessage()
    {
        var headers = _bus.CurrentMessageContext.Headers;

        if (headers.ContainsKey("TriggeredById"))
            Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(headers["TriggeredById"]), null);
        else
            Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(string.Empty), null);
    }

    public void HandleEndMessage()
    {

    }

    public void HandleError() { }
}
Morgan
  • 76
  • 8
  • 1
    Morgan. I am having trouble fully understanding your requirement. Do you have time for a skype call next week? i am "simon.cropp" on skype – Simon Jul 08 '16 at 02:08
  • Hi Simon, yes please. I will email you to arrange a time. – Morgan Jul 11 '16 at 07:13

1 Answers1

0

Thank you Simon for all your help. After extensively going through my problem and discussing how NServiceBus works internally I took your insight and implemented a unit of work module for NServiceBus.

What was happening is we were relying on the transaction created per message to commit our NHibernate session to the DB. This takes place on the distributed transaction controller (specifically it happens here NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext) which leverages a thread pool.

By using NServiceBus's IManageUnitsOfWork interface I was able to explicitly commit our transaction on the same thread as the message handler as below in the code example.

As a side note for any future readers, the best solution here is to not use Thread.CurrentPrincipal as this solution fails in multi-threaded environments as it has for me.

public class HiJumpNserviceBusUnitOfWork : IManageUnitsOfWork
{
    private readonly IUnitOfWork _uow;

    public HiJumpNserviceBusUnitOfWork(IUnitOfWork uow)
    {
        _uow = uow;
    }

    public void Begin()
    {
        _uow.ClearCache();
        _uow.BeginTransaction();
    }

    public void End(Exception ex = null)
    {
        if (ex != null)
        {
            _uow.ClearCache();
        }
        else
        {
            _uow.CommitTransaction();
        }
    }
}
Morgan
  • 76
  • 8