What a boon. MVC 4.0 + JQuery Mobile + SignalR

3 06 2013

MVC 4.0 + JQuery Mobile + SignalR

What a boon!

MVC 4.0 + JQuery Mobile + SignalR
Few of the best technologies when clubbed together can create a fabulous masterpiece within a time you blink your eye.

Here in this post I will brief you about how to create mobile application using MVC4.0, JQuery Mobile and SignalR.

For detail Chat application and SignalR technicalities please refer my previous posts.
https://dhavalupadhyaya.wordpress.com/2012/11/13/srchat-plug-play-enjoy/
https://dhavalupadhyaya.wordpress.com/2012/10/09/signalr-to-rescue/

Prerequisite:

MVC 4.0 ( http://www.asp.net/mvc/mvc4 )
Basic Knowledge of Razor ( http://www.w3schools.com/aspnet/razor_intro.asp )
Basic Knowledge of SignalR ( https://github.com/SignalR/SignalR/wiki )
Basic Knowledge of JQuery Mobile ( http://view.jquerymobile.com/1.3.1/dist/demos/ )

So let’s gets started

Step 1) Start visual studio and create a new Web Project with ASP.NET MVC 4 WEB Application template as shown below.

Step 1

Step 2) Select Mobile Application Project Template and click OK.

Step 2

Step 3) This will create a fresh web application with MVC 4.0 framework and some prebuild models, views and controllers for asp.net membership. We will use this as base project and add chat module to it in later steps. Change the “DefaultConnection” connectionstring into web.config file to point to your valid blank database. (Asp.Net will create the sql database to be used by membership when user registers for the first time)

Step 4) Install SignalR infrastructure. Go to Tools > Library Package Manager > Package Manager Console and Run below command as shown below

Install-Package Microsoft.AspNet.SignalR

Step 4

Step 4

Step 5) Once SignalR infrastructure is added, Goto Global.asax.cs and add “RouteTable.Routes.MapHubs();” line to the “Application_Start” method of the page as shown below. Without this route mapping MVC framework would not be able to map the requests sent by SignalR infrastructure. Also make sure you register this routs before you map any other routes in this method.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace SRJQM
{
    // Note: For instructions on enabling IIS6 or IIS7 classic mode,
    // visit http://go.microsoft.com/?LinkId=9394801

    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            // Register the default hubs route: ~/signalr
            RouteTable.Routes.MapHubs();

            AreaRegistration.RegisterAllAreas();

            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

        }
    }
}

Step 6) Create a new Class named “SRChatServer” that inherits from “Hub” class. Add a simple new method named “SendMessage” that will broadcast message to all the mobile clients that are connected to this hub.


using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hubs;
using Microsoft.AspNet.SignalR;

namespace SRJQM
{
    [HubName("sRChatServer")]
    public class SRChatServer : Hub
    {
        public void SendMessage(string user, string message)
        {
            Clients.All.addMessage(user, message);
        }
    }
}

Step 7) Now let’s create the Chat Model\View\Controller using which user will be able to send and receive broadcasted messages from the hub created in above step.

Step 8) Create a Class named “ChatController” in Controllers section. Add [Authorize] attribute to this class as we want to restrict its access only to the users who are logged into the application. Add “Index” method that will simply redirect the user to the Index.cshtml view inside the chat area which we will create in next step.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace SRJQM.Controllers
{
    [Authorize]
    public class ChatController : Controller
    {
        public ActionResult Index()
        {
            try
            {
                ViewBag.Message = "Welcome to chat sample : " + User.Identity.Name;
                return View();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

    }
}

9) Create a folder (area) named “Chat” inside “Views” folder. Add “Index.cshtml” inside this folder. This is the main page that will connect to SignalR Server Hub created above. It also contains UI that is used for sending and receiving messages that are broadcasted by the SignalR server hub.


<script src="../../Scripts/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="../../Scripts/jquery.signalR-1.1.2.min.js" type="text/javascript"></script>
<script src="~/signalr/hubs"></script>
@model SRJQM.Models.LoginModel
@{
    ViewBag.Title = "Chat Page";
}
<h2>@ViewBag.Message</h2>
<p>
    To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">
        http://asp.net/mvc</a>.
</p>
<div data-role="content">
    <div data-role="fieldcontain">
        <ul id="messages">
        </ul>
    </div>
    <div data-role="fieldcontain">
        <label for="txtMessage">
            Message:
        </label>
        <input name="txtMessage" id="txtMessage" placeholder="Type Message" value="" type="text">
    </div>
    <div class="ui-grid-a">
        <div class="ui-block-a">
            <a id="btnSendChat" data-role="button" data-theme="b" href="#">Send </a>
        </div>
        <div class="ui-block-b">
            <a id="btnClear" data-role="button" data-theme="b" href="#">Clear </a>
        </div>
    </div>
</div>
<ul data-role="listview" data-inset="true">
    <li data-role="list-divider">Navigation</li>
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    <li>@Html.ActionLink("About", "About", "Home")</li>
    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
</ul>
<script type="text/javascript">
    $(function () {
        var chatHub = $.connection.sRChatServer;

        // Start the connection
        $.connection.hub.start();

        // Declare a function on the blog hub so the server can invoke it
        chatHub.client.addMessage = function (user, message) {
            $('#messages').append('<li><strong>' + user + '</strong>: ' + message + '</li>');
        };

        $("#btnSendChat").click(function (event) {
            chatHub.server.sendMessage("@User.Identity.Name", $("#txtMessage").val());
            $("#txtMessage").val("")
        });

        $("#btnClear").click(function (event) {
            $("#txtMessage").val("")
        });
    });
</script>

10) Goto Views > Home folder and code conditional link inside Index.cshtml, About.cshtml, and Contact.cshtml views as shown below. This link will help user to navigate to chat module.


