LOADING...

加載過慢請開啟緩存(瀏覽器默認開啟)

loading

SignalR Hub 在 .NET MVC 中的詳細示例

最近在寫.NET MVC專案的時候,有用到SignalR的套件,這裡整理一些使用範例

  • 以下是範例環境
    • .NET Framework 4.7.2
    • Visual Studio Community 2019
  • 開啟 Visual Studio 2019 建立 .NET MVC 專案

  • 用 Nuget 安裝 SignalR 套件

    Install-Package Microsoft.AspNet.SignalR –Version 1.2.2
  • Global.asax中註冊Signalr

    在第一行加入

    RouteTable.Routes.MapHubs();

壹、在JS中調用 SignalR Hub

  • 在方案總管中建立一個 SignalR 套件用來溝通 Server 與 Client 的 Hub 物件,取名為 MyHub1

  • MyHub1.cs 程式碼如下

    using Microsoft.AspNet.SignalR;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace SignalRHubDemo
    {
        public class MyHub1 : Hub
        {
            public void Hello()
            {
                Clients.All.hello();
            }
    
            public void Hello(string message)
            {
                Clients.All.HubCallBack(message);
            }
        }
    }
    • 注意這裡 Hello(string message) 是我們自己建立的函數,這個函數會使用Hub類別才能讀取的物件Clients, 將接收到的字串參數message 推播到所有的瀏覽器(Client)上,透過HubCallBack這個Javascript Method。
    • 調用用戶端Javascript Method的語法為 Clients.All.JSMehtod.ALL表示推播到所有透過SignalR套件建立連線的瀏覽器上。
    • 特別注意 HubCallBack是覽器端的function,需要在瀏覽器端透過Call back fucntion 的方式,傳回到SignalR中,已提供MyHub1這個類別使用
  • HomeController 中新增一個 Action 命名為 JSdemo

    public ActionResult JSdemo()
    {
        return View();
    }
  • 在路徑\SignalRHubDemo\Views\Home\ View的資料夾之下,新增View JSdemo.cshtml

    @{
        Layout = null;
    }
    
    <!DOCTYPE html>
    
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>JSdemo</title>
    </head>
    <body>
        <input id="mybutton" type="button" onclick="CallHubOnServer()" value="JSdemo" />
    
        <div id="result"></div>
    
        <!--Reference the jQuery library. -->
            <script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-Validation-Engine/2.6.4/jquery-1.8.2.min.js"></script>
        <!--Reference the SignalR library. -->
        <script src="~/Scripts/jquery.signalR-1.2.2.js"></script>
        <!--Reference the autogenerated SignalR hub script. -->
        <script src="/signalr/hubs"></script>
        <script type="text/javascript">
            $(function () {
                // initialize the connection to the server
                var myhub = $.connection.myHub1;
                // client-side sendMessage function that will be called from the server-side
                myhub.client.HubCallBack = function (message) {
                    $("#result").html(message);
                };
    
                //connection to the server and start server-side operation
                $.connection.hub.start().done(function () {
                    console.log("Signalr 連線成功!");
                });
            });
    
            function CallHubOnServer() {
                var myhub = $.connection.myHub1;
                myhub.server.hello('安安你好'); //注意 camel-case (hello 才可以呼叫  Hello)
            }
        </script>
    </body>
    </html>
    • 注意 signalR-1.2.2 需要配合 JQuery 特定版本才能使用,這裡搭配 jquery-1.8.2.min.js
    • 注意用以下語法可以取得SignalR 連線中的Hub物件,注意在javascript端需要用Camel case (駝峰式大小寫),來取得Hub類別的名稱,在這個例子就是需要用 myhub1來取得 MyHub1.cs這個類別。
    var myhub = $.connection.myHub1;
    • 根據MyHub1中定義的Javascript Method,我們在Javascript端傳入 Call back function,這個方法中使用JQuery來更改 id為 result的div元素中的顯示值,將其值設定為參數message

      myhub.client.HubCallBack = function (message) {
          $("#result").html(message);
      };
    • SignalR已經建立完連線的時候,在Console中打印連線成功

      $.connection.hub.start().done(function () {
          console.log("Signalr 連線成功!");
      });
    • 最後定義input button 需要呼叫的javascript 函數

      function CallHubOnServer() {
          var myhub = $.connection.myHub1;
          myhub.server.hello('安安你好'); //注意 camel-case (hello 才可以呼叫  Hello)
      }

      注意在javascript 使用 Server side (MyHub1.cs)中的Method的時候,一樣要用Camel case 才能呼叫該方法

      myhub.server.hello('安安你好'); 透過 myhub這個Hub物件,呼叫Server Side的 Hello方法,並且將字串'安安你好' 當作參數傳入。

  • JSdemo.cshtml的畫面下按下Visual Studio 的編譯執行

  • 開啟瀏覽器的開發者模式,發現SignalR連線成功的訊息打印出來

  • 開啟三個瀏覽器

  • 按下其中一個瀏覽器的 JSdemo按鈕,所有瀏覽器都被推播'安安你好'訊息,即時添加這個訊息是透過Call back function傳入,然後被SignalRMyHub1.cs 物件呼叫的

  • 如果不想要對所有SignalR連線Client都推播,只想要對按下按鈕的那個瀏覽器推播,可以更改MyHub1.cs中的public void Hello(string message) Method程式碼

    public void Hello(string message)
    {
      Clients.Caller.HubCallBack(message);
    }
  • 只按下最下面那個瀏覽器的 JSDemo 按鈕,只會在最下面那個瀏覽器回應

