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.
Related
I just followed Discord's tutorial for hosting an application on a cloudflare worker. I'm trying to convert one of my bots but I've been having a problem for a few days now, I can't figure out how to use deffer reply. When I return my reply with type 5 (DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE) but after my request, my worker stops and the code that follows does not execute.
My question is how can I execute code after sending my response when I use cloudflare workers.
Here is my code:
// [...]
router.post('/', async (request, env) => {
const message = await request.json();
console.log(message);
if (message.type === InteractionType.APPLICATION_COMMAND) {
switch (message.data.name.toLowerCase()) {
case SETUP.name.toLowerCase(): {
sendDeferedReply(message, env); // Take a few seconds to run
return new JsonResponse({
type: 5,
})
}
});
});
// [...]
export default {
async fetch(request, env) {
if (request.method === 'POST') {
const signature = request.headers.get('x-signature-ed25519');
const timestamp = request.headers.get('x-signature-timestamp');
console.log(signature, timestamp, env.DISCORD_PUBLIC_KEY);
const body = await request.clone().arrayBuffer();
const isValidRequest = verifyKey(
body,
signature,
timestamp,
env.DISCORD_PUBLIC_KEY
);
if (!isValidRequest) {
console.error('Invalid Request');
return new Response('Bad request signature.', { status: 401 });
}
}
return router.handle(request, env);
},
};
Thank you in advance
You can use waitUntil() to schedule work that happens after the response. The runtime will wait up to 30 seconds after the response has been sent for waitUntil() tasks to complete before tearing down the event context.
async fetch(request, env, ctx) {
// ...
ctx.waitUntil(someAsyncFunction());
return response;
}
I'm using SignalR in asp.net core on server side and blazorise on client side. before I've used SignlaR basically just to chat and now I wanna use it to update a table when a record is inserted in some where else.
I think everything is ok on server side because as I trace it on server, it posts correct values but it does not update the table on client side. I don't know what's wrong.
here is my code on server side which is in a hub:
public async Task SendCartableUpdate(ResultData<PersonnelStationsInfo> resultData)
{
await Clients.All.SendAsync("RefreshCartable",resultData);
}
and this is how I use it on client side:
protected override async Task OnInitializedAsync()
{
//await base.OnInitializedAsync();
user = CurrentUserService.CurrentUser;
await CartableTableChangePage(1);
hubConnection = new HubConnectionBuilder()
.WithUrl(navigationManager.ToAbsoluteUri("http://localhost:15424/ProductionServiceHub"))
.Build();
hubConnection.On<ResultData<PersonnelStationsInfo>>("RefreshCartable", (_resultData) =>
{
StateHasChanged();
});
await hubConnection.StartAsync();
}
thanks for your helping
Finally I solved it and I'm so excited :)
here is how I changed my code on server side:
public async Task SendCartableUpdate()
{
await Clients.All.SendAsync("RefreshCartable");
}
and this how I changed my code on client side:
protected override async Task OnInitializedAsync()
{
//await base.OnInitializedAsync();
hubConnection = new HubConnectionBuilder()
.WithUrl(navigationManager.ToAbsoluteUri("http://192.168.2.72:1050/ProductionServiceHub"))
.Build();
hubConnection.On("RefreshCartable", () =>
{
CallLoadData();
StateHasChanged();
});
await hubConnection.StartAsync();
user = CurrentUserService.CurrentUser;
await CartableTableChangePage(1);
}
private void CallLoadData()
{
Task.Run(async () =>
{
await CartableTableChangePage(1);
StateHasChanged();
});
}
I have about 100 different types of messages and I'd like to know the correct way to consume them in RabbitMq.
I have 2 solutions and I don't know which one is the best way.
1: Implement 100 consumers for 100 different types of messages.
2: Implement 1 consumer and process the messages by for example switch case or etc.
In your opinion what's the best way?
If you are going to be calling 100 different messaging services then you will need to set up 100 different consumers BUT use only on rabbitmq connection
Here is how I implemented my rabbitmq consumer
module.exports.start = async () => {
try{
const queue1 = 'email';
const connection = await amqplib.connect(process.env.QUEUE_SERVICE);
const channel = await connection.createChannel();
await channel.assertQueue(queue1, { durable: true });
await channel.prefetch(1);
console.log('Listening for emails...');
channel.consume(queue1, async (msg) => {
if (msg !== null) {
winston.info(`Got message ${msg.content.toString()}`);
const queueMessage = JSON.parse(msg.content.toString());
Emails.Mailer(queueMessage)
.then(() => {
channel.ack(msg)
})
.catch(err=> err);
}
});
const queue2 = 'sms';
await channel.assertQueue(queue2, { durable: true });
await channel.prefetch(1);
console.log('Listening for SMS...');
channel.consume(queue2, async (msg) => {
if (msg !== null) {
const queueMessage = JSON.parse(msg.content.toString());
Sms.sendPaymentSms(queueMessage).then(() => channel.ack(msg));
}
});
} catch (error) {
return error
}
};
Another thing you can do to maintain code readability is to modularize the various services you want to call and call them in your consumer using conditionals or strategy design pattern.
.net core 2.1
Hub code:
[Authorize]
public class OnlineHub : Hub
{
public override async System.Threading.Tasks.Task OnConnectedAsync()
{
int userId = Context.UserIdentifier;
await base.OnConnectedAsync();
}
[AllowAnonymous]
public override async System.Threading.Tasks.Task OnDisconnectedAsync(Exception exception)
{
var b = Context.ConnectionId;
await base.OnDisconnectedAsync(exception);
}
Client code:
$(document).ready(() => {
let token = "token";
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://localhost:44343/online", { accessTokenFactory: () => token })
.configureLogging(signalR.LogLevel.Debug)
.build();
connection.start().catch(err => console.error(err.toString()));
});
Without [Authorize] all works fine, except Context.UserIdentifier in OnConnectedAsync, and it's explainable, but... with [Authorize] attribute on Hub class, OnConnectedAsync start working and OnDisconnected not fires at all, including 30sec timeout (by default).
Any ideas?
If you have a debugger attached and close the client by closing the browser tab, then you'll never observe OnDisconnectedAsync being fired. This is because SignalR check if a debugger is attached and don't trigger certain timeouts in order to making debugging easier. If you close by calling stop on the client then you should see OnDisconnectedAsync called.
Add
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtSecurityKey"]))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
This should trigger OnDisconnectedAsync in SignalR Hub
for blazor implement this code
#implements IAsyncDisposable
and paste this func. to code
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
This seems to be very old issue, but i will add some experience about troubleshooting, maybe it'll help someone once. After investigating, why signalR javascript client is not causing OnDisconnectedAsync when using Authorize attribute and HTTP connection for messaging, i found that the DELETE method it sends to server is blocked by CORS policy. So you can look to request responses, and if request is blocked as restricted by CORS, highly likely you need to allow DELETE (and OPTIONS as well) method to your CORS policy.
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);
}
}