<ul data-role="listview" data-inset="true">
    <li data-role="list-divider">Navigation</li>
    <li>@Html.ActionLink("About", "About", "Home")</li>
    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
    @if (Request.IsAuthenticated)
    {
        <li>@Html.ActionLink("Chat", "Index", "Chat", routeValues: null, htmlAttributes: null)</li>
    }
</ul>

That’s it.
Compile the project and open multiple instances of browser or mobile, login into the application using different registered users and start using the application.

Step 5          Step 6

Step 7          Step 8





SRChat Plug Play Enjoy

13 11 2012

SignalR Chat Plug Play Enjoy

Hey as per my promise and continuation to my previous SignalR To Rescue article here I present the more, in-depth concepts and overview of Chat application build over SignalR and asp.net.
For those who are novice to SignalR or have not read my previous post about basic infrastructure and overview and notification sample of SignalR are advised to go through this post first.
Coming straight to the point below is the overview of the chat module that you will build step by step while progressing this post
– It uses SignalR infrastructure as the base.
Used for coding “SRChatServer” server hub and “SRChatClient” client hub.
– It uses JQuery version 1.8.2
Used for all the power pact code on “SRChatClient” client hub
– It uses JQuery Template based binding mechanism for creating UI on client as part of best practice.
Used for binding the json data coming from “SRChatServer” methods and binding it to templates to generate the html based UI quickly
– It uses JQuery Dialog Extension from crab community at http://code.google.com/p/jquery-dialogextend/. Thanks a ton guys for such a beautiful and sleek master piece.
Used for showing the minimizable and movable dialog boxes for each chat instances with different users.

Brief Architecture Overview

Step 1) Create the asp.net web application project and install the SignalR infrastructure. Please refer SignalR to Rescue for basic configuration

Step 2) Create a serializable class named “MessageRecipient“. This class would act as an object for the users connecting to server hub. The reason for marking it serializable is to make easy transfer across the ajax request easily.

	[Serializable]
	public class MessageRecipient
	{
		publicMessageRecipient()
		{
			chatRoomIds = new List();
		}
		public string messageRecipientId { get; set; }
		public string messageRecipientName { get; set; }
		public string connectionId { get; set; }
		public ListchatRoomIds { get; set; }
	}

Step 3) Create a serializable class named “ChatRoom “.This class would act as a bridge between the users who want to chat with each other. The same concept can be enhanced for providing Group Chat feature to this module with few lines of code.

	[Serializable]
	public class ChatRoom
	{
		public string chatRoomId { get; set; }
		public string chatRoomInitiatedBy { get; set; }
		public string chatRoomInitiatedTo { get; set; }
		public ListmessageRecipients { get; set; }

		publicChatRoom()
		{
			chatRoomId = Guid.NewGuid().ToString();
			messageRecipients = new List();
		}
	}

Step 4) Create a serializable class named “ChatMessage“. This class would act as an object for each chat messages that are exchanged between the users over chat.

	[Serializable]
	public class ChatMessage
	{
		publicChatMessage()
		{
		}

		public string chatMessageId { get; set; }
		public string conversationId { get; set; }
		public string senderId { get; set; }
		public string senderName { get; set; }
		public string messageText { get; set; }
		public string displayPrefix { get { return string.Format("[{0}] {1}:", timestamp.ToShortTimeString(), senderName); } }
		publicDateTime timestamp { get; set; }
	}

Step 5) Create a serializable class named “OnlineContacts“. This class would act as an object for each userconnecting to server hub. It is used here so that a list of Message Recipients can be transferred back to client hub when the user wants to initiate the chat.

	[Serializable]
	public class OnlineContacts
	{
		public ListmessageRecipients { get; set; }
		publicOnlineContacts()
		{
		messageRecipients = new List();
		}
	}

Step 6) Finally create a class named “SRChatServer” and inherit from Hub class. This is the server hub that would act as the main heart for building the chat server. Decorate the class with [HubName] attribute. This is the name that Client hub uses to connect to sever hub. We will also add two private static variables for holding our data related to chat rooms and users connected to server hub. As SignalR core is totally thread safe I am using ConcurrentDictionary in order to maintain our collection in thread safe manner.

[HubName("sRChatServer")]
    public class SRChatServer : Hub
    {
		#region Private Variables
        private static readonly ConcurrentDictionary<string, MessageRecipient> _chatUsers = new ConcurrentDictionary<string, MessageRecipient>(StringComparer.OrdinalIgnoreCase);
        private static readonly ConcurrentDictionary<string, ChatRoom> _chatRooms = new ConcurrentDictionary<string, ChatRoom>(StringComparer.OrdinalIgnoreCase);
        #endregion
	 }

Now let’s start adding more ingredients to our server hub.

First we will add connect and disconnect method. Connect method is called whenever the client is loaded for first time and theninitiates the request to connect to server hub. It will simply fetch some data related to connecting client like its connection ID, user IDetc. and store or modify the collection maintained on server hub. Similarly the Disconnect method is called just before the client moves out from the server hub.

public bool Connect(string userId, string userName)
        {
            try
            {
                if (string.IsNullOrEmpty(userId) | string.IsNullOrEmpty(userName))
                {
                    return false;
                }
                if (GetChatUserByUserId(userId) == null)
                {
                    AddUser(userId, userName);
                }
                else
                {
                    ModifyUser(userId, userName);
                }
                SendOnlineContacts();
                return true;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Problem in connecting to chat server!");
            }
        }
        public override Task Disconnect()
        {
            try
            {
                DeleteUser(Context.ConnectionId);
                return null;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Problem in disconnecting from chat server!");
            }
        }

Now let’s add few methods that will help clients to initiate and end chat with other users. InitiateChat method simply takes in required data about the users who want to get connected and start the chat. The code simply builds a technical bridge between the two and initiates a pipeline through ChatRoom using which messages will be exchanged. The EndChat method simply ends the chat and removes the bridge between the two.

