4

首先让我们将'UserContext'定义为在用户的正确上下文中执行接收消息所需的许多属性,因此它不仅仅是一个string. 在我的情况下,这还包括用户正在使用哪个应用程序“实例”的数据。

正如我所看到的,有两个主要选项可以为消息提供“UserContext”:

  1. 作为标题
  2. 作为消息的基类

当使用 Header 时,我需要提供自己的序列化,当使用基类时,Rebus 会为我解决序列化。

所以我使用一个小示例程序使用了一个基类:

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);
    }
}

但是当我运行程序时,我看到SimpleMessage正在处理两次。这是“设计使然”还是错误?

另一方面,我可以取消注释 的注册UserContextHandler,而不是SimpleMessageHandler从继承UserContextHandler,但是我必须将UserContext塞进MessageContext,并从SimpleMessageHandler.

4

1 回答 1

1

在我看来,这两种方法都是有效的 - 就个人而言,我倾向于使用标题,因为它们噪音较小,而且因为这确实是它们的用途:) 但是,正如你正确陈述的那样,这需要你以某种方式采取关心将用户上下文“序列化”为一个或多个标头,并在收到每条消息时再次反序列化。

不过,标头方法可以非常优雅地完成,分别在用于发送和接收的MessageSent事件MessageContextEstablished中,远离消息处理程序,然后可以在消息上下文中提供用户上下文。

使用消息基类的另一种方法也绝对有效,我可以看到您对传入消息的查找将为每次查找获取一个新的处理程序实例这一事实感到震惊 - 因此,管道将包含两个处理程序实例,然后消息将“尽可能多地”分派(即每个兼容类型/超类型一次)到每个处理程序实例,从而有效地处理消息两次。

在您的情况下,我建议您按照最后提示的方式进行操作:UserContextHandler创建一个单独的处理程序,确保您在管道中排在第一位,从而允许它隐藏用户上下文以MessageContext.GetCurrent().Items供所有后续处理程序提取。

不过,我很想举一个例子,展示一种方法来完全满足您的需求,但是通过使用标题(可能以简单的;- 分隔的键值对列表或类似的形式),但我恐怕我不能保证这样的例子会在接下来的几天内出现。

让我知道它是否适合你:)

更新:我已经在 Rebus 的示例 repo 中添加了一个示例,该示例演示了如何在消息头中获取和传递环境用户上下文,包括一些关于配置和 DI 的小技巧——它被称为UserContextHeaders——检查一下 :)

于 2014-03-18T13:56:39.403 回答