Call AddConsumer using generic type - asp.net-core

I have a problem when I am using MassTransit v5.1.5 with the default ASP.NET Core DI. I have the following code:
var consumers = typeof(CompanyApplicationService).Assembly
.GetTypes()
.Where(t => typeof(IConsumer).IsAssignableFrom(t))
.ToList();
consumers.ForEach(
c => services.AddSingleton(typeof(IConsumer), c));
cqrsConfig.ServiceCollectionConfig = (x) =>
{
consumers.ForEach(consumer => x.AddConsumer<consumer>());
};
So I have a separate assembly where my implementations of IConsumer<T> are. I have those consumers loaded into a list, but when I try to add them using the MassTransit.ExtensionsDependencyInjectionIntegration.IServiceCollectionConfigurator.AddConsumer<T>() method I cannot pass the type that I have previously loaded. So any ideas on this?
I have tried to register the consumers like:
cqrsConfig.InMemoryBusConfig = (c) =>
{
var host = c.Host;
c.ReceiveEndpoint(busName, ep =>
{
ep.LoadFrom(services.BuildServiceProvider());
});
};
but that also doesn't work for me.

There's more than one thing that is wrong here.
In order to know, which message to handle, MassTransit needs to know the generic interface type of the consumer. You, however, register all consumers as IConsumer, which is apparently wrong.
Consumers are by definition isolated to the message scope. So, consumers are instantiated and disposed for each message, and, therefore, cannot be singletons.
You need to register your consumers like it is described in the documentation:
services.AddScoped<OrderConsumer>();
or
services.AddMassTransit(x =>
{
x.AddConsumer<OrderConsumer>();
});
When consumers are added to the service collection, you also need to register the bus as it is shown in the documentation, so you use the service provider delegate:
services.AddSingleton(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host("localhost", "/", h => { });
cfg.ReceiveEndpoint(host, "submit-order", e =>
{
e.LoadFrom(provider);
});
}));
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());

Related

MassTransit RMQ scheduling scheduled but not sent

I'm trying to implement scheduling mechanism by the masstransit/rabbitmq.
I've added the configuration as stated in the docs:
Uri schedulerEndpoint = new (Constants.MassTransit.SchedulerEndpoint);
services.AddMassTransit(mtConfiguration =>
{
mtConfiguration.AddMessageScheduler(schedulerEndpoint);
mtConfiguration.AddSagaStateMachine<ArcStateMachine, ArcProcess>(typeof(ArcSagaDefinition))
.Endpoint(e => e.Name = massTransitConfiguration.SagaQueueName)
.MongoDbRepository(mongoDbConfiguration.ConnectionString, r =>
{
r.DatabaseName = mongoDbConfiguration.DbName;
r.CollectionName = mongoDbConfiguration.CollectionName;
});
mtConfiguration.UsingRabbitMq((context, cfg) =>
{
cfg.UseMessageScheduler(schedulerEndpoint);
cfg.Host(new Uri(rabbitMqConfiguration.Host), hst =>
{
hst.Username(rabbitMqConfiguration.Username);
hst.Password(rabbitMqConfiguration.Password);
});
cfg.ConfigureEndpoints(context);
});
});
Then I'm sending a scheduled message using the Bus:
DateTime messageScheduleTime = DateTime.UtcNow + TimeSpan.FromMinutes(1);
await _MessageScheduler.SchedulePublish<ScheduledMessage>(messageScheduleTime, new
{
ActivationId = context.Data.ActivationId
});
_MessageCheduler is the IMessageScheduler instance.
I do see the Scheduler queue receive the scheduled message and I see the correct scheduledTime property in it but the message does not reach the state machine whenever its schedule should fire. Seems like I'm missing something in the configuration or some MassTransit service that is not started.
Please, assist.
If you actually read the documentation you would see that UseDelayedMessageScheduler is the proper configuration to use RabbitMQ for scheduling. And AddDelayedMessageScheduler for the container-based IMessageScheduler registration.

How to add a health check in .NET 5 for MassTransit Amazon SQS?