public bool InitiateChat(string fromUserId, string fromUserName, string toUserId, string toUserName)
        {
            try
            {
                if (string.IsNullOrEmpty(fromUserId) || string.IsNullOrEmpty(fromUserName) || string.IsNullOrEmpty(toUserId) || string.IsNullOrEmpty(toUserName))
                {
                    return false;
                }

                var fromUser = GetChatUserByUserId(fromUserId);
                var toUser = GetChatUserByUserId(toUserId);

                if (fromUser != null && toUser != null)
                {
                    if (!CheckIfRoomExists(fromUser, toUser))
                    {
                        //Create New Chat Room
                        ChatRoom chatRoom = new ChatRoom();
                        chatRoom.chatRoomInitiatedBy = fromUser.messageRecipientId;
                        chatRoom.chatRoomInitiatedTo = toUser.messageRecipientId;

                        chatRoom.messageRecipients.Add(fromUser);
                        chatRoom.messageRecipients.Add(toUser);

                        //create and save blank message to get new conversation id
                        ChatMessage chatMessage = new ChatMessage();
                        chatMessage.messageText = "Chat Initiated";
                        chatMessage.senderId = fromUser.messageRecipientId;
                        chatMessage.senderName = fromUser.messageRecipientName;

                        fromUser.chatRoomIds.Add(chatRoom.chatRoomId);
                        toUser.chatRoomIds.Add(chatRoom.chatRoomId);

                        //Create SignalR Group for this chat room and add users connection to it
                        Groups.Add(fromUser.connectionId, chatRoom.chatRoomId);
                        Groups.Add(toUser.connectionId, chatRoom.chatRoomId);

                        //Add Chat room object to collection
                        if (_chatRooms.TryAdd(chatRoom.chatRoomId, chatRoom))
                        {
                            //Generate Client UI for this room
                            Clients[fromUser.connectionId].initiateChatUI(chatRoom);
                        }
                    }
                }
                return true;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Problem in starting chat!");
            }
        }
        public bool EndChat(ChatMessage chatMessage)
        {
            try
            {
                ChatRoom chatRoom;
                if (_chatRooms.TryGetValue(chatMessage.conversationId, out chatRoom))
                {
                    if (_chatRooms[chatRoom.chatRoomId].chatRoomInitiatedBy == chatMessage.senderId)
                    {
                        chatMessage.messageText = string.Format("{0} left the chat. Chat Ended!", chatMessage.senderName);
                        if (_chatRooms.TryRemove(chatRoom.chatRoomId, out chatRoom))
                        {
                            Clients[chatRoom.chatRoomId].receiveEndChatMessage(chatMessage);
                            foreach (MessageRecipient messageReceipient in chatRoom.messageRecipients)
                            {
                                if (messageReceipient.chatRoomIds.Contains(chatRoom.chatRoomId))
                                {
                                    messageReceipient.chatRoomIds.Remove(chatRoom.chatRoomId);
                                    Groups.Remove(messageReceipient.connectionId, chatRoom.chatRoomId);
                                }
                            }
                        }
                    }
                    else
                    {
                        MessageRecipient messageRecipient = GetChatUserByUserId(chatMessage.senderId);
                        if (messageRecipient != null && messageRecipient.chatRoomIds.Contains(chatRoom.chatRoomId))
                        {
                            chatRoom.messageRecipients.Remove(messageRecipient);
                            messageRecipient.chatRoomIds.Remove(chatRoom.chatRoomId);
                            if (chatRoom.messageRecipients.Count < 2)
                            {
                                chatMessage.messageText = string.Format("{0} left the chat. Chat Ended!", chatMessage.senderName);
                                if (_chatRooms.TryRemove(chatRoom.chatRoomId, out chatRoom))
                                {
                                    Clients[chatRoom.chatRoomId].receiveEndChatMessage(chatMessage);
                                    foreach (MessageRecipient messageReceipient in chatRoom.messageRecipients)
                                    {
                                        if (messageReceipient.chatRoomIds.Contains(chatRoom.chatRoomId))
                                        {
                                            messageReceipient.chatRoomIds.Remove(chatRoom.chatRoomId);
                                            Groups.Remove(messageReceipient.connectionId, chatRoom.chatRoomId);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                chatMessage.messageText = string.Format("{0} left the chat.", chatMessage.senderName);
                                Groups.Remove(messageRecipient.connectionId, chatRoom.chatRoomId);
                                Clients[messageRecipient.connectionId].receiveEndChatMessage(chatMessage);
                                Clients[chatRoom.chatRoomId].receiveLeftChatMessage(chatMessage);
                                Clients[chatRoom.chatRoomId].updateChatUI(chatRoom);
                            }
                        }
                    }
                }
                else
                {
                    throw new InvalidOperationException("Problem in ending chat!");
                }
                return true;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Problem in ending chat!");
            }
        }

Finally add below methods that actually send the message to particular client. SendChatMessage sends the message from one user to another and invokes the client hub method to push message to that particular client. SendOnlineContacts is just used for getting the online users list using which the chat can be initiated with anyone of them. I have kept this method private so that I can call it from connect method to broad cast the entire list including new user to all the users. If you want to fetch online contacts on some trigger on client do not forget to make it public.

public bool SendChatMessage(ChatMessage chatMessage)
        {
            try
            {
                ChatRoom chatRoom;
                if (_chatRooms.TryGetValue(chatMessage.conversationId, out chatRoom))
                {
                    chatMessage.chatMessageId = Guid.NewGuid().ToString();
                    chatMessage.timestamp = DateTime.Now;
                    Clients[chatMessage.conversationId].receiveChatMessage(chatMessage, chatRoom);
                    return true;
                }
                else
                {
                    throw new InvalidOperationException("Problem in sending message!");
                }
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Problem in sending message!");
            }
        }
        private bool SendOnlineContacts()
        {
            try
            {
                OnlineContacts onlineContacts = new OnlineContacts();
                foreach (var item in _chatUsers)
                {
                    onlineContacts.messageRecipients.Add(item.Value);
                }
                Clients.onGetOnlineContacts(onlineContacts);
                return false;
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException("Problem in getting contacts!");
            }
        }

Apart from the above methods there are few auxiliary methods for supporting the above business logic.

That’s it for server hub. Now let’s plug Client Hub Code step by step

Step 1) Add a new asp.net webform and name it “SRChatClient.aspx”
This webform will be our chat client using which users can chat with each other. This webform when loaded will generate a random number and using this random number it connects with SRChatServer and registers itself. When multiple instances of different browsers are opened we have a group of users getting online who can chat with each other.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SRChatClient.aspx.cs" Inherits="SRChat.SRChatClient" %></pre>
<style><!--
        .chatRooms
        {
            max-height: 500px;
            overflow: auto;
        }
        .chatRoom
        {
            width: 100%;
            height: 250px;
            border: 1px solid #ccc;
        }
        .chatMessages
        {
            width: 100%;
            height: 200px;
            overflow: auto;
            margin-left: 0px;
            padding-left: 0px;
        }
        .chatMessages li
        {
            list-style-type: none;
            padding: 1px;
        }
        .chatNewMessage
        {
            border: 1px solid #ccc;
            width: 200px;
            float: left;
            height: 18px;
        }
        .chatMessage
        {
        }
        .chatSend
        {
            float: left;
        }

--></style>
<pre>

</pre>
<form id="form1">
<h3>SRChat - By Dhaval Upadhyaya - <a href="https://dhavalupadhyaya.wordpress.com/about-me/" target="_blank">https://dhavalupadhyaya.wordpress.com/about-me/</a></h3>
<div>
<div id="userNameLabel"></div>
<div id="chatRooms"></div>
<div id="chatOnlineContacts"></div>
</div>
</form>
<pre>

Step 2) Add references to style and js files required to bootstrap this page.

		<link href="Styles/jquery-ui.css" rel="stylesheet" /><script type="text/javascript" src="Scripts/jquery-1.8.2.js"></script><script type="text/javascript" src="Scripts/jquery-ui.js"></script>
<script type="text/javascript" src="Scripts/jquery.dialogextend.1_0_1.js"></script><script type="text/javascript" src="Scripts/jquery.signalR.js"></script>
<script type="text/javascript" src="Scripts/jQuery.tmpl.js"></script><script type="text/javascript" src="signalr/hubs"></script>

Step 3) Add templates that can be used to bind the json data and generate the html based UI quickly. Each template as the name justifies is used to generate the html UI when that particular type of triggers occur from the server.

