[SignalR] 實作聊天室功能

  今天搜尋文章時,看到MSDN有針對SngalR作一系列的悠閒Coding介紹,一時手癢跟著作一遍,來看看到底有多悠閒(誤),由於文章內的SignalR版本是1.1,到2.x後有點小變更,將內容順便整理如下:

安裝Nuget

1.先建立一個空白Web專案。
2.Nuget—>Microsoft ASP.NET SignalR,此套件會順便裝好其他必要套件
image

新增Hub類別

image

Hub類別如下:
    public class codingChatRoomHub : Hub
    { 
        // 宣告靜態類別,來儲存上線清單
        public static class UserHandler
        {
            public static Dictionary<string, string> ConnectedIds = new Dictionary<string, string>();
        }

        public void UserConnected(string name)
        {
            string message = " 歡迎使用者 " + name + " 加入聊天室 ";
            // 發送訊息給除了自己的其他使用者
            Clients.Others.addList(Context.ConnectionId, name);
           Clients.Others.hello(message);
            // 發送訊息至自己,並且取得上線清單
            Clients.Caller.getList(UserHandler.ConnectedIds.Select(p => new { id = p.Key, name = p.Value }));
            // 新增目前使用者至上線清單
            UserHandler.ConnectedIds.Add(Context.ConnectionId, name);
        }

        // 發送訊息給所有人
        public void SendAllMessage(string message)
        {
            message = HttpUtility.HtmlEncode(message);
            var name = UserHandler.ConnectedIds.Where(p => p.Key == Context.ConnectionId).FirstOrDefault().Value;
            string msg = name + " Say:" + message;
            Clients.All.sendAllMessage(msg);
        }

        public void SendMessage(string toId, string message)
        {
            var fromName = UserHandler.ConnectedIds.Where(p => p.Key == Context.ConnectionId).FirstOrDefault().Value;
            string msg = fromName + " <span style='color:red'> 悄悄的對你說 </span> : " + message;
            Clients.Client(toId).sendMessage(msg);
        }

        public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
        {
            // 當使用者離開時,移除在清單內的 ConnectionId
            Clients.All.removeList(Context.ConnectionId);
            UserHandler.ConnectedIds.Remove(Context.ConnectionId);
            return base.OnDisconnected(stopCalled);
        }
    }


程式重點:
  • UserConnected方法,讓使用者在前諯註冊後叫用,並將名字放在一個靜態集合(此部分實際運用可以改成用資料庫)
  • Clients類別提供的通知對象方法

    • All:通知全部
    • Others:通知全部但排除自己
    • Caller:通知自己
    • Client: 通知特定的ID
    • 叫用以上方法後會回傳dynamic型別的代理物件,用來叫用前端script的方法,例如 Client.All.sendMessage,紅字部份為動態方法
  • 覆寫OnDisconnected方法,使用者離線時從集合移合並通知前端

加入Owin啟動類別為SignalR註冊Route


image

在1.1版是在Global.asax的RouteTable註冊,但在2.x版是透過Owin的IAppBuilder擴充方法來註冊
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(SignalRPractice.Startup))]
namespace SignalRPractice
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            // 如需如何設定應用程式的詳細資訊,請參閱  http://go.microsoft.com/fwlink/?LinkID=316888
            app.MapSignalR();
        }
    }
}

Html內容:chatRoom.html


此部份有使用Bootstrap美化一下..
<div class="row">
        <div class="col-sm-10">
            <div class="panel panel-primary">
                <div class="panel-heading">Chating Content <span id="userName" class="glyphicon glyphicon-user pull-right"> Hi ! </span></div>
                <div id="messageBox" class="panel-body">
                    <ul id="messageList"></ul>
                </div>
            </div>
            <div id="bar">
                <div class="col-sm-4">
                    <select id="box" class="form-control">
                        <option value="all"> 所有人 </option>
                    </select>
                </div>
                <div class="col-sm-4">
                    <input type="text" id="message" class="form-control" />
                </div>
                <div class="col-sm-4">
                    <input type="button" id="send" value=" 發送 " />
                </div>

            </div>
        </div>
        <div class="col-sm-2">
            <div class="panel panel-info">
                <div class="panel-heading">Online User</div>
                <div class="panel-body">
                    <div id="chatList">
                        <p> 上線清單 </p>
                        <ul id="list"></ul>
                    </div>
                </div>
            </div>

        </div>
    </div>