I have a working and running .NET 5 application. There is a REST API to POST data. When data is posted, a MassTransit message is published. Using the AWS Explorer, I can clearly see that if the topic named customerinfo.fifo does not exist, it is created in Amazon by my application. Apparently, my application does something useful with Amazon SQS/SNS. I like that.
What I do not like, is that when adding a health check, an error appears causing my health check to be "Unhealthy" (after calling http://localhost:5000/health/ready ). This should not happen as my application is working fine and being able to publish messages. Logically, an "Unhealthy" status should only occur when there is something wrong. Here is my code responsible for the health check. This is part of the ConfigureServices method.
services.AddHealthChecks();
services.Configure<HealthCheckPublisherOptions>(options =>
{
options.Delay = TimeSpan.FromSeconds(2);
options.Predicate = (check) => check.Tags.Contains("ready");
});
To use the added health check, the following code is added to the Configure method.
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health/ready", new HealthCheckOptions()
{
Predicate = (check) => check.Tags.Contains("ready"),
});
endpoints.MapHealthChecks("/health/live", new HealthCheckOptions());
});
This code shown above is basically how it is documented in the MassTransit documentation. Moreover, I also have code to add MassTransit SQS itself. I have specific extension method for this:
public static void UseMassTransit(this IServiceCollection services, MassTransitConfiguration massTransitConfiguration)
{
services.AddMassTransit(x =>
{
x.AddConsumer<CustomerChangeConsumer>();
x.UsingAmazonSqs((context, cfg) =>
{
cfg.Host(massTransitConfiguration.Host, h =>
{
h.AccessKey(massTransitConfiguration.AccessKey);
h.SecretKey(massTransitConfiguration.SecretKey);
h.EnableScopedTopics();
});
cfg.ReceiveEndpoint("CustomerChangeConsumer",
configurator =>
{
configurator.ConfigureConsumer<CustomerChangeConsumer>(context);
});
cfg.Message<CustomerUpdate>(x =>
{
x.SetEntityName("customerupdate.fifo");
});
cfg.Publish<CustomerUpdate>(x =>
{
x.TopicAttributes["FifoTopic"] = "true";
});
});
});
services.AddMassTransitHostedService();
}
To solve this problem, two solution types are possible:
Just remove the MassTransit health check. It cannot fail it is not there any more.I need to find a way to implement a SQS/SNS health check myself when choosing such a solution.
Fix (my?) code in order to make the health check work properly.
Obviously, the last one is preferred but for both solution types, I have no idea how to implement them.
To further clarify my problem, the logged error message when doing a health check is shown here.
fail:
Microsoft.Extensions.Diagnostics.HealthChecks.DefaultHealthCheckService[103]
Health check masstransit-bus completed after 13.2985ms with status Unhealthy and description 'Not ready: not started'
MassTransit.AmazonSqsTransport.Exceptions.AmazonSqsConnectionException:
ReceiveTransport faulted: https://eu-west-2/
---> Amazon.SQS.AmazonSQSException: Access to the resource https://sqs.eu-west-2.amazonaws.com/ is denied.
---> Amazon.Runtime.Internal.HttpErrorResponseException: Exception of type 'Amazon.Runtime.Internal.HttpErrorResponseException'
was thrown.
at Amazon.Runtime.HttpWebRequestMessage.GetResponseAsync(CancellationToken
cancellationToken)
at Amazon.Runtime.Internal.HttpHandler1.InvokeAsync[T](IExecutionContext executionContext) at Amazon.Runtime.Internal.Unmarshaller.InvokeAsync[T](IExecutionContext executionContext) at Amazon.SQS.Internal.ValidationResponseHandler.InvokeAsync[T](IExecutionContext executionContext) at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext) --- End of inner exception stack trace --- at GreenPipes.Caching.Internals.NodeValueFactory1.CreateValue()
at GreenPipes.Caching.Internals.NodeTracker1.AddNode(INodeValueFactory1
nodeValueFactory)
--- End of inner exception stack trace ---
at MassTransit.Transports.ReceiveTransport1.ReceiveTransportAgent.RunTransport() at MassTransit.Transports.ReceiveTransport1.ReceiveTransportAgent.Run()
I really hope there someone can help me with this. It is so strange. MassTransit and SQS/SNS are working fine together. The only problem is that the health check denies that, which is really frustrating.
I found some link where they say you need to define the custom health check like below
https://github.com/MassTransit/MassTransit/issues/1513#issuecomment-554943164
Have you tried to set Autostart = true? It helped me with RabbitMq so possibly it will help you as well.
mt.UsingAmazonSqs((context, cfg) =>
{
cfg.Host(massTransitConfiguration.Host, h =>
{
h.AccessKey(massTransitConfiguration.AccessKey);
h.SecretKey(massTransitConfiguration.SecretKey);
h.EnableScopedTopics();
});
cfg.AutoStart = true;
cfg.ReceiveEndpoint("CustomerChangeConsumer",
configurator =>
{
configurator.ConfigureConsumer<CustomerChangeConsumer>(context);
});
cfg.Message<CustomerUpdate>(x =>
{
x.SetEntityName("customerupdate.fifo");
});
cfg.Publish<CustomerUpdate>(x =>
{
x.TopicAttributes["FifoTopic"] = "true";
});
});

