MassTransit.Kafka batching - c#

I'm using MassTransit.Kafka for produce and consume messages in batches. When I try to consume message one by one everything works fine, but when I try to consume messages in batches I get an error:
Confluent.Kafka.ConsumeException: Local: Value deserialization error
---> System.InvalidOperationException: Exception creating proxy (GreenPipes.DynamicInternal.MassTransit.Batch<Aiforfit.WSW.DataStructures.Events.UserEvent>) for MassTransit.Batch<Aiforfit.WSW.DataStructures.Events.UserEvent>
---> System.TypeLoadException: Method 'get_Item' in type 'GreenPipes.DynamicInternal.MassTransit.Batch<Aiforfit.WSW.DataStructures.Events.UserEvent>' from assembly 'MassTransitGreenPipes.DynamicInternal3c37dde6a7c744b796f7ac1cf544383b, Version=0.0.0.0, Cul
ture=neutral, PublicKeyToken=null' does not have an implementation.
Looks like it's NewtonSoft deserealization error, but everything done according to MassTransit documentation. I've tried to convert UserEvent to Interface because every model in documentation is interface, but it didn't help.
Configuration:
public static IServiceCollection AddKafka(this IServiceCollection services, IConfigurationSection section)
{
var config = section.Get<EventMessagingOptions>().Kafka;
services.AddMassTransitHostedService();
services.AddMassTransit(x =>
{
x.UsingInMemory((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
cfg.UseRawJsonSerializer();
});
x.AddRider(rider =>
{
rider.AddConsumer<UserEventConsumer>(typeof(UserEventConsumerDefinition));
rider.UsingKafka((ctx, k) =>
{
k.SecurityProtocol = config.SecurityProtocol;
k.Host(config.Host, configurator =>
{
configurator.UseSasl(saslConfigurator =>
{
saslConfigurator.Username = config.Username;
saslConfigurator.Password = config.Password;
saslConfigurator.Mechanism = config.SaslMechanism;
});
});
k.TopicEndpoint<Batch<UserEvent>>(config.Topics.UserEvent, config.Topics.UserEventGroupId, e =>
{
e.AutoOffsetReset = AutoOffsetReset.Earliest;
e.ConfigureConsumer<UserEventConsumer>(ctx);
});
});
});
});
return services;
}
public class UserEventConsumerDefinition : ConsumerDefinition<UserEventConsumer>
{
public UserEventConsumerDefinition()
=> Endpoint(x => x.PrefetchCount = 500);
protected override void ConfigureConsumer(
IReceiveEndpointConfigurator endpointConfigurator,
IConsumerConfigurator<UserEventConsumer> consumerConfigurator)
{
consumerConfigurator.Options<BatchOptions>(options => options
.SetMessageLimit(500)
.SetConcurrencyLimit(25));
}
}
public class UserEventConsumer : IConsumer<Batch<UserEvent>>
{
private readonly ICluster _cluster;
public UserEventConsumer(ICluster cluster)
=> _cluster = cluster;
public async Task Consume(ConsumeContext<Batch<UserEvent>> context)
{
Console.WriteLine(context.Message.Length);
}
}
public class UserEvent
{
public Guid EventId { get; set; } = Guid.NewGuid();
public Guid UserId { get; set; }
public string Test { get; set; }
}
Looks like it's NewtonSoft deserealization error, but everything done according to MassTransit documentation. I've tried to convert UserEvent to Interface because every model in documentation is interface, but it didn't help.

In the case of a topic endpoint, you still specify TopicEndpoint<UserEvent> for the TopicEndpoint, and consume Batch<UserEvent> in your consumer. When configured on the topic endpoint, using ConfigureConsumer<UserEventConsumer>(ctx) it will properly handle the mapping of a batch of events to your consumer.
Assuming you are on the latest version of MassTransit, it should increase the ConcurrentMessageLimit on the topic endpoint to match the batch message capacity.

Related

Using Consumer Class names as queue names