貳、在Controller 中調用 SignalR Hub

如果要在 MVC Controller 中是無法直接建立 Hub物件的,因為Hub物件的new與連線的維持都是透過 SignalR這個套件來支援,所以這裡我們要用比較迂迴的方法來使用 Hub

  • 在路徑\SignalRHubDemo\Views\Home\ View的資料夾之下,新增View ControllerDemo.cshtml

    @{
        Layout = null;
    }
    
    <!DOCTYPE html>
    
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>ControllerDemo</title>
    </head>
    <body>
        @using (Html.BeginForm("PostDemo", "Home", FormMethod.Post, new { target = "_blank" }))
        {
            <input type="text" id="message" name="message">
            <br />
            <input type="submit" value="Submit" />
        }
        <div id="result"></div>
    
        <!--Reference the jQuery library. -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-Validation-Engine/2.6.4/jquery-1.8.2.min.js"></script>
        <!--Reference the SignalR library. -->
        <script src="~/Scripts/jquery.signalR-1.2.2.js"></script>
        <!--Reference the autogenerated SignalR hub script. -->
        <script src="/signalr/hubs"></script>
        <script type="text/javascript">
            $(function () {
                // initialize the connection to the server
                var myhub = $.connection.myHub1;
                // client-side sendMessage function that will be called from the server-side
                myhub.client.HubCallBack = function (message) {
                    $("#result").html(message);
                };
    
                //connection to the server and start server-side operation
                $.connection.hub.start().done(function () {
                    console.log("Signalr 連線成功!");
                });
            });
        </script>
    </body>
    </html>
    • 與之前JSdemo.cshtml非常類似,只是取消了按下button呼叫Js的過程,改成對 web server發出一個Post請求,並且將text box的訊息傳入。

    • 注意 @using (Html.BeginForm("PostDemo", "Home", FormMethod.Post, new { target = "_blank" }))會在新的分頁中開啟Post的結果。會這樣設計是為了不要讓Post結果覆蓋原本的分頁,因為發出請求的瀏覽器上的 result element 會顯示出Post Request的結果。

  • HomeController 中新增一個 Action 命名為 ControllerDemo

    public ActionResult ControllerDemo()
    {
        return View();
    }
  • HomeController 中新增一個 Action 命名為 PostDemo,這個Action用來來處理 Post Request,詳細的Action邏輯稍後在定義

    [HttpPost]
    public ActionResult PostDemo(string message)
    {
        //Some Code
        return Content("<script>alert('Posted');</script>");
    }

    這個Post Action被呼叫之後,會用 Javascript 在網頁上跳出一個Alert ,其中顯示 'Posted'

    string message 這個輸入參數,會接收Form中name=message的input,這種傳入法也稱為Model Binding

    接著我們來討論如何呼叫Hub的方法。

  • 因為SignalR 的原因,Controller無法直接使用 Hub物件,所以我們需要透過以下HubHelper類別才能夠在Controller 使用Hub物件

    Reference: https://stackoverflow.com/questions/40884050/signalr-uncaught-typeerror-currenthub-server-onconnected-is-not-a-function

    建立HebHelper物件

    HubHelper.cs 物件如下:

    using Microsoft.AspNet.SignalR;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace SignalRHubDemo
    {
        public class HubHelper
        {
            private IHubContext hub;
            public HubHelper(IHubContext hub)
            {
                this.hub = hub;
            }
            public void sendMessageToAll(string message)
            {
                hub.Clients.All.HubCallBack(message);
            }
        }
    }

    這是一個依賴注入(DI)的 pattern,我們將從Controller中取得 IHubContext再透過該物件來使用 Client 端的 Javascript方法,在這個HubHelper的定義中,我們一樣採用前一個範例的HubCallBack方法命名。

  • 最後再回來完成HomeController.cs

    using Microsoft.AspNet.SignalR;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace SignalRHubDemo.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View();
            }
            public ActionResult JSdemo()
            {
                return View();
            }
    
            public ActionResult ControllerDemo()
            {
                return View();
            }
            [HttpPost]
            public ActionResult PostDemo(string message)
            {
                var hub = GlobalHost.ConnectionManager.GetHubContext<MyHub1>();
                var helper = new HubHelper(hub);
                helper.sendMessageToAll(message);
    
                return Content("<script>alert('Posted');</script>");
            }
            public ActionResult About()
            {
                ViewBag.Message = "Your application description page.";
    
                return View();
            }
    
            public ActionResult Contact()
            {
                ViewBag.Message = "Your contact page.";
    
                return View();
            }
        }
    }
    • var hub = GlobalHost.ConnectionManager.GetHubContext<MyHub1>();這行可以取得 IHubContext這個介面定義的物件,然後將其注入到HubHelper類別中
    • 最後在使用HubHelperinstance 來使用sendMessageToAll()這個方法,然後再使用Hub中的 Clients物件,Clients物件才有能力呼叫Client端的 javascript method。
  • ControllerDemo.cshtml中執行,開啟三個頁面網址均為Home/ControllerDemo,並且在最下面的那個瀏覽器上輸入'安安你好'且按下Submit

  • 最下面的網頁會跳出一個新分頁,出現以下畫面

  • 所有透過SignalR 建立連線的瀏覽器均出現'安安你好'訊息

