Blazor Server-SideアプリケーションでBlazorだけで、チャットアプリを作ります。
驚くほど簡単にできます
開発環境
Windows10
Visual Studio 2019
.Net Core 3.0
メッセージ管理クラスの作成
Sharedフォルダ
に、List<string>型
のチャット情報を保持するだけのChatClass
を作ります。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BlazorApp30.Shared
{
public class ChatClass
{
/// <summary>
/// チャットメッセージを保持する
/// </summary>
private List<string> _messages { get; set; } = new List<string>();
/// <summary>
/// 任意のイベントを実行するイベントハンドラ
/// </summary>
public event EventHandler StateChanged;
/// <summary>
/// チャットメッセージを取得する
/// </summary>
/// <returns></returns>
public List<string> GetMessages()
{
return _messages;
}
/// <summary>
/// チャットメッセージを追加する
/// </summary>
/// <param name="str"></param>
public void AddMessage(string str)
{
_messages.Add(str);
// チャットメッセージが追加されたら、イベントを実行する
this.StateHasChanged();
}
/// <summary>
/// チャットメッセージをクリアする
/// </summary>
public void ClearMessages()
{
_messages.Clear();
// チャットメッセージがクリアされたら、イベントを実行する
this.StateHasChanged();
}
/// <summary>
/// イベントを実行する
/// </summary>
private void StateHasChanged()
{
this.StateChanged?.Invoke(this, EventArgs.Empty);
}
}
}
ChatClass
は、チャットメッセージを保持するクラスです。メッセージの取得と追加、クリアには、それぞれ関数を用意し、更にEventHandler
を用意してあげることにより、使用する側でチャットメッセージが追加された事を契機に、任意のイベントを実行できるようにしておきます。
ChatClassをサービスに登録する
ASP.Net Coreでは、アプリケーション起動時に、任意のサービスのインスタンスを生成してくれる仕組みがあります。チャットアプリケーションでは、参加するユーザーに同期的にメッセージを表示させますので、ChatClass
をシングルトンで起動します。Blazor Server-Sideだからできることですが、これで ChatClass
は、アプリケーション全体で唯一無二の存在になりました。
using BlazorApp30.Shared;
~
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
// ChatClassをシングルトンで生成する
services.AddSingleton<ChatClass>();
}
クライアント画面
今回は、Pages/Index.razor
に入力ページ、Pages/FetchData.razor
に履歴ページを作ります。
ChatClass
インスタンスを複数の画面で使用する場合、それぞれの画面で inject
で宣言するよりも、MainLayout.razorにだけ
記述し、CascadingParameter
でインスタンスを渡す方が効率的です。
@inherits LayoutComponentBase
@inject BlazorApp30.Shared.ChatClass chatClass
@implements IDisposable
<div class="sidebar">
<NavMenu />
</div>
@*カスケードパラメータで子にインスタンスを渡す*@
<CascadingValue Value="@chatClass" Name="Chat">
<div class="main">
<div class="top-row px-4">
<a href="https://docs.microsoft.com/en-us/aspnet/" target="_blank">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
</CascadingValue>
@code{
protected override void OnInitialized()
{
// 初回時にイベントハンドラに、イベントの紐づけ
chatClass.StateChanged += OnChatStateChanged;
}
void OnChatStateChanged(
object sender, EventArgs e)
{
this.InvokeAsync(StateHasChanged);
}
void IDisposable.Dispose()
{
// 閉じられるとき、イベントのリリース
chatClass.StateChanged -= OnChatStateChanged;
}
}
少しイメージしにくいのですが、CascadingValueタグ で囲う ことにより、その配下のページ(子)
は、MainLayout(親)
と同じインスタンスを使えるようになります。 つまり、親でやったことは、子でやる必要はなくなります。
MainLayout
内では、ChatClass
のStateChangedイベントハンドラ
に、Componentクラスの StateHasChangedメソッド
を実行するようにセットしているので、チャットメッセージが追加されるとComponentクラスの StateHasChangedメソッド
により、画面が更新されるようになります。尚、画面更新時にthis.InvokeAsync
を使用するのは、別スレッドからコントロールを操作しようとして怒られるためです。
メッセージ入力画面
Pages/Index.razor
にチャットメッセージの入力画面を作ります。
@page "/"
<input type="text" @bind-value="InputMessage" />
<button class="btn btn-primary" @onclick="_=> chat.AddMessage(InputMessage)">送信</button>
@foreach (var m in chat.GetMessages())
{
<div style="padding: 10px; margin-bottom: 10px; border: 1px solid #333333; border-radius: 10px;">
@m
</div>
}
@code
{
[CascadingParameter(Name="Chat") ] protected ChatClass chat { get; set; }
string InputMessage = "";
}
チャットメッセージ履歴画面
Pages/FetchData.razor
にチャットメッセージの履歴画面を作ります。
@page "/fetchdata"
@using BlazorApp30.Data
<h3>チャットの履歴</h3>
<button class="btn btn-primary" @onclick="_=>chat.ClearMessages()">履歴クリア</button>
@foreach (var m in chat.GetMessages())
{
<div style="padding: 10px; margin-bottom: 10px; border: 1px solid #333333; border-radius: 10px;">
@m
</div>
}
@code
{
[CascadingParameter(Name = "Chat")] protected ChatClass chat { get; set; }
}
チャットメッセージを使う画面では、メッセージの送受信に関わるコードがなく、非常にスッキリ!
Server-Sideで画面をレンダリングする為、Server-Sideだからこそ成せるコードですが、非常に面白いですね。
参考
[blazorhelpwebsite.com Implementing State Management In Blazor] (http://blazorhelpwebsite.com/Blog/tabid/61/EntryId/4338/Implementing-State-Management-In-Blazor.aspx)