之前寫過一篇 [Node.js] 使用 Node.js 來達成電腦網頁與手機網頁即時互動,當時是用 Node.js,最近看到了 .NET 也提供了相似的解決方案 ── SignalR。
官方的介紹是:Async signaling library for .NET to help build real-time, multi-user interactive web applications.
SignalR 一樣可以做即時跟多使用者的互動網路應用程式。
在製作案件時,有時候是無法在客戶的主機上動手腳的,為了不要被技術跟環境侷限,多會一種解決方案也不賴呀。
而且寫的時候覺得 SignalR 寫起來更簡單,很多事都在背後偷偷幫你做完了(.NET 似乎都這樣封裝得好好的,但還是要搞懂其中原理比較好呀)。
廢話不多說,來看一下怎麼實作此次的需求囉。
此次開發使用:
- Visual Studio 2010 Professional
- ASP.NET 4.0 (C#)
如何透過 NuGet 安裝 SignalR
噢,之前好像沒寫過 NuGet,那就先簡單介紹一下如何透過 NuGet 安裝最新的 SignalR 吧。
1. 開啟 VS2010,建立新專案,選擇 Visual C# 的 ASP.NET 空白 Web 應用程式。
- 接著搜尋線上的套件:SignalR,記得把左上角的「僅限穩定」改為選擇「包括發行前版本」。
選擇安裝「Microsoft ASP.NET SignalR」,上面那幾個是相依套件,也會跟著一起安裝。
- 安裝完畢後,「參考」裡面會自動加入需要的 SignalR 參考,然後專案也會自動產生「Scripts」目錄,裡面自動加入 jQuery 以及 jQuery SignalR 的 .js 檔。
實戰
這次我們要做的也是讓手機掃描網頁上的 QRCode,進而用手機控制網頁的動作。
本次實作選擇使用 Hub 模式(另一種是 PersistentConnection)。
- 首先,新建一個新的類別 Core.cs,定義伺服器端的動作。
- Client 類別定義了 Client 端的屬性,有 UniqueKey(唯一 Key,用來讓電腦跟手機產生關連。在網頁端可以用 js 或透過 Flash ActionScript 生成。)跟 ConnectionId(連線後,SignalR 會自動分配給 Client 端的 GUID)
- Core 類別實作 Hub。
- RegisterClient:每次產生連線後,就會呼叫 RegisterClient 方法,也將 Client 端產生的 key 值註冊進去。
註冊到一個 Client 的集合,供接下來的動作做比對。
註冊完之後,就呼叫 Client 端的 registerComplete 事件。 - MobileOpenWebpage:主要動作之一。會在手機掃描完 QRCode 後打開手機版網頁,連線成功後呼叫這個事件,檢查相應的電腦端 Client,呼叫它的 playMovie 事件。
- ChangeBackground:也是主要動作之一。本例手機網頁上有個 Button,點擊後會呼叫這個事件,這個事件檢查相應的電腦端後,呼叫它的 changeBackground 事件。
- RetrieveClient:上面兩個方法都有用到的方法。用來檢查相應的手機端跟電腦端,取得各自的 ConnectionId,供上面的動作使用。
- RegisterClient:每次產生連線後,就會呼叫 RegisterClient 方法,也將 Client 端產生的 key 值註冊進去。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR.Hubs;
namespace SignalRQRCode
{
public class Client
{
// 唯一 Key, 用來讓電腦跟手機產生關連
public string UniqueKey { get; set; }
// 連線 guid
public string ConnectionId { get; set; }
}
public class Core : Hub
{
// Client 集合
public static List<Client> _clients = new List<Client>();
private object _syncRoot = new object();
public string MobileConnectionId { get; set; }
public string ComputerConnectionId { get; set; }
/// <summary>
/// 註冊目前使用者
/// </summary>
/// <param name="key"></param>
public void RegisterClient(string key)
{
lock (_syncRoot)
{
// 在集合中找出目前使用者
var client = _clients
.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
// 如果找不到,就新增到集合中(配上 javascript 生成的 key 值)
if (client == null)
{
client = new Client { ConnectionId = Context.ConnectionId, UniqueKey = key };
_clients.Add(client);
}
}
Clients.Client(Context.ConnectionId).registerComplete();
}
/// <summary>
/// 取得 Client 端
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
private bool RetrieveClient(string key)
{
// 找出手機端 (目前)
var mobile = _clients.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
// 若找不到目前手機端,應該是出了什麼錯 ... 不要繼續往下做
if (mobile == null) return false;
// 電腦端使用者
var computer = _clients
.FirstOrDefault(x => x.ConnectionId != Context.ConnectionId &&
x.UniqueKey == key);
// 找不到另一方裝置
if (computer == null)
{
Clients.Client(Context.ConnectionId).noOpponent();
return false;
}
// 紀錄手機端的 ConnectionId
MobileConnectionId = mobile.ConnectionId;
// 紀錄電腦端的 ConnectionId
ComputerConnectionId = computer.ConnectionId;
return true;
}
/// <summary>
/// 手機打開網頁事件
/// </summary>
/// <param name="key"></param>
public void MobileOpenWebpage(string key)
{
if (!RetrieveClient(key))
return;
if (MobileConnectionId == "" || ComputerConnectionId == "")
return;
// 叫電腦端做事
Clients.Client(ComputerConnectionId).playMovie();
}
/// <summary>
/// 變換電腦端背景顏色
/// </summary>
/// <param name="key"></param>
public void ChangeBackground(string key)
{
if (!RetrieveClient(key))
return;
if (MobileConnectionId == "" || ComputerConnectionId == "")
return;
// 叫電腦端做事
Clients.Client(ComputerConnectionId).changeBackground();
}
}
}
- 然後看看 Client 端的,先看電腦端(還有手機端)。這張網頁上可視的 element 只有一個 QRcode 的 div。
- head 內要引入三個 script:
- jQuery
- jQuery SignalR
- signalr/hubs。上面兩個東西,透過 NuGet 安裝後就會在 Scripts 目錄了,拖過來就有,但這行比較特別,你找不到這個目錄,但執行的時候就會出現了(真神奇!)。注意是寫這個應用程式的相對路徑。
- 主要 script 的部分:
- 建立連線成功後,將手機網頁 + key 的網址透過 Google Chart 產生 QRCode,顯示於 qrcode 這個 div 中。
- 呼叫伺服器端事件 RegisterClient,並將 key 帶進去註冊成新的 Client。(記得呼叫伺服器端事件,首字是小寫喔!)
- 然後定義三個 Client 端的事件:registerComplete、playMovie 與 changeBackground。
- registerComplete:將 Client 註冊完成後,伺服器端會呼叫這個 Client 端事件,這邊很簡單地只用 alert 顯示訊息。
- playMovie:手機端打開網頁後,檢查有相應的電腦端後,會透過伺服器端呼叫電腦端這個事件。原本是拿來控制 Flash 動畫的,所以叫 playMovie 😛
- changeBackground:手機端的網頁上有顆按鈕,點擊後會透過伺服器端呼叫電腦端網頁的這個事件。效果是讓背景顏色隨機變換。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="author" content="patw, Patrick Wang" />
<title>SignalR 電腦端網頁</title>
<script src="Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.signalR-1.0.0-alpha2.min.js" type="text/javascript"></script>
<script src="signalr/hubs"></script>
</head>
<body>
<script type="text/javascript">
$(document).ready(function () {
// 透過 $.connection.core 建立對應服務器端的類別 Core(名稱要一樣)
var core = $.connection.core;
var key = NewGuid();
// Start the connection
$.connection.hub.start().done(function () {
// 手機網頁的網址
var mobileurl = "http://手機網頁的網址/";
// 顯示給手機照的 qrcode
$("#qrcode").append("<img src='http://chart.apis.google.com/chart?chs=300x300&cht=qr&chl=" + mobileurl + "?key=" + key + "&choe=UTF-8' />");
// 將此 key 註冊到 server 端(伺服器端事件首字都小寫喔)
core.server.registerClient(key);
});
// Client 註冊完成事件
core.client.registerComplete = function () {
alert("Key: " + key + " 正在等待手機開啟中");
};
// 手機網頁開啟事件
core.client.playMovie = function () {
alert("手機開啟網頁了!");
};
// 更換背景顏色
core.client.changeBackground = function () {
var str = "0123456789abcdef", t = "";
for (j = 0; j < 6; j++) {
t = t + str.charAt(Math.random() * str.length);
}
$("body").attr("style", "background-color:#" + t);
};
});
// 用來產生類似 GUID 的字串
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
function NewGuid() {
return (S4() + S4());
}
</script>
<div id="qrcode"></div>
</body>
</html>
- 手機端的部分:
- 畫面上唯一可視的東西是一顆 Button,用來控制電腦端網頁的背景顏色。
- 跟電腦端一樣,head 內要引入三個 script(本例我是將手機網頁放在 mobile/ 子目錄中,因此會用 ../ 來設相對路徑):
- jQuery
- jQuery SignalR
- signalr/hubs
- getParameterByName:不想寫到後端程式,所以取得 Querystring Parameter 值的部分就用 JavaScript 處理了。
- 主要 script 的部分:
- 建立連線成功後,呼叫伺服器端事件 RegisterClient(一樣注意在這邊首字是小寫),將 key 註冊到 Client 集合中。
- 註冊成功後,再呼叫伺服器端事件 MobileOpenWebpage。這是手機端用來通知伺服器端「我已經將網頁打開囉,請再發個通知給電腦端吧!」的事件。
- 手機端唯一的 Client 端的事件是 noOpponent。伺服器端會在找不到相應 key 的電腦端時呼叫此事件,用來通知使用者:電腦端遺失,他可能得重掃。
- registerComplete:將 Client 註冊完成後,伺服器端會呼叫這個 Client 端事件,這邊很簡單地只用 alert 顯示訊息。
- #btnChangeBg 是畫面上唯一的 Button,這邊綁定它的 click 事件,點擊後會呼叫伺服器端的 changeBackground 事件,再轉知相符 key 電腦端的 Client 事件,用來改變網頁背景顏色。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="author" content="patw, Patrick Wang" />
<title>SignalR 手機端網頁</title>
<style type="text/css">
#btnChangeBg
{
width: 80%;
height: 300px;
font-size: 11pt;
}
</style>
<script src="../Scripts/jquery-1.6.4.min.js" type="text/javascript"></script>
<script src="../Scripts/jquery.signalR-1.0.0-alpha2.min.js" type="text/javascript"></script>
<script src="../signalr/hubs"></script>
<script type="text/javascript">
// 用 JavaScript 方式取得 GET 值
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.search);
if (results == null)
return "";
else
return decodeURIComponent(results[1].replace(/\+/g, " "));
}
</script>
</head>
<body>
<script type="text/javascript">
$(document).ready(function () {
// 透過 $.connection.core 建立對應服務器端的類別 Core(名稱要一樣)
var core = $.connection.core;
var key = getParameterByName("key");
if (key == "") {
alert("Hey.. key is empty! please scan QRCode to get this page!");
}
// Start the connection
$.connection.hub.start().done(function () {
// 將此 key 註冊到 server 端(伺服器端事件首字都小寫喔)
core.server.registerClient(key).done(function () {
// 註冊完,呼叫手機打開網頁事件,去找電腦端
core.server.mobileOpenWebpage(key);
});
});
// 找不到電腦端事件
core.client.noOpponent = function () {
alert("No opponent!\r\nPlease refresh your webpage on computer, and try rescan QRCode by your mobile!");
};
$("#btnChangeBg").bind("click", function () {
// 呼叫 Server 端的變換背景顏色事件
core.server.changeBackground(key);
});
});
</script>
<input type="button" id="btnChangeBg" value="用手機變換電腦端的網頁背景顏色" />
</body>
</html>
範例全部的程式碼如上,下面也附上範例檔:
本範例的 VS2010 專案檔下載
第一次寫 SignalR 的東西,若有錯誤的地方請偷偷告訴我啦 😛
下次再試試看用 PersistentConnection 模式來做些有趣的東西囉~
謝謝您的分享
大哥,專案無法下載
補檔如下
https://nofile.io/f/FMKgbXhFRts/SignalRQRCode.zip
大哥你好
我用了vs2017是可以執行
補充文件有三點:
1.OWIN啟動類別:Web>一般>OWIN啟動類別,命名為Startup.cs
目的: 服务器需要知道要截获并将定向到 SignalR 的 URL
不然執行會失敗
程式內只要加入一行指令即可
(中間略)
public void Configuration(IAppBuilder app)
{
// 如需如何設定應用程式的詳細資訊,請瀏覽 https://go.microsoft.com/fwlink/?LinkID=316888
加入下段
app.MapSignalR();//這一段程式主要的作用就是跟 MVC Router 一樣讓別人知道 SignalR 的位置。
}
(略)
2.另外我是掛載到IIS7,需要安裝NET Frame4.5(https://www.microsoft.com/zh-TW/download/confirmation.aspx?id=30653)
Web.config加入二行
<system.webServer>
</system.webServer>
這樣就不會被IIS誤判可能是虛擬目錄…
3.index.aspx其中一句
/signalr/hubs
這段在Debug模式下正常,但是IIS卻不能執行
必須改成
http://../(å°æ¡å稱)%20/signalr/hubs
例如: http://../WebApplication5/signalr/hubs
參考解決方法http://no2don.blogspot.com/2012/10/c-signalrhubs.html
前面發表文章過長,沒有被顯示,故再留言
剛才試vs2017,是成功的
注意有三點
1.OWIN啟動類別
選擇Web>一般>OWIN啟動類別,命名為Startup.cs
目的: 服务器需要知道要截获并将定向到 SignalR 的 URL。
否則無法執行
2.IIS7 環境下,要安裝NET4.5(https://www.microsoft.com/zh-TW/download/confirmation.aspx?id=30653)
Web.config加入二行
<system.webServer>
這樣就不會被IIS誤判可能是虛擬目錄…
參考解決方法http://no2don.blogspot.com/2012/10/c-signalrhubs.html