Blazor Server-Sideで作るペンライトアプリ

Blazor Advent Calendar 2019　の記事です。今回は、Blazor Server-Sideで遠隔操作できるペンライトアプリケーションを作ってみたいと思います。ライブ会場で販売されている同期するペンライトのスマホ版です。

Blazor Server-Sideアプリケーションにより、特定のページ(URL)に接続されたスマホの画面を遠隔で操作できるようにしたいと思います。

作成イメージ  左：管理画面　右：接続されたスマホの画面(実際の画面は遅延なし)
![xp828-yvk4c.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/a3e366d9-9d9d-feae-dad0-23c3a31b793f.gif)
![group_people_smartphone_no_dog.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/6b2399dd-64ac-3fb5-70e9-5257635e44c9.png)


# スタイルを動的に変更する
Blazorではタグ内に、動的変数を使用できます。つまり、`classセレクタ`や`style`の内容を動的に変更できるのです。値変更後も、`StateHasChangedメソッド`を呼ぶことで、DOMが再構成されページが生成されます。この仕組みで、`CSS`の変更だけでなく、コントロールなども動的に生成することができるので、こういったところは`JavaScriptレス`に書くことができて、Blazorのワクワクする部分ですね！

### classセレクタ や styleを返す
```c#

<div class=@GetClass style=@GetStyle>
</div>

@code{
    bool alarm = false;

    string GetClass()
    {
        return "main";
    }

    string GetStyle()
    {
        if(alarm)
        {
            return "warn";
        }
        else
        {
            return "normal";
        }
    }
}

```

### こんな書き方もOK
cssファイル内のスタイルを変更することはできませんが、インラインで書かれた`style`の内容であれば動的にセットすることができます。

```C#
<style>
    .test{
        @styleStr
    }
</style>

<div class="test">
    <p>Hello World</p>
</div>

@code{
    string styleStr = "color:black;";

    void SetColor(string colorName)
    {
        styleStr = "color:" + colorName + ";";
    }
}

```


この特性を活かし、ショートコーディングでパリピなアプリを作っていきたいと思います。

# 色管理クラス
全セッションで共有される色だけを保持するクラスを作ります。状態変化で任意のイベントを実行できるようにハンドラを用意しておきます。

```c#
public class ColorClass
{
    private string _colorStr = "";
    public string ColorStr
    {
        get
        {
            return _colorStr;
        }
        set
        {
            _colorStr = value;
            this.StateChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    public event EventHandler StateChanged;

}
```

# Startup.csの編集
色管理クラスをプログラムの起動時にシングルトンで生成させます。

```c#:Startup.cs

public class Startup
{
    // 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.AddRazorPages();
        services.AddServerSideBlazor();
        services.AddSingleton<WeatherForecastService>();

        //シングルトンでインスタンスを生成
        services.AddSingleton<ColorClass>();
    }
}
```

#MainLayout.razorの編集
`MainLayout.razor`は余計なものを取り、`@Body`だけ表示させます。色管理クラスを`Inject`し、初回に値の変更を契機に`StateHasChangedメソッド`をコールし画面の再生成をするように仕掛けておきます。

```c#:MainLayout.razor
@inherits LayoutComponentBase
@inject ColorClass colorCls;

@*子コンテンツにカスケードでインスタンスを渡す*@
<CascadingValue Value="@colorCls" Name="Color">
    @Body
</CascadingValue>

@code{  
    protected override void OnInitialized()
    {
        //値変更で、画面の再生成を行う
        colorCls.StateChanged += (s,e) =>
        {
            this.InvokeAsync(this.StateHasChanged);
        };
    }
}


```

# 色を変更できる画面(Manage.razor)
色を変更できる管理画面を作成します。ここでは、```/manage```でアクセスすると、この画面が表示されるようにします。
`id:main`に対し、`background-Color`を変化させることにより、ページ全体の背景色を動的に変化できるようにします。

```c#:Manage.razor
@page "/manage"

<div id="main" style=@colorClass.ColorStr>
    <div class="content-row">
        @foreach(var colorName in ColorNames)
        {
            <button class="btn " style="background-color:@colorName;width:150px"
                    @onclick=@(_=>SetStyle(colorName))>@colorName</button>
        }
    </div>
</div>


@code
{
    [CascadingParameter(Name="Color")]
    protected ColorClass colorClass { get; set; }

    string[] ColorNames = new string[0];

    protected override void OnInitialized()
    {
        // ConsoleColorから色名を取得 
        ColorNames = Enum.GetNames(typeof(ConsoleColor));
    }

    void SetStyle(string name)
    {
        // ここの変更で、背景色が変わる
        colorClass.ColorStr = "background-Color:" + name;
    }

}
```

# クライアント画面(Index.razor)
操作される側は、`/`でアクセスされたときに表示される画面です。管理画面と同じく、`ID:main`に対し`background-Color`を動的に変更できるようにします。

```c#:Index.razor
@page "/"

<div id="main" style=@colorClass.ColorStr>
</div>

@code
{
    [CascadingParameter(Name="Color")]
    protected ColorClass colorClass { get; set; }
}

```

#最後に
Blazor Server-Sideアプリケーションは、`SignalR`により常時セッションが張られ状態変化が起きる度にデータの転送が行われていますが、変化が行われた箇所のみ送信されるので、その転送量は大きくありません。例えば、このアプリケーションの場合、色の変化によって１つの接続先に転送される情報量は約100Byte程度のものでした。
今回は、一見すると`JavaScript`を使ったアプリケーションに見えますが、`JavaScriptレス`で書くことができました！また、驚くほどコーディング量も少ないところがBlazorの良いところですね:thumbsup: