Building A Private Chat Application Using SignalR

Download Source Code 

Introduction

Here I will be demonstrating an application to chat and chat privately as well, using SignalR. Before that we need to know first what is SignalR! SignalR is an open and free library which can be used to have real-time functionality integrated to your web applications. There are a hell of a  lot of areas where SignalR can come handy to make your application more handy and more integrated and more responsive to the end user as well. Real-time means having your server respond as quickly as possible to the client as and when a request is made.

Now, take for instance, we have a requirement to show the user uploading a file, the percentage of that file been uploaded on the server. Or I had come across a scenario where we had to show the end user who would be uploading a CSV file with 'n' number of rows and process each row for some validations. End user would be wondering what would be going on in the back-end. So, wouldn't it be great if we could show him how many rows have been processed and how many are left out! Similar to a progress window. Here comes the SignalR magic! Most of us think SignalR would be useful in making chat applications, but NO! It has much more than just chat! I don't think the makers of SignalR would have a thought in mind to make a chat application out of it! :P Much of a story now! Lets get into a bit of theory!

Theory

We will look into a simple image below and from that image we will try and gain some knowledge on the flow: 1

Nowadays, every application requires a load of server response in real time to sustain in the market, as user expectations have risen higher. Remote Procedure Calls (RPC) is the concept that takes place in the SignalR internally. SignalR provides an API which helps in making the RPC between the server and the client. Actually, from the client side, server side functions are called using JavaScript, once the connection to server is set. The SignalR API also helps create connections and manage them when required. In simple terms, SignalR provides connection between server and client, letting server call the funcitons on the client side and from the client side, calling the server side.

That somehow is called "Server-Push". SignalR starts with HTTP and then to a WebSocket if connection is available. From Wiki, "WebSocket is a protocol providing full-duplex communication channels over a single TCP connection." An advantage of using WebSocket is it can be used by both client and server applications. WebSocket is considered to be the most efficient and consistent medium of communication as it has the ability to manage server memory in a proper manner and being a full duplex communication, has low latency. These are the considerations made with SignalR which make it more efficient. The SignalR decides the transport based on the browser, i.e. if the browsers support the kind of transport required by the SignalR. We will discuss the kinds of transports next:

HTML 5 Transports

  • WebSockets as we have already discussed. This transport is considered to be true-persistent, creating a two way connection between client and server if the browsers support.

  • Server Sent events also called Event Source, which is supported by all browsers except IE.

Comet Transports

Comet usually,is a web application model in which a long-held HTTP request allows server to post data to a client (browser).
  • Forever frame This is supported by Internet Explorer only. Creates a hidden frame making a request to endpoint on the server. Server keeps pinging client or sends script to the client, thus providing a one way connection, creating a new connection on every request.

  • Ajax Polling This is actually long polling, which is never persistent. This polls the request and waits until and unless the response is received from the server. This introduces latency and the connection resets.

Practical

We will be creating a chat application in order to explain the flow of SignalR. We install the SignalR, create a hub to which the client will interact, calling the server methods and in return the server responds and interact with the client. You can directly add a new project in VS for the SignalR or create an MVC project and install the SignalR package/libraries from the Nuget.

PM > Install-Package Microsoft.AspNet.SignalR

This is download all the dependencies required for the SignalR.

2
 
After the successful installation, the above dll's or packages are installed into your project. There will be a class file which needs to be added to the root of your project, which would look like:
 
  1. using Owin;  
  2. using Microsoft.Owin;  
  3. [assembly: OwinStartup(typeof(SignalRChat.Startup))]  
  4. namespace SignalRChat   
  5. {  
  6.     public class Startup   
  7.     {  
  8.         public void Configuration(IAppBuilder app) { // Any connection or hub wire up and configuration should go here app.MapSignalR();   
  9.         }   
  10.     }  
  11.  }  
This is an OWIN based applicaiton. Every OWIN application will have a startup.cs class, where the component for the application pipeline are added. The OWIN attribute which specifies the type of property, specifying the project's start up and the configuration method, sets up the SignalR mapping for the App. There will be another two script files that would be added as we install the packages for SignalR.

  3

These script files are mandatory to be loaded onto the .cshtml page in order to activate the SignalR. Let's look into the code straight away: We need to add a new hub class inside a Hub folder. Lets name that LetsChatHub.cs, which would look like:

