ASP.NET Core SignalR & SignalR Debugging Tool

Gourav Dwivedi
9 min readSep 10, 2020

Overview

In this blog, we will understand & learn SignalR in-depth and how we can create, scale and debug SignalR application using SignalR Web Client. I hope you will enjoy this blog.

Traditionally, whenever a client needs to send/receive data to/from the server then it will make an HTTP request to the server and server respond to the client. If data changes, then again the client has to send the request to the server. Most of the times, the client is not aware of the latest changes.

SignalR solves this problem by using the Push Model. It provides the ability to the server to push the data to the client.

What is ASP.NET Core SignalR?

It is an open-source library that enables the application to do real-time communication between server and client. SignalR is an abstraction over different transport like WebSocket, Server-Sent Event, and Long Polling.

SignalR is available in ASP.NET core 2.1 onwards.

Features of ASP.NET Core SignalR

  • Handles connection management automatically.
  • Sends the message to all the connected clients or groups of clients or a specific client.
  • Easily scalable.
  • Supports the streaming of data.
  • Supports token-based authentication

When To Use SignalR

  • We can use the SignalR when there is a high frequency of data update on the server and the client needs the latest data always.
  • We can use the SignalR in applications like Games, Chat Apps, Stock market Apps, Notification Apps, Email Apps, News Apps, Voting Apps, Location Tracking Apps, Real-Time Targeted ads, etc.

SignalR Transport

SignalR supports different techniques for communication. WebSocket is an ideal transport for ASP.NET Core SignalR. However, if either client or server doesn’t support WebSocket, then SignalR will attempt to use other transports to make connections. Following are the supported transports:

  • WebSocket
  • Server-Sent Events
  • Long polling

WebSocket

It is the only transport that establishes a true persistent, two-way communication between the server and the client in SignalR.

WebSocket Connection flow

Step 1: The client sends a request to the server & server responds with supported transports like WebSocket, Server-Sent Event & Long polling.

Step 2: SignalR client library processes the server response & also check whether the client also supports WebSocket. If both client-server support WebSocket transport, then the client sends another request to the server to upgrade the existing connection to WebSocket. If the server accepts the request, then the server returns a response with the HTTP status code 101 which means client-server is switching the connection protocol and WebSocket connection has been established.

Step 3: Once the connection is established, the client sends the data to the server and the server also sends the data to the client (RPC) using the same connection. This connection gets closed when the page is refreshed or the browser is closed.

WebSocket supports SSL encryption. Url comparison with HTTP vs WebSocket.

HTTP vs WebSocket

Server-Sent Events (SSE)

SSE is a server push technology enabling a client to receive automatic updates from a server via HTTP connection. The SSE uses the EventSource object. EventSource creates a connection from the server to the client. Once the connection is established, the server can send the data to the client anytime using this connection. SSE is supported on all browsers, except Internet Explorer. SSE is unidirectional (Server to the client). We can use SSE for status updates, news feeds, etc. required from the server.

More information about EventSource is available here.

SSE Connection flow

Step 1: The client sends a request to the server & server responds with supported transports like WebSocket, Server-Sent Event & Long polling.

Step 2: SignalR client library process the server response & also check whether the client also supports SSE. If both client-server support SSE transport, then the client sends another request to the server to make a new SSE connection. If the server accepts the request, then the server returns a response with the HTTP status code 200 which means a connection established.

Step 3: Once the connection is established, only the server sends the data to the client. This connection is closed when the page is refreshed or the browser is closed.

Note: SignalR is able to send the data to the server even if the transport is SSE. SignalR internally makes an HTTP Post request to the server using connection id whenever the client wants to send the data to the server. This is the beauty of SignalR 😊.

Long Polling

It uses HTTP protocol, which is supported by all the platforms. It doesn’t create any persistent connection. It is completely based on the request-response model. It polls the server with a request, the server will hold the request and connection remains open until the server responds or connection closes. Once the connection is closed, a new connection is requested immediately. This may introduce some latency while the connection resets.

Long Polling Connection flow

Step 1: The client sends a request to the server & server responds with supported transports like WebSocket, Server-Sent Event & Long polling.

Step 2: When SignalR client or server doesn’t support WebSocket & SSE, then SignalR uses Long polling. It polls the server with a GET request, that request stays open until the server responds with the data or connection gets timed out.

SignalR also polls the server with an HTTP POST request to check the server availability.

SignalR Hub

SignalR uses Hub to communicate between client & server. It is a high-level pipeline that allows the client and the server to call each other methods. Client & server use hub to transmit & receive the data.

SignalR Scaling

We can scale the web application in two ways:

  • Scale Up(or Vertical Scaling): Use large server(Big size of RAM, a large number of CPUs, etc)
  • Scale-Out(or Horizontal Scaling): Add more server to handle the load & also required a load balancer.

The problem with scaling up is that you quickly hit a limit on the size of the machine. However, when you scale out, the client can get routed to different servers. A client connected to one server will not receive the message sent from another server.

The problem we face when we scale out the SignalR application is connection management. One server does not have knowledge about clients connected to another servers. When a server pushes a message to all the clients, the message is received only by the clients which are connected to that particular server and not to the clients which are connected to another server.