引用JavaScript


此地方要注意的是"/signalr/hubs"的引用,此引用目錄在執行時期才會動態產生,實體檔案並不會存在。ps:要放在jQuery及signalR之後
   <script src="Scripts/jquery-2.1.1.min.js"></script>
    <script src="Scripts/jquery.signalR-2.1.2.min.js"></script>
    <script src="/signalr/hubs"></script>

JavaScript內容

<script>
        var userID = "";
        $(function () {
            while (userID.length == 0) {
                userID = window.prompt("請輸入使用者名稱");
                if (!userID)
                    userID = "";
            }
            $("#userName").append(userID).show();

            //建立與Server端的Hub的物件,注意Hub的開頭字母一定要為小寫
            var chat = $.connection.codingChatRoomHub;
            //取得所有上線清單
            chat.client.getList = function (userList) {
                var li = "";
                $.each(userList, function (i, data) {
                    li += "<li id='" + data.id + "'><a href='#'><span class='glyphicon glyphicon-user'> " + data.name + "</span></a></li>";
                })
                $("#list").html(li);
                registerListClick();
            }
            //新增一筆上線人員
            chat.client.addList = function (id, name) {
                var li = "<li id='" + id + "'><a href='#'><span class='glyphicon glyphicon-user'> " + name + "</span></a></li>";
                $("#list").append(li);
                registerListClick();
            }

            //移除一筆上線人員
            chat.client.removeList = function (id) {
                $("#" + id).remove();
            }

            //全體聊天
            chat.client.sendAllMessage = function (message) {
                $("#messageList").append("<li>" + message + "</li>");
            }

            //密語聊天
            chat.client.sendMessage = function (message) {
                $("#messageList").append("<li>" + message + "</li>");
            }

            chat.client.hello = function (message) {
                $("#messageList").append("<li>" + message + "</li>");
            }

            //將連線打開
            $.connection.hub.start().done(function () {
                //當連線完成後,呼叫Server端的userConnected方法,並傳送使用者姓名給Server
                chat.server.userConnected(userID);
            });;

            $("#send").click(function () {
                var to = $("#box").val();
                //當to為all代表全體聊天,否則為私密聊天
                if (to == "all")
                    chat.server.sendAllMessage($("#message").val());
                else
                    chat.server.sendMessage(to, $("#message").val());
                $("#message").val("");
            });
        })

        function registerListClick() {
            $("#list li").unbind("click");
            $("#list li").on("click", function () {
                var $this = $(this);
                var id = $this.attr("id");
                var text = $this.text();

                //防止重複加入密語清單
                if ($("#box").has("." + id).length > 0) {
                    $("#box").find("[class=" + id + "]").attr({ "selected": "selected" });
                }
                else {
                    var option = "<option></option>"
                    $("#box").append(option).find("option:last").val(id).text(text).attr({ "selected": "selected" }).addClass(id);
                }

            });
        }
    </script>

程式重點:
  • 透過$.connection建立Hub物件(codingChatRoomHub)
  • 叫用Server端的類別、方法,第一個字需改成小寫
  • hub物件叫用Server均透過屬性server:chat.server.sendMessage
  • hub物件建立client端方法讓server端叫用:chat.client.removeList=function(){...}

測試程式


瀏覽chatRoom.html,輸入你的名稱

image

開啟另一視窗再加入另一個User"Guest”

image

發送訊息

image

參考來源


http://msdn.microsoft.com/zh-tw/library/dn535720.aspx

http://msdn.microsoft.com/zh-tw/library/dn535725.aspx

http://www.asp.net/signalr/overview/releases/upgrading-signalr-1x-projects-to-20

範例程式


https://github.com/kimx/SignalRPractice/

這個網誌中的熱門文章

[.NET Core] 將專案發行至IIS

[TFS] 分支與合併