<script id="new-online-contacts" type="text/x-jquery-tmpl">// <![CDATA[
<div>
<ul>
        {{each messageRecipients}}

	<li id="chatLink${messageRecipientId}"><a href="javascript:;" onclick="javascript:SRChat.initiateChat('${messageRecipientId}','${messageRecipientName}');">${messageRecipientName}</a></li>

        {{/each}}</ul>

</div>

// ]]></script>
<script id="new-chatroom-template" type="text/x-jquery-tmpl">// <![CDATA[

<div id="chatRoom${chatRoomId}" class="chatRoom">

<ul id="messages${chatRoomId}" class="chatMessages"></ul>

<form id="sendmessage${chatRoomId}" action="#">
            <input type="text" id="newmessage${chatRoomId}" class="chatNewMessage"/>

<div class="clear"></div>

            <input type="button" id="chatsend${chatRoomId}" value="Send" class="chatSend" onClick="javascript:SRChat.sendChatMessage('${chatRoomId}')" />
            <input type="button" id="chatend${chatRoomId}" value="End Chat" class="chatSend" onClick="javascript:SRChat.endChat('${chatRoomId}')" />
        </form>

</div>

// ]]></script>
<script id="new-chat-header" type="text/x-jquery-tmpl">// <![CDATA[

<div id="chatRoomHeader${chatRoomId}">
        {{each messageRecipients}}
            {{if $index == 0}}
                ${messageRecipientName}
            {{else}}
                , ${messageRecipientName}
            {{/if}}
        {{/each}}
<div>

// ]]></script>
<script id="new-message-template" type="text/x-jquery-tmpl">// <![CDATA[

	<li class="message" id="m-${chatMessageId}">
        <strong>${displayPrefix}</strong>
        {{html messageText}}</li>

// ]]></script>
<script id="new-notify-message-template" type="text/x-jquery-tmpl">// <![CDATA[

	<li class="message" id="m-${chatMessageId}">
        <strong>{{html messageText}}</strong></li>

// ]]></script>

Step 5) Finally we will plug the magical code that would connect to our SRChatServer and registers all the client methods that would be invoked from server side in order to trigger and inject various messages and chat window instances.

