Can a SignalR Hub receive events from clients? And if so, how? - asp.net-core

I have a signalR hub that needs to be able to receive an event from a client and then notify all other clients connected to the hub.
Is that possible?
I want my 'hub' application to be able to receive messages and send them. I can only figure out how to do the sending of messages. Here is what I have now:
Application 1-- Hub
Startup class:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR().AddHubOptions<EventsHub>(options =>
{
options.HandshakeTimeout = TimeSpan.FromMinutes(5);
options.EnableDetailedErrors = true;
});
services.AddTransient(typeof(BusinessLogic.EventsBusinessLogic));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSignalR((configure) =>
{
configure.MapHub<EventsHub>("/hubs/events", (options) =>
{
});
});
}
Set Up of the Hub in Application 1
public class EventsHub : Hub
{
public EventsHub()
{
}
public override Task OnConnectedAsync()
{
if (UserHandler.ConnectedIds.Count == 0)
{
//Do something on connect
}
UserHandler.ConnectedIds.Add(Context.ConnectionId);
Console.WriteLine("Connection:");
return base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
//Do something on Disconnect
}
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
}
BusinessLogic:
public class EventsBusinessLogic
{
private readonly IHubContext<EventsHub> _eventsHub;
public EventsBusinessLogic(IHubContext<EventsHub> eventsHub)
{
_eventsHub = eventsHub;
}
public async Task<Task> EventReceivedNotification(ProjectMoonEventLog eventInformation)
{
try
{
await _eventsHub.Clients.All.SendAsync("NewEvent", SomeObject);
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
}
In the second application, that listens for events or messages from the hub:
Startup.cs
private static void ConfigureAppServices(IServiceCollection services, string Orale, string Sql)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddOptions();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
//set up of singletons and transients
services.AddHostedService<Events.EventingHubClient>();
}
The ClientHub to connect to application 1:
public class EventingHubClient : IHostedService
{
private HubConnection _connection;
public EventingHubClient()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:61520/hubs/events")
.Build();
_connection.On<Event>("NewEvent",
data => _ = EventReceivedNotification(data));
}
public async Task<Task> EventReceivedNotification(Event eventInformation)
{
try
{
//Do something when the event happens
return Task.CompletedTask;
}
catch (Exception e)
{
throw new Exception(e.Message);
}
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// Loop is here to wait until the server is running
while (true)
{
try
{
await _connection.StartAsync(cancellationToken);
Console.WriteLine("Connected");
break;
}
catch (Exception e)
{
Console.WriteLine(e.Message);
await Task.Delay(100);
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return _connection.DisposeAsync();
}
}
This works, but now I want application 2 to be able to send a message to application 1? So I need a similar piece of code as in the EventsBusinessLogic class in application2 to send messages to application 1.
I hope this is clear enough? Is this the purpose of SignalR?

Please refer to signalR documentation signalR documentation for .net client
I guess in your Hub method like this
public async Task SendTransaction(Transaction data)
{
await Clients.All.SendAsync("TransactionReceived", data);
}
Then add methods in client side
in constructor add
connection.On<Transaction>("TransactionReceived", (data) =>
{
this.Dispatcher.Invoke(() =>
{
var transactionData = data;
});
});
and then SendTransaction expected on server
private async void SendTransaction(Transaction data)
{
try
{
await connection.InvokeAsync("SendTransaction", data);
}
catch (Exception ex)
{
//
throw
}
}

Related

IHostedService - background task locks app

I want to run task in different thread which will synchornize data, but it locks whole server (asp net core). i have no idea where i did something wrong.
I tried with tasks, and then with thread, but it always lock my app.
public interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
public class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ISynchronizationBackgroundService _syncService;
public ScopedProcessingService(ISynchronizationBackgroundService syncService)
{
_syncService= syncService;
}
public Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
_syncService.Synchronize();
}
catch (Exception ex)
{
Log.Error($"SYNCHRONIZATION ERROR {ex.Message} {ex.InnerException?.Message}");
}
Thread.Sleep(5000);
}
return Task.CompletedTask;
}
}
and the consumer
public class ConsumeScopedServiceHostedService : BackgroundService
{
public IServiceProvider Services { get; }
public ConsumeScopedServiceHostedService(IServiceProvider services)
{
Services = services;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
Thread t = new Thread(() =>
{
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
scopedProcessingService.DoWork(stoppingToken);
}
});
t.Start();
return Task.CompletedTask;
}
}
and in the end registration:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
services.AddScoped<ISynchronizationBackgroundService, SynchronizationBackgroundService>();
Would be grateful for any advice.