In this video by Garry Taylor, between minutes 35:00 to 39:50, he is able to create queues in RabbitMQ that are named based upon the class names of the consumer. He does this by calling the ConfigureEndpoints method on the RabbitMQ BusFactory Configurator and passing a Bus Registration Context as a parameter to that method like so:
rmqBusFactoryConfigurator.ConfigureEndpoints(busRegistrationContext);
I was wondering if I can achieve the same thing with the ReceiveEndpoint method on the RabbitMQ BusFactory Configurator.
My setup is as follows:
.NET6 WepApi (Publisher) :
===========================
Program.cs
public static IServiceCollection EnableMessagePublisher(this IServiceCollection services)
{
services.AddMassTransit(busRegistrationConfigurator =>
{
busRegistrationConfigurator.SetKebabCaseEndpointNameFormatter();
busRegistrationConfigurator.UsingRabbitMq((busRegistrationContext, rmqBusFactoryConfigurator) =>
{
rmqBusFactoryConfigurator.Host("busbunny", "/", "15672", rmqHostConfigurator =>
{
rmqHostConfigurator.Username("guest");
rmqHostConfigurator.Password("guest");
});
rmqBusFactoryConfigurator.Message<ICreateResourceMessage>(messageTopologyConfigurator =>
{
messageTopologyConfigurator.SetEntityName("ResourceCreator.Exchange");
});
rmqBusFactoryConfigurator.Publish<ICreateResourceMessage>(rmqMessageTopologyConfigurator =>
{
rmqMessageTopologyConfigurator.ExchangeType = ExchangeType.Fanout;
});
});
});
return services;
}
Controller.cs
[ApiController]
[Route("api/resources")]
public class ResourcesController : ControllerBase
{
private readonly IPublishEndpoint publishEndpoint;
public ResourcesController(IPublishEndpoint publishEndpoint)
{
this.publishEndpoint = publishEndpoint;
}
// POST
[HttpPost]
public async Task<IActionResult> CreateResource([FromBody]Resource resource)
{
if (!ModelState.IsValid)
{
return BadRequest(modelState: ModelState);
}
ICreateResourceMessage createResourceMessage =
new CreateResourceMessage(Guid.NewGuid(), resource.Name, resource.Description);
await this.publishEndpoint.Publish<ICreateResourceMessage>(createResourceMessage);
return Ok(createResourceMessage);
}
}
.NET6 Worker Service (Consumer) :
==================================
Consumer.cs
I have two consumers both have the same lines of code as below so I am only including one for brevity.
public class Resource1MessageConsumer : IConsumer<ICreateResourceMessage>
{
private readonly ILogger<Resource1MessageConsumer> logger;
public Resource1MessageConsumer(ILogger<Resource1MessageConsumer> logger)
{
this.logger = logger;
}
public async Task Consume(ConsumeContext<ICreateResourceMessage> context)
{
var x = context.Message;
}
}
Program.cs
public static IServiceCollection EnableMessageConsumers(this IServiceCollection services)
{
services.AddMassTransit(busRegistrationConfigurator =>
{
busRegistrationConfigurator.SetKebabCaseEndpointNameFormatter();
busRegistrationConfigurator.AddConsumer<Resource1MessageConsumer, Resource1MessageConsumerDefinition>();
busRegistrationConfigurator.AddConsumer<Resource2MessageConsumer, Resource2MessageConsumerDefinition>();
busRegistrationConfigurator.UsingRabbitMq((busRegistrationContext, rmqBusFactoryConfigurator) =>
{
rmqBusFactoryConfigurator.Host("bugsbunny", "/", "15672", rmqHostConfigurator =>
{
rmqHostConfigurator.Username("guest");
rmqHostConfigurator.Password("guest");
});
rmqBusFactoryConfigurator.ReceiveEndpoint(rmqReceiveEndpointConfigurator =>
{
rmqReceiveEndpointConfigurator.ConfigureConsumer(busRegistrationContext, typeof(Resource1MessageConsumer));
rmqReceiveEndpointConfigurator.ConfigureConsumer(busRegistrationContext, typeof(Resource2MessageConsumer));
rmqReceiveEndpointConfigurator.Bind("ResourceCreator.Exchange");
});
});
});
return services;
}
From my understanding, one could either use the ConfigureEndpoints method or the ReceiveEndpoints method. In my case, the ReceiveEndpoints works well for what I am trying to achieve as I can specifically bind a consumer to an exchange.
However, I would like the consumer's queues and their related exchanges (in RabbitMQ) to have the same naming convention as the consumer classes i.e. exactly the way it works with the ConfigureEndpoints.
Has anyone been able to achieve this?
Thanks in advance.
You have two consumers on the endpoint, each of which would have a different name. So the code below configures them using one of the consumers names:
busRegistrationConfigurator.UsingRabbitMq((busRegistrationContext, rmqBusFactoryConfigurator) =>
{
rmqBusFactoryConfigurator.Host("bugsbunny", 5672, "/", rmqHostConfigurator =>
{
rmqHostConfigurator.Username("guest");
rmqHostConfigurator.Password("guest");
});
var formatter = busRegistrationContext.GetService<IEndpointNameFormatter>()
?? DefaultEndpointNameFormatter.Instance;
var endpointName = formatter.Consumer<Resource1MessageConsumer>();
rmqBusFactoryConfigurator.ReceiveEndpoint(endpointName, rmqReceiveEndpointConfigurator =>
{
rmqReceiveEndpointConfigurator.ConfigureConsumer<Resource1MessageConsumer>(busRegistrationContext);
rmqReceiveEndpointConfigurator.ConfigureConsumer<Resource2MessageConsumer>(busRegistrationContext);
rmqReceiveEndpointConfigurator.Bind("ResourceCreator.Exchange");
});
});
Note, I also fixed the PORT in your host configuration since it was wrong.

