C# net core巧妙简化Lambda表达式-更新
C# net core simplifying Lambda expression cleverly - updated
我正在寻找一个聪明的解决方案来简化越来越长的 Lambda 表达式。它看起来像这样:
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
cfg.ReceiveEndpoint(host, "some_endp_1", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<Class1>(e.InputAddress);
});
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<Class2>(e.InputAddress);
});
// ... a lot more of these here ....
cfg.ReceiveEndpoint(host, "some_endp_n", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<ClassN>(e.InputAddress);
});
}));
这是一个简单网络 api 项目的一部分,但这里的问题不是服务的设置方式,也不是 API 端点的工作方式。只是这段代码变得冗长和冗余,我希望我能避免。
也许这很简单,我如何概括它并只传递一个具有相关 class 名称的端点字符串列表而不需要这么大的代码?
谢谢 - 我尝试对此进行研究,但没有发现任何对这种特殊情况有用的东西。
更新 1
根据大家的宝贵意见,对代码进行了多处改写。我有以下帮手:
void ConfigureEndPoint<T>(IRabbitMqHost host, ref IRabbitMqBusFactoryConfigurator cfg, IServiceProvider provider, string endPointName)
where T:class
{
cfg.ReceiveEndpoint(host, endPointName, e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<T>(e.InputAddress);
});
}
然后,Startup.cs
有一些烦人的部分,这些部分也很多余,但一定很容易重写,例如:
services.AddScoped<MyConsumer1>();
services.AddScoped<MyConsumer2>();
// a lot of them here
services.AddScoped<MyConsumerN>();
为什么不简单?每个consumer继承MassTransit.IConsumer<T>
,但这里的T要么是command,要么是query,commands从中继承一种interface,查询完全不同类型的interface。因此,我真的不确定如何使它通用?也许将其拆分为 2 部分,1 部分用于命令,1 部分用于查询?
我也不知道如何将 e.LoadFrom
正确更改为 e.Consumer<SomeConsumer>(provider);
,因为我设置服务的方式默认情况下与该调用不兼容。
消费者在添加 .AddScoped
后也被添加到 MassTransit:
services.AddMassTransit(x =>
{
x.AddConsumer<MyConsumer1>();
// the rest of the consumers all here, all added the same way
});
然后,同样,从最初的问题来看,CreateUsingRabbitMq
稍微好一点,但仍然有 N 行:
ConfigureEndPoint<SomeCommandOrQuery>(host, ref cfg, provider, "endpoint_name_here");
我相信有一个很好的解决方法可以使代码更短、更易读,但是你有什么想法,我该怎么做?我怎样才能将 e.LoadFrom
替换为 e.Consume<SomeConsumer>()
?
谢谢,非常感谢社区和您的想法!
更新 2
我不确定我错过了什么 - 我的消费者都实现了 IConsumer<T>
,它来自 MassTransit 命名空间。
免责声明:我对 MassTransit 一无所知——我以前从未使用过它,事实上,在阅读问题之前我什至从未听说过它。
您可能应该查看 ,因为他建议使用不同的方式来处理 MassTransit 配置。 (没有使用过这个工具的经验,我什至不知道它是否更好,但这可能是你可以自己检查的东西。)
我的回答纯粹是从C#的角度来看的。
首先想到的是创建一个方法来配置 ReceiveEndpoint
- 像这样(我不知道涉及的类型,您可能需要更改它们):
void ConfigureEndPoint<T>(Host host, Config cfg, Provider provider, string endPointName)
{
cfg.ReceiveEndpoint(host, endPointName, e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<T>(e.InputAddress);
}
}
然后,在您的 lambda 表达式中,您只需像这样使用它:
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
ConfigureEndPoint<Class1>(host, cfg, provider, "some_endp_1");
ConfigureEndPoint<Class2>(host, cfg, provider, "some_endp_2");
// ... a lot more of these here ...
ConfigureEndPoint<Classn>(host, cfg, provider, "some_endp_n");
}));
假设: 我的回答假设 .Map()
有一个重载需要 Type
而不是 Generic
。 (例如 .Map(typeof(Class1), e.InputAddress)
),因为这在此类通用函数中很常见。
如果不是这种情况,请忽略此答案。我觉得它对发现这个问题的处于类似情况的其他人仍然有用。
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
var mappingTypes = new Type[] { typeof(Class1), typeof(Class2), ... etc };
foreach (var mappingType in mappingTypes)
{
// I did mappingType.Name for simplicity, but you could get the mappingType's index in the collection and use that as well if you need this to be a number.
cfg.ReceiveEndpoint(host, "some_endp_" + mappingType.Name, e =>
{
e.LoadFrom(provider);
// See note at the top of answer.
EndpointConvention.Map(mappingType, e.InputAddress);
});
}
}));
看来您误解了端点、消费者和约定的概念。
我非常担心您对每个端点都使用 LoadFrom
。这意味着 所有 您在容器中注册的消费者将在每个端点中监听。
通常,如果您想按端点拆分消费者,则需要通过调用 ep.Consumer<MyConsumer>(provider)
显式配置端点。
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.Consumer<SomeConsumer>(provider);
});
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.Consumer<SomeOtherConsumer>(provider);
});
当您使用 LoadFrom
时,您的每个端点都将有来自容器的 所有 消费者。如果您对每个端点使用 LoadFrom
,则每个端点都将订阅您的所有消息,并且您收到每条消息的次数与此类端点的数量一样多。绝对不是你想要的。
你可能误解了EndpointConventions
的意思。约定仅用于发送消息,不用于接收消息。端点将接收它可以使用的所有消息。
解决方案 1
如果您不希望有很多流量,您可以将所有消费者放在一个端点中,然后您可以使用 LoadFrom
.
cfg.ReceiveEndpoint(host, "my_service", e =>
{
e.LoadFrom(provider);
});
方案二:
如果您想将消费者与每个端点一个消费者分开,并使用类似于单行端点配置的东西,可以使用以下代码轻松完成:
using System;
using MassTransit.RabbitMqTransport;
namespace MassTransit.TestCode
{
public static class BusConfigurationExtensions
{
public static void ConfigureEndpoint<T>(this IRabbitMqBusFactoryConfigurator cfg,
IRabbitMqHost host, string endpointName, IServiceProvider provider)
where T : class, IConsumer
=> cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<T>(provider));
}
}
然后您可以像这样使用它:
cfg.ConfigureEndpoint<SubmitOrderConsumer>(host, "submit_order", provider);
cfg.ConfigureEndpoint<MarkOrderAsPaidConsumer>(host, "mark_paid", provider);
cfg.ConfigureEndpoint<ShipOrderConsumer>(host, "ship_order", provider);
使用此扩展程序时,您不得使用MassTransit.Extensions.DependencyInjection
的AddMassTransit
方法,但您需要注册所有消费者dependencies 在服务集合中。
但是,如果您仍然可以在总线配置代码中使用这个单行代码,我真的不明白有这样一个扩展的意义。
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<SubmitOrderConsumer>(provider));
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<MarkOrderAsPaidConsumer>(provider));
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<ShipOrderConsumer>(provider));
事实上,在我的第一个片段中我使用了方法组并不意味着你不能只使用表达式 lambda。
在下一个版本中将有一个新包 MassTransit.AspNetCore
将 MassTransit 与 ASP.NET Core 更好地集成。它将配置总线托管、正确注册总线实例并应用日志记录。配置将如下所示(此代码有效,我刚刚编写并测试了它):
public void ConfigureServices(IServiceCollection services)
{
services.AddMassTransit(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ReceiveEndpoint(host, "message_one", ep => ep.Consumer<MessageOneConsumer>(provider));
cfg.ReceiveEndpoint(host, "message_two", ep => ep.Consumer<MessageTwoConsumer>(provider));
}));
}
请记住,每个端点都有自己的队列。如果您刚刚开始工作并想尝试一下,您可以选择第一个解决方案,这样您就会有一个队列。及时,您可以拆分端点。
我正在寻找一个聪明的解决方案来简化越来越长的 Lambda 表达式。它看起来像这样:
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
cfg.ReceiveEndpoint(host, "some_endp_1", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<Class1>(e.InputAddress);
});
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<Class2>(e.InputAddress);
});
// ... a lot more of these here ....
cfg.ReceiveEndpoint(host, "some_endp_n", e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<ClassN>(e.InputAddress);
});
}));
这是一个简单网络 api 项目的一部分,但这里的问题不是服务的设置方式,也不是 API 端点的工作方式。只是这段代码变得冗长和冗余,我希望我能避免。
也许这很简单,我如何概括它并只传递一个具有相关 class 名称的端点字符串列表而不需要这么大的代码?
谢谢 - 我尝试对此进行研究,但没有发现任何对这种特殊情况有用的东西。
更新 1
根据大家的宝贵意见,对代码进行了多处改写。我有以下帮手:
void ConfigureEndPoint<T>(IRabbitMqHost host, ref IRabbitMqBusFactoryConfigurator cfg, IServiceProvider provider, string endPointName)
where T:class
{
cfg.ReceiveEndpoint(host, endPointName, e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<T>(e.InputAddress);
});
}
然后,Startup.cs
有一些烦人的部分,这些部分也很多余,但一定很容易重写,例如:
services.AddScoped<MyConsumer1>();
services.AddScoped<MyConsumer2>();
// a lot of them here
services.AddScoped<MyConsumerN>();
为什么不简单?每个consumer继承MassTransit.IConsumer<T>
,但这里的T要么是command,要么是query,commands从中继承一种interface,查询完全不同类型的interface。因此,我真的不确定如何使它通用?也许将其拆分为 2 部分,1 部分用于命令,1 部分用于查询?
我也不知道如何将 e.LoadFrom
正确更改为 e.Consumer<SomeConsumer>(provider);
,因为我设置服务的方式默认情况下与该调用不兼容。
消费者在添加 .AddScoped
后也被添加到 MassTransit:
services.AddMassTransit(x =>
{
x.AddConsumer<MyConsumer1>();
// the rest of the consumers all here, all added the same way
});
然后,同样,从最初的问题来看,CreateUsingRabbitMq
稍微好一点,但仍然有 N 行:
ConfigureEndPoint<SomeCommandOrQuery>(host, ref cfg, provider, "endpoint_name_here");
我相信有一个很好的解决方法可以使代码更短、更易读,但是你有什么想法,我该怎么做?我怎样才能将 e.LoadFrom
替换为 e.Consume<SomeConsumer>()
?
谢谢,非常感谢社区和您的想法!
更新 2
我不确定我错过了什么 - 我的消费者都实现了 IConsumer<T>
,它来自 MassTransit 命名空间。
免责声明:我对 MassTransit 一无所知——我以前从未使用过它,事实上,在阅读问题之前我什至从未听说过它。
您可能应该查看
我的回答纯粹是从C#的角度来看的。
首先想到的是创建一个方法来配置 ReceiveEndpoint
- 像这样(我不知道涉及的类型,您可能需要更改它们):
void ConfigureEndPoint<T>(Host host, Config cfg, Provider provider, string endPointName)
{
cfg.ReceiveEndpoint(host, endPointName, e =>
{
e.LoadFrom(provider);
EndpointConvention.Map<T>(e.InputAddress);
}
}
然后,在您的 lambda 表达式中,您只需像这样使用它:
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
ConfigureEndPoint<Class1>(host, cfg, provider, "some_endp_1");
ConfigureEndPoint<Class2>(host, cfg, provider, "some_endp_2");
// ... a lot more of these here ...
ConfigureEndPoint<Classn>(host, cfg, provider, "some_endp_n");
}));
假设: 我的回答假设 .Map()
有一个重载需要 Type
而不是 Generic
。 (例如 .Map(typeof(Class1), e.InputAddress)
),因为这在此类通用函数中很常见。
如果不是这种情况,请忽略此答案。我觉得它对发现这个问题的处于类似情况的其他人仍然有用。
services.AddSingleton(provider => MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("xyz", "/", hst =>
{
hst.Username("user");
hst.Password("pass");
});
var mappingTypes = new Type[] { typeof(Class1), typeof(Class2), ... etc };
foreach (var mappingType in mappingTypes)
{
// I did mappingType.Name for simplicity, but you could get the mappingType's index in the collection and use that as well if you need this to be a number.
cfg.ReceiveEndpoint(host, "some_endp_" + mappingType.Name, e =>
{
e.LoadFrom(provider);
// See note at the top of answer.
EndpointConvention.Map(mappingType, e.InputAddress);
});
}
}));
看来您误解了端点、消费者和约定的概念。
我非常担心您对每个端点都使用 LoadFrom
。这意味着 所有 您在容器中注册的消费者将在每个端点中监听。
通常,如果您想按端点拆分消费者,则需要通过调用 ep.Consumer<MyConsumer>(provider)
显式配置端点。
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.Consumer<SomeConsumer>(provider);
});
cfg.ReceiveEndpoint(host, "some_endp_2", e =>
{
e.Consumer<SomeOtherConsumer>(provider);
});
当您使用 LoadFrom
时,您的每个端点都将有来自容器的 所有 消费者。如果您对每个端点使用 LoadFrom
,则每个端点都将订阅您的所有消息,并且您收到每条消息的次数与此类端点的数量一样多。绝对不是你想要的。
你可能误解了EndpointConventions
的意思。约定仅用于发送消息,不用于接收消息。端点将接收它可以使用的所有消息。
解决方案 1
如果您不希望有很多流量,您可以将所有消费者放在一个端点中,然后您可以使用 LoadFrom
.
cfg.ReceiveEndpoint(host, "my_service", e =>
{
e.LoadFrom(provider);
});
方案二:
如果您想将消费者与每个端点一个消费者分开,并使用类似于单行端点配置的东西,可以使用以下代码轻松完成:
using System;
using MassTransit.RabbitMqTransport;
namespace MassTransit.TestCode
{
public static class BusConfigurationExtensions
{
public static void ConfigureEndpoint<T>(this IRabbitMqBusFactoryConfigurator cfg,
IRabbitMqHost host, string endpointName, IServiceProvider provider)
where T : class, IConsumer
=> cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<T>(provider));
}
}
然后您可以像这样使用它:
cfg.ConfigureEndpoint<SubmitOrderConsumer>(host, "submit_order", provider);
cfg.ConfigureEndpoint<MarkOrderAsPaidConsumer>(host, "mark_paid", provider);
cfg.ConfigureEndpoint<ShipOrderConsumer>(host, "ship_order", provider);
使用此扩展程序时,您不得使用MassTransit.Extensions.DependencyInjection
的AddMassTransit
方法,但您需要注册所有消费者dependencies 在服务集合中。
但是,如果您仍然可以在总线配置代码中使用这个单行代码,我真的不明白有这样一个扩展的意义。
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<SubmitOrderConsumer>(provider));
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<MarkOrderAsPaidConsumer>(provider));
cfg.ReceiveEndpoint(host, endpointName, ep => ep.Consumer<ShipOrderConsumer>(provider));
事实上,在我的第一个片段中我使用了方法组并不意味着你不能只使用表达式 lambda。
在下一个版本中将有一个新包 MassTransit.AspNetCore
将 MassTransit 与 ASP.NET Core 更好地集成。它将配置总线托管、正确注册总线实例并应用日志记录。配置将如下所示(此代码有效,我刚刚编写并测试了它):
public void ConfigureServices(IServiceCollection services)
{
services.AddMassTransit(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ReceiveEndpoint(host, "message_one", ep => ep.Consumer<MessageOneConsumer>(provider));
cfg.ReceiveEndpoint(host, "message_two", ep => ep.Consumer<MessageTwoConsumer>(provider));
}));
}
请记住,每个端点都有自己的队列。如果您刚刚开始工作并想尝试一下,您可以选择第一个解决方案,这样您就会有一个队列。及时,您可以拆分端点。