Click here to Skip to main content
15,867,308 members
Articles / Web Development / ASP.NET

Simple SignalR Server and Client Applications Demonstrating Common Usage Scenarios

Rate me:
Please Sign up or sign in to vote.
5.00/5 (17 votes)
16 Jul 2019CPOL6 min read 89.1K   33   20
SignalR server and client applications for demonstrating common scenarios like connecting, disconnecting, joining and leaving groups, sending messages to all clients, to specific or group of clients

Introduction

SignalR is a library that simplifies the real-time communication between the web page and the server. It uses WebSockets or long-polling method depending on the web browser used.

There are too many articles about hosting a SignalR hub and communicating with it from a client applications. But some of them are too simple (just show how to send a message), some of them are too complex that are hard to understand and some of them have badly organized code. I wanted to demonstrate simple scenarios with simple code samples with almost no extra code.

Demonstrated scenarios are:

  • Self hosting a SignalR server with a sample hub on a WindowsForms application
  • Connecting to SignalR hub from WindowsForms and JavaScript clients
  • Subscribing to hub events (callback methods) from client applications
  • Calling methods defined on a hub, from client applications
  • Joining and leaving groups from client applications
  • Broadcasting messages to all clients, to group of clients or to a single client from a SignalR hub

WinForms Server Application

WindowsForms self hosted SignalR application looks like this.

Image 1

To host a SignalR server on a WindowsForms application, the following nuget packages must be installed either from Package Manager Console or from Manage Nuget Packages window.

  • Microsoft.Owin.SelfHost
  • Microsoft.Owin.Cors
  • Microsoft.AspNet.SignalR.Core
  • Microsoft.AspNet.SignalR.SelfHost

The next step is preparing the self-hosting environment. To do this, create a class named Startup and add the following code into it. The Configuration method will be called automatically by the self hosting environment.

C#
class Startup
{
     public void Configuration(IAppBuilder app)
     {
          //CORS need to be enabled for calling SignalR service 
          //from external web applications/pages
          app.UseCors(CorsOptions.AllowAll);
          //Find and register SignalR hubs
          app.MapSignalR();
     }
}

To start Web Application which hosts the SignalR service, call WebApp.Start method with the desired SignalR service address.

C#
_signalR = WebApp.Start<Startup>(txtUrl.Text);
The next step is preparing the SignalR hub class which will handle the communication with the clients. Every SignalR hub class derives from Microsoft.AspNet.SignalR.Hub class. It includes methods that can be called by the clients. It can also raise events (call callback methods on clients) that clients can listen to.
C#
public class SimpleHub : Hub
{
//Called when a client is connected
override OnConnected()

//Called when a client is disconnected
override OnDisconnected(bool stopCalled)

//Public methods inside this region are callable from any SignalR client
#region Client Methods
//A client provides a user name for himself
void SetUserName(string userName)

//Client calls this to join a group
Task JoinGroup(string groupName)

//Client calls this to leave a group
Task LeaveGroup(string groupName)

//Clients call this to send a message to the hub
//Hub then broadcasts the message to all the connected clients
void Send(string msg)

#endregion
}

Context property of a hub whose type is HubCallerContext has several properties that provide information about current request like ConnectionId, Headers and RequestCookies.

Clients property can be used to perform operations on all the connected clients or on specific clients. Every client has a ConnectionId which can be used for performing operations on a client. The following calls return dynamic objects which can be used to invoke operations on the client side.

C#
//For all the clients
Clients.All 

//For a specific client
Clients.Client(connectionId)

//For clients that are members of a group
Clients.Group(groupName)

The following code block invokes addMessage operation on all the clients. If no such operation is defined on the client side, nothing happens.

C#
Clients.All.addMessage(senderUserName, message);

Groups property can be used to add or remove clients to groups.

C#
//To add a user to a group
Groups.Add(userConectionId, groupName);

//To remove a user from a group
Groups.Remove(userConectionId, groupName);

A new instance of the hub class is created for each request, so hub classes cannot contain any state. Also, it is not possible to add instance events to a hub class and track the operations by subscribing to these events.

To overcome this problem, two methods can be used:

  1. Adding static state variables and events
  2. Injecting a state object to each created hub instance, using dependency injection

In this sample project, I have used static variables and events approach for simplicity. So the SimpleHub class contains a static dictionary for holding state data for connected clients and several static events to inform its subscribers.

C#
static ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>();

public static event ClientConnectionEventHandler ClientConnected;

public static event ClientConnectionEventHandler ClientDisconnected;

//...