How to configure EF Core persistence in MassTransit and Automatonymous?

I am trying to configure Automatonymous worker implementation with EF Core as persistence. I publish event via api and process it in hosted service using RabbitMq as a transport. Unfortunately database does not store state of machine. I applied migrations and I see table OrderState, but there is no data in it after I publish OrderSubmmited event. It gets processed properly, because I see a log in a console, but database table remains empty. What am I missing?
This is my code:
Event:
public interface OrderSubmitted : CorrelatedBy<Guid>
{
}
Consumer:
public class OrderSubmittedConsumer : IConsumer<OrderSubmitted>
{
public Task Consume(ConsumeContext<OrderSubmitted> context)
{
Console.Out.WriteLine($"Order with id {context.Message.CorrelationId} has been submitted.");
return Task.CompletedTask;
}
}
My saga instance:
public class OrderState : SagaStateMachineInstance
{
public Guid CorrelationId { get; set; }
public string CurrentState { get; set; }
}
State machine:
public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
public State Submitted { get; private set; }
public Event<OrderSubmitted> OrderSubmitted { get; set; }
public OrderStateMachine()
{
Event(() => OrderSubmitted);
InstanceState(x => x.CurrentState);
Initially(
When(OrderSubmitted)
.TransitionTo(Submitted));
DuringAny(
When(OrderSubmitted)
.TransitionTo(Submitted));
}
}
public class OrderStateMachineDefinition : SagaDefinition<OrderState>
{
public OrderStateMachineDefinition()
{
ConcurrentMessageLimit = 15;
}
}
DbContext:
public class OrderStateDbContext : SagaDbContext
{
public OrderStateDbContext(DbContextOptions options) : base(options)
{
}
protected override IEnumerable<ISagaClassMap> Configurations
{
get
{
yield return new OrderStateMap();
}
}
}
public class OrderStateMap : SagaClassMap<OrderState>
{
protected override void Configure(EntityTypeBuilder<OrderState> entity, ModelBuilder model)
{
entity.Property(x => x.CurrentState).HasMaxLength(64);
}
}
Program class:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<OrderStateDbContext>(builder =>
builder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=OrderState;Trusted_Connection=True;", m =>
{
m.MigrationsAssembly(Assembly.GetExecutingAssembly().GetName().Name);
m.MigrationsHistoryTable($"__{nameof(OrderStateDbContext)}");
}));
services.AddMassTransit(config =>
{
config.AddSagaRepository<OrderState>()
.EntityFrameworkRepository(r =>
{
r.ExistingDbContext<OrderStateDbContext>();
r.LockStatementProvider = new SqlServerLockStatementProvider();
});
config.AddConsumer<OrderSubmittedConsumer>();
config.UsingRabbitMq((ctx, cfg) => {
cfg.Host("amqp://guest:guest#localhost:5672");
cfg.ReceiveEndpoint("order-queue", c => {
c.ConfigureConsumer<OrderSubmittedConsumer>(ctx);
// I'm assuming this is the place where something like c.StateMachineSaga() is missing, but I don't know how should this look like with EF Core
});
});
});
services.AddHostedService<Worker>();
});
}
Worker class:
public class Worker : IHostedService
{
private readonly IBusControl _bus;
public Worker(IBusControl bus)
{
_bus = bus;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await _bus.StartAsync(cancellationToken).ConfigureAwait(false);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _bus.StopAsync(cancellationToken);
}
}
In your code example, you aren't adding the saga, or configuring the saga on a receive endpoint. The consumer is a separate thing, and completely unrelated to the saga. You should be calling:
AddSagaStateMachine<OrderStateMachine, OrderState, OrderStateMachineDefinition>()
And then either using ConfigureSaga<OrderState> or switching to ConfigureEndpoints for the endpoint to be configured automatically.

