.NET5 Blazor WASM + SignalR 手札

於 Blazor WASM 預設的通訊方式是 HTTP,不過我們想用 SignalR 做為 Blazor WASM 的主要通訊方法。SignalR 本身是設計成非同步通訊的,其實它也可以同步通訊。必竟大部份的商業表單運算與資料交換還是以同步通訊為主因為這樣比較好控制。

參考文章

下面文章說明如何在 Blazor WASM 專案加入 SignalR 支援,故本人不再說明。這裡的 SignalR 正式的名稱是:ASP.NET Core SignalR

搭配使用 SignalR ASP.NET Core Blazor

下面文章有 SignalR 同步指令 InvokeAsync<TResult> 的範例,故本人不再說明。

ASP.NET Core SignalR .Net 用戶端

一些指令紀錄

  • 非同步通訊用 SendAsync(...)

  • 同步通訊用InvokeAsync<TResult>(...),可用來取代HTTP通訊。

記錄練習過程中比較另人在意的部份。

Client Side

\Pages\ChatClient\_ChatClient.razor
@using Microsoft.AspNetCore.SignalR.Client
@page "/chatclient"
@inject NavigationManager NavigationManager
@implements IAsyncDisposable //------ ※實作介面

<MudContainer MaxWidth="MaxWidth.Large">
    <MudText Typo="Typo.h3">SignalR 聊天室</MudText>

    <MudTextField @bind-Value="userInput" Label="User" Variant="Variant.Outlined" />
    <MudTextField @bind-Value="messageInput" Label="Message" Variant="Variant.Outlined" />
    <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="Send" Disabled="@(!IsConnected)">Send</MudButton>
    <MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="PostFormData">同步呼叫SignalR</MudButton>

    <MudDivider Class="my-2" />
    <MudPaper Class="pa-2">
        <MudText Typo="Typo.h5">聯天內容</MudText>
        <MudList Clickable="true">
            @foreach (var message in messages)
            {
                <MudListItem Text="@message" Icon="@Icons.Material.Filled.Chat" />
            }
        </MudList>
        <MudText Typo="Typo.h5">個人送達紀錄</MudText>
        <MudList Clickable="true">
            @foreach (var msg in hasSentMessage)
            {
                <MudListItem Text="@msg" Icon="@Icons.Material.Filled.ArtTrack" />
            }
        </MudList>
    </MudPaper>

    <MudDivider Class="my-2" />
    <MudPaper Class="pa-2">
        <MudText Typo="Typo.body1">
            同步通訊結果: @resultMessage
        </MudText>
    </MudPaper>
</MudContainer>

@code{
    private HubConnection hubConn;
    private List<string> messages = new List<string>();
    private List<string> hasSentMessage = new List<string>();
    private string userInput = "郝聰明";
    private string messageInput = "今天天氣真好";
    private string resultMessage;

    protected override async Task OnInitializedAsync()
    {
        hubConn = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
            .WithAutomaticReconnect() /// 將會自動重新連線三次
            .Build();

        /// SignalR 通訊範例 - 註冊:接收回傳訊息函式
        hubConn.On<string, string>("ReceiveMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            messages.Add(encodedMsg);
            StateHasChanged();
        });

        /// SignalR 通訊範例 - 註冊:接收回傳訊息函式
        hubConn.On<string, string>("HasSentMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            hasSentMessage.Add(encodedMsg);
            StateHasChanged();
        });

        // SignalR 連線
        await hubConn.StartAsync();
    }

    /// SignalR 通訊範例 - 送出訊息
    async Task Send() =>
        await hubConn.SendAsync("SendMessage", userInput, messageInput);

    /// SignalR 同步通訊範例,可用來取代HTTP通訊。
    async void PostFormData()
    {
        /// SignalR 送出並接收回傳訊息
        resultMessage = await hubConn.InvokeAsync<string>("PostFormData", "user01", "我是訊息。");
        StateHasChanged();
    }

    public bool IsConnected => hubConn.State == HubConnectionState.Connected;

    public async ValueTask DisposeAsync()
    {
        await hubConn.DisposeAsync();
    }
}

Server Side

SignalR Hub 範例

\Hubs\ChatHub.cs
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;

namespace UeaseMan.Server.Hubs
{
    public class ChatHub : Hub
    {
        /// <summary>
        /// for 非同步呼送訊息
        /// </summary>
        public async Task SendMessage(string user, string message)
        {
            await Task.Run(() =>
            {
                // SignalR 回送訊息給傳訊息
                Clients.Caller.SendAsync("HasSentMessage", user, message);
                // SingalR 轉送訊息給全部線上使用者
                Clients.All.SendAsync("ReceiveMessage", user, message);
            });
        }

        /// <summary>
        /// for 同步呼叫並回傳值
        /// </summary>
        public async Task<string> PostFormData(string user, string message)
        {
            string result = "未處理";
            result = await Task.Run<string>(() =>
            {
                Clients.Caller.SendAsync("HasSentMessage", user, message);
                return $"收到訊息:{user}:{message}";
            });
            return result;
        }

    }
}

新增中樞的服務和端點 SignalR

Startup.cs
using ......
using UeaseMan.Server.Hubs; //※※※ ------

namespace UeaseMan.Server
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR(); //※ 新增 SignalR 軟體服務 ------
            services.AddControllersWithViews();
            services.AddRazorPages();
            services.AddMudServices();
            
            //※ 新增回應壓縮中介軟體服務 ------
            services.AddResponseCompression(opts =>
            {
                opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
                    new[] { "application/octet-stream" });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            //※ 在處理管線的設定頂端使用回應壓縮中介軟體。 ------
            app.UseResponseCompression();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseWebAssemblyDebugging();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseBlazorFrameworkFiles();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
                endpoints.MapControllers();
                
                //※ 在控制器的端點和用戶端的回復之間,新增中樞的端點。 ------
                endpoints.MapHub<ChatHub>("/chathub");
                
                endpoints.MapFallbackToFile("index.html");
            });
        }
    }
}

先這樣。

Last updated