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

A Simple Chat Website Using ASP.NET and AngularJS

Rate me:
Please Sign up or sign in to vote.
4.66/5 (33 votes)
24 Aug 2013CPOL4 min read 127.8K   6.4K   71   44
This article provides information to create a simple chat website.

Introduction

As the title suggests, this is a very basic website that implements global chat, meaning that the users can chat globally with each other by just registering their name. The code uses ASP.NET web services along with a little jQuery and AngularJS.

If you are new to AngularJS, its documentation can be found here.

Background

The idea that I had in mind before creating this was to utilize the capability of AngularJS to automatically update the DOM whenever its models are updated. The source of this idea is a Google presentation that I saw some time back about AngularJS and Firebase (video). I thought that maybe we can implement something like this at a very small scale using the Application State of ASP.NET.

Using the Code

The code is fairly simple, the user's information and the global chat data is stored in the application state. Periodic web service calls are made to retrieve the data and then the angular view models are updated with that data. The DOM is updated asynchronously by the angular whenever its models are updated.

Follow the steps given below to create this chat website:

Step 1

Create a new ASP.NET website, and open the 'Global.asax' file. Now add the following code to the 'Application_Start' event.

C#
/// <summary>
/// Code that runs on application startup
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void Application_Start(object sender, EventArgs e)
{
    //this is our list of users.
    Dictionary<String, String> userList = new Dictionary<String, String>();
    Application.Add("UserList", userList);

    //this is the place where we put all the global chat data.
    List<Object> globalChat = new List<Object>();
    Application.Add("GlobalChat", globalChat);
}

Here, we are initializing a Dictionary object to store the list of users and a List object to store the global chat data, and then adding them to the application state.

Step 2

Add a new web service to the website and name it 'ChatService'. Add the [ScriptService] attribute and the following web methods to the code behind:

C#
[System.Web.Script.Services.ScriptService]
public class ChatService : System.Web.Services.WebService
{... 
C#
/// <summary>
/// Adds a new user.
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
[WebMethod(EnableSession = true)]
public String AddUser(String userName)
{
    //add our new user to the application object
    String newId = Session.SessionID;
    if (!((Dictionary<String, String>)Application["UserList"]).Keys.Contains(newId))
        ((Dictionary<String, String>)Application["UserList"]).Add(newId, userName);

    return "Success";
}

/// <summary>
/// Adds a new chat message.
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
[WebMethod(EnableSession = true)]
public String AddGlobalChat(String message)
{
    String userId = Session.SessionID;
    ((List<Object>)Application["GlobalChat"]).Add(
        new { time = DateTime.Now.ToString("hh:mm"),
              message = ((Dictionary<String, String>)Application
              ["UserList"])[userId] + ": " + message
            });

    return "Success";
}

/// <summary>
/// Returns the global chat data.
/// </summary>
/// <returns>Object containing the global chat data</returns>
[WebMethod(EnableSession = true)]
public Object GetGlobalChat()
{
    List<Object> messages = (List<Object>)Application["GlobalChat"];
    return messages;
}

Here is the description of the web methods that are given above:

  • AddUser: Adds a new user to the global user list if it does not already exist. Only one user can be added per session.
  • AddGlobalChat: Adds a new message to the global chat collection.
  • GetGlobalChat: Returns a list containing all the global chat data present in the Application State. Angular view model is then later updated with this data.

Step 3

Remove the master page bindings (if any) from your default aspx page and add the following code to it for the <head> and <body> sections. Keep the rest as default.

HTML
 <head id="Head1" runat="server">
    <title></title>
    <script type="text/javascript" 
      src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
    <script type="text/javascript" src="Helper.js"></script>
</head>
<body>
   <form id="form1" runat="server">
    
    <div>
        <h3>Enter your name:</h3>        
        <input id = "txtName" size = "30" />&nbsp;
        <input type = "button" id = "btnAddUser" 
          value = "Add" onclick = "AddUser();" />
    </div>
    </form>

    <script type ="text/javascript">        
        var btnAddUser = $("#btnAddUser");
        var txtName = $("#txtName");

        function AddUser() {
            var chatService = new ServiceCall("AddUser", 
                "{'userName':'" + txtName.val() + "'}");
            chatService.callService(addUser_Complete);
        }