<script type="text/javascript">// <![CDATA[
$(document).ready(function () {             SRChat.attachEvents();         });         SRChat = new function () {             var chatRooms = 0;             var numRand = Math.floor(Math.random() * 1000)             var senderId = numRand;             var senderName = 'User ' + numRand;             var sRChatServer;             window.onbeforeunload = function () {                 if (chatRooms > 0)
                    return "All chat instances will be ended!";
            };

            this.attachEvents = function () {
                $("#userNameLabel").html(senderName);
                if ($.connection != null) {
                    jQuery.support.cors = true;
                    $.connection.hub.url = 'signalr/hubs';
                    sRChatServer = $.connection.sRChatServer;

                    $.connection.hub.start({ transport: 'auto' }, function () {
                        sRChatServer.server.connect(senderId, senderName).fail(function (e) {
                            alert(e);
                        });
                    });

                    sRChatServer.client.initiateChatUI = function (chatRoom) {
                        var chatRoomDiv = $('#chatRoom' + chatRoom.chatRoomId);
                        if (($(chatRoomDiv).length > 0)) {
                            var chatRoomText = $('#newmessage' + chatRoom.chatRoomId);
                            var chatRoomSend = $('#chatsend' + chatRoom.chatRoomId);
                            var chatRoomEndChat = $('#chatend' + chatRoom.chatRoomId);

                            chatRoomText.show();
                            chatRoomSend.show();
                            chatRoomEndChat.show();
                        }
                        else {
                            var e = $('#new-chatroom-template').tmpl(chatRoom);
                            var c = $('#new-chat-header').tmpl(chatRoom);

                            chatRooms++;

                            //dialog options
                            var dialogOptions = {
                                "id": '#messages' + chatRoom.chatRoomId,
                                "title": c,
                                "width": 360,
                                "height": 365,
                                "modal": false,
                                "resizable": false,
                                "close": function () { javascript: SRChat.endChat('' + chatRoom.chatRoomId + ''); $(this).remove(); }
                            };

                            // dialog-extend options
                            var dialogExtendOptions = {
                                "close": true,
                                "maximize": false,
                                "minimize": true,
                                "dblclick": 'minimize',
                                "titlebar": 'transparent'
                            };

                            e.dialog(dialogOptions).dialogExtend(dialogExtendOptions);

                            $('#sendmessage' + chatRoom.chatRoomId).keypress(function (e) {
                                if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
                                    $('#chatsend' + chatRoom.chatRoomId).click();
                                    return false;
                                }
                            });
                        }
                    };

                    sRChatServer.client.updateChatUI = function (chatRoom) {
                        var chatRoomHeader = $('#chatRoomHeader' + chatRoom.chatRoomId);
                        var c = $('#new-chat-header').tmpl(chatRoom);
                        chatRoomHeader.html(c);
                    };

                    sRChatServer.client.receiveChatMessage = function (chatMessage, chatRoom) {
                        sRChatServer.client.initiateChatUI(chatRoom);
                        var chatRoom = $('#chatRoom' + chatMessage.conversationId);
                        var chatRoomMessages = $('#messages' + chatMessage.conversationId);
                        var e = $('#new-message-template').tmpl(chatMessage).appendTo(chatRoomMessages);
                        e[0].scrollIntoView();
                        chatRoom.scrollIntoView();
                    };

                    sRChatServer.client.receiveLeftChatMessage = function (chatMessage) {
                        var chatRoom = $('#chatRoom' + chatMessage.conversationId);
                        var chatRoomMessages = $('#messages' + chatMessage.conversationId);
                        var e = $('#new-notify-message-template').tmpl(chatMessage).appendTo(chatRoomMessages);
                        e[0].scrollIntoView();
                        chatRoom.scrollIntoView();
                    };

                    sRChatServer.client.receiveEndChatMessage = function (chatMessage) {
                        var chatRoom = $('#chatRoom' + chatMessage.conversationId);
                        var chatRoomMessages = $('#messages' + chatMessage.conversationId);
                        var chatRoomText = $('#newmessage' + chatMessage.conversationId);
                        var chatRoomSend = $('#chatsend' + chatMessage.conversationId);
                        var chatRoomEndChat = $('#chatend' + chatMessage.conversationId);

                        chatRooms--;

                        var e = $('#new-notify-message-template').tmpl(chatMessage).appendTo(chatRoomMessages);

                        chatRoomText.hide();
                        chatRoomSend.hide();
                        chatRoomEndChat.hide();

                        e[0].scrollIntoView();
                        chatRoom.scrollIntoView();
                    };

                    sRChatServer.client.onGetOnlineContacts = function (chatUsers) {
                        var e = $('#new-online-contacts').tmpl(chatUsers);
                        var chatLink = $('#chatLink' + senderId);
                        e.find("#chatLink" + senderId).remove();
                        $("#chatOnlineContacts").html("");
                        $("#chatOnlineContacts").html(e);
                    };
                }
            };

            this.sendChatMessage = function (chatRoomId) {
                var chatRoomNewMessage = $('#newmessage' + chatRoomId);

                if (chatRoomNewMessage.val() == null || chatRoomNewMessage.val() == "")
                    return;

                var chatMessage = {
                    senderId: senderId,
                    senderName: senderName,
                    conversationId: chatRoomId,
                    messageText: chatRoomNewMessage.val()
                };

                chatRoomNewMessage.val('');
                chatRoomNewMessage.focus();
                sRChatServer.server.sendChatMessage(chatMessage).fail(function (e) {
                    alert(e);
                });

                return false;
            };

            this.endChat = function (chatRoomId) {
                var chatRoomNewMessage = $('#newmessage' + chatRoomId);

                var chatMessage = {
                    senderId: senderId,
                    senderName: senderName,
                    conversationId: chatRoomId,
                    messageText: chatRoomNewMessage.val()
                };
                chatRoomNewMessage.val('');
                chatRoomNewMessage.focus();
                sRChatServer.server.endChat(chatMessage).fail(function (e) {
                    //alert(e);
                });
            };

            this.initiateChat = function (toUserId, toUserName) {
                if (sRChatServer == null) {
                    alert("Problem in connecting to Chat Server. Please Contact Administrator!");
                    return;
                }
                sRChatServer.server.initiateChat(senderId, senderName, toUserId, toUserName).fail(function (e) {
                    alert(e);
                });
            };

        };
// ]]></script>

That It run the application and start chatting.

In next version I will add the feature of group chat. Though just few lines of code with the above version will do the job, I leave this to reader as a small exercise.

The full source code of Version 1.0 can be found on my GitHub repo at below link.

https://github.com/upadhyayadhaval/SRChat





SignalR To Rescue

9 10 2012

SignalR To Rescue

Asynchronous library for .NET to help build real-time, multi-user interactive web applications.

Many asp.net based real time applications uses some traditional techniques to trigger some business on client. Some old techniques include an recursive timely call like Ajax call to server to fetch data at intervals, a trigger from server using duplex channel when using WCF etc… These all traditional techniques has some or more disadvantages like a constant callbacks increasing server load, firewalls restricting duplex protocols hiding clients and resulting in broken links in duplex channels etc.

But now SignalR has emerged to rescue us from these types of architectural bottlenecks. One can easily build notification systems, chat applications, triggers across multiple applications and other myriads of real-time application using this simple yet powerful technology.

