如何使用C#和RabbitMQ通过一个队列使用多种消息类型

如何使用C#和RabbitMQ通过一个队列使用多种消息类型,c#,json,rabbitmq,C#,Json,Rabbitmq,目前,我尝试使用RabbitMQ和C#实现一个MessagingClient 我的想法是,每个服务都使用自己的队列来接收消息,并且每个消息类型都有自己的交换。例如,消息类型可以是“下载/请求”或“下载/启动”。这些交换属于“扇出”类型。现在,希望侦听某些消息的客户机将其队列绑定到相应的交换。例如,下载服务将其队列“下载机器人”绑定到exchange“下载/请求” 我现在的问题是,我真的无法想象,如何在解析站点上处理不同类型的消息。我使用Newtonsoft.Json对消息对象进行编码/解码。对于

目前,我尝试使用RabbitMQ和C#实现一个MessagingClient

我的想法是,每个服务都使用自己的队列来接收消息,并且每个消息类型都有自己的交换。例如,消息类型可以是“下载/请求”或“下载/启动”。这些交换属于“扇出”类型。现在,希望侦听某些消息的客户机将其队列绑定到相应的交换。例如,下载服务将其队列“下载机器人”绑定到exchange“下载/请求”

我现在的问题是,我真的无法想象,如何在解析站点上处理不同类型的消息。我使用Newtonsoft.Json对消息对象进行编码/解码。对于每种不同的消息类型,我都要执行单独的“处理程序”。此处理程序应将消息(反序列化)作为参数获取。我的问题是,对于一个队列上的多个消息类型,似乎只有一个处理程序qer队列。如果我在编译时不知道消息的类型,我如何找出要执行的具体处理程序以及如何解析消息?我知道一个重要的关键词将是“反射”,但我无法修补一起工作的东西。我应该说我对C#还很陌生。有人能提供一个这样的工作例子吗?或者这是我应该做的事

作为一个工作区,目前我得到了以下(简化的)示例(每个消息类型和服务使用一个队列)

namespace Lib.Message{
公共类RabbitMqMessageClient:IMessageClient{
私有静态NLog.ILogger logger=Common.logger.GetLogger();
私有RabbitMqConfig配置;
私有IModel模型;
private const string DOWNLOAD_REQUESTED=“DOWNLOAD/REQUESTED”;
公共RabbitMqMessageClient(RabbitMqConfig配置){
this.config=config;
var factory=newconnectionfactory(){HostName=config.HostName};
var connection=factory.CreateConnection();
this.model=connection.CreateModel();
}
请求的公共任务PublishDownloadRequested(字符串userRef、字符串id、字符串url){
var message=新的下载请求消息(userRef、id、url);
返回this.publish(请求下载,消息);
}
已请求公共任务SubscribeToDownloadRequested(操作操作){
返回this.subscribe(请求下载,操作);
}
请求的公共任务SubscribeToDownloadRequested(Func操作){
返回this.subscribe(请求下载,操作);
}
公共任务开始(){
返回Task.CompletedTask;
}
私有任务订阅(字符串主题、操作){
Func wrappedAction=(TPayloadType参数)=>{
行动(args);
返回Task.CompletedTask;
};
返回this.subscribe(主题、包装);
}
专用任务订阅(字符串主题、函数操作){
var queue=$“{config.QueueName}--{topic}”;
model.ExchangeClare(主题,ExchangeType.Fanout,true);
model.QueueDeclare(
队列
是的,
独家:假,,
自动删除:false,
参数:null);
model.QueueBind(config.QueueName,主题“foo”);
var消费者=新事件基本消费者(模型);
//此使用者将共享多个队列,对吗?
//也许在这个方法中没有,但是在“开始”方法中
consumer.Received+=异步(模型,ea)=>{
Info($“handling{ea.Exchange}”);
var jsonString=Encoding.UTF8.GetString(ea.Body.Span.ToArray());
//这里我需要知道如何从“ea.Exchange”的值反序列化有效负载
var message=JsonConvert.DeserializeObject(jsonString);
//我想我必须将操作放在一个映射(?)中,然后通过交换名称找到具体的处理程序?
等待行动(信息);
};
model.BasicConsume(config.QueueName,true,consumer);
返回Task.CompletedTask;
}
私有任务发布(字符串主题、对象负载){
logger.Info($“publishing{topic}”);
string message=JsonConvert.SerializeObject(有效负载);
var bytes=Encoding.UTF8.GetBytes(消息);
model.BasicPublish(
交流:话题,,
路由键:“foo”,
基本属性:null,
正文:字节
);
返回Task.CompletedTask;
}
}

}

