上一篇介紹了前端 JavaScript 的 WebSocket,
今天來介紹 .Net Core Web API 的 WebSocket。
首先要到 Startup.cs > Configure 去設定一個 WebSocket 的路徑,
然後設定使用 WebSocket 及接受連線的方法。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... // 設定 WebSocket 的路徑 app.Map("/websocket", con => { // 使用 WebSocket con.UseWebSockets(); // 接受連線的方法 con.Use(async (context, _) => { var socket = await context.WebSockets.AcceptWebSocketAsync(); while (socket.State == WebSocketState.Open) { // 因為我只需要後端傳到前端 // 所以就不處理前端傳來的訊息了 XD await socket.ReceiveAsync( new ArraySegment<byte>(new byte[1]), CancellationToken.None); } }); }); // ... }
傳送訊息至前端就需要用到前面 AcceptWebSocketAsync 回傳的實例。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.Map("/websocket", con => { con.UseWebSockets(); con.Use(async (context, _) => { var socket = await context.WebSockets.AcceptWebSocketAsync(); // 傳送連線成功訊息 var message = "連線成功囉~"; // 傳送的訊息參數類型是 bytes 要轉一下 var messageBuffer = Encoding.UTF8.GetBytes(message); await socket.SendAsync( buffer, WebSocketMessageType.Text, true, CancellationToken.None); while (socket.State == WebSocketState.Open) { await socket.ReceiveAsync( new ArraySegment<byte>(new byte[1]), CancellationToken.None); } }); }); // ... }
理論上,
我們不會只在接收連線時傳送訊息,
也不會只傳訊息給一個連線。
相反的,
我們應該在接受連線時,將其放入一個集合。
並在需要發送訊息時,
使用這個集合來以廣播的方式發送訊息給所有連線。
所以這裡我會傾向用一個 Service 來處理連線。
public class WebsocketHandler { // 存放連線的集合 private List<WebSocket> _sockets = new List<WebSocket>(); // 接受連線的方式 public async Task Connect(HttpContext context, Func<Task> _) { var socket = await context.WebSockets.AcceptWebSocketAsync(); _sockets.Add(socket); // 將連線加入集合 while (socket.State == WebSocketState.Open) { await socket.ReceiveAsync( new ArraySegment<byte>(new byte[1]), CancellationToken.None); } } // 發送訊息 public async Task SendAsync(object data, CancellationToken ct = default) { // 過濾沒有開啟的連線 _sockets = _sockets.Where(socket => socket.State == WebSocketState.Open).ToList(); // 逐一發送訊息 foreach (WebSocket socket in _sockets) { await SendAsync(socket, data, ct); } } private async Task SendAsync(WebSocket socket, object data, CancellationToken ct) { // 把資料轉成 JSON string 再轉成 bytes var dataStr = JObject.FromObject(data).ToString(); var buffer = Encoding.UTF8.GetBytes(dataStr); // 發送訊息 await socket.SendAsync(buffer, WebSocketMessageType.Text, true, ct); } }
然後調整 Startup.cs 的設定
public void ConfigureServices(IServiceCollection services) { // ... // 記得要用 Singleton 連線集合才不會出錯 services.AddSingleton<WebsocketHandler, WebsocketHandler>(); // ... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.Map("/websocket", con => { con.UseWebSockets(); con.Use(app.ApplicationServices.GetService<WebsocketHandler>().Connect); }); // ... }
在發送訊息的部分,我會希望將訊息的格式統一成:
{ "type": "訊息類型", "data": "訊息資料" }
方便前端區分資料類型。
所以這邊我會建立一個 Abstract class ,
讓所有需要發送的通知的部分來繼承。
public abstract class AbstractWebsocket { private readonly WebsocketHandler _handler; private readonly WebsocketType _type; // 除了注入剛剛的 handler 來處理發送訊息的部分 // 還要傳入一個參數來定義資料的類型 protected AbstractWebsocket(WebsocketHandler handler, WebsocketType type) { _handler = handler; _type = type; } protected async Task SendAsync(object data) { // 傳送的訊息為類型加上資料 await _handler.SendAsync(new { type = _type, data }); } } // 繼承範例 public class ExampleWebsocket : AbstractWebsocket, IExampleWebsocket { public ExampleWebsocket(WebsocketHandler handler) // 注入 handler : base(handler, WebsocketType.Example) // 設定類型 { } public async Task SendAsync(ExampleModel data) // 這裡可以使用想要的類別 { await base.SendAsync(data); // 使用抽象類別的發送方法 } }
在 Startup.cs > ConfigureServices 加入 IExampleWebsocket :
public void ConfigureServices(IServiceCollection services) { // ... // 這邊就可以用 Scoped 或是 Transient services.AddTransient<IExampleWebsocket, ExampleWebsocket>(); // ... }
要使用的時候就直接注入 IExampleWebsocket 來使用:
public class ExampleService { private readonly IExampleWebsocket _websocket; public ExampleService(IExampleWebsocket websocket) { _websocket = websocket; } public async Task SendNewExample() { await _websocket.SendAsync(new ExampleModel()); } }
以上就是我在 .Net Core Web Api 中使用 Websocket 的方式。
如果有更好的方式,一樣歡迎留言推薦給我噢!