SignalR: Websocket closed with error: InternalServerError

On the front side when the user adds a new mp3. In addition to writing this to the database, I also need to transfer that mp3 to the client side. But my client side is Worker Service. I need to transfer this mp3 to that Worker Service via SignalR.
My SignalR server project codes :
Startup hub endpoint :
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action}/{id?}");
endpoints.MapHub<TrackHub>("/hubs/track");
});
Hub :
public class TrackHub : Hub
{
public static Dictionary<int, string> ActiveUsers = new Dictionary<int, string>();
public TrackHub()
{
}
public async Task SendMessage(string message)
{
var connectionId = Context.ConnectionId;
await Clients.Others.SendAsync("ReceiveMessage", message + connectionId);
}
public override async Task OnConnectedAsync()
{
await Clients.All.SendAsync("ActiveUsers");
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await Clients.All.SendAsync("ActiveUsers");
}
}
HubContext
public class TrackBusiness
{
private readonly IHubContext<TrackHub> _hubContext;
public TrackBusiness(IHubContext<TrackHub> hubContext)
{
_hubContext = hubContext;
}
public Task SendMessage()
{
return _hubContext.Clients.All.SendAsync("Receive");
}
}
And my client side - worker service project :
public class SocketService : ISocketService
{
private HubConnection? _connection;
private readonly IConfiguration _configuration;
private readonly ILogger<SocketService> _logger;
public event OnNewTrackAddedEventHandler NewTrackAdded;
public event OnDeviceDeletedEventHandler DeviceDeleted;
public SocketService(IConfiguration configuration, ILogger<SocketService> logger)
{
_configuration = configuration;
_logger = logger;
}
public async Task Start()
{
_connection = new HubConnectionBuilder()
.WithUrl(_configuration.GetValue<string>("SocketUrl"))
.WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10) })
.Build();
_connection.Reconnected += connectionId =>
{
if (_connection.State == HubConnectionState.Connected)
{
_logger.LogInformation("Socket reconnected");
}
if (_connection.State == HubConnectionState.Reconnecting)
{
_logger.LogInformation("Socket try to reconnect");
}
return Task.CompletedTask;
};
_connection.On<string, byte[]>("SendMessage", (imei, track) =>
{
NewTrackAdded.Invoke(imei, track);
});
_connection.On<string>("DeviceDeleted", (imei) =>
{
DeviceDeleted.Invoke(imei);
});
try
{
await _connection.StartAsync();
_logger.LogInformation("Socket started");
}
catch (Exception e)
{
_logger.LogWarning("Socket can't connect : {0}", e.Message);
}
}
}
My appSettings :
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ApiUrl": "localhost",
"SocketUrl": "http://localhost:29082/hubs/track"
}
But I run into this error when I start the project. Can you help with the problem?

Blazor Server Page Unit Testing - How to mock secondary signalR client connection used in Blazor Server Page