"INVALID_OPERATION" error with GraphQL.Net library in .NET Core 3.0

I have a problem with GraphQL.Net library. I've used "GraphQL" Version="3.2.0", "GraphQL.Server.Ui.Playground" Version="4.4.0" and TargetFramework = netcoreapp3.0
I'm trying to see how it works and I created a simple class:
public class Temp
{
public int MyProperty { get; set; }
}
public class TempType : ObjectGraphType<Temp>
{
public TempType()
{
Name = "TEmp";
Field(t => t.MyProperty).Description("temp");
}
}
public class SolDataQuery : ObjectGraphType
{
public SolDataQuery(ISolDataFill sdFill)
{
Name = "Query";
Field<IntGraphType>("soldata", resolve: context => 5);
Field<TempType>("wheater", resolve: context => new Temp { MyProperty = 2 });
}
}
After start in playground I requested a query : { wheather {myProperty}} and
I saw: "message": "Error executing document.", "code": "INVALID_OPERATION".
If I changed my code and comment line "Field("wheater", resolve: context => new Temp { MyProperty = 2 });" I requested a query {soldata} and saw the correct result "5".
Could anybody tell me, please, where is my errors?
I forgot to add my TempType to ServiceProvider in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddSingleton<TempType>();
...
}

MassTransit - Can Multiple Consumers All Receive Same Message?