hub
 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Web;  
  5. using Microsoft.AspNet.SignalR;  
  6. namespace LetsChatApplication.Hubs  
  7. {  
  8.     public class LetsChatHub: Hub  
  9.     {  
  10.         public void Send(string name, string message, string connId)  
  11.         {  
  12.             Clients.Client(connId).appendNewMessage(name, message);  
  13.         }  
  14.     }  
  15. }  
The above send method accepts the parameters, name (which you would be giving once you navigate onto the url), the message (which the user would be sending from the UI). The other parameter is connId, which would help us have chat private and not send to every user who navigates to the site. If you would like to have every user receive and send the messages who navigates to the URL. To allow every users the access, the code you change as below:
  1. namespace LetsChatApplication.Hubs  
  2. {  
  3.     public class LetsChatHub: Hub  
  4.     {  
  5.         public void Send(string name, string message)  
  6.         {  
  7.             Clients.All.appendNewMessage(name, message);  
  8.         }  
  9.     }  
  10. }  
The Send method, is requested from the client with the parameters after the connection is set on the client side and once the server receives the request, it processes and sends back the response to the client, using the appendNewMessage. This appendNewMessage method is added on the client side to receive the response and display on the UI to the client. You need to add a controller, lets suppose: "LetsChat" with an action "LetsChat", add a view to that with the below code to it. The client side code would look like below:
  1. @{  
  2.     ViewBag.Title = "LetsChat";  
  3.   
  4. }  
  5.   
  6.   
  7. <h2>Lets Chat</h2>  
  8. <link href="~/Content/sweetalert.css" rel="stylesheet" />  
  9.   
  10. <div class="form-group col-xl-12">  
  11.     <label class="control-label">Your connection Id</label><br />  
  12.     <input type="text" class="col-lg-12 text-primary" id="frndConnId" placeholder="Paste your friend's connection Id" /><br /><br />  
  13.     <label class="control-label">Your Message</label><br />  
  14.     <textarea type="text" class="col-lg-10 text-primary" id="message"></textarea>      
  15.   
  16.     <input type="button" class="btn btn-primary" id="sendmessage" value="Send" /><br /><br />  
  17.     <img src="~/Content/smile.jpg" width="20" height="20" id="smile" style="cursor:pointer"/>  
  18.     <img src="~/Content/uff.jpg" width="20" height="20" id="ufff" style="cursor:pointer" />  
  19.     <div class="container chatArea">  
  20.         <input type="hidden" id="displayname" />  
  21.         <ul id="discussion"></ul>  
  22.     </div>  
  23. </div>  
  24. <br />  
  25. <input type="hidden" id="connId" />  
  26.   
  27. <!--Reference the autogenerated SignalR hub script. -->  
  28. @section scripts {  
  29.     <script src="~/Scripts/jquery-1.10.2.min.js"></script>  
  30.     <script src="~/Content/sweetalert.min.js"></script>  
  31.     <script src="~/Scripts/jquery.signalR-2.2.0.min.js"></script>  
  32.     <script src="~/signalr/hubs"></script>  
  33.     <script>  
  34.         //var userName = "";  
  35.         //var sessionVal = '';  
  36.         $(function () {  
  37.             // Reference the auto-generated proxy for the hub.  
  38.             var chat = $.connection.letsChatHub;  
  39.               
  40.             debugger;  
  41.   
  42.             // Create a function that the hub can call back to display messages.  
  43.             chat.client.addNewMessageToPage = function (name, message) {  
  44.                 // Add the message to the page.  
  45.                 $('#discussion').append('<li><strong>' + htmlEncode(name)  
  46.                     + '</strong>: ' + htmlEncode(message) + '</li>');  
  47.             };  
  48.             // Get the user name and store it to prepend to messages.  
  49.             swal({  
  50.                 title: "Lets Chat!",  
  51.                 text: "<span style='color:#f8bb86;font-weight:700;'>Enter your name:</span>",  
  52.                 type: "input",  
  53.                 html: true,  
  54.                 showCancelButton: true,  
  55.                 closeOnConfirm: true,  
  56.                 animation: "slide-from-top",  
  57.                 inputPlaceholder: "Your Name"  
  58.             },  
  59.             function (inputValue) {  
  60.                 userName = inputValue;  
  61.                 if (inputValue === false) return false;  
  62.                 if (inputValue === "") {  
  63.                     swal.showInputError("You need to type your name!");  
  64.                     return false;  
  65.                 }  
  66.                 $('#displayname').val(inputValue);  
  67.             });  
  68.             // Set initial focus to message input box.  
  69.             $('#message').focus();  
  70.             $('#message').keypress(function (e) {  
  71.                 if (e.which == 13) {//Enter key pressed  
  72.                     $('#sendmessage').trigger('click');//Trigger search button click event  
  73.                 }  
  74.             });  
  75.             $("#smile").click(function () {  
  76.   
  77.             });  
  78.             // Start the connection.  
  79.             $.connection.hub.start().done(function () {  
  80.   
  81.                 $('#sendmessage').click(function () {  
  82.                     // Call the Send method on the hub.  
  83.                     var connId = $("#connId").val();  
  84.                     var frndConnId = $("#frndConnId").val();  
  85.                     var finalConnId = frndConnId == "" ? $.connection.hub.id : frndConnId;  
  86.                     chat.server.send($('#displayname').val(), $('#message').val(), finalConnId);  
  87.                     $("#connId").val($.connection.hub.id);  
  88.                     if (frndConnId == "") {  
  89.                         swal("You connection Id", $.connection.hub.id, "success");  
  90.                     }  
  91.                     // Clear text box and reset focus for next comment.  
  92.                     $('#discussion').append('<li><strong>' + htmlEncode($('#displayname').val())  
  93.                    + '</strong>: ' + htmlEncode($('#message').val()) + '</li>');  
  94.                     $('#message').val('').focus();  
  95.                 });  
  96.             });  
  97.      
  98.         });  
  99.     // This optional function html-encodes messages for display in the page.  
  100.     function htmlEncode(value) {  
  101.         var encodedValue = $('<div />').text(value).html();  
  102.         return encodedValue;  
  103.     }  
  104.     </script>  
  105. }  
 