Can we use RabbitMQ and Mediatr together using masstransit?

I created a microservice application that microservices using MassTransit and RabbitMQ for communication.
Each microservice developed using clean architecture, so we have MediatR inside each microservice.
Is it possible to use MassTransit for inside communication as well? so I can use the same signature for all services and when I want to expose a service to be used inter-microservice, it will be doable with ease.
So MediatR used for intra-communication and RabbitMQ used for inter-communication, and whole universe is on MassTransit system.
[Update] My question is how we can configure consumers so some can be used for inside communication (via MediatR) and some can be used for external communication (via RabbitMQ) and easily change them from inside to outside.
[Update2] for example here is my MassTransit registration:
services.AddMassTransit(x =>
{
x.AddConsumers(Assembly.GetExecutingAssembly());
x.AddBus(provider =>
Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(config.RabbitMQ.Address), h =>
{
h.Username(config.RabbitMQ.Username);
h.Password(config.RabbitMQ.Password);
});
cfg.ReceiveEndpoint("my-queue", ep => { ep.ConfigureConsumers(provider); });
}));
x.AddMediator((provider, cfg) => { cfg.ConfigureConsumers(provider); });
});
How can I differ in internal communication and external communication? in other words, how can I register some consumers to MediatR and some to RabbitMQ?
They can be used together, and MassTransit has its own Mediator implementation as well so you can write your handlers once and use them either via the mediator or via a durable transport such as RabbitMQ.
There are videos available that take you through the capabilities, starting with mediator and moving to RabbitMQ.
I found that I should create a separate bus for each. then external services inherit from an interface like IExternalConsumer, so I can separate them form internal ones and add them to related bus:
UPDATED for version 7
// find consumers
var types = AssemblyTypeCache.FindTypes(new[]{Assembly.GetExecutingAssembly()},TypeMetadataCache.IsConsumerOrDefinition).GetAwaiter().GetResult();
var consumers = types.FindTypes(TypeClassification.Concrete | TypeClassification.Closed).ToArray();
var internals = new List<Type>();
var externals = new List<Type>();
foreach (Type type in consumers)
{
if (type.HasInterface<IExternalConsumer>())
externals.Add(type);
else
internals.Add(type);
}
services.AddMediator(x =>
{
x.AddConsumers(internals.ToArray());
x.ConfigureMediator((provider, cfg) => cfg.UseFluentValidation());
});
services.AddMassTransit<IExternalBus>(x =>
{
x.AddConsumers(externals.ToArray());
x.AddBus(provider =>
Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(config.RabbitMQ.Address), h =>
{
h.Username(config.RabbitMQ.Username);
h.Password(config.RabbitMQ.Password);
});
cfg.ReceiveEndpoint(apiProviderName, ep => { ep.ConfigureConsumers(provider); });
}));
});
services.AddMassTransitHostedService();

can't handle multiple request using rabbitmq and masstransist in .net core

