15
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Blazor Server-Side アプリで作る 簡単チャットアプリ

Last updated at Posted at 2019-11-11

Blazor Server-SideアプリケーションでBlazorだけで、チャットアプリを作ります。
驚くほど簡単にできます:open_mouth:

jytc9-9qsu4.gif

開発環境

Windows10
Visual Studio 2019
.Net Core 3.0

メッセージ管理クラスの作成

Sharedフォルダに、List<string>型のチャット情報を保持するだけのChatClassを作ります。

Shared/ChatClass.cs
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は、アプリケーション全体で唯一無二の存在になりました。

Startup.cs
        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でインスタンスを渡す方が効率的です。

Shared/MainLayout.razor
@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内では、ChatClassStateChangedイベントハンドラに、Componentクラスの StateHasChangedメソッドを実行するようにセットしているので、チャットメッセージが追加されるとComponentクラスの StateHasChangedメソッドにより、画面が更新されるようになります。尚、画面更新時にthis.InvokeAsyncを使用するのは、別スレッドからコントロールを操作しようとして怒られるためです。

メッセージ入力画面

Pages/Index.razorにチャットメッセージの入力画面を作ります。

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にチャットメッセージの履歴画面を作ります。

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)

15
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?