Search  
Always will be ready notify the world about expectations as easy as possible: job change page
Aug 26, 2022

SignalR: The best ASP.NET Core Library for Building Interactive Apps

Author:
Fiodar Sazanavets
Source:
Views:
4516

Real-time interactivity is the bread and butter of modern web, mobile, and desktop applications. It doesn’t matter how hard you worked to build your app. If it’s not interactive enough, users will just ignore it in favor of competing apps that are interactive.

When you open your social media profile, you expect the notifications and messages to get delivered to you in real-time. You aren’t expected to keep manually refreshing the page to see if there are any updates. We all take this for granted now.

Although it makes using the application more convenient for the user, it can be a source of a major headache for developers. After all, it’s easy to get your client application to submit requests to the server, but it’s not as easy to get the server to deliver updates to the client in real-time.

Here, I will propose a solution to this problem that is incredibly easy to implement, as long as your server application is written on ASP.NET Core platform. We will cover the following:

  • Why building interactive apps is so difficult.
  • How SignalR solves these problems with elegance.
  • How SignalR supports any client types.
  • What makes SignalR so easy to scale on premise and in the cloud.

So, let’s begin!

Why Building Interactive Apps is so Difficult

Of course, there are ways to do so. But each of the traditional methods has its pros and cons. For example, you can utilize a technique called long polling. This is when you keep sending asynchronous requests to the server with the expectation that the server might not respond right away. The client just waits for the response for as long as it can. Once you receive the response, you start over. And you do it indefinitely while your application is running.

Even though this approach works on pretty much any system, it’s not without drawbacks. Long-polling isn’t the most efficient communication mechanism, but it’s still efficient enough for most network configurations. The biggest problem with long polling is the complexity of its implementation. The code for doing long-polling requests is typically verbose and not easy to maintain.

Another alternative would be WebSockets protocol. It’s more efficient than long polling, as it allows you to establish a lightweight persistent connection between the client and the server. Once established, the messages can be sent either way. This makes WebSockets an excellent choice from the performance point of view. But it has the following caveats:

  • The network configuration of the server or client might not support it.
  • There is a limit to the number of simultaneous connections your server can keep open and scaling this can be a challenge.
  • WebSockets send and receive raw bytes, which add additional handling to the codebase to make these something meaningful.

The latter can be seen in the following, a C# implementation of WebSockets, which, despite being a very basic message handler, is represented by some complex code:

