SignalR In .NET 6 Using NCache As Backplane

This article will explain SignalR and how to use it with NCache as a backplane in .NET 6. It will be given a practical example of a Web Application using SignalR with NCache as a backplane in .NET 6.

The practical example will explain how to set up the project and how to describe its main methods usage. In the end, we will have the Web Application working, with clients sending messages from one to another through NCache Backplane.

SignalR is a very powerful library, but that does not mean it is complex to set up and use. You only need a few lines of code in order to start using SignalR in your Web App and provide real-time communication to your App clients.

What is SignalR?

SignalR is an open-source library used for real-time web apps. Its main feature is that the ability to push content from your server-side to your clients instantly. Clients can also push content to the server instantly.

Common usage for SignalR

  • Apps with high-frequency updates like games, social networks, maps, and GPS apps;
  • Collaborative apps like whiteboard and team meeting apps;
  • Apps with notifications like email, and chat;
  • Basically any app that requires instant communication.

Message transports

SignalR automatically chooses among the best transport method based on client and server characteristics. These are the following transportation methods supported by SignalR:

  • WebSockets
  • Server-Sent Events
  • Long Polling.

Hubs

Hubs are SignalR's core engine. They is where the communication between client and server is managed.

First, the backend server contacts the hub sending the method alongside the parameters. The hub automatically handles this by calling the client-side code looking for a match with the method name. When it matches the method, then it calls the method in the client-side code with the parameters deserialized.

There are two built-in Hub Protocols to transport the parameters:

  • Text protocol based on JSON
  • Binary protocol based on MessagePack

SignalR Methods

In SignalR the methods work in a very similar way to the Queues on RabbitMQ.

Every message must be sent through a method. Then, SignalR looks for a connected client who matches the method in order to direct the message to the correct client(s).

Main Communication Flows

The following methods are the most used when handling communication among clients:

  • Clients.All.SendAsync

    *   Invokes the method on the message hub, sending the objects to all clients.
    
  • Clients.Caller.SendAsync

    *   Invokes the method on the message hub, sending the objects to itself.
    
  • Clients.Client({connectionId}).SendAsync

    *   Invokes the method on the message hub, sending the objects to the specified client.
    
  • Groups.AddToGroupAsync

    *   Adds a client to the specified group.
    
  • Clients.Group({group}).SendAsync

    *   Invokes the method on the message hub, sending the objects to all clients in the specified group.
    

What is NCache?

NCache is also an open-source and cross-platform software. Its cache server offers a scalable in-memory distributed cache for .NET, Java, Scala, Python, and Node.js. As this article will be focusing on .NET technologies, we can use NCache to take advantage of the following usages:

  • ASP.NET session state storage;
  • ASP.NET view state caching;
  • ASP.NET output cache;
  • Entity Framework cache;
  • NHibernate second-level cache.

Benefits of using NCache as SignalR backplane

The following reasons make NCache one of the best options when talking about third-party SignalR backplane providers:

  • It is 100% .NET native, and is the only truly native .NET distributed cache available in the market.
  • Provides support for groups and users, also makes it possible to send messages to all connections.
  • Fast & scalable, NCache is extremely fast because is an in-memory distributed cache. Provides linear scalability.
  • It has high availability and resiliency, with its self-healing peer-to-peer cluster architecture not allowing a single point of failure.

Scalability

The main problem that we can solve with  NCache as a backplane is scalability. A backplane is a group of parallel connectors, and NCache’s backplane connects multiple servers whereas we developers do not need to manage this complexity.

NCache provides a distributed linear scalability, enabling each application to send messages to the backplane. Therefore, NCache’s backplane sends the message back to the client’s application.

Setting up NCache as a backplane

There is a single method that needs to be called in order to use NCache as a backplane for SignalR. This method is called in the Program.cs when configuring SignalR services.

AddNCache

  • Parameters

    *   Cache Name. Mandatory, name of the cache.
    
    • Application Id. Mandatory, name of the application.
    • User Id. Optional, only used when enabled cache security
    • Password. Optional. only used when enabled cache security
builder.Services.AddSignalR().AddNCache(ncacheOptions => {
    ncacheOptions.CacheName = builder.Configuration["NCacheConfiguration:CacheName"];
    ncacheOptions.ApplicationID = builder.Configuration["NCacheConfiguration:ApplicationID"];
    // In case of enabled cache security specify the security credentials
    //ncacheOptions.UserId = builder.Configuration["NCacheConfiguration:UserID"];
    //ncacheOptions.Password = builder.Configuration["NCacheConfiguration:Password"];
});

SignalR with NCache in .NET 6 Implementation

Create a Web APP project using .NET 6.

Required packages to be installed

Nuget packages:

  • AspNetCore.SignalR.NCache
  • Microsoft.AspNetCore.SignalR.Common

Client-side library:

  • @microsoft/signalr

    *   Destination:
    
            *   wwwroot/js/signalr/
    
    • Files:

          *   dist/browser/signalr.js
      
      • dist/browser/signalr.min.js

SignalR and NCache set up

  • AppSettings.json

Including NCache server settings:

"NCacheConfiguration": {
    "CacheName": "demoLocasCache",
    "ApplicationID": "chatApplication",
    "UserID": "your-username",
    "Password": "your-password"
}

Program.cs

