Question

First let's define 'UserContext' as being a number of properties required to execute the receiving message in the correct context of the user, so it is a bit more than just a string. In my case this also includes data on which application 'instance' the user was working.

As I see it there are 2 main options to provide a 'UserContext' for a message:

  1. As a Header
  2. As a base class for the message

When using a Header, I need to provide my own serialization, when using a base class, Rebus will solve the serialization for me.

So I spiked using a base class using a little sample program:

public class UserContext
{
    public string Name { get; set; }

    public int UserId { get; set; }

    public Guid AppId { get; set; }
}

public class UserContextMessageBase
{
    public UserContext UserContext { get; set; }
}

public class SimpleMessage : UserContextMessageBase
{
    public string Data { get; set; }
}


internal class Program
{
    private static void Main(string[] args)
    {
        using (var adapter = new BuiltinContainerAdapter())
        using (var timer = new Timer())
        {
            //adapter.Register(typeof(UserContextHandler));
            adapter.Register(typeof(SimpleMessageHandler));

            var bus = Configure.With(adapter)
                               .Transport(t => t.UseMsmqAndGetInputQueueNameFromAppConfig())
                               .MessageOwnership(d => d.FromRebusConfigurationSection())

                               //.SpecifyOrderOfHandlers(o => o.First<UserContextHandler>())

                               .CreateBus()
                               .Start();

            timer.Elapsed += delegate
            {
                bus.Send(new Messages.SimpleMessage { Data = Guid.NewGuid().ToString() });
            };
            timer.Interval = 10000;
            timer.Start();

            Console.WriteLine("Press enter to quit");
            Console.ReadLine();
        }
    }
}

internal class UserContextHandler : IHandleMessages<UserContextMessageBase>
{
    protected UserContext _context;

    public void Handle(UserContextMessageBase message)
    {
        var old = Console.ForegroundColor;
        if (_context != null)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Context is already populated");
        }

        Console.ForegroundColor = ConsoleColor.DarkYellow;
        Console.WriteLine("Processing UserContextMessageBase");
        // create the correct Context to process the message
        _context = message.UserContext;
        Console.ForegroundColor = old;
    }
}

internal class SimpleMessageHandler : **UserContextHandler**, IHandleMessages<SimpleMessage>
{
    public void Handle(SimpleMessage message)
    {
        // allow to use the _context to process this message
        Console.WriteLine("Received SimpleMessage {0}", message.Data);
    }
}

But when I run the program, I see that the SimpleMessage is getting processed twice. Is this 'by design' or perhaps a bug?

On the other hand, I can uncomment the registration for the UserContextHandler, and not inherit the SimpleMessageHandler from the UserContextHandler, but then I would have to stuff the UserContext into the MessageContext, and use it as such from the SimpleMessageHandler.

Was it helpful?

Solution

In my opinion, both approaches are valid - personally, I'd lean towards using headers because they're less noisy, and because that's really what they're there for :) but, as you correctly state, that requires that you somehow take care of "serializing" the user context into one or more headers, deserializing it again upon receiving each message.

The header approach could be done pretty elegantly, though, in the MessageSent and MessageContextEstablished events for sending and receiving respectively, staying out of your message handlers, and then the user context could be made available in the message context.

The other approach with using a message base class is definitely valid too, and I can see that you're hit by the fact that the lookup for the incoming message will get a new handler instance for each lookup - therefore, the pipeline will contain two handler instances, and the message will then be dispatched "as much as possible" (i.e. once for each compatible type/supertype) to each handler instance, thus resulting in effectively handling the message twice.

In your case, I suggest you do as you hint at towards the end: Make the UserContextHandler a separate handler that you ensure gets to be first in the pipeline, thus allowing it to stash the user context in MessageContext.GetCurrent().Items for all subsequent handlers to extract.

I'd love to cook an example, though, showing a way to do exactly what you need, but by using headers (possibly in the form of simply a ;-separated list of key-value pairs, or something similar), but I'm afraid I cannot promise that such an example would be available within the next few days.

Let me know if it works out for you :)

Update: I've added a sample to Rebus' sample repo that demonstrates how an ambient user context can be picked up and passed around in a message header, including a few nifties around configuration and DI - it's called UserContextHeaders - check it out :)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top