static async Task ReceiveAsync(ClientWebSocket ws)
{
    var buffer = new byte[4096];
 
    try
    {
        while (true)
        {
            var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
            if (result.MessageType == WebSocketMessageType.Close)
            {
                await ws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                break;
            }
            else
            {
                Console.WriteLine(Encoding.Default.GetString(Decode(buffer)));
                buffer = new byte[4096];
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
        return;
    }
}
 
static byte[] Decode(byte[] packet)
{
    var i = packet.Length - 1;
    while (i >= 0 && packet[i] == 0)
    {
        --i;
    }
 
    var temp = new byte[i + 1];
    Array.Copy(packet, temp, i + 1);
    return temp;
}

How SignalR Solves These Problems With Elegance

SignalR is an inbuilt ASP.NEt Core library that is designed to be incredibly easy to use, this is what makes it so powerful. The fact that it abstracts away all implementational complexity of two-way communication between the client and the server, allows you to write your code in such a way as if you are calling remote procedures. When you need to trigger a method on the server, your client-side code will look almost like you are just calling a local method. The same goes for calling methods on the connected clients from the server.

Behind the scenes, SignalR uses the following communication mechanisms in this priority order: WebSockets, server-sent events, or long polling. It can also be set explicitly in either the client or the server configuration.

The beauty of this abstraction and ability to fallback is that, regardless of which communication mechanism is being used, your code will be identical. SignalR will handle everything for you. This means you don’t have to rewrite your app if you find out that the system it gets deployed on doesn’t support WebSockets.

But to demonstrate how easy SignalR is to use, let’s dive into some examples. To enable SignalR on the server side, all you have to do is create a class that inherits from Microsoft.AspNetCore.SignalR.Hub:

using Microsoft.AspNetCore.SignalR;
 
namespace SignalRServer.Hubs
{
    public class LearningHub : Hub
    {
        public async Task SendMessage(string message)
        {
            await Clients.All.SendAsync("ReceiveMessage", message);
        }
    }
}

In this example, our Hub implementation has a SendMessage method that can be triggered by connected clients. And inside this method, all we do is re-send the message to all connected clients. SignalR can do far more than this, but we will keep it simple for our examples.

To register this Hub implementation as an HTTP endpoint that the clients can connect to, all we have to do is add this statement to our Program.cs file (or Startup class if you are using an old-style ASP.NET project template). 

builder.Services.AddSignalR();

And have the actual endpoint registered via a statement similar to this: 

app.MapHub<LearningHub>(“/learningHub”);

And that’s it. Our basic server implementation is done. I will now demonstrate how easy it is to configure the clients.

How SignalR Supports any Client Types

Officially, three types of SignalR clients are supported by Microsoft — .NET, Java, and JavaScript. Microsoft maintains libraries for them and keeps them up to date. But because SignalR is an open-source library, client libraries have been developed for pretty much any language and framework you can think of. And they work just as well as the official libraries. Regardless of what type of client you are using to connect to your SignalR server, the process of using it will be the same.

Once you’ve added an appropriate library to your project, the first thing you will need to do is create an object that represents a SignalR Hub connection. You will need to specify the URL to your SignalR Hub endpoint. And you may also add any other configuration (for example, enable or disable specific transport mechanisms). A JavaScript implementation of this would look similar to this:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/learningHub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

Next, you will need to assign named event handlers to this object. These will be the events that SignalR Hub will be able to trigger from the server. In the Hub implementation I created earlier, we are sending a message to the event handler that’s called ReceiveMessage. So we can add that to our JavaScript example client like this:

connection.on("ReceiveMessage", (message) => {
    $('#signalr-message-panel').prepend($('<div />').text(message));
});

Once we have registered all the event handlers, we can connect our Hub connection object to our server-side hub. We can do this with the dedicated method on the connection object, like so:

await connection.start();

Now we have the connection your client can trigger any methods on the server-side Hub. You just specify the method name and insert any other parameters that the method expects. For example, the SendMessage method that we added to our Hub earlier can be triggered like this:

connection
   .invoke("BroadcastMessage", message)
   .catch(err => console.error(err.toString()));

That’s it! That’s how easy it is to use SignalR. Everything else is handled for you. A persistent connection between the client and the server is maintained. An appropriate communication mechanism is selected and implemented. And so on.

I’m sure this raises some questions for you: ”How does SignalR perform at scale?”; “How does it deal with the limit on many simultaneous connections on a single server?”; ”If you had multiple instances of the server, then how would you send messages between the servers?” Well, the good news is that SignalR has all this covered too.

What Makes SignalR so Easy to Scale on Premise and in the Cloud

If you run your server application on premises, then you can scale SignalR by connecting it to Redis backplane. All you have to do is add Microsoft.AspNetCore.SignalR.StackExchangeRedis NuGet package to your application. Then, provide it with the Redis connection string while registering SignalR dependencies:

builder.Services
       .AddSignalR()
       .AddStackExchangeRedis(“localhost: 6380”);

Redis backplane will take care of routing requests received by a specific server to the right instance of the server app and even out to another client if needed.

If you are scaling your application via the cloud, then the process of scaling out SignalR is even easier, especially if you use Microsoft Azure. You can create an instance of an Azure SignalR Service and connect your server-side application to it. All you have to do to enable this is install Microsoft.Azure.SignalR NuGet package and change the SignalR registration statement in your code to this:

builder.Services.AddSignalR().AddAzureSignalR();

In this case, all your SignalR connections will be managed by an elastic and stateless SignalR Service in the cloud. It will scale automatically in response to the demand. The service is fully managed by Azure, so you won’t have to worry about it.

On top of this, SignalR is also easy to configure both from the server and client sides. For most scenarios, the default configuration will be sufficient. But you can fine-tune it as much as you want. Anything from healthcheck poll frequency to data buffer size can be configured to suit your specific needs.

Wrapping up

This was a brief overview of what SignalR is capable of and how easy it is to use. If you want to try SignalR out, the documentation on the official Microsoft website will help you to get started.

This documentation is great for getting started and will be sufficient for the majority of scenarios. However, if you want to go beyond the simple use cases, dig into the details, or even find the answers to some more obscure questions I would suggest checking out my book. SignalR on .NET 6 — the complete guide is where I consolidated all of the information I could find on SignalR.

I have gone through the official SignalR documentation, identified the gaps in it and looked at the SignalR source code, so you don’t have to. I’ve also added some previously undocumented scenarios that I have found useful in my own work, such as connecting a raw WebSocket client to the SignalR server. This allowed me to support both bespoke SignalR clients and pre-built clients that only supported raw WebSockets protocol.

Happy coding!

Similar
Jan 29
Author: Alex Maher
Performance tricks & best practices, beginner friendly Hey there! Today, I’m sharing some straightforward ways to speed up your .NET Core apps. No fancy words, just simple stuff that works. Let’s dive in! • • • 1. Asynchronous programming Asynchronous...
Sep 14, 2023
Author: Mickvdv
In the world of modern software architecture, reliable communication between different components or microservices is crucial. This is where RabbitMQ, a queue based message broker, can play a vital role. RabbitMQ is a popular choice for implementing message queuing systems,...
Mar 28, 2023
Author: Anupam Maiti
Introduction This article demonstrates Middleware concepts in ASP.NET Core. At the end of this article, you will have clear understanding on below points: What is Middleware? Why Middleware ordering is important? Understanding of Run, Use and Map Method. How to...
Jun 25, 2022
Author: Philipp Bauknecht
Sometimes your web app needs to do work in the background periodically e.g. to sync data. This article provides a walkthrough how to implement such a background task and how to enabled/disable a background task during runtime using a RESTful...
Send message
Type
Email
Your name
*Message