I am a new user to bUnit and have managed to get some tests running for NavMenu to grasp the basic concepts.
However, a different Blazor page makes a request to a secondary signalR hub for communication of workflow state.
How to mock signalR connection?
https://github.com/dotnet/aspnetcore/issues/14924
Server Page that uses an additional signalR connection to communicate workflow state
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var hubUrl = NavigationManager.BaseUri.TrimEnd('/') + "/motionhub";
try
{
Logger.LogInformation("Index.razor page is performing initial render, connecting to secondary signalR hub");
hubConnection = new HubConnectionBuilder()
.WithUrl(hubUrl)
.ConfigureLogging(logging =>
{
logging.AddConsole();
logging.AddFilter("Microsoft.AspNetCore.SignalR", LogLevel.Information);
})
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions = JsonConvertersFactory.CreateDefaultJsonConverters(LoggerMotionDetection, LoggerMotionInfo, LoggerJsonVisitor);
})
.Build();
hubConnection.On<MotionDetection>("ReceiveMotionDetection", ReceiveMessage);
hubConnection.Closed += CloseHandler;
Logger.LogInformation("Starting HubConnection");
await hubConnection.StartAsync();
Logger.LogInformation("Index Razor Page initialised, listening on signalR hub => " + hubUrl.ToString());
}
catch (Exception e)
{
Logger.LogError(e, "Encountered exception => " + e);
}
}
}
Stub Unit Test Class
public class IndexTest : TestContext, IDisposable
{
private MotionDetectionRepository repo;
public IndexTest()
{
var mock = new Mock<IMotionDetectionSerializer<MotionDetection>>();
repo = new MotionDetectionRepository(mock.Object, new NullLogger<MotionDetectionRepository>());
Services.AddScoped<ILogger<MotionDetectionRepository>, NullLogger<MotionDetectionRepository>>();
Services.AddScoped<ILogger<MotionInfoConverter>, NullLogger<MotionInfoConverter>>();
Services.AddScoped<ILogger<MotionDetectionConverter>, NullLogger<MotionDetectionConverter>>();
Services.AddScoped<ILogger<JsonVisitor>, NullLogger<JsonVisitor>>();
Services.AddScoped<ILogger<WebApp.Pages.Index>, NullLogger<WebApp.Pages.Index>>();
Services.AddScoped<IMotionDetectionRepository>(sp => repo);
Services.AddScoped<MockNavigationManager>();
Services.AddSignalR();
}
[Fact]
public void Test()
{
var cut = RenderComponent<WebApp.Pages.Index>();
Console.WriteLine($"{cut.Markup}");
}
}
I use these brokers.
public interface IHubConnectionsBroker
{
HubConnection CreateHubConnection(string endpoint, bool autoReconnect);
}
public interface IChatHubBroker
{
event EventHandler<ChatMessage> OnReceiveMessage;
event EventHandler<Exception> OnConnectionClosed;
event EventHandler<Exception> OnReconnecting;
event EventHandler<string> OnReconnected;
bool IsConnected { get; }
string ConnectionId { get; }
TimeSpan HandshakeTimeout { get; set; }
TimeSpan KeepAliveInterval { get; set; }
TimeSpan ServerTimeout { get; set; }
ChatHubBrokerState ChatHubBrokerState { get; }
ValueTask StartAsync();
ValueTask StopAsync();
ValueTask SendAsync(ChatMessage message);
ValueTask DisposeAsync();
}

My AspnetCore application does not receive publications from a .net core console application via SignalR