        function addUser_Complete() {
            window.open("ChatPage.aspx", "_self");
        }      
    </script>
</body> 

Image 1

The AddUser function will invoke on the onclick event of the button. This will call the web service to add the user name to our application state.

Now we will add a JavaScript file to hold the code required for the web service calls. Add a new JavaScipt file to your website and name it 'Helper.js' and add the following code to it:

JavaScript
; var ServiceCall = function (serviceName, serviceParams) {
    this._serviceName = serviceName;
    this._serviceParams = serviceParams;
    return this;
};

ServiceCall.prototype = {
    callService: function (serviceCallSuccess) {
        $.ajax({
            type: "POST",
            url: 'ChatService.asmx/' + this._serviceName,
            data: this._serviceParams,
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            success: serviceCallSuccess,
            error: function (e) {
                //alert("Error in calling service.");
            }
        });
    }
}

var Helper = {
    //We need to escape and un-escape strings in many cases so as to avoid the corruption of the
    //json that we are passing. This can also be used in many places in the web pages, for ex: sometimes
    //we need to call functions from onclick attributes then escaping and un-escaping is better
    //than tweaking the string concatenations.
    //http://stackoverflow.com/questions/1219860/html-encoding-in-javascript-jquery
    htmlEscape: function (str) {
        return String(str)
            .replace(/&/g, '&amp;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
    },
    htmlUnescape: function (value) {
        return String(value)
            .replace(/&quot;/g, '"')
            .replace(/&#39;/g, "'")
            .replace(/&lt;/g, '<')
            .replace(/&gt;/g, '>')
            .replace(/&amp;/g, '&');
    }
}; 

I have defined a generic serviceCall object to call our web services. This is very basic prototyping, we will inherit this serviceCall object into new objects to call our web services. For this, we will provide the method name and the parameters in the JSON format at the time of object initialization. After that, we will invoke the callService method to call the web service. We need to pass a function in the serviceCallSuccess if we want to do something on the success of our web service call.

Step 4

Now we need to add the angularJs controller. The complete documentation about angular controllers can be found here.

For this, we will add a new js file and name it 'ChatCtrl.js'. Add the following code to this file:

JavaScript
function ChatCtrl($scope) {
    $scope.globalChat = [];
}

This is very basic containing only our model for the global chat. You can even use a script tag for this code but I prefer a separate file for further expansions. We will initialize globalChat as an empty list.

Lastly, add a new aspx file to our website and name it 'ChatPage.aspx'. Add the following code to it for the <head> and <body> sections. Keep the rest as default.

HTML
<head runat="server">
    <title></title>
    <script type="text/javascript" 
    src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
    <script type="text/javascript" 
    src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
    <script type="text/javascript" 
    src="Helper.js"></script>
    <script type="text/javascript" 
    src="ChatCtrl.js"></script>
    <style type="text/css">
        .Messages
        {
            overflow:auto;
            height:200px;
            width:550px;
            text-align:left;
            color:Gray;
            font-family:@Arial Unicode MS;
            margin:0 auto
        }
        
    </style>
</head>
<body ng-app>
    <form id="form1" runat="server">
    <div id = "container" style = " text-align:center">
        <%--<div>
            <textarea id = "txtGlobalChat" 
            rows = "20" cols = "80"></textarea>
        </div>--%>
        <div id = "divMessages" 
        ng-controller = "ChatCtrl"  class="Messages">
            <table width = "100%">
                <tr ng-repeat = "msg in globalChat">
                    <td>({{msg.time}})&nbsp;{{msg.message}}</td>
                </tr>
            </table>
        </div>
        <div>
            <input id = "txtMessage" size = "100" 
            maxlength = "90" placeholder = "Enter your message" />
        </div>
    
    </div>
    </form>

    <script type ="text/javascript">
        $('#txtMessage').bind("keypress", function (e) {
            if (e.keyCode == 13) {
                AddGlobalChatMsg();
                $('#txtMessage').val("");                
                return false;
            }
        });

        function AddGlobalChatMsg() {
            var chatService = new ServiceCall("AddGlobalChat", 
            "{'message':'" + Helper.htmlEscape($('#txtMessage').val()) + "'}");
            chatService.callService(addGlobalChat_Complete);
            //getGlobalChat();
        }

        function addGlobalChat_Complete() {}

        function ContentLoaded() {
            updateChatArea();
        }

        function updateChatArea() {
            getGlobalChat();
        }

        function getGlobalChat() {
            var chatService = new ServiceCall("GetGlobalChat", "{}");
            chatService.callService(getGlobalChat_Complete);
        }

        function getGlobalChat_Complete(msg) {
            //$("#txtGlobalChat").val(msg.d);
            var scope = AngularScope();
            var scroll = scrollBarAtBottom();
            scope.globalChat = [];
            var i = 0;
            for (; i < msg.d.length; i++) {
                msg.d[i].message = Helper.htmlUnescape(msg.d[i].message); //unEscape the message string
                scope.globalChat.push(msg.d[i]);
            }
            scope.$apply();            
            if (scroll === true) {
                setTimeout("scrollToBottom();", 50);
            }
            setTimeout("getGlobalChat(false);", 100);
        }

        function scrollToBottom() {
            $('#divMessages').scrollTop($('#divMessages')[0].scrollHeight);
        }

        function AngularScope() {
            return angular.element($("#divMessages")).scope();
        }

        function scrollBarAtBottom() {
            var divMessages = $("#divMessages");
            var scrollTop = divMessages.scrollTop();
            var height = divMessages.height();
            var scrollHeight = divMessages[0].scrollHeight;
            if (scrollTop >= scrollHeight - height) {
                return true;
            }
            else {
                return false;
            }
        }

        window.addEventListener("DOMContentLoaded", ContentLoaded, false); 
    </script>
</body> 

Image 2

We will use AngularJS to display all the chat messages and an input field to add new message. AngularJS will dynamically add <table> columns, this needs to be done periodically so I have used setTimeout to call the service again if our previous web service call is a success.

Here is the description of the methods used above:

  • AddGlobalChatMsg: Adds a new message to the global message list
  • GetGlobalChat: Retrieves all the chat messages
  • AngularScope: Returns the scope object being used for the divMessages
  • GetGlobalChat_Complete: This will update our angular model that we are using to dynamically update the chat messages to our page

AngularJS Part: Our controller here is ChatCtrl and we are using $scope.globalChat model to bind our chat data to the page. Angular will dynamically add a single row per chat message whenever our view model is updated.

Conclusion

That was all guys, simple enough right!

Feel free to provide your updates/bugs/corrections for this code. I hope this proves useful for some of you at some point or the other.

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)
India India
Just a regular guy interesting in programming, gaming and a lot of other stuff Smile | :)

Please take a moment to visit my YouTube Channel and subscribe to it if you like its contents!
My YouTube Channel

Don't be a stranger! Say Hi!!

Cheers!

Comments and Discussions

 
Questionknockout & AngularJS both are used for same purpose Pin
Tridip Bhattacharjee30-Jul-13 8:42
professionalTridip Bhattacharjee30-Jul-13 8:42 
GeneralRe: knockout & AngularJS both are used for same purpose Pin
Nitij30-Jul-13 19:00
professionalNitij30-Jul-13 19:00 
GeneralRe: knockout & AngularJS both are used for same purpose Pin
arhoads767-Aug-13 10:52
arhoads767-Aug-13 10:52 
QuestionScreen shots Pin
Boipelo30-Jul-13 4:36
Boipelo30-Jul-13 4:36 
QuestionI am getting an exception!! Pin
Mannava Siva Aditya30-Jul-13 2:44
Mannava Siva Aditya30-Jul-13 2:44 
AnswerRe: I am getting an exception!! Pin
Nitij30-Jul-13 2:51
professionalNitij30-Jul-13 2:51 
GeneralRe: I am getting an exception!! Pin
Mannava Siva Aditya30-Jul-13 2:56
Mannava Siva Aditya30-Jul-13 2:56 
Question[ScriptService] Pin
Member 988248630-Jul-13 1:43
Member 988248630-Jul-13 1:43 
Remember to put the attribute [ScriptService] int the begin of the service. Smile | :)
AnswerRe: [ScriptService] Pin
Nitij30-Jul-13 2:49
professionalNitij30-Jul-13 2:49 
GeneralMy vote of 5 Pin
giri00129-Jul-13 20:53
giri00129-Jul-13 20:53 
GeneralNice Pin
CodeHawkz29-Jul-13 20:39
CodeHawkz29-Jul-13 20:39 
GeneralRe: Nice Pin
Nitij29-Jul-13 22:51
professionalNitij29-Jul-13 22:51 
GeneralMy vote of 1 Pin
alvas29-Jul-13 14:23
alvas29-Jul-13 14:23 
GeneralRe: My vote of 1 Pin
Nitij29-Jul-13 17:32
professionalNitij29-Jul-13 17:32 
GeneralRe: My vote of 1 Pin
alvas29-Jul-13 22:27
alvas29-Jul-13 22:27 
Questionplease upload ur source code in downloadable format Pin
Tridip Bhattacharjee29-Jul-13 4:38
professionalTridip Bhattacharjee29-Jul-13 4:38 
BugError calling service when apostrophe is used. Pin
kccodr29-Jul-13 4:19
professionalkccodr29-Jul-13 4:19 
GeneralRe: Error calling service when apostrophe is used. Pin
Nitij29-Jul-13 17:55
professionalNitij29-Jul-13 17:55 

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.