In this article I will jot down a very basic sample of broadcasting message to a group or to a specific client using SignalR and asp.net. For further technical documentation please refer https://github.com/SignalR/SignalR/wiki

Prerequisites

In order to take advantage of SignalR a small integration of its infrastructure is required. To keep it simple I will use use Package Manager Console and Nuget package of SignalR later in the project.

Application

Two important parts needs to be coded for creating basic SignalR based applications.

Server Hub: A simple class containing methods that can be called from client hub. Also it is the main container of logic on server side. It can trigger methods on client hub with specific connection or with a group of connections.

Client Hub: A piece of code that is used for registering to server hub. It can contain methods that call server hub methods. It can also contain methods that are called from server hub. Client hubs can be coded in native ASP.Net or JavaScript.

Step 1) Create a new Asp.Net Web Application (“SignalRNotification”) in Visual Studio. Remove all the default folders and web forms created by template if any.

Step 2) Install SignalR infrastructure
Go to Tools > Library Package Manager > Package Manager Console

Run below command
Install-Package SignalR

The above steps will install SignalR infrastructure required to make use of this technology

For detail description and latest news for SignalR release please refer http://nuget.org/packages/signalr

Step 3) Create a new class named NotificationHub that inherits from Hub.

using System;
using SignalR.Hubs;

namespace SignalRNotification
{
    [HubName("notificationHub")]
    public class NotificationHub:Hub
    {
        public void NotifyUsers(string message)
        {
            //Invokes onReceiveNotification function for all the Clients
            Clients.onReceiveNotification(string.Format("{0} {1}", message, DateTime.Now.ToString()));
        }
    }
}

In the above code one can chose to send message to all clients, a group of clients or to a specific client. To keep things simple the above code simply broadcast the message to all clients.

Step 4) Create a web form Admin.aspx. Add a textbox and a button to it as shown below. It includes reference to magical scripts that is actually responsible for connecting to SignalR hub and creating the proxy class. Internally this script registers the client using a unique GUID by making an ajax call. We will use this form for sending messages.


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Admin.aspx.cs" Inherits="SignalRNotification.Admin" %>

    Admin
<script type="text/javascript" src="Scripts/jquery-1.6.4.min.js"></script><script type="text/javascript" src="Scripts/jquery.signalR-0.5.3.min.js"></script>
<script type="text/javascript" src="/signalr/hubs"></script><script type="text/javascript">// <![CDATA[
        $(function () {
            // the generated client-side hub proxy
            var server = $.connection.notificationHub;
            // Start the connection
            $.connection.hub.start();
            $('#xbtnNotify').click(function () {
                var message = document.getElementById('txtMessage').value;
                //call the method on server using the proxy
                server.notifyUsers(message);
            });
        });

// ]]></script>

<form id="form1">
<h1>Administrator</h1>
<div>Message :
 <textarea id="txtMessage" style="width: 400px;" rows="3" cols="1"></textarea>
 <input id="xbtnNotify" type="button" value="Send this to All Users" /></div>
</form>

Step 5) Create a web form User.aspx as shown below. We will use this form for receiving messages.


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="User.aspx.cs" Inherits="SignalRNotification.User" %>

    User

<script type="text/javascript" src="Scripts/jquery-1.6.4.min.js"></script><script type="text/javascript" src="Scripts/jquery.signalR-0.5.3.min.js"></script>
<script type="text/javascript" src="/signalr/hubs"></script><script type="text/javascript">// <![CDATA[
// the generated client-side hub proxy
        $(function () {
            var server = $.connection.notificationHub;
            //This is the method that will be invoked from the server!!
            server.onReceiveNotification = function (message) {
                $("#messages").append('
	<li>' + message + '</li>

');
            };
            // Start the connection
            $.connection.hub.start();
        });
// ]]></script><pre>
<form id="form1">
<div>
<h1>User</h1>
</div>
<ul id="messages"></ul>
</form>
</pre>

That’s it.

A simple way to do complex things.
In my next article I will explain how to create CHAT server using SignalR.





Screen Readers with Silverlight Applications

13 01 2012

Screen Readers with Silverlight Applications.


Web based applications fly across global boundaries due to which accessibility and ease of use becomes one of the most important factor that drives the sales count of the service we provide.

As per 508 Compliance if we require our service served to audience with disabilities it should follow various aspects.

For thorough details please refer

http://en.wikipedia.org/wiki/Section_508_Amendment_to_the_Rehabilitation_Act_of_1973

This article is specifically targeted for users interested to know

How do we provide support for Screen Readers software with the applications developed in one of the most cutting edge technology Silverlight?

Traditional web based applications that emits html to browsers easily comply with Screen readers based on specifications followed for HTML.
With Silverlight based applications the Silverlight plug-in takes care for screen readers. We just need to follow some methodologies provided in form of Automation Properties in Silverlight. Automation Properties provide several methods and attachable properties which help us define access keys, instruction text for screen readers and much more.

For details please refer

http://msdn.microsoft.com/en-us/library/system.windows.automation.automationproperties(v=vs.95).aspx

By following these properties the work is pretty much simple.

Sample 1

<TextBox  id=” nameTextBox” AutomationProperties.Name=”Please enter name” />

Screen reader will voice over “Please enter name” when this textbox receives focus.

Sample 2

<TextBlock id=”nameTextBlock” Text=”Please enter name” />
<TextBox  id=”nameTextBox” AutomationProperties.LabeledBy="{Binding ElementName= nameTextBlock }" />

Screen reader will voice over “Please enter name” when this textbox receives focus. Here is the case when we want to directly bind the AutomationProperties.Name property of textbox to any control, in this case a label beside text box.

Pretty much simply till here.

But wait!

Silverlight SDK and Controls are very powerful and includes number of controls that can be bound directly to objects. Here is where most of the screen readers fail while generating voice over for such control. I will depict the problem with one of the control “Combo Box”

Problem
If your combo box is bound to any dictionary object including collection than screen readers will work perfectly. But if combo box is bound to any object like “Person” than screen readers will fail.

Sample Code

<ComboBox Name="personsComboBox" ItemsSource="{Binding}" DisplayMemberPath="Name"/>

 

public class Person : INotifyPropertyChanged
    {
        private string name;
        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                if (value != name)
                {
                    name = value;
                    OnPropertyChnaged("Name");
                }
            }
        }

        private string surname;
        public string Surname
        {
            get
            {
                return surname;
            }
            set
            {
                if (value != surname)
                {
                    surname = value;
                    OnPropertyChnaged("Surname");
                }
            }
        }

        private Int32 age;
        public Int32 Age
        {
            get
            {
                return age;
            }
            set
            {
                if (value != age)
                {
                    age = value;
                    OnPropertyChnaged("Age");
                }
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChnaged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion

    }

Solution

Screen Readers works on strings attached to the controls using attachable property AutomationProperties.Name as described in above example code. With the case were we are binding objects directly to control, screen readers fail and voice over the entire path (Namespace.ObjecName) were the object reside. In order to overcome this issue simply override ToString() method for your objects.

Adding this code with the Person object designed above will ask screen readers to voice over “Name” property.

public override string ToString()
        {
            return Name;
        }

Tricky but yet simple.

Enjoy Coding.





Automatic File Download

12 02 2011

Troubleshooting automatic file download issue in IE7.0 and later version

Problem: File download using .aspx page or HTTP handler does not work in IE 7.0 and later version.

Solution: If you are storing your files in database and using .aspx page or HTTP handler to write the file to the response, than you must have been bugged by this issue. This is not an error or IE bug but it’s just that the newer version of IE wants to protect its users from malicious contents that could sniff or simply harm your computer or data.
To make it more secure IE has provided option to its users to allow\disallow the automatic downloading of files. This can be set in any kind of security zones. To turn On\Off user needs to perform below steps

Step1) Open IE and click Tools > Internet Options
Step2) Go to Security Tab. Select desired zone and then click Custom Level Button.
Step3) Now look for Downloads > Automatic prompting for file downloads. By default this is disabled due to which any automatic file downloads will be blocked. Enable this feature as shown below.

