In this blog, we will take a look at a simple scenario to use SignalR to track live data and feed it in our client application. For this let us consider a simple scenario where we need to know what's the price of a BitCoin in various countries right now or get stock market prices. You can also think of any other scenario where the data gets changed every second.
Let's look at what you need to have and what you need to know before we get into detail.
Prerequisites
- A computer 💻 😝
- Visual Studio or VS Code 🎨
- Knowledge in consuming API's using C#
- Angular basics to visualize things
Getting started
- Create a simple ASP.NET Web application using the default template. This will provide you with a weather forecast controller
- Create a simple angular app using ng new
signalR-client
- Since we know that any angular app by default will run in the
port 4200
let's solve the CORS issue first. If you have experience in creating apps earlier you knew this is the first most thing to be done for the client-server communication to happen.
Add the below piece to your startup to be able to communicate with the client without any issues.
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder
.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
Please note that we are not using the AllowAnyOrigin()
method to enable cors from any origin, but we explicitly say which origin to allow WithOrigins(“[http://localhost:4200](http://localhost:4200/)”)
. We are doing this because in .NET Core 3.0 the combination of AllowAnyOrigin
and AllowCredentials
is considered as an insecure CORS configuration.
Server side code
Now we need to bring some data. How do we do that? As we will need Live BitCoin price, we will do with the coindesk
API. Let's create a simple service to obtain data from the coindesk API and convert it based on our response model.
Since by default the coindesk API response gives out results for three countries.
- USD
- GBP
- EUR
We will plot charts for all three of them. Just for convenience, I am converting the response into a JSON object like this.
public class CoinResponse {
public string Name { get; set; }
public List<ReportResult> ReportData { get; set; }
}
public class ReportResult {
public string Group { get; set; }
public string Count { get; set; }
}
We are grouping each currency based on the currency code and the price. Now that we have a simple service to get data, let's look at the signalR stuff to be able to send data to the client-side in real-time.
Adding SignalR
We need to use the Hub
class from the Microsoft.AspNetCore.SignalR
and create a class like this
public class CoinHub : Hub{
}
What is this Hub? Why do we need it? And why is the class empty?
Hub is nothing but a communication pipeline between client and server using SignalR. We don't need any methods here, because we are just doing one-way
communication, where the server will send data to the client.
Adding a controller
Now that we have everything in place, the last part is to add an Endpoint. Let's call the controller as CoinController
and we will be using the IHubContext
and call our service which fetches data from the coindesk API.
The controller will look like this. But notice something called TimerManager
.
[Route("api/coinPrice")]
[ApiController]
public class CoinController : ControllerBase
{
private readonly IHubContext<CoinHub> _hub;
public CoinController(IHubContext<CoinHub> hub)
{
_hub = hub;
}
public IActionResult Get()
{
var timerManager = new TimerManager(() => _hub.Clients.All.SendAsync("getCoinPrice", CoinService.GetCoinPrice()));
return Ok(new { Message = "Request Completed" });
}
}
Once you have added a controller don't forget to add the endpoint to your startup pointing to the empty Hub class that we created
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<CoinHub>("/coinPrice");
});
The route in the MapHub
should match the controller endpoint path that we created above.
TimerManager
This is a class to be able to repeatedly poll for whatever interval that you specify and send results to the client. Here I am adding a timer for every 10 seconds. You can modify the interval as per your needs.
public class TimerManager {
private readonly Timer _timer;
private readonly AutoResetEvent _autoResetEvent;
private readonly Action _action;
public DateTime TimerStarted { get; }
public TimerManager(Action action)
{
_action = action;
_autoResetEvent = new AutoResetEvent(false);
_timer = new Timer(Execute, _autoResetEvent, 10000, 10000);
TimerStarted = DateTime.Now;
}
public void Execute(object stateInfo)
{
_action();
if ((DateTime.Now - TimerStarted).Seconds > 60)
{
_timer.Dispose();
}
}
}
}
Enabling Angular client to consume SignalR
-
The first thing to do is install the signalR library. You can do it by running
npm i @aspnet/signalr
Now let's create a service to consume data using the SignalR Hub
private _hubConnection: signalR.HubConnection
public startConnection() {
this._hubConnection = new signalR.HubConnectionBuilder()
.withUrl('https://localhost:44316/coinPrice').build();
this._hubConnection.start()
.then(() => console.log('connection started'))
.catch(error => console.log('Error while creating connection:' + error));
}
public addCoinPriceListener = () => {
this._hubConnection.on('getCoinPrice', (response) => {
this.data = response;
this.convertDateToTimeStamp();
})
}
- The code above is self-explanatory and very simple to understand. We are using the
SignalRHubConnectionBuilder
with our endpoint URL and we start the connection. - Once we have the connection started using the connection call the hub's method. From the server-side, on the
Get
method in the controller, we can give the method name asgetCoinPrice
and obtain the response. - Now the coindesk results response has only the date string. We need to convert that into a realTime object with an interval in minutes. I have written a simple method to map the result
convertDateToTimeStamp() {
this.data.filter(x => {
x.reportData.filter(r => {
var timeStamp = moment.utc(r.group).toDate();
const dataInDecimal = r.count.replace(/\,/g, '');
var type = '';
if (x.name === 'USD') {
}
// plot the data only when it has changed
if (dataInDecimal != this.dataInDecimalcopy) {
const dataCopy = this.series.find(s => s.name === x.name).data.slice(0);
const timeCopy = this.timestamps.find(t => t.name === x.name).timedata.slice(0);
dataCopy.push(parseFloat(dataInDecimal))
timeCopy.push(timeStamp.getHours() + ":" + timeStamp.getMinutes() + ":" + timeStamp.getSeconds())
this.dataInDecimalcopy = +dataInDecimal;
// *optional: limit amount of data points shown
if (dataCopy.length > 20) { dataCopy.shift(); }
if (timeCopy.length > 20) { timeCopy.shift(); }
// set the OG data equal to the copy
this.series.find(s => s.name === x.name).data = dataCopy;
this.timestamps.find(t => t.name === x.name).timedata = timeCopy;
}
});
});
}
Once we have done that, you can use any Chart Library or a simple table to visualize the data. Here I will be using KendoCharts
to visualize and plot data. The HTML code for KendoCharts may look like this.
<kendo-chart>
<kendo-chart-title text="Bitcoin live pricing chart"></kendo-chart-title>
<kendo-chart-legend position="bottom"
orientation="horizontal"></kendo-chart-legend>
<kendo-chart-tooltip format="{0}"></kendo-chart-tooltip>
<kendo-chart-value-axis>
<kendo-chart-value-axis-item [title]="{ text: 'Price' }"
[min]="6000"
[max]="10000">
</kendo-chart-value-axis-item>
</kendo-chart-value-axis>
<kendo-chart-category-axis>
<kendo-chart-category-axis-item [title]="{ text: 'Time stamps' }"
[categories]="_coinService.timestamps[0].timedata">
</kendo-chart-category-axis-item>
</kendo-chart-category-axis>
<kendo-chart-series>
<kendo-chart-series-item *ngFor="let item of _coinService.series"
[style]="'smooth'"
type="line"
[line]="{ style:'smooth' }"
[data]="item.data"
[name]="item.name">
</kendo-chart-series-item>
</kendo-chart-series>
</kendo-chart>
Once you have done that you will be able to visualize live bitcoin prices like this. You can also change the type of chart. Here I have used Line
chart, you may also use Area
, Bar
💹
Conclusion
I hope this blog gives you an understanding of using signalR to your own use cases. You can also try out various scenarios to be able to get more familiar with SignalR 💥
This was originally posted on Hariharan Subramanian
Top comments (0)