We can solve this problem using Backplane or Azure SignalR Service.

Backplane

The backplane is aware of all the servers, and it doesn’t matter which server a client is connected to. When a server sends a message, it will pass to the backplane. And the backplane will forward the messages to all the server. Then, the server will send that message to all the connected clients.

We can use Redis, NCache, Orleans and other third-party SignalR backplane providers. We can create custom Backplane using the Azure service bus, etc.

Redis backplane is based on Publisher Subscriber pattern. With minimal changes in the code, we can easily integrate the Redis Backplane:

  • Install nuget package -> Microsoft.AspNetCore.SignalR.StackExchangeRedis
  • Modify Startup.cs
public void ConfigureServices(IServiceCollection services){...services.AddSignalR().AddStackExchangeRedis("<your_Redis_connection_string>");}

Points to be considered while using Backplane:

  • Throughput is lower as compared to the client directly connected to the single node server.
  • Sticky Session is required when the client uses transport like Long Polling or SSE. Sticky Session is not needed when all the clients use only Web Socket or client enable SkipNegotation setting.
  • It is recommended to use Backplane if the application is hosted on your own infrastructure (on-premises).

Azure SignalR Service

The Azure SignalR Service is a proxy rather than a backplane. It manages all the client connections and app servers information.

Each time a client initiates a connection to the server, the client is redirected to connect to the Azure service. So, all the SignalR client-server communication happen through Azure SignalR Service.

When the server wants to send a message to all the client, then it first sends the message to Azure SignalR Service, then the Azure Service forwards that message to all the clients because it has all the details about the clients.

Azure SignalR Service manages all the client connections and app servers. With minimal changes in the code, we can easily integrate the Azure SignalR Service:

public void ConfigureServices(IServiceCollection services){services.AddSignalR().AddAzureSignalR("<Connection String>");}public void Configure(IApplicationBuilder app, IHostingEnvironment env){app.UseAzureSignalR(option =>{  option.MapHub<SampleHub>(new PathString("/Test/Hub")); });}Modify Startup.cs
  • The backplane is aware of all the servers, and it doesn’t matter which server a client connects to.
  • Azure SignalR Service allows multiple instances to work together to scale to millions of client connections.

What is SignalR Web Client?

It is a debugging tool to test the SignalR hub without writing the SignalR client code.

In the case of REST API, we use POST man to test the API. Similarly, we can test Hub’s using SignalR Web Client.

SignalR Web Client is available here.

Source Code on GitHub

SignalR Web Client Demo

How to create a SignalR application?

  1. Create an ASP.NET Core Application. You can choose any template API or Web Application, based on your need. For this sample, we are using ASP.NET Core 3.1 MVC Web Application template.

2. Once your project is ready, create a new folder called Hubs.

3. Create a new file “NotificationHub.cs” under the Hubs folder.

using Microsoft.AspNetCore.SignalR;public class NotificationHub : Hub{public void SendMessage(string message){Clients.All.SendAsync(“Notification”, message);}}

4. We have to enable SignalR, so we have to do some changes in the Startup.cs file.

Statup.cs

public void ConfigureServices(IServiceCollection services){services.AddCors(options => options.AddPolicy("Cors", 
builder =>
{builder.AllowAnyMethod().AllowAnyHeader().AllowCredentials().WithOrigins("http://localhost:8080");}));services.AddSignalR();services.AddControllersWithViews();}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){….app.UseCors("Cors");….app.UseSignalR(config =>{config.MapHub<NotificationHub>(new PathString("/Notification"));});app.UseEndpoints(endpoints =>{endpoints.MapControllerRoute(name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");});}

We have to use CORS when our client is running in a different domain. In the above example, the client is running under “localhost:8080”.

5. We can use NotificationHub inside the controller. We need to just inject the IHubContext in the controller.

public class DemoController : Controller
{
private IHubContext<NotificationHub> _hubContext;
public DemoController(IHubContext<NotificationHub> hubContext)
{
_hubContext = hubContext;
}
public async Task<IActionResult> Index()
{
return View();
}
public async Task<string> Notify()
{
await _hubContext.Clients.All.SendAsync("Notification", $"This message has been sent from Demo controller.");
return "Notification Sent to all the clients";
}
}

6. JavaScript Client code

<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/3.1.3/signalr.min.js"></script>
</head>
<body>
<div>
Message: <input type="text" id="msg" />
<button id="send-btn">Send</button>
</div>
<div id="Response">
<ul id="messagesList">
</ul>
</div>
</body>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("/Notification")
.configureLogging(signalR.LogLevel.Information)
.build();
async function start() {
try {
await connection.start();
updateUI("Connected")
} catch (err) {
console.log(err);
setTimeout(() => start(), 5000);
}
};
function updateUI(data) {
const li = document.createElement("li");
li.textContent = data;
document.getElementById("messagesList").appendChild(li);
}
connection.on("Notification", (data) => {
updateUI(data);
});
connection.onclose(async () => {
await start();
});
// Start the connection.
start();
document.getElementById("send-btn").addEventListener("click", function ()
{
var data = document.getElementById("msg").value;
connection.invoke("SendMessage", data).catch(err => console.error(err));
})
</script>
</html>
Demo

References

--

--