I have a web project which requires stats/logs on a web page to be updated from an external soap service. The approach I have decided to take with this is to use signalR, by having code that would execute a service method and return the results to all connected clients. This code will be continuously executed, with a delay between service calls.
I am having trouble putting all of the pieces together, probably because I am not sure where everything should go! Here is a rough breakdown at what I have done so far
class Data { ... }
interface IDataService
{
Task GetDataAsync(TimeSpan interval, CancellationToken cancellationToken, Action<Data> callback);
}
class DataService : IDataService
{
private readonly ExternalService _externalService;
public async Task GetDataAsync(TimeSpan interval, CancellationToken cancellationToken, Action<Data> callback)
{
while (!cancellationToken.IsCancellationRequested)
{
var data = await this._externalService.GetData();
callback(data);
await Task.Delay(interval).ConfigureAwait(false);
}
}
}
So above is the logic that gets the data from the external service, and executes a callback. As for signalR, the only thing I have done is as follows
public class DataHub : Hub
{
private readonly IDataService _service;
public DataHub(IDataService service)
{
this._service = service;
}
public async Task GetData()
{
var tenSeconds = new TimeSpan(0, 0, 10);
var token = new CancellationToken();
await _service.GetDataAsync(tenSeconds, token, d =>
{
// signal all connected clients
});
}
}
The GetData()
method can be called when clients connect (if it isn't already running) and the token can be cancelled when there are no more clients connected to the hub.
(Potential) Problems:
GetData
method running (unless I make the DataService class a singleton/injected as a singleton).Would be grateful if someone can point me to the right/best approach!
I have made a library that decouples your domain from SignalR, it will solve problem 1-3 for you.
Please have a look here https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/wiki
Problem 4, you should absolutely use a ASP.NET background worker over a standard thread, otherwise ASP won't know it exists and recycle the app etc
1,2,4: I would use a background task and then take advantage of the Connectionmanager class to retrieve the appropriate hub. Here you find more info about how to make calls from outside the hub.
3: To keep my services clean I usually register so called message services. My data service then calls a method from the message service which internally resolves a hub and propagates the message. On this way I keep my data services tidy as they only call a specific message service method (typically a one-liner). My data services dont need any knowledge of what my message service is doing.
I have managed to solve this issue by using the IRegisteredObject interface in the System.Web.Hosting namespace. So my scheduled task is:
public class DataTask : IRegisteredObject
{
private readonly IGlobalData _global;
private readonly IDataService _service;
private readonly IHubContext _hub;
private Timer _timer;
public DataTask(IGlobalData global, IDataService service, IHubContext hub)
{
this._global = global;
this._service = service;
this._hub = hub;
var interval = new TimeSpan(0, 0, 10);
this._timer = new Timer(updateClients, null, TimeSpan.Zero, interval);
// register this task with asp.net
HostingEnvironment.RegisterObject(this);
}
public void Stop(bool immediate)
{
_timer.Dispose();
HostingEnvironment.UnregisterObject(this);
}
private async void updateClients(object state)
{
var result = await this._service.GetData();
// call the hub
this._hub.Clients.All.updateData(result);
}
}
I did have quite a few issues! But this was due to the custom dependency resolver I was using for signalR (the problem was the client js function was not being called from this task). Once that was resolved, everything works as expected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With