Real-time Streaming with SignalR and Angular

Zdravko Kolev
Ignite UI
Published in
7 min readMar 30, 2021

In this topic, you’ll learn how to create apps for both streaming and receiving data with ASP.NET Core SignalR and Angular.

What you’ll need:

  • Basic knowledge of ASP.NET Core and Angular.
  • .NET Core 3.1 installed and Visual Studio 2019 IDE.

What you’ll know by the end of this topic:

  • How to add and use SignalR.
  • How to open Client connection and use the method invocation concept to stream data per Client.
  • How to consume the SignalR service with Angular application by using Observables.

SignalR takes advantage of several transports and it automatically selects the best available transport given the client and server’s capabilities — WebSockets, Server Send Events, or Long-polling.

When we talk in terms of WebSockets (Putting SSE and Long-polling out of the equation) when the client is real-time connected to the server, whenever something happens the server will know to send a message over that WebSocket back to the client. With old-school clients and servers, the Long-polling transport would be used.

This is how SignalR handles modern clients and servers, it uses WebSockets under the hood when available, and gracefully falls back to other techniques and technologies when it isn’t:

Negotiation process

It’s like a handshake, the Client and Server agree on what to use and they use it. This is called process negotiation.

Let’s party!

SignalR Demo

The purpose of this demo is to showcase a financial screen board with a Real-time data stream using ASP.NET Core SignalR. Demo in full screen.

SignalR Server Configuration

Create ASP.NET Core App

Let’s see how to set up the ASP.NET Core SignalR application. In Visual Studio from File >> New project choose ASP.NET Core Web Application and follow the setup. Feel free to follow the official Microsoft documentation tutorial if you experience any configuration difficulties.

Visual Studio -> New Project

SignalR Config Setup

Add the following code to the Startup.cs file:

  • Endpoint part of the Configure method.
app.UseEndpoints(endpoints => 
{
endpoints.MapControllers();
endpoints.MapHub<StreamHub>("/streamHub");
});
  • Add SignalR usage to the ConfigureServices method.
services.AddSignalR(options => 
{
options.EnableDetailedErrors = true;
});

The changes above are adding SignalR to the ASP.NET Core dependency injection and routing system.

Now, let’s set up an additional basic configuration. Open the properties/launchSettings.json file and modify it accordingly:

"profiles": { 
"WebAPI": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "https://localhost:5001; http://localhost:5000",
"environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development"
}
}
}

Our server-side project will run on localhost:5001 and the client side will run on localhost:4200, so in order to establish communication between those two, we need to enable CORS. Let's open the Startup.cs class and modify it:

public void ConfigureServices(IServiceCollection services) 
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithOrigins("http://localhost:4200"));
});
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseCors("CorsPolicy");
...

If you experience a specific problem with enabling Cross-origin resource sharing, check out the official Microsoft topic.

SignalR Hub Setup

Let’s start by explaining what is a SignalR hub? The SignalR Hub API enables you to call methods on connected clients from the server. In the server code, you define methods that are called by the client. In SignalR there is this concept called Invocation — you can actually be calling the hub from the client with a particular method. In the client code, you define methods that are called from the server.

The actual hub lives on the server-side. Imagine you have Clients and the Hub is between all of them. You can say something to all the Clients with Clients.All.doWork() by invoking a method on the hub. This will goes to all connected clients. Also, you can communicate with only one client, which is the Caller, because he is the caller of that particular method.

We’ve created a StreamHub class that inherits the base Hub class, which is responsible for managing connections, groups, and messaging. It’s good to keep in mind that the Hub class is stateless and each new invocation of a certain method is in a new instance of this class. It’s useless to save state in instance properties, rather we suggest using static properties, in our case we use static key-value pair collection to store data for each connected client.

Other useful properties of this class are Clients, Context, and Groups. They can help you to manage certain behavior based on the unique ConnectionID. Also, this class provides you with the following useful methods:

  • OnConnectedAsync() — Called when a new connection is established with the hub.
  • OnDisconnectedAsync(Exception) — Called when a connection with the hub is terminated.

