LinkedIn About Home

Setting up a simple SignalR Core web chat service.

Introduction to SignalR Core and setting up a simple web chat server

Published Dec 18, 2017

What is SignalR?

ASP.NET SignalR is a library for ASP.NET developers that simplifies the process of adding real-time web functionality to applications.

Real-time web functionality is the ability to have server code push content to connected clients instantly as it becomes available, rather than having the server wait for a client to request new data.

Link to offcial docs

Since I study the Microsoft Technlogy Stack, I was in for a treat when we had a course in SignalR. And what a treat it was! SignalR blew my mind. It is one of the coolest technlogies I have ever come across. SignalR has four main technlogy routes for setting up its connection to your clients. Four!

Websockets

WebSocket (if the both the server and browser indicate they can support Websocket). WebSocket is the only transport that establishes a true persistent, two-way connection between client and server. However, WebSocket also has the most stringent requirements; it is fully supported only in the latest versions of Microsoft Internet Explorer, Google Chrome, and Mozilla Firefox, and only has a partial implementation in other browsers such as Opera and Safari.

Server Sent Events

Server Sent Events, also known as EventSource (if the browser supports Server Sent Events, which is basically all browsers except Internet Explorer.)

Forever Frame

Forever Frame (for Internet Explorer only). Forever Frame creates a hidden IFrame which makes a request to an endpoint on the server that does not complete. The server then continually sends script to the client which is immediately executed, providing a one-way realtime connection from server to client. The connection from client to server uses a separate connection from the server to client connection, and like a standard HTTP request, a new connection is created for each piece of data that needs to be sent.

Ajax long polling

Ajax long polling. Long polling does not create a persistent connection, but instead polls the server with a request that stays open until the server responds, at which point the connection closes, and a new connection is requested immediately. This may introduce some latency while the connection resets.

Scope of the project we will be building here:

A simple web application where you can choose two rooms where you will be prompt for a user name and where you can chat with all the other people who have entered the chat room.

The beauty of SignalR here is that whenever somebody enters a message in the chat, the message will be pushed to all of the connected clients in this room.

Time to spin up a ASP.NET Core SignalR Server!

We start by creating an ASP.NET Core Project and proceed to nuget to install the latest SignalR Core Package.

testimage.jpg

This installs all the packages needed and adds a javascript file to wwwroot/js/ which you need to reference in your html page.

testimage.jpg

You also have to configure the pipeline in Startup.cs to use SignalR and map our up coming ChatHub.cs

public void ConfigureServices(IServiceCollection services)
{
  services.AddSignalR();
  services.AddMvc();
}


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  if (env.IsDevelopment())
  {
    app.UseDeveloperExceptionPage();
    app.UseBrowserLink();
  }
  else
  {
    app.UseExceptionHandler("/Home/Error");
  }

  app.UseStaticFiles();

  app.UseSignalR(routes =>
  {
    routes.MapHub<ChatHub>("chatHub");
  });

  app.UseMvc(routes =>
  {
      routes.MapRoute(
          name: "default",
          template: "{controller=Home}/{action=Index}/{id?}");
  });
}

Now lets add a index page where you can choose your desired chat room. Here is some html with bootstrap to get you started:

<div class="container">
  <div style="margin-top:50px;" class="jumbotron">
    <h1>Welcome to a SignalR chat server!</h1>
    <p>Please choose your desired chat room below</p>
  </div>
  <div class="col-md-6">
    <a asp-action="ChooseChatRoom" asp-controller="Home" asp-route-chatroom="1" style="text-decoration:none;">
      <div class="panel panel-info">
        <div class="panel-heading">
          <h3 class="panel-title">Chat room 1</h3>
        </div>
        <div class="panel-body">
          <span id="chatroom1Members"</span>
        </div>
      </div>
    </a>
    </div>
    <div class="col-md-6">
      <a asp-action="ChooseChatRoom" asp-controller="Home" asp-route-chatroom="2" style="text-decoration:none;">
      <div class="panel panel-success">
        <div class="panel-heading">
          <h3 class="panel-title">Chat room 2</h3>
        </div>
        <div class="panel-body">
          <span id="chatroom2Members"</span>
        </div>
      </div>
    </a>
  </div>
</div>

This should produce something like this:

testimage.jpg

Now lets add some javascript in our index page which we will use to show all users presently attending each chat room in realtime.

let connection = new signalR.HubConnection('/chatHub');

connection.start()
.then(() => connection.invoke("GetChatRoom1Members"))
.then(() => connection.invoke("GetChatRoom2Members"));

connection.on('GetChatRoom1Members', data => {
const spanMembers1 = document.getElementById("chatroom1Members");
let members = "";
for (var i = 0; i < data.length; i++) {
          members += data[i].name + ",";
      }
      spanMembers1.innerHTML = members;
  });

  connection.on('GetChatRoom2Members', data => {   
      const spanMembers2 = document.getElementById("chatroom2Members");
      let members = "";
      for (var i = 0; i < data.length; i++) {
  members += data[i].name + ",";
  }
  spanMembers2.innerHTML = members;
  });

This is ofcourse accompanied with some server side code. Lets create our ChatHub.cs and a ConnectionHelper.cs class. Note that we have to derive from the SignalR hub class for the ChatHub.cs

public class ChatHub : Hub
{
  public Task Send(string message, string group)
  {
    var fromId = Context.ConnectionId;
    var client = ConnectionHelper.Connections.FirstOrDefault(c => c.ConnectionId.Equals(fromId));
    return Clients.Group(group).InvokeAsync("send", $"{DateTime.Now.ToShortTimeString()} {client.Name}: {message}");
  }

  public Task GetChatRoom1Members()
  {
    return Clients.All.InvokeAsync("GetChatRoom1Members", ConnectionHelper.Connections.Where(c => c.ChatRoom == ChatRoom.chatroom1));
  }

  public Task GetChatRoom2Members()
  {
    return Clients.All.InvokeAsync("GetChatRoom2Members", ConnectionHelper.Connections.Where(c => c.ChatRoom == ChatRoom.chatroom2));
  }

  public void RegisterMember(string name, string chatRoom)
  {
    var client = new MyClient();
    client.ConnectionId = Context.ConnectionId;
    client.Name = name;

    if (chatRoom == "chatRoom1")
    {
      client.ChatRoom = ChatRoom.chatroom1;
      Groups.AddAsync(Context.ConnectionId, "chatRoom1");
    }
    else if (chatRoom == "chatRoom2")
    {
      client.ChatRoom = ChatRoom.chatroom2;
      Groups.AddAsync(Context.ConnectionId, "chatRoom2");
    }

    ConnectionHelper.Connections.Add(client);
  }

  public override Task OnDisconnectedAsync(Exception exception)
  {
    var client = ConnectionHelper.Connections.FirstOrDefault(c => c.ConnectionId == Context.ConnectionId);

    if(client != null)
    {
      ConnectionHelper.Connections.Remove(client);
    }

    return base.OnDisconnectedAsync(exception);
  }


}
public static class ConnectionHelper
{
  public static List<MyClient> Connections = new List<MyClient>();

public class MyClient
{
  public string Name { get; set; }
  public ChatRoom ChatRoom { get; set; }
  public string ConnectionId { get; set; }
}

public enum ChatRoom
{
  chatroom1,
  chatroom2
}

}

Note that I have chosen to implement my own chatroom enums where I can keep check on who is in which chat room. This is not really nessecery since SignalR have a built in functionality called groups. Where you can add and remove clients from. But for the sake of time I'm keeping my implementation. I might update this in the future.

So lets add one of the chat room pages. I created two chat room pages but it would be nicer to have just one dynamic chat room page. This is only to give you an idea of what is possible.

<div class="container">
  <div style="margin-top:50px;" class="jumbotron">
    <h1>Welcome to chat room 1</h1>
    <p>Current users in chat room:</p>
    <p id="chatroom1Members"></p>
</div>

<div class="row">
    <div id="board" class="col-md-8">
    </div>
    <div class="col-md-4">
      <div class="input-group">
        <input id="txtMessage" type="text" class="form-control">
          <span class="input-group-btn">
            <button class="btn btn-default" onclick="SendMessage()" type="button">Send</button>
          </span>
        </div>
    </div>
  </div>
</div>

<style<
  .well{
  min-height:10px;
  margin:0;
  padding:2px;
  }

  #board{
  height:300px;
  overflow:auto;
  }
</style<

This should result in something like this:

testimage.jpg

Now for the last piece of javascript embedded in the chatroom page.

var name = prompt("Who are you?", "Your name..");

let connection = new signalR.HubConnection('/chatHub');
let chatroom = "chatRoom1";
connection.start()
.then(() => connection.invoke("RegisterMember", name, chatroom))
.then(() => connection.invoke("GetChatRoom1Members"));

connection.on('GetChatRoom1Members', data => {
const spanMembers1 = document.getElementById("chatroom1Members");
let members = "";
for (var i = 0; i < data.length; i++) {
          members += data[i].name + " ";
      }
      spanMembers1.innerHTML = members;
  });

  connection.on('Send', data =< {
      const board = document.getElementById('board');
      board.innerHTML += `<div class="well"<${data}</div<`;
$("#board").animate({ scrollTop: 1000 }, 100);
});

function SendMessage() {
  const group = "chatRoom1";
  const input = document.getElementById("txtMessage");
  if (input.value != "") {
  connection.invoke('Send', input.value, group);
  input.value = "";
  }
}

(function () {
  document.querySelector("body").addEventListener("keyup", event =< {
if (event.key !== "Enter") return;
SendMessage();
});
})();

This concludes my first simple implementation of a SignalR Core web application.

You can find my github repo Here