Note: If you do not want your users to make any changes for above mentioned settings than you can achieve the automatic download but as a tweak you need to have an interim page which requires some user action like button click to download a file.





Animating Grid Column

12 02 2011

Troubleshooting InvalidOperationException: DoubleAnimation cannot be used to animate property Width due to incompatible type.

Silverlight has great support for Animation and you might have experienced animating most of the controls on one or the other aspect. But animating ColumnDefinition.Width or RowDefinition.Height properties for Grid control is not straight forward. To achieve this one has to make a tweak.

Problem : InvalidOperationException: DoubleAnimation cannot be used to animate property Width due to incompatible type.

Reason : Width property of grid is not of type double but is of type GridLength. Further GridLength has a property called Value of type Double, but Value is ReadOnly so to change the width or height you have to create a new instance of the GridLength object and set it to the Width Property of the grid.

Solution: To achieve this we simply create a dependency property in our page and in the change event of that property we set the new instance of GridLength to width property of the grid.


<Grid x:Name="myGrid">
        <Grid.Resources>
            <Storyboard x:Name="myStoryboardC">
                <DoubleAnimation From="150" To="0" Duration="00:00:1"
                Storyboard.TargetName="myPageName"
                Storyboard.TargetProperty="ColumnWidth">
                    <DoubleAnimation.EasingFunction>
                        <CubicEase EasingMode="EaseOut"/>
                    </DoubleAnimation.EasingFunction>
                </DoubleAnimation>
            </Storyboard>
            <Storyboard x:Name="myStoryboardE">
                <DoubleAnimation From="0" To="150" Duration="00:00:1"
                Storyboard.TargetName= “myPageName"
                Storyboard.TargetProperty="ColumnWidth">
                    <DoubleAnimation.EasingFunction>
                        <CubicEase EasingMode="EaseOut"/>
                    </DoubleAnimation.EasingFunction>
                </DoubleAnimation>
            </Storyboard>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="Column1" Width="150" MaxWidth="250" MinWidth="0">
            </ColumnDefinition>
            <ColumnDefinition Width="15" MaxWidth="15" MinWidth="15"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Rectangle x:Name="myRectangle"
     Fill="Blue" Width="200" Height="30" Grid.Column="0"/>

        <layoutToolkit:Accordion x:Name="acc" SelectionMode="ZeroOrMore" Grid.Column="0">
            <layoutToolkit:AccordionItem Header="hi">
                <StackPanel>
                    <TextBlock Text="1"/>
                    <TextBlock Text="2"/>
                    <TextBlock Text="3"/>
                </StackPanel>
            </layoutToolkit:AccordionItem>
            <layoutToolkit:AccordionItem Header="hi" Content="Task 2"/>
            <layoutToolkit:AccordionItem Header="hi" Content="Task 3"/>
        </layoutToolkit:Accordion>

        <Button x:Name="btnEC" Content="&lt;" Grid.Column="1" Click="Button_Click" />

        <data:DataGrid x:Name="dataGrid1" Margin="0" Grid.Column="2">
            <data:DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    <StackPanel Background="LightBlue">
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="This item has details." />
                        </StackPanel>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="Here is some data: " />
                            <TextBlock Text="{Binding FirstName}" />
                            <TextBlock Text=" LastName" />
                            <TextBlock Text="{Binding LastName}" />
                        </StackPanel>
                    </StackPanel>
                </DataTemplate>
            </data:DataGrid.RowDetailsTemplate>
        </data:DataGrid>

    </Grid>


