If been spending many hours on this problem and I found a lot of different strategies, but none of them worked for me. (This code is just a proof of concept ofcourse.)
I have the following setup using Asp.net core 2.1 (on .Net Framwork 4.7.2):
I have made a signalr hub which has a method to send a number:
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace TestRandomNumberSignalR
{
public class TestHub : Hub
{
public async Task SendRandomNumber(int number)
{
await Clients.All.SendAsync("ReceiveRandomBumber", number);
}
}
}
I've also made a class that updates a random number every 3 seconds and added it as a singleton:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace TestRandomNumberSignalR
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
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)
{
services.AddSingleton(new UpdateRandomNumber());
services.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.UseSignalR(routes =>
{
routes.MapHub<TestHub>("/testHub");
});
}
}
}
Here is the random number class:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TestRandomNumberSignalR
{
public class UpdateRandomNumber
{
private bool _continue = true;
public UpdateRandomNumber()
{
var task = new Task(() => RandomNumberLoop(),
TaskCreationOptions.LongRunning);
task.Start();
}
private void RandomNumberLoop()
{
Random r = new Random();
while (_continue)
{
Thread.Sleep(3000);
int number = r.Next(0, 100);
Console.WriteLine("The random number is now " + number);
// Send new random number to connected subscribers here
// Something like TestHub.SendRandomNumber(number);
}
}
public void Stop()
{
_continue = false;
}
}
}
Now from this class (as I wrote in the comment) I want to send the new random number using SignalR. Only how to get the hub context in there?
Also I want to be able to acces the Stop() method on the class from within a controller, how can I acces that?
I now this is a well discussed subject, but still I can't find a working solution anywhere. Hope you can help me.
EDIT
Question 1
While the random loop is starting now (with many thanks to rasharasha), still some questions remain. I'm now unable to inject the proper UpdateRandomNumber into a controller. Lets say I want to be able to stop the loop calling the UpdateRandomNumber.Stop() method, how can I inject the UpdateRandomNumber singleton into a controller. I tried creating an interface:
public interface IUpdateRandomNumber
{
void Stop();
}
Changing the RandomNumber method to implement this:
public class UpdateRandomNumber : IUpdateRandomNumber
{
private bool _continue = true;
private IHubContext<TestHub> testHub;
public UpdateRandomNumber(IHubContext<TestHub> testHub)
{
this.testHub = testHub;
var task = new Task(() => RandomNumberLoop(),
TaskCreationOptions.LongRunning);
task.Start();
}
private void RandomNumberLoop()
{
Random r = new Random();
while (_continue)
{
Thread.Sleep(3000);
int number = r.Next(0, 100);
Console.WriteLine("The random number is now " + number);
// Send new random number to connected subscribers here
// Something like TestHub.SendRandomNumber(number);
}
}
public void Stop()
{
_continue = false;
}
}
And changing the add singleton method so it wil use the interface:
services.AddSingleton<IUpdateRandomNumber>(provider =>
{
var hubContext = provider.GetService<IHubContext<TestHub>>();
var updateRandomNumber = new UpdateRandomNumber(hubContext);
return updateRandomNumber;
});
I can now create a controller with a method to stop the randomnumber loop:
[Route("api/[controller]")]
[ApiController]
public class RandomController : ControllerBase
{
private readonly IUpdateRandomNumber _updateRandomNumber;
public RandomController(IUpdateRandomNumber updateRandomNumber)
{
_updateRandomNumber = updateRandomNumber;
}
// POST api/random
[HttpPost]
public void Post()
{
_updateRandomNumber.Stop();
}
However, this implementation will prevent the loop from starting again. So how can I acces the rondomnumber singleton from a controller?
Question 2
From my UpdateRandomNumber class I can now call:
testHub.Clients.All.SendAsync("ReceiveRandomBumber", number);
But why did I make the method in my testhub:
public async Task SendRandomNumber(int number)
{
await Clients.All.SendAsync("ReceiveRandomBumber", number);
}
It would be much more convienent to create the methods in the hub and them call them directly. Can this be done?
You can inject the TestHub into the controller using Constructor Injection. Since its already registered in the DI Container.
public class UpdateRandomNumber
{
private bool _continue = true;
private IHubContext<TestHub> testHub;
private Task randomNumberTask;
public UpdateRandomNumber(IHubContext<TestHub> testHub)
{
this.testHub=testHub;
randomNumberTask = new Task(() => RandomNumberLoop(),
TaskCreationOptions.LongRunning);
randomNumberTask.Start();
}
private async void RandomNumberLoop()
{
Random r = new Random();
while (_continue)
{
Thread.Sleep(3000);
int number = r.Next(0, 100);
Console.WriteLine("The random number is now " + number);
// Send new random number to connected subscribers here
await testHub.Clients.All.SendAsync($"ReceiveRandomNumber", number);
}
}
public void Stop()
{
_continue = false;
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
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)
{
services.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSingleton(provider =>
{
var hubContext = provider.GetService<IHubContext<TestHub>>();
var updateRandomNumber = new UpdateRandomNumber(hubContext);
return updateRandomNumber;
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
var updateRandonNumber = app.ApplicationServices.GetService<UpdateRandomNumber>();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
app.UseSignalR(routes =>
{
routes.MapHub<TestHub>("/testHub");
});
}
}