Friday, June 20, 2014

Step by Step : SignalR chat application


Hi, 

In this article we will learn how to do the following

1) Make a C# class library as SignalR server
2) How to host SignalR server in IIS
3) Develop a WPF and ASP.NET Web SignalR client applications which send chat messages to each other.

This is just one of the way which I followed in my projects. I am not saying this is the best and it doesnot have cons. My main intension is to share my experience so that it will be useful for the needy. I am new to blogging and I tried to make this post as much interesting as possible. Please excuse me for my grammatical errors and typos. I welcome your constructive comments and suggestions which helps in improving this post and my technical skills.

In this article we will develop two seperate solutions using Visual Studio 2013.

SignalRDemo.sln - This has C# library that is a SignalR server and a ASP.NET Empty Web application that host SignalR server

SignalRClients Demo - This has one WPF application and ASP.NET Web application

In simple words, if server has any content that needs to be pushed to client, server needs a request from client either in form of page refresh, or clicking a button on client browser or so. In case of SignalR, server doesnot need any explicit request from client. The server just pushes the content to client as and when it is available. So we need to make server and client to have SignalR capabilities and in this article we will see how we can make server and client SignalR aware. So let's rollup our sleeves and start doing the work

For this article, I am using the following tools/technologies
Visual Studio 2013
C#
.NET Framework 4.5
IIS ( to host my website) 
Windows Server 2008 R2 

I always have the habit of creating empty solution first and then work on it. Here also I did same thing. I created a folder called "SignalR" in "D\XXXX\My Programs" folder and created an empty visual studio solution titled "SignalRDemo"

Now, add a new Class library project and add name it as "SignalRServer"  and delete Class1.cs file in it.















Now we need add SignalR capabilities to SignalRServer class library. There are two ways doing it.

1) Manage Nuget Packages: Right on SignalRServer and click Manage Nuget Package as shown in below screen shot.




This will open Manage Nuget packages dialog. Search for SignalR. Click Install button beside  "Microsoft ASP.NET SignalR" as shown in below screen shot which brings everything need to run it on IIS and ASP.NET




2) Package Manager Console :  Open Package Manager Console. Please see below for its location.


Type following command in Package Manager Console after making sure that you select SignalRServer project under Default project

Install-Package Microsoft.AspNet.SignalR

This will install various libraries (javascript and others) and will show you a successful message if everything goes fine.










 Now we need to add a Hub class which facilitates server to call methods on client side and clients to call methods on server side. Adding a Hub class can be done in two ways

1) If using VS 2013, right-click the project, select Add  and then SignalR Hub Class (v2). Name the class MyHub.cs

2) Simply add new class called "MyHub"

Add below code to MyHub

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace SignalRServer
{

    [HubName("MyHub")]
    public class MyHub : Hub
    {
        [HubMethodName("SendMessage")]
        public void SendMessage(string name, string message)
        {           
            try
            {
                //All connected clients
                Clients.All.addMessage(name, message);
            }
            catch (HubException hubException)
            {
                throw new HubException("Exception occured", new {user = Context.User.Identity.Name, message = message});
            }
        }

        [HubMethodName("JoinGroup")]
        public async Task JoinGroup(string groupName, string clientName)
        {
            await Groups.Add(Context.ConnectionId, groupName);

            Clients.Group(groupName).addedToGroupMessage(clientName + " joined  " + groupName);
        }
    }
}

 
 Next step is to create a OWIN - Startup class to configure the application. This can be done in two ways
 1) If using VS 2013, right-click the project, select Add and then OWIN Start up Class. Name the class Startup.cs

2) Simply add new class called "Startup"
Add below code
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(SignalRServer.Startup))]

namespace SignalRServer
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=316888

            //define the route that clients will use to connect to your Hub
            app.MapSignalR();
        }
    }
}


Please see below how SignalRServer project looks like
 

 Add new ASP.NET Empty Web Application  and name it as "SignalRServerHost" and add SignalRServer.dll to it.

 Now we need to add hosting SignalRServer capabilities to SignalRServerHost project. This can be done easily by using Package Manager Console





 Type following command in Package Manager Console after making sure that you select SignalRServerHost project under Default project

Install-Package Microsoft.AspNet.SignalR.SelfHost