We have a normal UI in place to add your message and send button to call the server methods. Let's understand the code above part by part.
  1. var chat = $.connection.letsChatHub;  
Here, we set the connection to the Hub class. As you can notice here, letsChatHub, is the same hub class file name which we added to set up the server. The convention as follows, the intial of the methods or the class name starts with lowercase. From here, we use chat to access the Send method.
  1. $.connection.hub.start().done(function()  
  2.             {  
  3.             $('#sendmessage').click(function()  
  4.                          {  
  5.                         // Calls the Send method on the hub. chat.server.send($('#displayname').val(), $('#message').val(), finalConnId);  
chat.server.send,is self explanatory, it sets the chat connection to call the server Send method once the connection is set and started.
 
  1. chat.client.appendNewMessage = function (name, message) {  
  2. //  
  3. }  
This is called, when the Server receives the request and calls back the method on client side.

How the sample would work!

The sample provided for download, will be having few instructions to follow:
  • When you navigate to the LetsChat/LetsChat route, an alert would pop up asking you for the name with which you would like to chat. Sweet Alert !!
    5

  • Once you enter the name, you see a textbox which asks you for "Your friend's connection Id", but since you are starting the chat, you just send a message, and another pop up comes up, with your connection ID, which you need to share with your friends to share your message. Remember, only those who have your ID use it while chatting will be able to see and send through messages to you.6
  • When you friend navigates, he ought to generate his connection ID and share with you inorder to set up your connection completely. Thus, you need to have the connID to whom you will be sending and vice-versa with your friend as well. Then just chat!! :)7
If you would want to send message to all and make that common, then use the Clients.All code snippet to send all. Another interesting thing, which was figured out is, the use of @section scripts{}, that lets the Signal R scripts render and active on your page and also using the @section scripts provides your code a good outlook.

Share and Send files using SignalR!!

Ooops!! Nice question right! It is ideally not advised, rather I would not recommend to send or share files using Signal R. There always is a better way to accomplish that. The idea would be using API, you can have an upload area, and use SignalR to show the progress and once upload completes, update the user regarding the completion and generate a download link on the UI for the users to download and view the file. This is not always the best idea, but is just another idea. :)

Conclusion

This is just a simple Chat application, which you can use to chat with your friends, if you host on Azure or any other domain. But again,SignalR is not just this much. There are a lot of other effective uses of SignalR. I will be sharing more utility of SignalR in future articles.

References

Read more articles on SignalR: