I had a problem sending messages to clients via MassTransit and SignalR
Startup:
//SignalR
services.AddSignalR().AddMassTransitBackplane();
#region MassTransit RabbitMq
services.AddScoped<SendCosistListToScaleConsumer>();
services.AddScoped<CreateConsistListConsumer>();
services.AddMassTransit(x =>
{
x.AddSignalRHubConsumers<NotifyHub>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(conf =>
{
conf.Host(Configuration["Rabbit:Host"], host => {
host.Username(Configuration["Rabbit:Username"]);
host.Password(Configuration["Rabbit:Password"]);
});
conf.ReceiveEndpoint(Configuration["Rabbit:ReceiveEndpoint"], e => {
e.PrefetchCount = 16;
e.UseMessageRetry(n => n.Interval(3, 100));
#region Consumers
e.Consumer<SendCosistListToScaleConsumer>();
e.Consumer<CreateConsistListConsumer>();
#endregion
});
conf.AddSignalRHubEndpoints<NotifyHub>(provider);
}));
});
services.AddMassTransitHostedService();
#endregion
....
app.UseSignalR(endpoints =>
{
endpoints.MapHub<NotifyHub>("/notify");
});
Consumer:
public class CreateConsistListConsumer : IConsumer<ICreateConsistList>
{
IReadOnlyList<IHubProtocol> protocols = new IHubProtocol[] { new JsonHubProtocol() };
public Task Consume(ConsumeContext<ICreateConsistList> context)
{
context.Publish<All<NotifyHub>>(
new
{
Message = protocols.ToProtocolDictionary("SendMessageToAllUsers", new object[] { "CompanyId", context.Message.CompanyId })
});
return Task.CompletedTask;
}
}
Console App (SignalR Client):
hubConnection.On<Object>("SendMessageToAllUsers", param => {
Console.WriteLine(param);
});
If I understand correctly how MassTransii and SignalR work, then this code is enough to send messages to clients.
With the help of debugging, I looked that CreateConsistListConsumer is working, but clients do not receive reporting.
At the same time, the client connects to the hub and correctly receives messages from other sources, but not from MassTransit.
What am I doing wrong?
I have been facing the same issue last week.
It seem SignalR is doing some special work with handling hubs, and couldn't make Masstransit SignalR service to work.
I ended up using a static hub reference as described here.
Basically, I am just calling Core DI to get my hub context, then store it into a static property (as in the sample in the Github issue listed above).
When needed, I call the reference from within my MassTransit Consumer, and I am done.
Related
I'm using MassTransit with RabbitMQ on a .net core 6 web application. My goal is to keep in sync several instances of an application, running on different plants. The application needs to be able to publish / consume messages.
When a site publishes something, this is broadcasted to all the sites queues (also itself, it will simply discard the message).
In order to do it, I configured MassTransit queue names with the suffix of the plant: eg norm-queue-CV, norm-queue-MB. I configured also the Consumer to bind to a generic fanout exchange name (norm-exchange).
Here my configuration extract:
public void ConfigureServices(IServiceCollection services)
{
services.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(_configuration["RabbitMQ:URI"] + _configuration["RabbitMQ:VirtualHost"]), $"ENG {_configuration["Application:PlantID"]} Producer", h =>
{
h.Username(_configuration["RabbitMQ:UserName"]);
h.Password(_configuration["RabbitMQ:Password"]);
});
cfg.Publish<NormCreated>(x =>
{
x.Durable = true;
x.AutoDelete = false;
x.ExchangeType = "fanout"; // default, allows any valid exchange type
});
}));
});
// consumer
var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(_configuration["RabbitMQ:URI"] + _configuration["RabbitMQ:VirtualHost"]), $"ENG {_configuration["Application:PlantID"]} Consumer", h =>
{
h.Username(_configuration["RabbitMQ:UserName"]);
h.Password(_configuration["RabbitMQ:Password"]);
});
cfg.ReceiveEndpoint($"norm-queue-{_configuration["Application:PlantID"]}", e =>
{
e.Consumer<NormConsumer>();
e.UseConcurrencyLimit(1);
e.UseMessageRetry(r => r.Intervals(100, 200, 500, 800, 1000));
e.Bind<NormCreated>();
e.Bind("norm-exchange");
});
});
busControl.Start();
And here how NormConsumer is defined
public class NormConsumer : IConsumer<NormCreated>
{
private readonly ILogger<NormConsumer>? logger;
public NormConsumer()
{
}
public NormConsumer(ILogger<NormConsumer> logger)
{
this.logger = logger;
}
public async Task Consume(ConsumeContext<NormCreated> context)
{
logger.LogInformation("Norm Submitted: {NormID}", context.Message.NormID);
//await context.Publish<NormCreated>(new
//{
// context.Message.OrderId
//});
}
}
Here the queues automatically created. To me they look fine
And here the exchange created. I was trying to get only one exchange (norm-exchange), but also the other 2 are created.
My problem is first of all understand if my layout makes sense (I'm quite new to Rabbit/Masstransit).
Moreover I'd like to override how exchanges are named, forcing to have for this queues only one exchange: "norm-exchange". I tried to override it in "producer" part, but not able to do it
RabbitMQ broker topology is covered extensively in RabbitMQ - The Details, and also in the documentation.
You do not need to call Bind in the receive endpoint, consumer message types are already bound for you. Remove both Bind statements, and any published messages will be routed by type to the receive endpoints.
I am currently using SignalR in my .NET framework project to send updates to the client for a long running process. There can be many processes running simultaneously and the client will subscribe to any one of the process using an unique ID. I am using Groups to identify the clients who are subscribed to a particular process. If a client subscribes to a process in middle, I must send all the previous messages to that client. The code goes something like this
public class ProgressHub : Hub
{
public async Task SubscribeToProgress(string id)
{
foreach (var message in GetPreviousMessages(id)) // Getting all the previous messages
{
await Clients.Caller.SendMessage(message); // Sending Messages to the current caller alone
}
await Groups.Add(Context.ConnectionId, id); // Added the current client to a group to be used further
}
}
The client listens to Send Message
The above code snippet is not working (No messages in the network tab).
I tried many things
await Clients.Client(Context.ConnectionId).SendMessage(message);
await Clients.All.SendMessage(message); // Just to check if it works
all the above without await, but nothing seems to work.
After fiddling around a bit, I was able to come up with this
public class ProgressHub : Hub
{
public async Task SubscribeToProgress(string id)
{
await Groups.Add(Context.ConnectionId, id); // Adding client to the group first
foreach (var message in GetPreviousMessages(id))
{
await Clients.Group(id).SendMessage(message); // Sending messages to the group all together
}
}
}
But this has an undesirable side effect of sending the older messages to client who are already connected. Sure, I can exclude the other connectionIDs and send out the message, but this seems like an hack. Logically speaking, the first snippet should have worked just fine.
are you add configuration in Program.cs ?
using SignalRChat.Hubs;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapHub<ChatHub>("/chatHub");
app.Run();
and you can read this reference :
Microsoft
Environment: Ubuntu 20.04
I am running a BlueZ BLE gatt server and need to know when a client connects.
I have a simple program to monitor BlueZ device connections that is based on Tmds.Dbus (https://github.com/tmds/Tmds.DBus). Here is the relevant code:
using(var connection = new Connection(Address.System)) {
connection.StateChanged += (s, e) => OnStateChanged(e);
await connection.ConnectAsync();
var dev = connection.CreateProxy<bluez.DBus.IDevice1>("org.bluez", "/org/bluez/hci0");
await dev.WatchPropertiesAsync(p => {
Console.WriteLine("Property");
});
var objManager = connection.CreateProxy<bluez.DBus.IObjectManager>("org.bluez", "/org/bluez/hci0");
await objManager.WatchInterfacesAddedAsync(obj => {
Console.WriteLine("Added");
});
...
[DBusInterface("org.bluez.Device1")]
interface IDevice1 : IDBusObject
{
...
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
}
[DBusInterface("org.freedesktop.DBus.ObjectManager")]
interface IObjectManager : IDBusObject
{
Task<IDisposable> WatchInterfacesAddedAsync(Action<(ObjectPath #object, IDictionary<string, IDictionary<string, object>> interfaces)> handler, Action<Exception> onError = null);
...
}
The problem I am running into is that I never get any event at all when a client connects or disconnects. However, if I run dbus-monitor, I can see InterfacesAdded on ObjectManager and Property changes on Device1.
Wondering if I am just missing something. Tmds.Dbus is just a .NET core wrapper over DBus APIs. Regards
SignalR gives me 404 when trying to connect for some users. URLs are the same except for access_token.
It is stable reproducible per user (I mean that some users are stable OK, some users are stable 404).
access_token parsed jwt diff (left is OK user, right gets 404):
I did a trace level of logs and have next:
For the OK user:
For the user that gets 404:
Note: URLs under black squares are the same.
Front End is Angular 9 with package "#microsoft/signalr": "^3.1.8", and here's the code that builds the connection:
private buildHub(): HubConnection {
console.log(this.authService.accessToken);
let builder = new HubConnectionBuilder()
.withAutomaticReconnect()
.configureLogging(LogLevel.Information)
.withUrl('ws/notificationHub', {
accessTokenFactory: () => this.authService.accessToken
});
if (this.debugMode) {
builder = builder.configureLogging(LogLevel.Trace);
}
return builder.build();
}
Backend is using next code in Startup for configuring signalR hub:
In public void ConfigureServices(IServiceCollection services):
services.AddSignalR()
.AddJsonProtocol(options =>
{
options.PayloadSerializerSettings.ContractResolver = new DefaultContractResolver();
});
In public void Configure(IApplicationBuilder app, IHostingEnvironment env):
app.UseSignalR(route =>
{
route.MapHub<NotificationHub>("/ws/notificationHub");
});
Also we use custom authentication, so we have Authorize attribute for the Hub class:
[Authorize]
public class NotificationHub: Hub<INotificationHubClient>
and this code in public void ConfigureServices(IServiceCollection services):
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = identityServerSettings.Url;
options.Audience = identityServerSettings.ApiScopeName;
options.RequireHttpsMetadata = identityServerSettings.RequireHttpsMetadata;
options.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/ws"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
Unfortunately, I don't have the full access to the environment where it is reproducible, but I can request to see any settings or try to make some changes.
What else can I try to troubleshoot the issue?
UPDATE: negotiate is fine for both users.
I had this issue recently, after the size of my JWT increased. I found that in my case the 404 error was being thrown by IIS because the query string exceeded the limit of 2048. After increasing the query string max length, my issue was resolved.
i have a xamarin app that is trying to talk to use SignalR in Azure functions.
i have 2 azure functions as per the documentation.
public static class NegotiateFunction
{
[FunctionName("negotiate")]
public static SignalRConnectionInfo GetSignalRInfo(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequest req,
[SignalRConnectionInfo(HubName = "chat")] SignalRConnectionInfo connectionInfo)
//, UserId = "{headers.x-ms-client-principal-id}"
{
return connectionInfo;
}
}
and
public static class SendMessageFunction
{
[FunctionName("Send")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
// var chatObj = (ChatObject)(message);
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
// UserId = chatObj.ReciversId,
Target = "Send",
Arguments = new[] { message }
});
}
}
in my xamarin client i am connecting like this.
try
{
_connection = new HubConnectionBuilder()
.WithUrl("http://192.168.1.66:7071/api")
.Build();
_connection.On<string>("Send", (message) =>
{
AppendMessage(message);
});
await _connection.StartAsync();
}
I send message using this code in one of the pages of Xamarin app page.
try
{
await _connection.SendAsync("Send", MessageEntry.Text);
MessageEntry.Text = "";
}
connection code works it hits "negotiate" function properly but when i call SendAsync it does not hit break-point in [FunctionName("Send")] and nothing happens. It doesn't give me any exception as well.
local settings are like this
Update
i also tried Invoke. it didnt worked.
Should i try making a POST call to [FunctionName("Send")] ?
The way SignalR SaaS works in Functions is slightly different to using the NuGet package in a .NET Application.
You can't invoke a function using the SignalR library, as you can see on the attribute in your function, it's expecting a Http trigger so you have to do a POST to this endpoint instead of invoking it as you normally would.
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]
You still want to listen to the Send target as normal.