I am currently working with micro service architecture and .net core.
Rabbit MQ + MassTransit are being used to send and receive data between the micro services.
I have a host application in IIS and from 2 separate browsers I send the same request to micro service and that microservice calls other service using RabbitMQ.
I expect to get 2 separate requests hitting the consumer but instead get an internal server error.
Startup:
services.AddScoped<OrderCompletedEventConsumer>();
services.AddMassTransit(x =>
{
x.AddConsumer<Controllers.OrderController>();
});
services.AddSingleton(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("http://192.168.101.111:5672"),
"/", h =>
{
h.Username("Test");
h.Password("test");
});
cfg.ReceiveEndpoint(host, "TestQUE", e =>
{
e.Consumer<Controllers.OrderController>(provider);
});
}));
//Register Publish Endpoint of RabbitMQ bus service
services.AddSingleton<IPublishEndpoint>(provider => provider.GetRequiredService<IBusControl>());
//Register Send Endpoint of RabbitMQ bus service
services.AddSingleton<ISendEndpointProvider>(provider => provider.GetRequiredService<IBusControl>());
//Register Bus control for RabbitMQ
services.AddSingleton<IBus>(provider => provider.GetRequiredService<IBusControl>());
//Regster Bus Service hosting
services.AddSingleton<IHostedService, BusService>();
Request From One microservice:-
IRequestClient<IAddRequest<IOrder>, IAddResponse<IOrder>> orderClient =
new MessageRequestClient<IAddRequest<IOrder>, IAddResponse<IOrder>>(_bus,
EndpointAddress("orderQue"), TimeSpan.FromSeconds(Convert.ToDouble("150")));
var addResponse = orderClient.Request(new
{
entity = order
});
await Task.WhenAll(addResponse);
Consumer
public async Task Consume(ConsumeContext<IGetRequest<IOrder>> context)
{
// Operation and return result
await context.RespondAsync<IGetResposne<IOffice>>(new
{
// send result.
});
}
In the consumer the 2 separate requests from different browser arrive but both are unsuccessful. However if I do one request at a time then it will work, why is this?
Please give any idea, suggestion or hint.
Thank you,

CreateQueues and Exchanges - MassTransit

I'm using massTransit with RabbitMQ. Publishing messages with massTransit will create an exchange for my message type. And a masstransit consumer will create queues and bindings to an exchange. Great, make things easy.
Before looking at massTransit I used rabbitMQ's api to create queues, exchanges and binding. I would get both publisher and consumers to run the same setup code. So no matter who ran first all queues, exchanges and binding will be created no matter which part of the application ran first. This was great when running in a development environment.
I was wondering if something similar could be achieved with massTransit?
With MassTransit should be the same: consumers will create queues bound to the exchanges of the messages they consume (with names equal to the messages types).
Publishers will create the exchanges with same names of the types of the messages they publish.
Remember that if the messages published or consumed have super classes or implement interfaces, MassTransit will create the same hierarchy, creating and binding as many exchanges as your message class hierarchy has.
You could use HareDu 2 to achieve this with the below code. This works with both Autofac and .NET Core DI. Check the docs here: https://github.com/ahives/HareDu2
// Create a queue
var result = _container.Resolve<IBrokerObjectFactory>()
.Object<Queue>()
.Create(x =>
{
x.Queue("fake_queue");
x.Configure(c =>
{
c.IsDurable();
c.AutoDeleteWhenNotInUse();
c.HasArguments(arg =>
{
arg.SetQueueExpiration(1000);
arg.SetPerQueuedMessageExpiration(2000);
});
});
x.Targeting(t =>
{
t.VirtualHost("fake_vhost");
t.Node("fake_node");
});
});
// Create an exchange
var result = _container.Resolve<IBrokerObjectFactory>()
.Object<Exchange>()
.Create(x =>
{
x.Exchange("fake_exchange");
x.Configure(c =>
{
c.IsDurable();
c.IsForInternalUse();
c.HasRoutingType(ExchangeRoutingType.Fanout);
c.HasArguments(arg =>
{
arg.Set("fake_arg", "fake_arg_value");
});
});
x.Targeting(t => t.VirtualHost("fake_vhost"));
});
// Create a binding
var result = _container.Resolve<IBrokerObjectFactory>()
.Object<Binding>()
.Create(x =>
{
x.Binding(b =>
{
b.Source("fake_exchange");
b.Destination("fake_queue");
b.Type(BindingType.Exchange);
});
x.Configure(c =>
{
c.HasRoutingKey("your_routing_key");
c.HasArguments(arg =>
{
arg.Set("your_arg", "your_arg_value");
});
});
x.Targeting(t => t.VirtualHost("fake_vhost"));
});