I have one .NET 4.5.2 Service Publishing messages to RabbitMq via MassTransit.
And multiple instances of a .NET Core 2.1 Service Consuming those messages.
At the moment competing instances of the .NET core consumer service steal messages from the others.
i.e. The first one to consume the message takes it off the queue and the rest of the service instances don't get to consume it.
I want ALL instances to consume the same message.
How can I achieve this?
Publisher Service is configured as follows:
builder.Register(context =>
{
MessageCorrelation.UseCorrelationId<MyWrapper>(x => x.CorrelationId);
return Bus.Factory.CreateUsingRabbitMq(configurator =>
{
configurator.Host(new Uri("rabbitmq://localhost:5671"), host =>
{
host.Username(***);
host.Password(***);
});
configurator.Message<MyWrapper>(x => { x.SetEntityName("my.exchange"); });
configurator.Publish<MyWrapper>(x =>
{
x.AutoDelete = true;
x.Durable = true;
x.ExchangeType = true;
});
});
})
.As<IBusControl>()
.As<IBus>()
.SingleInstance();
And the .NET Core Consumer Services are configured as follows:
serviceCollection.AddScoped<MyWrapperConsumer>();
serviceCollection.AddMassTransit(serviceConfigurator =>
{
serviceConfigurator.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost:5671"), hostConfigurator =>
{
hostConfigurator.Username(***);
hostConfigurator.Password(***);
});
cfg.ReceiveEndpoint(host, "my.exchange", exchangeConfigurator =>
{
exchangeConfigurator.AutoDelete = true;
exchangeConfigurator.Durable = true;
exchangeConfigurator.ExchangeType = "topic";
exchangeConfigurator.Consumer<MyWrapperConsumer>(provider);
});
}));
});
serviceCollection.AddSingleton<IHostedService, BusService>();
And then MyWrapperConsumer looks like this:
public class MyWrapperConsumer :
IConsumer<MyWrapper>
{
.
.
public MyWrapperConsumer(...) => (..) = (..);
public async Task Consume(ConsumeContext<MyWrapper> context)
{
//Do Stuff
}
}
It sounds like you want to publish messages and have multiple consumer service instances receive them. In that case, each service instance needs to have its own queue. That way, every published message will result in a copy being delivered to each queue. Then, each receive endpoint will read that message from its own queue and consume it.
All that excessive configuration you're doing is going against what you want. To make it work, remove all that exchange type configuration, and just configure each service instance with a unique queue name (you can generate it from host, machine, whatever) and just call Publish on the message producer.
You can see how RabbitMQ topology is configured: https://masstransit-project.com/advanced/topology/rabbitmq.html
Thanks to the Answer from Chris Patterson and the comment from Alexey Zimarev I now believe I have this working.
The guys pointed out (from my understanding, correct me if I am wrong) that I should get rid of specifying the Exchanges and Queues etc myself and stop being so granular with my configuration.
And let MassTransit do the work in knowing which exchange to create & publish to, and which queues to create and bind to that exchange based on my type MyWrapper. And my IConsumerimplementation type MyWrapperConsumer.
Then giving each consumer service its own unique ReceiveEndpoint name we will end up with the exchange fanning out messages of type MyWrapper to each unique queue which gets created by the unique names specified.
So, in my case..
THE PUBLISHER SERVICE config relevant lines of code changed FROM:
configurator.Message<MyWrapper>(x => { x.SetEntityName("my.exchange"); });
configurator.Publish<MyWrapper>(x =>
{
x.AutoDelete = true;
x.Durable = true;
x.ExchangeType = true;
});
TO THIS
configurator.Message<MyWrapper>(x => { });
configurator.AutoDelete = true;
AND EACH CONSUMERS SERVICE instance config relevant lines of code changed FROM:
cfg.ReceiveEndpoint(host, "my.exchange", exchangeConfigurator =>
{
exchangeConfigurator.AutoDelete = true;
exchangeConfigurator.Durable = true;
exchangeConfigurator.ExchangeType = "topic";
exchangeConfigurator.Consumer<MyWrapperConsumer>(provider);
});
TO THIS:
cfg.ReceiveEndpoint(host, Environment.MachineName, queueConfigurator =>
{
queueConfigurator.AutoDelete = true;
queueConfigurator.Consumer<MyWrapperConsumer>(provider);
});
Note, the Environment.MachineName gives the unique queue name for each instance
We can achieve it by having separate queue for each consumer services and each queue bind with a single exchange. When we publish message to exchange it will send copy of message to each queue and eventually received by each consumer services.
Messages :
namespace Masstransit.Message
{
public interface ICustomerRegistered
{
Guid Id { get; }
DateTime RegisteredUtc { get; }
string Name { get; }
string Address { get; }
}
}
namespace Masstransit.Message
{
public interface IRegisterCustomer
{
Guid Id { get; }
DateTime RegisteredUtc { get; }
string Name { get; }
string Address { get; }
}
}
Publisher Console App :
namespace Masstransit.Publisher
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("CUSTOMER REGISTRATION COMMAND PUBLISHER");
Console.Title = "Publisher window";
RunMassTransitPublisher();
}
private static void RunMassTransitPublisher()
{
string rabbitMqAddress = "rabbitmq://localhost:5672";
string rabbitMqQueue = "mycompany.domains.queues";
Uri rabbitMqRootUri = new Uri(rabbitMqAddress);
IBusControl rabbitBusControl = Bus.Factory.CreateUsingRabbitMq(rabbit =>
{
rabbit.Host(rabbitMqRootUri, settings =>
{
settings.Password("guest");
settings.Username("guest");
});
});
Task<ISendEndpoint> sendEndpointTask = rabbitBusControl.GetSendEndpoint(new Uri(string.Concat(rabbitMqAddress, "/", rabbitMqQueue)));
ISendEndpoint sendEndpoint = sendEndpointTask.Result;
Task sendTask = sendEndpoint.Send<IRegisterCustomer>(new
{
Address = "New Street",
Id = Guid.NewGuid(),
RegisteredUtc = DateTime.UtcNow,
Name = "Nice people LTD"
}, c =>
{
c.FaultAddress = new Uri("rabbitmq://localhost:5672/accounting/mycompany.queues.errors.newcustomers");
});
Console.ReadKey();
}
}
}
Receiver Management console app :
namespace Masstransit.Receiver.Management
{
class Program
{
static void Main(string[] args)
{
Console.Title = "Management consumer";
Console.WriteLine("MANAGEMENT");
RunMassTransitReceiver();
}
private static void RunMassTransitReceiver()
{
IBusControl rabbitBusControl = Bus.Factory.CreateUsingRabbitMq(rabbit =>
{
rabbit.Host(new Uri("rabbitmq://localhost:5672"), settings =>
{
settings.Password("guest");
settings.Username("guest");
});
rabbit.ReceiveEndpoint("mycompany.domains.queues.events.mgmt", conf =>
{
conf.Consumer<CustomerRegisteredConsumerMgmt>();
});
});
rabbitBusControl.Start();
Console.ReadKey();
rabbitBusControl.Stop();
}
}
}
Receiver Sales Console app:
namespace Masstransit.Receiver.Sales
{
class Program
{
static void Main(string[] args)
{
Console.Title = "Sales consumer";
Console.WriteLine("SALES");
RunMassTransitReceiver();
}
private static void RunMassTransitReceiver()
{
IBusControl rabbitBusControl = Bus.Factory.CreateUsingRabbitMq(rabbit =>
{
rabbit.Host(new Uri("rabbitmq://localhost:5672"), settings =>
{
settings.Password("guest");
settings.Username("guest");
});
rabbit.ReceiveEndpoint("mycompany.domains.queues.events.sales", conf =>
{
conf.Consumer<CustomerRegisteredConsumerSls>();
});
});
rabbitBusControl.Start();
Console.ReadKey();
rabbitBusControl.Stop();
}
}
}
You can find a working solution on https://github.com/prasantj409/Masstransit-PublishMultipleConsumer.git
By default, RabbitMQ sends each message to all the consumers in sequence. This type of dispatching is called "round-robin" and made for load balancing (you can have multiple instances of your service consuming the same message).
As Chris pointed, to ensure that your service always receives its copy of a message, you need to provide the unique Queue name.
What you need to do:
Make sure that your consumers implements IConsumer interface with the same generic type
Register all this consumers
Use Publish method to send message
Generally there are two types of messages in MassTransit: Events and Commands, and in this case your message is Event. In the case when your message is a Command, only one consumer receives message and you need to use Send method.
Example of Event DTO:
public class OrderChecked
{
public Guid OrderId { get; set; }
}
Consumers:
public class OrderSuccessfullyCheckedConsumer : IConsumer<OrderChecked>
{
public async Task Consume(ConsumeContext<OrderChecked> context)
{
// some your consuming code
}
}
public class OrderSuccessfullyCheckedConsumer2 : IConsumer<OrderChecked>
{
public async Task Consume(ConsumeContext<OrderChecked> context)
{
// some your second consuming code
}
}
Configuring:
services.AddMassTransit(c =>
{
c.AddConsumer<OrderSuccessfullyCheckedConsumer>();
c.AddConsumer<OrderSuccessfullyCheckedConsumer2>();
c.SetKebabCaseEndpointNameFormatter();
c.UsingRabbitMq((context, cfg) =>
{
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService(true);
Publishing the message:
var endpoint = await _bus.GetPublishSendEndpoint<OrderChecked>();
await endpoint.Send(new OrderChecked
{
OrderId = newOrder.Id
});
I want to share a slightly different code example.
instanceId:
Specifies an identifier that uniquely identifies the endpoint
instance, which is appended to the end of the endpoint name.
services.AddMassTransit(x => {
x.SetKebabCaseEndpointNameFormatter();
Guid instanceId = Guid.NewGuid();
x.AddConsumer<MyConsumer>()
.Endpoint(c => c.InstanceId = instanceId.ToString());
x.UsingRabbitMq((context, cfg) => {
...
cfg.ConfigureEndpoints(context);
});
});

How can you do message interception in MassTransit?

I want to inspect each message before it hits consumers or sagas.
I think I want an IInboundMessageInterceptor but I can't see an easy way to inject a custom one.
How can I achieve message interception in MT? And/Or how can I configure the bus with a custom IInboundMessageInterceptor?
You can do the following.
Your interceptor must implement IInboundMessageInterceptor
Bus.Initialize(sbc =>
{
// ... Initialise Settings ...
var busConfiguratorr = new PostCreateBusBuilderConfigurator(bus =>
{
var interceptorConfigurator = new InboundMessageInterceptorConfigurator(bus.InboundPipeline);
interceptorConfigurator.Create(new MyIncomingInterceptor(bus));
});
sbc.AddBusConfigurator(busConfiguratorr);
});
You can then write an interceptor like so
internal class MyInboundInterceptor : IInboundMessageInterceptor
{
public MyInboundInterceptor(ServiceBus bus)
{
}
public void PreDispatch(IConsumeContext context)
{
IConsumeContext<AwesomeCommand> typedContext;
if (context.TryGetContext(out typedContext))
{
// Do SOmething with typedContext.Message
}
}
public void PostDispatch(IConsumeContext context)
{
}
}

Categories

Resources