My AspnetCore application does not receive publications from a .net core console application via SignalR
I have an aspnet core 2.1 web application that contains a SignalR Hub.
I need to make a .net core 2.1 console application that send information to my hub on my web system.
I did a development here, however when I run my console app, no exception appears, but my web application does not receive the information.
See what I've done so far.
Aspnet Core 2.1
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession();
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSignalR();
services.AddMvc(
config => {
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
config.Filters.Add(new AuthorizeFilter(policy));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(CookieAuthenticationDefaults.AuthenticationScheme,
options =>
{
options.ExpireTimeSpan = TimeSpan.FromDays(1);
options.LoginPath = "/Home/Login";
options.LogoutPath = "/Usuario/Logout";
options.SlidingExpiration = true;
});
//provedor postgresql
services.AddEntityFrameworkNpgsql()
.AddDbContext<MesContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("MesContext")));
services.AddScoped<SeedingService>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SeedingService seedingService)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
seedingService.Seed();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseSignalR(routes =>
{
routes.MapHub<MesHub>("/MesHub");
});
}
}
View Index
//teste
// ATUALIZA LISTAGEM
connection.on("teste", function (sucesso, msg) {
if (sucesso) {
console.log(msg);
}
});
Hub
public class MesHub : Hub
{
public static ConcurrentDictionary<string, List<string>> ConnectedUsers = new ConcurrentDictionary<string, List<string>>();
public void SendAsync(string message)
{
// Call the addMessage method on all clients
Clients.All.SendAsync("teste", true, message);
}
public override Task OnConnectedAsync()
{
string name = Context.User.Identity.Name;
Groups.AddToGroupAsync(Context.ConnectionId, name);
return base.OnConnectedAsync();
}
}
Console aplication
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Teste Renan!");
var url = "https://localhost:44323/meshub";
var connection = new HubConnectionBuilder()
.WithUrl($"{url}")
.WithAutomaticReconnect() //I don't think this is totally required, but can't hurt either
.Build();
var t = connection.StartAsync();
Console.WriteLine("conected!");
//Wait for the connection to complete
t.Wait();
Console.WriteLine(connection.State);
//connection.SendAsync("SendAsync", "renan");
connection.InvokeAsync<string>("SendAsync", "HELLO World ").ContinueWith(task => {
if (task.IsFaulted)
{
Console.WriteLine("There was an error calling send: {0}",
task.Exception.GetBaseException());
}
else
{
Console.WriteLine(task.Result);
}
});
Console.WriteLine("enviado!");
}
}
In the console app I add the Microsoft.aspnetCore.SignalR.Client package
Signalr works very well within the aspnet core web system, as it is not receiving information externally.
I'm forgetting something? what am I doing wrong?
I define same hub method and do a test to invoke it from the console client, which work as expected.
Besides, I test it with my SignalR console client app, it works well too. If possible, you can test it and check if it works for you.
static void Main(string[] args)
{
Console.WriteLine("Client App Starts...");
Program p = new Program();
p.Myfunc().Wait();
Console.ReadLine();
}
public async Task Myfunc()
{
HubConnection connection = new HubConnectionBuilder()
.WithUrl("https://localhost:44323/meshub")
.Build();
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0, 5) * 1000);
await connection.StartAsync();
};
await connection.StartAsync();
try
{
await connection.InvokeAsync("SendAsync", "HELLO World ");
//await connection.InvokeAsync("SendMessage", "console app", "test mes");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
Test Result
Updated:
in my Hub I have an OnConnectedAsync method where the name of the group is informed
You can try to update the OnConnectedAsync method as below, then check if your SignalR console client app can connect to hub and communicate with web client well.
public override async Task OnConnectedAsync()
{
string name = Context.User.Identity.Name;
if (name != null)
{
await Groups.AddToGroupAsync(Context.ConnectionId, name);
}
await base.OnConnectedAsync();
}

How do I push data from hub to client every second using SignalR

Working on building signalR hub, I'm able to get data from hub to the client but I'm, not sure how do I push it every 1 second.
I'm not sure where do I set the timer in the controller where getApps method exists or in the hub?
Hub:
public class nphub : Hub
{
public readonly sbController _sbcontroller;
public nphub(sbController sbcontroller)
{
_sbcontroller = sbcontroller;
}
public async Task NotifyConnection()
{
IActionResult result = await _sbcontroller.getApps();
await Clients.All.SendAsync("TestBrodcasting", result);
}
}
In Controller:
public async Task<IActionResult> getApps()
{
// var request = new HttpRequestMessage(HttpMethod.Get, "apps");
// var response = await _client_NP.SendAsync(request);
// var json = await response.Content.ReadAsStringAsync();
return Ok($"Testing a Basic HUB at {DateTime.Now.ToLocalTime()}");
}
Client:
let connection = new signalR.HubConnectionBuilder()
.withUrl("/nphub").build();
connection.start().then(function () {
TestConnection();
}).catch(function (err) {
return console.error(err.toString());
});
function TestConnection() {
connection.invoke("NotifyConnection").catch(function (err) {
return console.error(err.toString());
});
}
connection.on("TestBrodcasting", function (time) {
document.getElementById('broadcastDiv').innerHTML = time.value;
document.getElementById('broadcastDiv').style.display = "block";
});
Just for the test purpose to see realtime changes, I'm trying to return time. I'm able to see time on the client but it's not changing.
You need to use a hosted service, as described in the docs. Add a class like:
internal class SignalRTimedHostedService : IHostedService, IDisposable
{
private readonly IHubContext<nphub> _hub;
private readonly ILogger _logger;
private Timer _timer;
public SignalRTimedHostedService(IHubContext<nphub> hub, ILogger<SignalRTimedHostedService> logger)
{
_hub = hub;
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
// send message using _hub
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Note: A hosted service lives in singleton scope. You can inject IHubContext<T> directly, though, because it too is in singleton scope.
Then in ConfigureServices:
services.AddHostedService<SignalRTimedHostedService>();