[Azure雲端服務及應用開發]_創建跨各種應用都能調用使用的Class Library測試SignalR Hub連線_part2


在很久之前有分享如何創建自己的Class Library(dll)


因此這次就不再多贅述
在同樣一方案(solution)下新增Class Library 專案

選.net standard版




預設產生好的專案刪除掉
建立一個Services Folder

新增一個Interface 名稱IChatService

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace KYChat.Core.Services
{
    public interface IChatService
    {
        bool IsConnected { get; }
        string ConnectionToken { get; set; }
        Task InitAsync(string userId);
    }
}

每組連線是否有連接成功
每組連線唯一識別的token
非同步初始化


再新增3個Class
一個Config
用來做組態設定
(for Endpoint切換)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
using System;
using System.Collections.Generic;
using System.Text;

namespace KYChat.Core
{
    public static class Config
    {
        public static string MainEndPoint = "http://localhost:7071";
        public static string NegotiateEndPoint = $"{MainEndPoint}/api/negotiate";
    }
}

一個是放在Models目錄下的
ConnectionInfo

可以直接複製Postman的json資料(可以看到有url跟token)

然後在vs當中的Edit選擇性貼上(用json)在將類別名稱修改一下
即可快速把類別創建完






之後的
ChatService
去實作IChatService介面具體任務

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace KYChat.Core.Services
{
    public class ChatService : IChatService
    {
        public bool IsConnected { get; set; }

        public string ConnectionToken { get; set; }

        private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);

        private HttpClient httpClient;
        public async Task InitAsync(string userId)
        {
            await semaphoreSlim.WaitAsync();

            if(httpClient == null)
            {
                httpClient = new HttpClient();
            }
            var result = await httpClient.GetStringAsync($"{Config.NegotiateEndPoint}/{userId}");

            semaphoreSlim.Release();
        }
    }
}

由於 await 後頭的程式碼,不保證做的任務都會在同一個 Thread 中執行(非同步)。
因此才需要類似用lock的機制(同一個時間點我希望只會有一個程序可存取)
去做控管
再此用SemaphoreSlim 來完成跨執行緒的鎖定機制,指定可同時
授與信號並行的初始要求數目及最大數目,在此都先設置為1。



接著至Nuget套件下載安裝
Newton Json


到ChatService我們撰寫藉由JsonConvert獲取反序列回來的連線資訊


透過http client GetStringAsync 取得Config戳打Negotiate End Point結果
在將其json結果給反序列成程式後端較好處理的.Net物件資料型態


再來安裝我們的另一套件
Microsoft.AspNetCore.SignalR.Client




然後補撰寫設計好的剩餘片段
建立Hub相關的程式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using KYChat.Core.Models;
using Microsoft.AspNetCore.SignalR.Client;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace KYChat.Core.Services
{
    public class ChatService : IChatService
    {
        public bool IsConnected { get; set; }

        public string ConnectionToken { get; set; }

        private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);

        private HttpClient httpClient;

        HubConnection hub;

        public async Task InitAsync(string userId)
        {
            await semaphoreSlim.WaitAsync();

            if(httpClient == null)
            {
                httpClient = new HttpClient();
            }
            var result = await httpClient.GetStringAsync($"{Config.NegotiateEndPoint}/{userId}");

            var info = JsonConvert.DeserializeObject<ConnectionInfo>(result);

            var connectionBuilder = new HubConnectionBuilder();

            connectionBuilder.WithUrl(info.Url, (obj) =>
             {
                 obj.AccessTokenProvider = () => Task.Run(() => info.AccessToken);
             });

            hub = connectionBuilder.Build();

            await hub.StartAsync();

            ConnectionToken = hub.ConnectionId;

            IsConnected = true;

            semaphoreSlim.Release();
        }
    }
}



在Interface當中補上實作斷線操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace KYChat.Core.Services
{
    public interface IChatService
    {
        bool IsConnected { get; }
        string ConnectionToken { get; set; }
        Task InitAsync(string userId);

        Task DisconnectionAsync();
    }
}

之後補實作
async Task DisconnectionAsync()


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
using KYChat.Core.Models;
using Microsoft.AspNetCore.SignalR.Client;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace KYChat.Core.Services
{
    public class ChatService : IChatService
    {
        public bool IsConnected { get; set; }

        public string ConnectionToken { get; set; }

        private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);

        private HttpClient httpClient;

        HubConnection hub;

        public async Task InitAsync(string userId)
        {
            await semaphoreSlim.WaitAsync();

            if (httpClient == null)
            {
                httpClient = new HttpClient();
            }
            var result = await httpClient.GetStringAsync($"{Config.NegotiateEndPoint}/{userId}");

            var info = JsonConvert.DeserializeObject<ConnectionInfo>(result);

            var connectionBuilder = new HubConnectionBuilder();

            connectionBuilder.WithUrl(info.Url, (obj) =>
             {
                 obj.AccessTokenProvider = () => Task.Run(() => info.AccessToken);
             });

            hub = connectionBuilder.Build();

            await hub.StartAsync();

            ConnectionToken = hub.ConnectionId;

            IsConnected = true;

            semaphoreSlim.Release();
        }

        public async Task DisconnectionAsync()
        {
            if (!IsConnected)
                return;

            try
            {
                await hub.DisposeAsync();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            IsConnected = false;
        }
    }
}


對方案新建一個.net core  Console App  用來模擬client
(由於console較輕量便利因此一開始先用這專案類型做連線測試)






對.net core Console專案右鍵添加參考
選取Chat.Core的Class Library專案





接著撰寫調用封裝好的dll中程式相關物件跟函數


大致寫完Client端模擬連線到Hub的過程之後

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using KYChat.Core.Services;
using System;
using System.Threading.Tasks;

namespace KYChat.Client
{
    class Program
    {
        static ChatService myService;
        static string userName;

        static async Task Main(string[] args)
        {
            Console.WriteLine("User Name:");
            userName = Console.ReadLine();

            myService = new ChatService();
            await myService.InitAsync(userName);
            Console.WriteLine("You are connected now !!");

            bool IsKeepGoing = true;
            do
            {
                var text = Console.ReadLine();
                if (text.Trim().ToLower().Equals("exit"))
                {
                    await myService.DisconnectionAsync();
                    IsKeepGoing = false;
                }
            } while (IsKeepGoing);


        }
    }
}

在此我們執行看看(這裡可到方案右鍵屬性設置改為多個專案初始執行)



這裡可注意到一次開啟兩隻不同種類專案的程式
先等Azure 被動監聽的常駐程式開啟執行後
對.net core console project輸入user id
隨意輸入名字之類的
就可看到連線成功~














留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題