SignalR client not receiving messages in integration tests - c#

I am trying to test my SignalR connections in my integration tests.
The client looks like this:
var connection = new HubConnectionBuilder()
.WithUrl(
$"{client.BaseAddress}meeting-notifications",
o =>
{
o.HttpMessageHandlerFactory = _ => Server?.CreateHandler();
})
.Build();
connection.On<BoardDto>("BoardStateChanged", board => { Do Something... });
await connection.StartAsync();
I am calling the method in my ASP-NET Core backend like so:
public async Task BroadcastBoardStateAsync(int boardId, BoardDto board)
{
await _notificationHub.Clients.All.BoardStateChanged(board);
}
The client is able to call a method on the server but not the other way around.
Does anyone know what I am missing here?
Edit: I debugged the server call and the _notificationHub contains the connection-id of the client.

Turns out SignalR v3.x does json serialization via System.Text.Json which had some problem with my POCO's.
To fix this, I had to explicitly tell SignalR to use NewtonsoftJson for serialization via this method call:
var connection = new HubConnectionBuilder()
.WithUrl(
$"{client.BaseAddress}meeting-notifications",
o =>
{
o.HttpMessageHandlerFactory = _ => Server?.CreateHandler();
})
----> .AddNewtonsoftJsonProtocol()
.Build();

Related

Masstransit not creating exchange

Good day
I'm solving the problem that exchange is not being automatically created
I have registered Masstransit in net6 net core application using such uri opions (have tried both):
rabbitmq://myurl
rabbitmq://myurl:5672
Registration looks like this:
services.AddMassTransit(mt =>
{
mt.UsingRabbitMq((context, cfg) =>
{
cfg.Host(new Uri(
RabbitMqOptions.RabbitMqUri),
RabbitMqOptions.VHost,
credentials =>
{
credentials.Username(RabbitMqOptions.UserName);
credentials.Password(RabbitMqOptions.Password);
});
cfg.AutoStart = true;
cfg.Publish<IServerNotificationMessage>(e => e.ExchangeType = RabbitMQ.Client.ExchangeType.Direct);
});
});
services.AddMassTransitHostedService();
Debugging publishing code shows that actual port used is 0 and bus control is null and not started
see the print screen
How can I make the bus start? (as I understand cfg.Host returns void, rather than buscontrol, so that it cannot be explicitly started, have specified autostart option, though its still down)
Thank you in advance
A URI is not required to configure MassTransit, you might just simplify your configuration as shown below.
services.AddMassTransit(mt =>
{
mt.UsingRabbitMq((context, cfg) =>
{
cfg.Host(RabbitMqOptions.Host,
RabbitMqOptions.Port,
RabbitMqOptions.VHost,
h =>
{
h.Username(RabbitMqOptions.UserName);
h.Password(RabbitMqOptions.Password);
});
cfg.AutoStart = true;
cfg.Publish<IServerNotificationMessage>(e => e.ExchangeType = RabbitMQ.Client.ExchangeType.Direct);
});
});
services.AddMassTransitHostedService();
The logs should show the bus starting, if they don't, then the hosted service is not being started. Is this an ASP.NET project, or a project using the .NET Generic Host?

Connecting to SignalR Server from Client

I have a web server acting as SignalR server today, where the connections from JS are coming in to correct Hub and are handled correctly.
Example of the Register and start JS side
hub = $.connection.webRTCHub;
$.connection.hub.qs = "type=pusher";
$.connection.hub.start().done(function () {
connectionId = $.connection.hub.id;
log("Connected with id ", $.connection.hub.id);
});
When trying to connect to this SignalR server with the C# SignalR Client Nuget-package, I get connected, I get a connection ID, but I do not think I get connected to correct hub because non of the logging is triggered, nor the correct responses are sent to rest of clients.
I am using the trace log for SignalR and it is showing connections, and showing that the ID is connecting. Below is the connection code from the C# client
connection = new HubConnection("http://localhost/signalr/hubs/webRTCHub");
await connection.Start();
MessageBox.Show(connection.ConnectionId);
I have also tried
connection = new HubConnection("http://localhost/signalr/webRTCHub");
and
connection = new HubConnection("http://localhost/");
Can someone point me into the right direction where to start?
I cant see it here, but you need to create a HubProxy for the Hub you want to connect to.
I assume your hub is "webRTCHub".
using(var connection = new HubConnection("http://localhost/"))
{
var hubProxy = _connection.CreateHubProxy("webRTCHub");
hubProxy.On("yourevent", () =>
{
_logger.Debug("Event recieved");
});
await _connection.Start();
}
Make sure you're registering your hub's route in app start, for example in case your using .NET core:
app.UseSignalR(routes =>
{
routes.MapHub<webRTCHubHub>("/signalr/hubs/webRTCHub");
});
While the class webRTCHub should look something like this:
public class webRTCHub : Hub
{
public async Task SendNotification(string userId, string message)
{
await Clients.User(userId).SendAsync("ReceiveNotification", "You have a new message: " + message);
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
}
}
For the js side:
"use strict";
var connection;
connection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost/signalr/hubs/webRTCHub')
.build();
connection.on('ReceiveNotification', (message) => {
// show the message maybe
})
connection.start().catch(function (err) {
return console.error(err.toString())
});
connection.on('finished',(update)=>{
connection.stop();
});
To send back a message from the client to the server you should create a method as well in the class and call that from the script
Update: Packages and Services
for ASP.NET:
NuGet Packages:
Microsoft.AspNet.SignalR
Mapping Route in Application_Start
RouteTable.Routes.MapHubs("/signalr/hubs/webRTCHub", new webRTCHub());
for .NET Core:
Make sure to install the following package and add SignalR in ConfigureServices
Microsoft.AspNetCore.SignalR
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSignalR();
// ...
}
I guess you have not created any custom routes to handle signalr requests. You should initialize the HubConnection object without any url which will initialize the url of the connection object to "/signalr" as a default value.
connection = new HubConnection("");
or just
connection = new HubConnection();
Since you are using .NET FW and not .NET Core, you should configure the hub on the server like:
On your startup:
public void Configuration(IAppBuilder app)
{
//Branch the pipeline here for requests that start with "/signalr"
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration { };
map.RunSignalR(hubConfiguration);
});
}
The package you use:
Microsoft.AspNet.SignalR;
Microsoft.Owin;
Then on client side is the same for FW and Core, just point to your hub.