In order to send a message from server to clients (or invoke an operation on a client), we need to access the Clients property of the hub, but it is normally only available when processing a request in a hub method. There is, of course, another way to access hub related context outside a hub class. Microsoft.AspNet.SignalR.GlobalHost has several static properties that can be used to access and manipulate SignalR host. One of them is ConnectionManager property.

The following code gets the hub context for SimpleHub type and invokes a method on all the connected clients.

C#
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SimpleHub>();

hubContext.Clients.All.addMessage("SERVER", txtMessage.Text);

WinForms Client Application

WindowsForms client application looks like this:

Image 2

For SignalR client components, add Microsoft.AspNet.SignalR.Client nuget package to the project.

The first step of connecting to a SignalR hub is creating a HubConnection to the SignalR server. Then, getting the proxy object that will be used to communicate with the desired hub.

C#
//Create a connection for the SignalR server
_signalRConnection = new HubConnection(txtUrl.Text);

//Get a proxy object that will be used to interact with the specific hub on the server
//There may be many hubs hosted on the server, so provide the type name for the hub
_hubProxy = _signalRConnection.CreateHubProxy("SimpleHub");

The next step is to register hub events (methods invoked by the hub). The following code registers a handler method for AddMessage event. Registered lambda expression takes two parameters and writes them to log area.

C#
_hubProxy.On<string, string>("AddMessage", 
                     (name, message) => writeToLog($"{name}:{message}"));            

After configuring the hub events, it is time to connect to SignalR hub by calling the Start method of the HubConnection object.

C#
await _signalRConnection.Start();

Public operations defined on the connected hub can be invoked using the IHubProxy objects Invoke method with the remote operation's name and required parameters. Sample code invokes the Send method of SimpleHub class.

C#
_hubProxy.Invoke("Send", txtMessage.Text);

JavaScript Client Application

JavaScript client application looks like the WinForms client application. I tried to construct a similar interface and implement exactly the same operations. The only functional difference is, user interface elements are enabled and disabled based on the current state.

Image 3

JavaScript client uses jQuery and jQuery.signalR script libraries. You can add them to your project by installing the following nuget packages:

  • jQuery
  • Microsoft.AspNet.SignalR.JS

In order to connect and use remote SignalR hub, another script library must be referenced. But it is not a file that you can locate and add as a script reference in HTML file. This script is automatically generated by the SignalR server. Script address is "signalR address" + "/hubs"

HTML
<script src="http://localhost:8080/signalr/hubs"></script>
<!-- or relative path -->
<script src="~/signalr/hubs"></script>

The test client application can connect any signalR service by entering the URL address of the service into the Url inputbox. So the script location is not to be fixed for this sample application. In order to solve dynamic script location problem, I have loaded this automatically created hubs script inside the connect function using the jQuery.getScript function. getScript function takes two parameters; address of the JavaScript to be loaded and a function that will be called when downloading the script has finished. As you can see in connect JavaScript function in the sample Index.html page, actual connection operations are performed after this auto generated has downloaded successfully.

JavaScript
//Connect to the SignalR server and get the proxy for simpleHub
function connect() {

     //Load auto generated hub script dynamically and perform connection 
     //operation when loading completed
     //SignalR server location is specified by 'Url' input element, 
     //hub script must be loaded from the same location
     //For production, remove this call and uncomment the script block in the header part
      $.getScript( $("#txtUrl").val() + "/hubs", function() {

                $.connection.hub.url = $("#txtUrl").val();

                // Declare a proxy to reference the hub.
                simpleHubProxy = $.connection.simpleHub;

                //Register to the "AddMessage" callback method of the hub
                //This method is invoked by the hub
                simpleHubProxy.client.addMessage = function (name, message) {
                    writeToLog(name + ":" + message);
                };

                //Connect to hub
                $.connection.hub.start().done( function () {
                    writeToLog("Connected.");
                    simpleHubProxy.server.setUserName($('#txtUserName').val());
                });
        });
} 

Steps for connecting and interacting with a SignalR hub are the same as WindowsForms client. Sample code demonstrates how to prepare a connection to a SignalR hub, create a proxy for interacting with it, registering a server event (or callback) and calling (invoking) a method (setUserName in this case) on the server side.

There is a simple JavaScript function for each operation. For example, following is the sendMessage function that send the provided message to the server by invoking the "send" method with the text entered into txtMessage input box.

JavaScript
//Send a message to server
function sendMessage() {
    if(simpleHubProxy != null) {
        simpleHubProxy.server.send($('#txtMessage').val());
    }
}

History

  • 16th July, 2019: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Freelance