如果您不能/不想使用多个队列,一种常见的方法是在所有消息中使用EventName(或类似)属性,json解析它,然后json反序列化为已知类型(您需要一个字典/开关来映射字符串->类|处理程序)。首先,感谢您的快速回复。我已经尝试使用类型为“IDictionAry”的私有成员“handlers”:您至少有两种方法:强制转换方法(接口和接受
object
并强制转换的方法)和切换方法(
switch x{case y:await y.Run(…);…
)方法接受具体类型。你正在重新发明轮子,真的没有必要。人们已经解决了这些问题,我们有很好的框架,如MassTransit或Rebus.FM。两者都很好,我多年来都在使用这两种方法,我个人推荐这两种方法。这两种方法都有很好的文档记录,你应该能够快速开始。
namespace Lib.Message {
public class RabbitMqMessageClient : IMessageClient {

    private static NLog.ILogger logger = Common.Logger.GetLogger ();
    private RabbitMqConfig config;
    private IModel model;

    private const string DOWNLOAD_REQUESTED = "download/requested";

    public RabbitMqMessageClient (RabbitMqConfig config) {
        this.config = config;
        var factory = new ConnectionFactory () { HostName = config.Hostname };
        var connection = factory.CreateConnection ();
        this.model = connection.CreateModel ();
    }

    public Task PublishDownloadRequested (string userRef, string id, string url) {
        var message = new DownloadRequestMessage (userRef, id, url);
        return this.publish (DOWNLOAD_REQUESTED, message);
    }
    public Task SubscribeToDownloadRequested (Action<DownloadRequestMessage> action) {
        return this.subscribe<DownloadRequestMessage> (DOWNLOAD_REQUESTED, action);
    }
    public Task SubscribeToDownloadRequested (Func<DownloadRequestMessage, Task> action) {
        return this.subscribe<DownloadRequestMessage> (DOWNLOAD_REQUESTED, action);
    }

    public Task Start () {
        return Task.CompletedTask;
    }

    private Task subscribe<TPayloadType> (string topic, Action<TPayloadType> action) {
        Func<TPayloadType, Task> wrappedAction = (TPayloadType args) => {
            action (args);
            return Task.CompletedTask;
        };

        return this.subscribe<TPayloadType> (topic, wrappedAction);
    }

    private Task subscribe<TPayloadType> (string topic, Func<TPayloadType, Task> action) {
        var queue = $"{config.QueueName}--{topic}";

        model.ExchangeDeclare (topic, ExchangeType.Fanout, true);

        model.QueueDeclare (
            queue,
            durable : true,
            exclusive : false,
            autoDelete : false,
            arguments : null);

        model.QueueBind (config.QueueName, topic, "foo");

        var consumer = new EventingBasicConsumer (model);
        // this consumer will be shared for multiple queues right ?
        // maybe even not here in this method, but in the "Start" method
        consumer.Received += async (model, ea) => {
            logger.Info ($"handling {ea.Exchange}");

            var jsonString = Encoding.UTF8.GetString (ea.Body.Span.ToArray ());
            // here I need to know how to deserialize the payload from the value of "ea.Exchange"
            var message = JsonConvert.DeserializeObject<TPayloadType>(jsonString);
            // I think i have to put the action in a map (?) instead and then find the concrete handler by the exchange name? 
            await action(message);
        };

        model.BasicConsume (config.QueueName, true, consumer);


        return Task.CompletedTask;
    }

    private Task publish (string topic, object payload) {
        logger.Info ($"publishing {topic}");

        string message = JsonConvert.SerializeObject (payload);
        var bytes = Encoding.UTF8.GetBytes (message);

        model.BasicPublish (
            exchange: topic,
            routingKey: "foo",
            basicProperties : null,
            body : bytes
        );

        return Task.CompletedTask;
    }
}