public partial class MainPage : UserControl
    {

        bool toogle;
        public MainPage()
        {
            InitializeComponent();
            this.Name = "myPageName";
        }

        public static readonly DependencyProperty ColumnWidthProperty = DependencyProperty.Register("ColumnWidth",
                                                                                                    typeof(double),
                                                                                                    typeof(MainPage),
                                                                                                    new PropertyMetadata
                                                                                                      (ColumnWidthChanged));
        public double ColumnWidth
        {
            get
            {
                return (double)GetValue(ColumnWidthProperty);
            }
            set
            {
                SetValue(ColumnWidthProperty, value);
            }
        }

        private static void ColumnWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var mainPage = (MainPage)d;
            mainPage.Column1.Width = new GridLength((double)e.NewValue);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (!toogle)
            {
                myStoryboardC.Begin();
                btnEC.Content = ">";
                toogle = !toogle;
            }
            else
            {
                myStoryboardE.Begin();
                btnEC.Content = "<";
                toogle = !toogle;
            }
        }
    }





Show loading while downloading XAP modules in prism.

4 09 2010

Show loading while downloading XAP modules in prism.

Scenario : To show progress bar when modules (xap’s) in prism are downloaded.

Problem : Currently in prism the default ModuleManager class does not expose a progress event that can be tracked to show some progressing of the modules (xap’s) being downloaded.

Solution : Prism’s ModuleManager internally uses instance of XapModuleTypeLoader to load modules (xap’s) asynchronously. Thanks to the community of prism for providing CreateDownloader() as virtual method.

Straight to the point I will jot down the steps to achieve the above mentioned goal.

Step 1 – Create an event and an event argument classes that will be used in custom class below


public class ModuleDownloadProgressEvent : CompositePresentationEvent<ModuleDownloadProgressArgs>
{
}

public class ModuleDownloadProgressArgs
{
    public ModuleDownloadProgressArgs(int BytesReceived, bool IsComplete) { _BytesReceived = BytesReceived; _IsComplete = IsComplete; }
    private int _BytesReceived;
    public int BytesReceived { get { return _BytesReceived; } set { _BytesReceived = value; } }
    private bool _IsComplete;
    public bool IsComplete { get { return _IsComplete; } set { _IsComplete = value; } }
}

Step 2 – Create a class that derives from IFileDownloader.


public class CustomFileDownloader : IFileDownloader
{
    private readonly WebClient webClient = new WebClient();
    private readonly IEventAggregator eventAgg;

    public CustomFileDownloader()
    {
        eventAgg = ServiceLocator.Current.GetInstance<IEventAggregator>();
    }

    private event EventHandler<DownloadCompletedEventArgs> _downloadCompleted;
    public event EventHandler<DownloadCompletedEventArgs> DownloadCompleted
    {
        add
        {
            if (this._downloadCompleted == null)
            {
                this.webClient.OpenReadCompleted += this.WebClient_OpenReadCompleted;
                this.webClient.DownloadProgressChanged += this.DownloadProgressChanged;
            }

            this._downloadCompleted += value;
        }

        remove
        {
            this._downloadCompleted -= value;
            if (this._downloadCompleted == null)
            {
                this.webClient.OpenReadCompleted -= this.WebClient_OpenReadCompleted;
                this.webClient.DownloadProgressChanged -= this.DownloadProgressChanged;
            }
        }
    }

    void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
    {
        RaiseDownloadProgressChanged(e.ProgressPercentage, false);
    }

    private void RaiseDownloadProgressChanged(int val, bool isComplete)
    {
        eventAgg.GetEvent<ModuleDownloadProgressEvent>().Publish(new ModuleDownloadProgressArgs(val, isComplete));
    }

    public void DownloadAsync(Uri uri, object userToken)
    {
        this.webClient.OpenReadAsync(uri, userToken);
    }

    private void WebClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
    {
        this._downloadCompleted(this, ConvertArgs(e));
        RaiseDownloadProgressChanged(0, true);
    }

    private static DownloadCompletedEventArgs ConvertArgs(OpenReadCompletedEventArgs args)
    {
        return new DownloadCompletedEventArgs(args.Error == null ? args.Result : null, args.Error, args.Cancelled, args.UserState);
    }
}

Step 3 – Create a class that derives from XapModuleTypeLoader and override its virtual method CreateDownloader returning your class object prepared in step 1


public class CustomXapModuleTypeLoader : XapModuleTypeLoader
{
    protected override IFileDownloader CreateDownloader()
    {
        return new CustomFileDownloader();
    }
}

Step 4 – Create a class that derives from ModuleManager and override its virtual method ModuleTypeLoaders replacing default XapModuleTypeLoader with your class object prepared in step 2.


public class CustomModuleManager : ModuleManager
{
    public CustomModuleManager(IModuleInitializer moduleInitializer, IModuleCatalog moduleCatalog, ILoggerFacade loggerFacade)
        : base(moduleInitializer, moduleCatalog, loggerFacade) { }
    private System.Collections.Generic.IEnumerable<IModuleTypeLoader> typeLoaders;
    public override System.Collections.Generic.IEnumerable<IModuleTypeLoader> ModuleTypeLoaders
    {
        get
        {
            if (this.typeLoaders == null)
            {
                this.typeLoaders = new List<IModuleTypeLoader>()
                                      {
                                          new CustomXapModuleTypeLoader()
                                      };
            }

            return this.typeLoaders;
        }
        set
        {
            this.typeLoaders = value;
        }
    }
}

Step 5 – Register your custom ModuleManager class prepared in step 4, in overridden method ConfigureContainer() of your Bootstrapper class.


	base.RegisterTypeIfMissing(typeof(IModuleManager), typeof(CustomModuleManager), true);

Step 6 – Create a method in Shell that will respond to the ModuleDownloadProgressEvent event when published


public void ShowHide(ModuleDownloadProgressArgs e)
{
	// Your Dream Goes Here
}

Step 7 – Finally in the Shell subscribe to the ModuleDownloadProgressEvent event prepared in Step 1.


	eventAggregator.GetEvent<ModuleDownloadProgressEvent>().Subscribe(ShowHide, ThreadOption.UIThread);

That’s it!!!

Enjoy Prism.