Sweden Sweden
Experienced senior C# developer, sometimes codes in Java and C++ also. He designs and codes desktop applications (WinForms, WPF), windows services and Web APIs (WCF, ASP.Net MVC). He is currently improving his ASP.Net MVC and JavaScript capabilities. He acts as an architect, coder and trainer.
He is mostly experienced in Media Asset Management (MAM) applications, broadcasting sector and medical applications.

LinkedIn: www.linkedin.com/mustafa-kok/
GitHub: github.com/nthdeveloper
StackOverflow: stackoverflow.com/users/1844220/nthdeveloper

Comments and Discussions

 
GeneralMy vote of 5 Pin
NightKoder3-Mar-22 4:50
NightKoder3-Mar-22 4:50 
BugNot work in different machines Pin
iruedag24-Mar-21 6:20
iruedag24-Mar-21 6:20 
Question.NET Core/.NET 5 Pin
Member 1508201224-Feb-21 5:09
Member 1508201224-Feb-21 5:09 
SuggestionHow to deploy this in IIS Pin
victorbos22-Feb-21 7:18
victorbos22-Feb-21 7:18 
GeneralLink to excellent intro to SignalR video Pin
victorbos11-Feb-21 5:34
victorbos11-Feb-21 5:34 
SuggestionDisable/Enable [Connect] and [Disconnect] buttons in JavaScriptClient Pin
victorbos5-Feb-21 7:20
victorbos5-Feb-21 7:20 
In case this helps anyone reduce ambiguity in the JavaScriptClient UI (and mimic behavior in the WinFormsClient) edit index.html as follows:

Step 1 of 3: Edit the HTML button tag and set the disabled attribute for the btnDisconnect to look like this and :
<button id="btnDisconnect" onclick="disconnect(); return false;" disabled>Disconnect</button>

Step 2 of 3: In the JS connect() method, add code to set the disabled attribute for btnConnect and btnDisconnect like this:
$.connection.hub.start().done( function () {
 writeToLog("Connected.");
 simpleHubProxy.server.setUserName($('#txtUserName').val());
 btnConnect.disabled = true;           //<<< added
 btnDisconnect.disabled = false;       //<<< added
});

Step 3 of 3: In the JS disconnect() method, add code to set the disabled attribute for btnConnect and btnDisconnect like this:
$.connection.hub.stop();
simpleHubProxy = null;
writeToLog("Disconnected.");
btnConnect.disabled = false;          //<<< added
btnDisconnect.disabled = true;        //<<< added

You can follow the same pattern for Group Join and Leave buttons, though I have not tried those.
Bug$.connection.hub.stop().done(..) > "is not a function" exception Pin
victorbos5-Feb-21 6:01
victorbos5-Feb-21 6:01 
GeneralMy vote of 5 Pin
victorbos27-Jan-21 5:39
victorbos27-Jan-21 5:39 
PraiseKudos for providing such a clean solution Pin
victorbos27-Jan-21 5:37
victorbos27-Jan-21 5:37 
QuestionAsync Core Port Pin
Member 132391933-Oct-20 17:52
Member 132391933-Oct-20 17:52 
AnswerRe: Async Core Port Pin
Member 1508201224-Feb-21 5:10
Member 1508201224-Feb-21 5:10 
PraiseJust what I have been searching for the past two weeks Pin
Member 1184617610-Jul-20 19:49
Member 1184617610-Jul-20 19:49 
QuestionReliability Pin
okansarica3-Apr-20 23:31
okansarica3-Apr-20 23:31 
Questionsignalr Pin
mevlüt bostancı27-Dec-19 0:47
mevlüt bostancı27-Dec-19 0:47 
QuestionError trying to use a local IP address Pin
bioan21-Nov-19 23:38
professionalbioan21-Nov-19 23:38 
QuestionState in signalR for one instance Pin
Member 1466365821-Nov-19 3:14
Member 1466365821-Nov-19 3:14 
QuestionClient is unable connect after a re-start Pin
Serdar Serbetci18-Sep-19 3:05
Serdar Serbetci18-Sep-19 3:05 
AnswerRe: Client is unable connect after a re-start Pin
Serdar Serbetci18-Sep-19 4:05
Serdar Serbetci18-Sep-19 4:05 
GeneralRe: Client is unable connect after a re-start Pin
ssaddam21-Oct-19 20:19
ssaddam21-Oct-19 20:19 
GeneralRe: Client is unable connect after a re-start Pin
Johannes Grunedal1-Mar-22 4:22
Johannes Grunedal1-Mar-22 4:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.