Setting up SignalR with NCache and mapping the Hub.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using System.Configuration;
using Alachisoft.NCache.AspNetCore.SignalR;
using NCacheSignalR.NCacheHelper;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.Configure < NCacheConfiguration > (builder.Configuration.GetSection("NCacheConfiguration"));
builder.Services.AddSignalR().AddNCache(ncacheOptions => {
    ncacheOptions.CacheName = builder.Configuration["NCacheConfiguration:CacheName"];
    ncacheOptions.ApplicationID = builder.Configuration["NCacheConfiguration:ApplicationID"];
    // In case of enabled cache security specify the security credentials
    //ncacheOptions.UserId = builder.Configuration["NCacheConfiguration:UserID"];
    //ncacheOptions.Password = builder.Configuration["NCacheConfiguration:Password"];
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment()) {
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapHub < SampleHub > ("/messages");
app.Run();

Core Objects

SampleHub.cs

SignalR Message Hub, SignalR's core class with the methods to send information to the NCache's Server.

public class SampleHub: Hub {
    public Task SendMessageToAll(string message) {
        return Clients.All.SendAsync("ReceiveMessage", message);
    }
    public Task SendMessageToCaller(string message) {
        return Clients.Caller.SendAsync("ReceiveMessage", message);
    }
    public Task SendMessageToUser(string connectionId, string message) {
        return Clients.Client(connectionId).SendAsync("ReceiveMessage", message);
    }
    public Task JoinGroup(string group) {
        return Groups.AddToGroupAsync(Context.ConnectionId, group);
    }
    public Task SendMessageToGroup(string group, string message) {
        return Clients.Group(group).SendAsync("ReceiveMessage", message);
    }
    public override async Task OnConnectedAsync() {
        await Clients.All.SendAsync("UserConnected", Context.ConnectionId);
        await base.OnConnectedAsync();
    }
    public override async Task OnDisconnectedAsync(Exception ex) {
        await Clients.All.SendAsync("UserDisconnected", Context.ConnectionId);
        await base.OnDisconnectedAsync(ex);
    }
}

Index.cshtml

Page updated in order to integrate with SignalR's message hub.

@page
<div class="container">
    <div class="row">&nbsp;</div>

    <div class="row">
        <div class="col-6">
            <input type="button" id="joinGroup" value="Join Private Group" />
        </div>
    </div>

    <div class="row">

        <div class="col-2">Send message to</div>
        <div class="col-4">
            <select id="group">
                <option value="All">Everyone</option>
                <option value="Myself">Myself</option>
                <option value="PrivateGroup">Private Group</option>
            </select>
        </div>
    </div>
    <div class="row">
        <div class="col-2">User</div>
        <div class="col-4"><input type="text" id="userInput" /></div>
    </div>
    <div class="row">
        <div class="col-2">Message</div>
        <div class="col-4"><input type="text" id="message" /></div>
    </div>
    <div class="row">&nbsp;</div>
    <div class="row">
        <div class="col-6">
            <input type="button" id="sendButton" value="Send Message" />
        </div>
    </div>
</div>
<div class="row">
    <div class="col-12">
        <hr />
    </div>
</div>
<div class="row">
    <div class="col-6">

    </div>
</div>

<script src="~/js/signalr/dist/browser/signalr.js"></script>
<script src="~/js/chat.js"></script>

Chat.js

Javascript class is used to integrate SignalR's Javascript library.

"use strict";
var connection = new signalR.HubConnectionBuilder().withUrl("/messages").build();
connection.on("ReceiveMessage", function(message) {
    var msg = message.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    var div = document.createElement("div");
    div.innerHTML = msg + "<hr/>";
    document.getElementById("messages").appendChild(div);
});
connection.on("UserConnected", function(connectionId) {
    var groupElement = document.getElementById("group");
    var option = document.createElement("option");
    option.text = connectionId;
    option.value = connectionId;
    groupElement.add(option);
});
connection.on("UserDisconnected", function(connectionId) {
    var groupElement = document.getElementById("group");
    for (var i = 0; i < groupElement.length; i++) {
        if (groupElement.options[i].value == connectionId) {
            groupElement.remove(i);
        }
    }
});
connection.start().catch(function(err) {
    return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function(event) {
    var message = document.getElementById("message").value;
    var user = document.getElementById("userInput").value;
    var groupElement = document.getElementById("group");
    var groupValue = groupElement.options[groupElement.selectedIndex].value;
    var finalMessage = `${user} says ${message}`;
    if (groupValue === "All" || groupValue === "Myself") {
        var method = groupValue === "All" ? "SendMessageToAll" : "SendMessageToCaller";
        connection.invoke(method, finalMessage).catch(function(err) {
            return console.error(err.toString());
        });
    } else if (groupValue === "PrivateGroup") {
        connection.invoke("SendMessageToGroup", "PrivateGroup", finalMessage).catch(function(err) {
            return console.error(err.toString());
        });
    } else {
        connection.invoke("SendMessageToUser", groupValue, finalMessage).catch(function(err) {
            return console.error(err.toString());
        });
    }
    event.preventDefault();
});
document.getElementById("joinGroup").addEventListener("click", function(event) {
    connection.invoke("JoinGroup", "PrivateGroup").catch(function(err) {
        return console.error(err.toString());
    });
    event.preventDefault();
});

Results

Console with events:

Client #1 sending messages to everyone:

Client #1 sending message to Client #3:

Client #2 sending a message to himself:

Client #1 and Client #3 join the private group. Client #3 sends a message to the private group.

Congratulations! You have successfully created your first SignalR project in .NET 6 using NCache as a backplane.

External References