This will install various libraries (javascript and others) and will show you a successful message if everything goes fine.
 

 Now right click on SignalRServerHost project and add "Global Application Class" which is nothing but Global.asax and below code

using Microsoft.Owin.Hosting;
using SignalRServer;


namespace SignalRServerHost
{
    public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            WebApp.Start<Startup>("http://localhost:83/BloggingSignalRHost/signalr");
        }

    }
}

 Note : In IIS, I am going to create a website with port 83

http://localhost:83/BloggingSignalRHost/signalr

Open IIS and create a new website by Right clicking on Sites and click "Add Web Site..."
Please see below  

  


Right click on BloggingWebsite that you created in above step and click "Add Application.."
I gave physical path to my SignalRServerHost location 

Please see below


 Please see below how my IIS looks now





Now lets create clients who consume SignalR server. Open new instance of Visual Studio and create a new empty solution called SignalRDemoClients. I created it in folder "D\XXXX\My Programs\SignalR" 

Add one WPF project called "WPFClient" and add SignalClient capabilities by using Package Manager Console


Type following command in Package Manager Console after making sure that you select WPFClient project under Default project

Install-Package Microsoft.AspNet.SignalR.Client 

This will install various libraries (javascript and others) and will show you a successful message if everything goes fine.

This is what WPFClient does
1) Make a hub connection
2) Display a message saying that client joined WPFClient group
3)When user clicks "Send Message" button, message is send Signalr server which in turn sends to all connected clients

Please see below code for MainWindow.xaml and MainWindow.xaml.cs

MainWindow.xaml

<Window x:Class="WPFClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525" Loaded="MainWindow_OnLoaded">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition />
            <ColumnDefinition  Width="100"/>
        </Grid.ColumnDefinitions>

        <TextBlock Text="Message :" Grid.Row="0" Grid.Column="0" Margin="1,2,5,5"/>
        <TextBox x:Name="txtMessage" Grid.Row="0" Grid.Column="1" Margin="3,2,5,5"/>
        <Button x:Name="btnSend" Content="Send" Grid.Row="0" Grid.Column="2" Margin="3,2,5,5" Click="btnSend_Click"/>

        <ListBox x:Name="lstMessages" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Margin="0,5,0,5"/>


    </Grid>
</Window>


MainWindow.xaml.cs

using Microsoft.AspNet.SignalR.Client;