How can I add publish message configuration on a running IBusControl

I'm using MassTransit with RabbitMqTransport.
Assume I have run IBusControl using:
var control = Bus.Factory.CreateUsingRabbitMq(c =>
{
var host = confgurator.Host(config.BuildHostUri(), h =>
{
...
});
...
});
await control.StartAsync();
Later I connected new endpoint to this running instance, using:
host.ConnectReceiveEndpoint(Configuration.QueueName, this.ConfigureEndpoint);
Is there a way to configure Publish/Send for new Message types at this moment also? By "configure Publish/Send" I mean using methods like existing on IRabbitMqBusFactoryConfigurator:
confgurator.Send<MessageContract>(_ =>
{
_.UseRoutingKeyFormatter(__ => Configuration.QueueName);
});
confgurator.Message<MessageContract>(x => x.SetEntityName(nameof(MessageContract)));
confgurator.Publish<MessageContract>(_ =>
{
...
}
As per Chris Patterson comment, configuring Publish/Send for message type can only be done during configuration, prior to starting the bus.

IdentityServer4 Testserver could not found

I am writing a test to get a token from identity server4 using Microsoft.AspNetCore.TestHost
var hostBuilder = new WebHostBuilder()
.ConfigureServices(services =>
{
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers())
;
})
.Configure(app =>
{
app.UseIdentityServer();
});
var server = new TestServer(hostBuilder);
var client = server.CreateClient();
client.BaseAddress = new Uri("http://localhost:5000");
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
Then disco.Error comes up with the following error
Error connecting to
http://localhost:5001/.well-known/openid-configuration: An error
occurred while sending the request.
What am i missing?
The discovery client is obviously doing an external call to that actual address. You want it to call the test server that happens to "live" InMemory.
Take a look at these tests here for IdentityServer4 that tests the discovery document.
To answer your question though you need to use one of the overloaded methods for the DiscoveryClient that takes in a handler that would make the correct "call" to your InMemory test server. Below is an example of how this could be done.
var server = new TestServer(hostBuilder);
var handler = server.CreateHandler();
var discoveryClient = new DiscoveryClient("http://localhost:5000", handler);
var discoveryDocument = await discoveryClient.GetAsync();
Also I highly recommend going over the IdentityServer4 integration tests if youre going to be doing some of your own tests like this.

Integration testing microservices using MassTransit

I am trying to create black box integration tests around some services that I have created using MassTransit. The gist of the service is that it receives a message on a MassTransit channel, processes the message and sends the message on a different MassTransit channel (note that this is not a Request-Reply semantic but more of a component in a pipeline):
IBusControl bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri("rabbitmq://localhost"),
h =>
{
h.Username("guest");
h.Password("guest");
});
cfg.ReceiveEndpoint(host, "Queue1", ep =>
{
ep.Handler<ItemStarted>(context =>
{
ItemFinished item = FinishMessage(context.Message);
context.Publish(item);
});
});
});
bus.Start();
In order to test this I think what I need is a synchronous way to receive messages from the bus. Is there any way to do this:
IBusControl bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
var host = cfg.Host(new Uri("rabbitmq://localhost"),
h =>
{
h.Username("guest");
h.Password("guest");
}));
bus.Start();
bus.Publish(new ItemStarted());
// This is what does not seem to exist
ItemFinished finished = bus.Receive<ItemFinished>(timeout : Timespan.FromSeconds(5));
// Assertions about finished
Is there a way to receive messages directly from MassTransit without wiring up a consumer class or lambda?
There is a MultiTestConsumer that can be used to receive and track messages that are produced during testing. You can see the class here:
https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit/Testing/MultiTestConsumer.cs
And you can see how it is used here:
https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit.Tests/MultiTestConsumer_Specs.cs
It can also be used as a regular subscribed consumer, which sets up bindings in RabbitMQ to receive published messages.
I would recommend checking out the TestFramework, as there are a bunch of asynchronous testing helpers in there.

Categories