MassTransit consume non MassTransit message - rabbitmq

I have a console app that is publishing messages to a RabbitMQ exchange. Is it possible for a subscriber that is built with MassTransit to consume this message?
This is the publisher code:
public virtual void Send(LogEntryMessage message)
{
using (var connection = _factory.CreateConnection())
using (var channel = connection.CreateModel())
{
var props = channel.CreateBasicProperties();
props.CorrelationId = Guid.NewGuid().ToString();
var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(message));
channel.BasicPublish(exchange: _endpointConfiguration.Exchange, routingKey: _endpointConfiguration.RoutingKey, basicProperties: null,
body: body);
}
}
This is the subscriber code:
IBusControl ConfigureBus()
{
return Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost"), h =>
{
h.Username(username);
h.Password(password);
});
cfg.ReceiveEndpoint(host, "LogEntryQueue", e =>
{
e.Handler<LogEntryMessage>(context =>
Console.Out.WriteLineAsync($"Value was entered: {context.Message.MessageBody}"));
});
});
}
This is the consumer code:
public class LogEntryMessageProcessor : IConsumer<LogEntryMessage>
{
public Task Consume(ConsumeContext<LogEntryMessage> context)
{
Console.Out.WriteLineAsync($"Value was entered:
{context.Message.Message.MessageBody}");
return Task.FromResult(0);
}
}

I hope you can get the answer in the Interoperability section, in particular look at the example message.
Basically, you need to construct a JSON object according to some simple rules.
Example message looks like this:
{
"destinationAddress": "rabbitmq://localhost/input_queue",
"headers": {},
"message": {
"value": "Some Value",
"customerId": 27
},
"messageType": [
"urn:message:MassTransit.Tests:ValueMessage"
]
}
You can easily check how more complex messages look like by creating both publisher and consumer, run the program in order to create bindings, then stop the consumer and publish some messages. They will be in the subscriber queue so you can easily read them using the management plugin.

For MassTransit to process a message that is published by non-MassTransit client,
the message has to contain the metadata required by MassTransit as described in the Interoperability page.
The consumer of the message has to process the payload of the message.
In the code below, the payload is LogEntryPayload:
public class LogEntryMessageProcessor : IConsumer<LogEntryPayload>
{
public Task Consume(ConsumeContext<LogEntryPayload> context)
{
//var payload = context.GetPayload<LogEntryPayload>();
Console.Out.WriteLineAsync($"Value was entered: {context.Message.Id} - {context.Message.MessageBody}");
return Task.FromResult(0);
}
}

Related

Why am I getting no data from my Azure SignalR Service back to my KendoUI Grid?

I'm using a KendoUI Grid in my ASP.NET Core 5 application. The grid is SignalR enabled and my application is hosted in Azure. I also use the Azure SignalR Service to handle the hubs which is what is recommended by the documentation.
I have 4 hubs powering my 4 different grids and yet, none of them are receiving data back from the SignalR Service in Azure it seems and I have no idea what is wrong. The grids seem to load, missing data, I can see the negotiations in the console with signalR and they return 200 OK. However, there is just no data being returned.
I'm not sure if it's a problem with the way I've set up my application or my Azure SignalR Service.
Here is how I have implemented the Azure SignalR Service in my application.
Startup.cs (ConfigureServices)
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var mvcBuilder = services.AddControllersWithViews(options => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
mvcBuilder.AddMicrosoftIdentityUI().AddJsonOptions(options =>
{
//Use the default property (Pascal) casing.
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
services.AddRazorPages();
services.AddSignalR(options => {
//Debug only
options.EnableDetailedErrors = true;
}).AddAzureSignalR()
.AddJsonProtocol(options =>
{
//Prevents signalr converting all text to lower case.
options.PayloadSerializerOptions.PropertyNamingPolicy = null;
});
//Kendo
services.AddKendo();
services.AddHealthChecks();
}
Startup.cs (Configure)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//Localization needed for date formats on datepickers
var supportedCultures = new[] {
new CultureInfo("en-GB")
};
//Localization set to En GB for datetime using the above supported cultures
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-GB"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/healthcheck");
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
app.UseAzureSignalR(endpoints =>
{
endpoints.MapHub<RequirementHub>("/requirementHub");
endpoints.MapHub<PositionHub>("/positionHub");
endpoints.MapHub<FixtureHub>("/fixtureHub");
endpoints.MapHub<NewswireHub>("/newswireHub");
});
}
I am using SignalR installed in my client library in it's latest version.
libman.js
{
"provider": "unpkg",
"library": "#microsoft/signalr#latest",
"destination": "wwwroot/vendor/signalr/",
"files": [
"dist/browser/signalr.js",
"dist/browser/signalr.min.js"
]
},
I have hub controllers for each of my grids that uses SignalR. I have shown the code for one of the hub controllers but they are all structured exactly the same, the only thing that changes is the name of the repository they are getting data from.
RequirementHub
using MyCompany.Data;
using MyCompany.Repo;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyCompany.UI.Hubs
{
public class RequirementHub : Hub
{
private readonly IRepository<Requirement> _requirement;
public RequirementHub(IRepository<Requirement> requirement)
{
_requirement = requirement;
}
public override Task OnConnectedAsync()
{
Groups.AddToGroupAsync(Context.ConnectionId, GetGroupName());
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception e)
{
Groups.RemoveFromGroupAsync(Context.ConnectionId, GetGroupName());
return base.OnDisconnectedAsync(e);
}
public IEnumerable<Requirement> Read()
{
var data = _requirement.GetAll();
return data;
}
public async Task Update(Requirement model)
{
await _requirement.UpdateAsync(model);
await Clients.OthersInGroup(GetGroupName()).SendAsync("update", model);
}
public string GetGroupName()
{
return GetRemoteIpAddress();
}
public string GetRemoteIpAddress()
{
return Context.GetHttpContext()?.Connection.RemoteIpAddress.ToString();
}
}
}
This now brings us to the grid itself. Here is the code for the requirement grid, again, there are 4 grids and they are all structured the same but with different names and referencing their respective hubs.
Home.cshtml
<div id="requirement-grid"></div>
<script>
$('#requirement_grid').kendoGrid({
dataSource: {
type: "signalr",
autoSync: true,
pageSize: 20,
sort: [
{
field: "Id",
dir: "desc"
}
],
schema: {
model: {
id: "Id",
fields: {
"Id": {
editable: false,
nullable: true
}
}
}
},
transport: {
signalr: {
promise: requirement_hub_start,
hub: requirement_hub,
server: {
read: "read",
update: "update",
create: "create",
destroy: "destroy"
},
client: {
read: "read",
update: "update",
create: "create",
destroy: "destroy"
}
}
},
autoBind: true,
reorderable: true,
sortable: true,
pageable: {
pageSize: 30,
refresh: true
},
columns: [
{
field: "Id"
}
]
});
</script>
You'll notice that the promise and hub are defined as requirement_hub_start and requirement_hub the code for those lives in its own JavaScript file as:
hubs.js
//Hub URL
var requirement_url = "/requirementHub";
var fixture_url = "/fixtureHub";
var position_url = "/positionHub";
var newswire_url = "/newswireHub";
//Connection Builder
var requirement_hub = new signalR.HubConnectionBuilder().withUrl(
requirement_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
var position_hub = new signalR.HubConnectionBuilder().withUrl(
position_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
var fixture_hub = new signalR.HubConnectionBuilder().withUrl(
fixture_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
var newswire_hub = new signalR.HubConnectionBuilder().withUrl(
newswire_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
//Hub Start
var position_hub_start = position_hub.start({
json: true
});
var requirement_hub_start = requirement_hub.start({
json: true
});
var fixture_hub_start = fixture_hub.start({
json: true
});
var newswire_hub_start = newswire_hub.start({
json: true
});
I looked at the documentation and have placed my SignalR Service connection string in my appsettings.json (until I can get this working):
appsettings.json
"Azure": {
"SignalR": {
"ConnectionString": "Endpoint=https://mycompanysignalr.service.signalr.net;AccessKey=xxx=;Version=1.0;",
"Enabled": "true"
}
}
Additionally, I have ensured that web sockets are switched ON in my Azure web app which was another recommendation of the documentation. I'm a little confused at this point, I've been over my setup 100 times and I can't see anything obvious that could be preventing data coming back.
Is there another step I need to follow or have I done something wrong? Has anyone had this problem before?
Please check the Signalr ConnectionStrings in your appsettings.json. You can copy it from azure portal and replace it, then try.
The above suggestion is to consider whether you have used different Signalr ConnectionStrings from the Dev environment to the production environment, which caused this problem.
In addition, what pricing tier are you using? If you use Free tier, azure signalr service will limit your use.
That will happen, you have no problem at the beginning, the program is normal, and when the limit is exceeded, the program will be abnormal. At the beginning of the next day, it can be used for a while. This is a restriction given by the Free pricing tier.

Change rabbitmq exchange with nestjs

I am using rabbitmq with nestjs. I need to replicate a message from one queue to another. I set up an exchange on rabbitmq to make it work. But how can I change the exchange of rabbitmq inside nestjs?
my api gateway
my current rabbitmq configuration inside nestjs:
constructor( ) {
this.rabbitmq = ClientProxyFactory.create({
transport: Transport.RMQ,
options: {
urls: [`amqp://${this.configService.get<string>('RABBITMQ_USER')}:${this.configService.get<string>('RABBITMQ_PASSWORD')}#${this.configService.get<string>('RABBITMQ_URL')}`],
queue: 'students'
}
})
}
createStudent(#Body() body: CreateStudentDto): Observable<any> {
return this.rabbitmq.send('createStudent', body)
}
my client
#MessagePattern('createStudent')
async createStudent(#Payload() student: Student, #Ctx() context: RmqContext) {
const channel = context.getChannelRef()
const originalMsg = context.getMessage()
try {
let response = await this.studentService.createStudent(student)
await channel.ack(originalMsg)
return response;
} catch(error) {
this.logger.log(`error: ${JSON.stringify(error.message)}`)
const filterAckError = ackErrors.filter(ackError => error.message.includes(ackError))
if (filterAckError.length > 0) {
await channel.ack(originalMsg)
}
}
}
I need the message to be sent to two queues.

Why is my function not being called on the SignalR hub

I am experimenting with SignalR right now with a simple chat. I am trying to send a message to a user2 if user1 provides user2's userId from Context.ConnectionId. My Client Side code:
const sendMessage = async (user, message) => {
const chatMessage = {
user: "generic user",
message: message
};
if (connection.connectionStarted) {
try {
console.log("user: " + user);
console.log("message: " + message);
console.log("message sent");
await connection.send('SendMessage',user, chatMessage);
}
catch(e) {
console.log(e);
}
}
else {
alert('No connection to server yet.');
}
}
When I send a message from the client side, I receive the correct console.logs, making me believe that my client side code is fine. However, my Hub server is not receiving my message.
public async Task SendMessage(string user, ChatMessage message)
{
Console.WriteLine("Received user:" + user);
await Clients.Group(user).ReceiveMessage(message);
}
I never get the the user in my console. But I can't figure out why I never get the message.
If you're using Context.ConnectionId as the "user" value, then you should be using Clients.Client(user).ReceiveMessage(message); instead.
Clients.Group(...) takes a group name that you've added clients to with Groups.AddToGroupAsync(Context.ConnectionId, groupName);.

AddWebhookNotification to call Method in Controller

I have this configured in my StartUp.cs:
public void ConfigureServices(IServiceCollection services)
{
services
.ConfigureEmail(Configuration)
.AddHealthChecksUI(setupSettings: setup =>
{
setup
.AddWebhookNotification("WebHookTest", "/WebhookNotificationError",
"{ message: \"Webhook report for [[LIVENESS]]: [[FAILURE]] - Description: [[DESCRIPTIONS]]\"}",
"{ message: \"[[LIVENESS]] is back to life\"}",
customMessageFunc: report =>
{
var failing = report.Entries.Where(e => e.Value.Status == UIHealthStatus.Unhealthy);
return $"{failing.Count()} healthchecks are failing";
},
customDescriptionFunc: report =>
{
var failing = report.Entries.Where(e => e.Value.Status == UIHealthStatus.Unhealthy);
return $"HealthChecks with names {string.Join("/", failing.Select(f => f.Key))} are failing";
});
})
.AddControllers();
}
public void Configure(IApplicationBuilder app)
{
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
app.UsePathBase(pathBase);
}
app.ConfigureExceptionHandler();
app
.UseRouting()
.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecksUI(options =>
{
options.ResourcesPath = string.IsNullOrEmpty(pathBase) ? "/ui/resources" : $"{pathBase}/ui/resources";
options.UIPath = "/hc-ui";
options.ApiPath = "/api-ui";
});
endpoints.MapDefaultControllerRoute();
});
}
And in the Controller:
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
public async Task<IActionResult> WebhookNotificationError([FromBody] string id)
{
MimeMessage mimeMessage = new MimeMessage { Priority = MessagePriority.Urgent };
mimeMessage.To.Add(new MailboxAddress(_configuration.GetValue<string>("ConfiguracionCorreoBase:ToEmail")));
mimeMessage.Subject = "WebHook Error";
BodyBuilder builder = new BodyBuilder { HtmlBody = id };
mimeMessage.Body = builder.ToMessageBody();
await _appEmailService.SendAsync(mimeMessage);
return Ok();
}
The watchdog application is configured in the appSettings.json to listen to different APIs.
So far everything works fine, but, if I force an error, I'd like to receive a notification email.
The idea is that, when an error occurs in any of the Healths, you send an email.
Environment:
.NET Core version: 3.1
Healthchecks version: AspNetCore.HealthChecks.UI 3.1.0
Operative system: Windows 10
It's look like you problem in routes. Did you verify that method with Postman?
Also check if your webHook request body is a text, try to change your template payload:
{ "message": "Webhook report for [[LIVENESS]]: [[FAILURE]] - Description: [[DESCRIPTIONS]]"}",
and in the controller change string to object. And check what you receive in DEBUG.
Try using Api/WebhookNotificationError inst. of /WebhookNotificationError if your controller is ApiController. The controller name seems to be missing
I think you should try this. It works for me.
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
public async Task<IActionResult> WebhookNotificationError()
{
using (var reader = new StreamReader(
Request.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false))
{
var payload = await reader.ReadToEndAsync();
//do whatever with your payloade here..
//I am just returning back for a example.
return Ok(payload);
}
}

.NET Core Hosted services timeout

I've started using .NET Core Hosted services to handle some pub/sub functionality. I plan to use cancellation tokens to shut the services down. And I wonder whether there is some timeout for them? I want the service to run forever if it has not been explicitly stopped.
Appreciate your help.
protected override Task ExecuteAsync(CancellationToken cancellationToken)
{
return Task.Factory.StartNew(
() =>
{
var redisPubSub = new RedisPubSubServer(ClientsManager, Constants.RedisChannel)
{
OnMessage = (channel, msg) =>
{
Trace.WriteLine($"Received '{msg}' from channel '{channel}'");
var message = JsonConvert.DeserializeObject<Message>(msg);
_hubContext.Clients.User(message.UserId.ToString()).SendAsync("ReceiveMessage", message, cancellationToken);
},
OnUnSubscribe = (message) =>
{
Trace.WriteLine($"OnUnSubscribe returns {message}");
},
OnError = (exception) =>
{
Trace.WriteLine($"OnError returns {exception}");
},
OnStart = () =>
{
Trace.WriteLine($"OnStart has been fired.");
},
OnStop = () =>
{
Trace.WriteLine($"OnStop has been fired");
}
};
redisPubSub.Start();
Trace.WriteLine($"OnStop has been fired {redisPubSub.WaitBeforeNextRestart} {redisPubSub.HeartbeatTimeout} {redisPubSub.HeartbeatInterval}");
}, cancellationToken);
}
If you check out my accepted answer here, and your hostedservice kinda looks like that, you can just do the following within the while-loop contained in the ExecuteAsync method.
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
The method must be marked as async of course.