They allow us to perform any additional logic when a connection is established or closed. In our application, we’ve also added UpdateParameters method that gets a Context connection ID and uses it to send back data at a certain interval. As you can see we communicate over a unique ConnectionID which prevents a streaming intervention from other Clients.

public async void UpdateParameters(int interval, int volume, bool live = false, bool updateAll = true) 
{
...
var connection = Context.ConnectionId; var clients = Clients;
...
if (!clientConnections.ContainsKey(connection))
{
clientConnections.Add(connection, new TimerManager(async() =>
{
...
Send(newDataArray, client, connection);
}, interval));
} else
{
clientConnections[connection].Stop();
clientConnections[connection] = new TimerManager(async () =>
{
var client = clients.Client(connection);
...
await Send(newDataArray, client, connection);
}, interval);
}
...
}

When the data is ready we transfer it by emitting a transferdata event with the help of SendAsync Method.

public async Task Send(FinancialData[] array, IClientProxy client, string connection) 
{
await client.SendAsync("transferdata", array);
}
...
// Called when a connection with the hub is terminated public override Task OnDisconnectedAsync(Exception exception)
{
StopTimer();
clientConnections.Remove(Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}

Our client application would be listening to the registered events:

private registerSignalEvents() 
{
this.hubConnection.onclose(() => {
this.hasRemoteConnection = false;
});
this.hubConnection.on('transferdata', (data) => {
this.data.next(data);
});
};

The Public GitHub repository of the ASP.NET Core Application could be found here.

Create SignalR Client Library

We will create an Angular project in order to consume the SignalR service. Github repository with the actual application can be found here.

First, start by installing SignalR:

npm install @microsoft/signalr

Keep in mind that we are going to send the HTTP request towards our server, so we need HttpClientModule as well.

Below you will find the signal-r.service.ts file that handles the hub connection builder.

export class SignalRService implements OnDestroy {
public data: BehaviorSubject<any[]>;
public hasRemoteConnection: boolean;
private hubConnection: signalR.HubConnection;
...
constructor(private zone: NgZone, private http: HttpClient) {
this.data = new BehaviorSubject([]);
}
...
// Start Hub Connection and Register events
public startConnection = (interval = 500, volume = 1000, live = false, updateAll = true) => {
this.hubConnection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl('https://www.infragistics.com/angular-apis/webapi/streamHub')
.build();
this.hubConnection
.start()
.then(() => {
...
this.registerSignalEvents();
this.broadcastParams(interval, volume, live, updateAll);
})
.catch(() => { ... });
}
// Change the parameters like frequency and data volume
public broadcastParams = (frequency, volume, live, updateAll = true) => {
this.hubConnection.invoke('updateparameters', frequency, volume, live, updateAll)
.then(() => console.log('requestLiveData', volume))
.catch(err => {
console.error(err);
});
}
// Register events
private registerSignalEvents() {
this.hubConnection.onclose(() => {
this.hasRemoteConnection = false;
});
this.hubConnection.on('transferdata', (data) => {
this.data.next(data);
})
}

In your app.component add use the newly created startConnection method

constructor(public dataService: SignalRService) {} public ngOnInit() { 
this.dataService.startConnection(this.frequency, this.dataVolume,
true, false);
}

Grid Data Binding

As we have seen so far in our client code we set up a listener for transferdata event, which receives as an argument the updated data array. In order to pass the newly received data to our grid, we use an observable. In order to set that, we need to bind the grid's data source to the data observable like so:

Every time when new data is received from the server to the client we call the next() method of the data observable.

this.hubConnection.on('transferdata', (data) => 
{
this.data.next(data);
});

Topic Takeaways

If you don’t want to refresh your application, rather just see when the data is updated, you should consider ASP.NET Core SignalR. I definitely recommend going for streaming content when you think your data is large, or if you want a smooth user experience without blocking the client by showing endless spinners.

Using SignalR Hub communication is easy and intuitive and with the help of Angular Observables, you can create a powerful application that uses data streaming with WebSockets.

Originally published at https://www.infragistics.com.

--

--

Zdravko Kolev
Ignite UI

Product Development Manager at Infragistics, passionate about technology, innovation, personal growth, leadership, and team development.