namespace WPFClient
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private string hostUrl = "http://localhost:83/BloggingSignalRHost/signalr";
       
        public IHubProxy Proxy { get; set; }
        public HubConnection Connection { get; set; }

        public MainWindow()
        {
            InitializeComponent();
        }

        private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            try
            {
                Connection = new HubConnection(hostUrl);
               
                Proxy = Connection.CreateHubProxy("MyHub");

                Proxy.On<string, string>("SendMessage", OnSendData);

                Proxy.On<string>("AddedToGroupMessage", OnAddedToGroupMessage);

                await Connection.Start();

                await Proxy.Invoke("JoinGroup", "WPFClientGroup", "WPFClient");

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        private void OnSendData(string name, string message)
        {
            string formattedMessage = string.Format("{0} : {1}", name, message);
            Dispatcher.Invoke(() =>
            {
                lstMessages.Items.Add(formattedMessage);
                txtMessage.Clear();
            });

        }

        private void OnAddedToGroupMessage(string message)
        {
            string formattedMessage = string.Format("{0}", message);
            Dispatcher.Invoke(() =>
            {
                lstMessages.Items.Add(formattedMessage);
                txtMessage.Clear();
            });

        }

        private async void btnSend_Click(object sender, RoutedEventArgs e)
        {
            //await Proxy.Invoke("AddMessage", "WPF Client", txtMessage.Text);

            try
            {
                //[HubMethodName("BroadcastMessage")]
                await Proxy.Invoke("BroadcastMessage", "WPF Client", txtMessage.Text);
            }
            catch (HubException ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

    }
}



Now lets create ASP.NET Web client. Add one ASP.NET Web Empty project called "ASPNETWebClient" and add SignalClient capabilities by using Package Manager Console as shown in above for WPFClient


Type following command in Package Manager Console after making sure that you select ASPNETWebClient project under Default project

Install-Package Microsoft.AspNet.SignalR.Client

Add new Html page and name it as Default.html and make this as startup page like below


Open Default.html and add below code.

<!DOCTYPE html>
<html>
<head>
    <title>ASPNETWebClient</title>
    <style type="text/css">
        .container {
            background-color: #99CCFF;
            border: thick solid #808080;
            padding: 20px;
            margin: 20px;
        }
    </style>
</head>
<body>
    <div class="container">       
        <input type="text" id="message" />
        <input type="button" id="sendmessage" value="Send" />
        <input type="hidden" id="displayname" />
        <ul id="discussion"></ul>
    </div>
    <!--Script references. -->
    <!--Reference the jQuery library. -->
    <script src="Scripts/jquery-1.6.4.min.js"></script>
    <!--Reference the SignalR library. -->
    <script src="Scripts/jquery.signalR-2.0.3.min.js"></script>
    <!--Reference the autogenerated SignalR hub script. -->
    <script src="http://localhost:8080/signalr/hubs"></script>
    <!--Add script to update the page and send messages.-->
    <script type="text/javascript">
        $(function () {
            //Set the hubs URL for the connection
            $.connection.hub.url = "http://localhost:83/BloggingSignalRHost/signalr";

           
            // Declare a proxy to reference the hub.
            var chat = $.connection.
MyHub;

            // Create a function that the hub can call to broadcast messages.
            chat.client.addMessage = function (name, message) {
                // Html encode display name and message.
                var encodedName = $('<div />').text(name).html();
                var encodedMsg = $('<div />').text(message).html();
                // Add the message to the page.
                $('#discussion').append('<li><strong>' + encodedName
                    + '</strong>:&nbsp;&nbsp;' + encodedMsg + '</li>');              
            };

            // Create a function that the hub can call to broadcast messages.
            chat.client.addedToGroupMessage = function (message) {                               
                // Add the message to the page.
                $('#discussion').append('<li><strong>' + message + '</strong>:&nbsp;&nbsp; </li>')
            };

            // Get the user name and store it to prepend to messages.
            //$('#displayname').val(prompt('Enter your name:', ''));
            // Set initial focus to message input box.
            //$('#message').focus();

            // Start the connection.
            $.connection.hub.start().done(function () {

                //[HubMethodName("JoinGroup")]
                chat.server.JoinGroup('ASPNETWebClientGroup', 'ASPNETWebClient');

                $('#sendmessage').click(function () {
                    // Call the AddMessage method on the hub.
                    //chat.server.addMessage($('#displayname').val(), $('#message').val());

                    //[HubMethodName("BroadcastMessage")]
                    chat.server.BroadcastMessage('ASPNETWebClient', $('#message').val());

                    // Clear text box and reset focus for next comment.
                    $('#message').val('').focus();
                });
            });
        });
      
    </script>
</body>
</html>


Build SignalRDemoClients solution and run both clients. Please see below for my clients





Conclusion : 
In this article we learned how we can create a SignalR server and host it in IIS and who use SignalRServer to dynamically push content to clients as and when it is available using a chat application. We also how we can make a WPF and ASP.NET Web application as SignalR client applications.

Please feel free to let me know your valuable and constructive comments and suggestions which can help me to make this article better and improve my technical and written skills. Last but not the least please excuse me for my grammar and typos.

Thanks and have a nice and wonderful day.

3 comments:

  1. Prajakta ChaudhariJune 8, 2015 at 5:14 AM

    Sir can you guide me - how to save chat using signalr in asp.net? I got one link- http://www.asp.net/signalr/overview/performance/scaleout-with-sql-server It save data in sql server but creates its own table. also, chat is stored in encrypted(binary) format. whenever i wants to get chat history, it does not decrypt. Can you help me in this pls?

    ReplyDelete
    Replies
    1. I am sorry for being so late to reply. I was busy with my work and did some travelling so was unable to reply you back. I am sorry when I learned SignalR my task was just to publish and subscribe messages. We also had a road map to store messages and by the time it was implemented I came out of my contract but I also came across few links online which showed how we can stored data into database using SignalR. Please let me know if you find any such links so that I can write new post include that.

      Delete
  2. Keep up the good work , I read few posts on this web site and I conceive that your blog is very interesting and has sets of fantastic information. 1337x

    ReplyDelete

Please add your comments. Please avoid personal attacks and racist comments. Your constructive comments are always welcome. I will try my best to address your comments in time.