參、在Controller 中調用 SignalR Hub,只針對呼叫的那瀏覽器回應

要只針對特定瀏覽器回應,需要將connectionID 也當作Form的資訊Post到 Controller上,再做進一步處理

  • ControllerDemo.cshtml改寫為

    @{
        Layout = null;
    }
    
    <!DOCTYPE html>
    
    <html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>ControllerDemo</title>
    </head>
    <body>
        @using (Html.BeginForm("PostDemo", "Home", FormMethod.Post, new { target = "_blank" }))
        {
            <input type="text" id="message" name="message">
            <br />
            <input type="hidden" id="connectionId" name="connectionId" value="test"/>
            <br />
            <input type="submit" value="Submit" />
        }
        <div id="result"></div>
    
        <!--Reference the jQuery library. -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jQuery-Validation-Engine/2.6.4/jquery-1.8.2.min.js"></script>
        <!--Reference the SignalR library. -->
        <script src="~/Scripts/jquery.signalR-1.2.2.js"></script>
        <!--Reference the autogenerated SignalR hub script. -->
        <script src="/signalr/hubs"></script>
        <script type="text/javascript">
            $(function () {
                // initialize the connection to the server
                var myhub = $.connection.myHub1;
                // client-side sendMessage function that will be called from the server-side
                myhub.client.HubCallBack = function (message) {
                    $("#result").html(message);
                };
    
                //connection to the server and start server-side operation
                $.connection.hub.start().done(function () {
                    console.log("Signalr 連線成功! connectionID=" + myhub.connection.id);
                    $("#connectionId").val(myhub.connection.id);
                });
            });
        </script>
    </body>
    </html>
    • 在Form中新增一個hidden的input

      <input type="hidden" id="connectionId" name="connectionId" value="test"/>

    • SignalR建立連線成功後,會在console打印出連線成功,並且顯示連線的connectionId,並且將connectionId透過JQuery寫入Form的hidden input

      $.connection.hub.start().done(function () {
          console.log("Signalr 連線成功! connectionID=" + myhub.connection.id);
          $("#connectionId").val(myhub.connection.id);
      });
  • 修改 HubHelper.cs

    using Microsoft.AspNet.SignalR;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace SignalRHubDemo
    {
        public class HubHelper
        {
            private IHubContext hub;
            public HubHelper(IHubContext hub)
            {
                this.hub = hub;
            }
            public void sendMessageToAll(string message)
            {
                hub.Clients.All.HubCallBack(message);
            }
            public void sendMessageToCaller(string connectionId, string message)
            {
                hub.Clients.Client(connectionId).HubCallBack(message);
            }
        }
    }
    • 這裡新增了一個Method 透過給定 connectionId來對特定的瀏覽器回應
  • 修改HomeController.cs

    using Microsoft.AspNet.SignalR;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace SignalRHubDemo.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index()
            {
                return View();
            }
            public ActionResult JSdemo()
            {
                return View();
            }
    
            public ActionResult ControllerDemo()
            {
                return View();
            }
            [HttpPost]
            public ActionResult PostDemo(string message, string connectionId)
            {
                var hub = GlobalHost.ConnectionManager.GetHubContext<MyHub1>();
                var helper = new HubHelper(hub);
    
                helper.sendMessageToCaller(connectionId, message);
                return Content("<script>alert('Posted');</script>");
            }
            public ActionResult About()
            {
                ViewBag.Message = "Your application description page.";
    
                return View();
            }
    
            public ActionResult Contact()
            {
                ViewBag.Message = "Your contact page.";
    
                return View();
            }
        }
    }
  • ControllerDemo.cshtml中執行結果,並且開啟開發者模式,觀察Console

  • 開啟三個分頁,且在最下面的分頁執行Submit

  • 結果只有最下面的瀏覽器得到回應

Reference

img_show