2024: Microsoft のチュートリアルの Blazor Todo リスト アプリを MudBlazor に変換

## MudBlazor テンプレートのインストール
1. PowerShellで下記のコマンドを実行：
```
> dotnet new install MudBlazor.Templates
次のパッケージがインストールされます:
   MudBlazor.Templates

成功: MudBlazor.Templates::0.6.5により次のテンプレートがインストールされました。
テンプレート名     短い名前   言語  タグ
-----------------  ---------  ----  --------------------------------
Blazor Web アプリ  mudblazor  [C#]  Web/Blazor/WebAssembly/MudBlazor

>
```
## テンプレートの MudBlazor プロジェクトの新規作成
2. Visual Studio 2022を開いて
3. プロジェクトの新規作成を押下
![project_create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/ddb0a302-c56c-e48b-e5e2-17b37d34583d.png)
4. Fluent Blazorのウエブアプリテンプレートを選択
5. 「次」を押下
![mbb_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/3e2a5afd-f11a-207d-9515-7fb9258b82f8.png)
6. プロジェクト名を入力
7. 「次」を押下
![mbb_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/4632d2e8-edf3-8ed7-cb07-233ce9c0af59.png)
8. 「作成」を押下
![fluent_create_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/7d64dc7d-4cf3-f569-ba6c-6b7dcd47a26c.png)

## デフォルトのアプリ
![mbb_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/3018f52d-3a63-eb16-7a8d-9eed6612d45e.png)

デフォルトのアプリは [MudBlazor](https://www.mudblazor.com/) を使用しています。

## Todo リスト アプリを構築する
このアプリを、概要ページを備えた ToDo リスト アプリに変換しましょう。
### 不要機能の削除
まず、「Counter（カウンター）」と「Weather（天気）」のページを削除しましょう。
#### ファイル削除
1. `Components/Pages/Counter.razor`と`Components/Pages/Weather.razor`のファイルを選択
2. 右クリックすると表示されるコンテキストメニューで Delete を選択
![mbb_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/3b802bf7-3811-a991-be61-3cd7817e31d2.png)
#### リンク処理
`Components/Layout/NavMenu.razor`ファイルの中で：
1. 1番目の「Home（ホーム）」のリンクの名前を「Todo リスト」に変更
2. 1番目のリンクのアイコンをチェックされたチェックボックスに変更
    ```
    <MudNavMenu>
        <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Outlined.CheckBox">Todo リスト</MudNavLink>
        <MudNavLink Href="counter" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Add">Counter</MudNavLink>
        <MudNavLink Href="weather" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.List">Weather</MudNavLink>
    </MudNavMenu>
    ```
3. 2番目と3番目のリンクを削除
    ```
    <MudNavMenu>
        <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.CheckBox">Todo リスト</MudNavLink>
    </MudNavMenu>
    ```
4. 「About」ページへのリンクを追加する
    ```
    <MudNavMenu>
        <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Outlined.CheckBox">Todo リスト</MudNavLink>
        <MudNavLink Href="about" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Outlined.Info">About</MudNavLink>    
    </MudNavMenu>
    ```
### Todo リスト追加
1. `Components/Pages/Home.razor`を開いて
2. 相対 URL `/` とともに @page ディレクティブを追加。
3. そのまま静的にレンダリングされないように、ページの対話機能を有効にします。 対話型サーバー レンダリング モードにすると、コンポーネントでサーバーからの UI イベントを処理できます。
4. ページに HTML <title> 要素を追加することを可能にする PageTitle コンポーネントを使用してページ タイトルを追加します。
    ```
    @page "/"
    @rendermode InteractiveServer
    
    <PageTitle>Todo</PageTitle>
    
    <h3>Todo</h3>
    
    @code {
    
    }
    ```
5. プロジェクト（`MudBlazorTodoList`）フォルダーを右クリックすると表示されるコンテキストメニューで Add ＞ New Folder を選択
![mbb_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/5e1d32cc-f738-acf4-1229-208a9a8daea3.png)
6. モデル クラスの保持に使用するため、新しいフォルダーを「Models」に名付け
![mbb_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/60e0e05b-f26a-1b00-4b95-f73c220262f6.png)
7. `Models`フォルダーを右クリックすると表示されるコンテキストメニューで Add ＞ New Item を選択
![mbb_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/d6e8dc1b-f368-22e0-5882-0c86b67e56de.png)
8.  Todo アイテムを表すクラスを保持するために、プロジェクト `Models`フォルダーに `TodoItem.cs` ファイルを追加
![mbb_9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/ee1049a1-98d4-55cb-5c74-1f378d822331.png)
9. 次の C# コードを挿入して `TodoItem` クラスを肉付け
    ```
    namespace MudBlazorTodoList.Models
    {
        public class TodoItem
        {
            public string? Title { get; set; }
            public DateTime? DueDate { get; set; }
            public bool IsDone { get; set; }
        }
    }
    ```
10. Todo コンポーネントに戻り
11. @namespace でモデルの `TodoItem` クラスを使用可能にする
12. Todo アイテム用のフィールドを @code ブロックに追加（Todo コンポーネントでは、このフィールドを使って ToDo リストの状態を維持）
13. リストを表す縦のフレックスボックス列（`div`）を追加
14. Todo リスト（[`MudList`](https://www.mudblazor.com/components/list)）の中、各 Todo アイテム（[`MudListItem`](https://www.mudblazor.com/components/list#properties)）を横のフレックスボックス（`div`）を含むカード（[`MudCard`](https://www.mudblazor.com/components/card)）としてレンダリングするために、 **foreach** ループを追加
    ```
    @page "/"
    @rendermode InteractiveServer
    @namespace MudBlazorTodoList.Models
    
    <PageTitle>Todo</PageTitle>
    
    <h3>Todo</h3>

    <MudList>
        @foreach (var todo in todos)
        {
            <MudListItem>
                <MudCard Height="64px">
                    <div style="display:flex;flex-direction:row;gap:4px;justify-content:start;align-items:center;">
                        <MudCheckBox @bind-Value="@todo.IsDone" Label="Is Done?" />
                        <MudTextField T="string" @bind-Value="@todo.Title" Label="Title" Placeholder="title" />
                        <MudDatePicker Label="Due" @bind-Value="@todo.DueDate" />
                     </div>
                 </MudCard>
            </MudListItem>
        }
    </MudList>

    @code {
        private List<TodoItem> todos = new();
    }
    ```
15. Todo リスト（[`MudList`](https://www.mudblazor.com/components/list)）を別の縦のフレックスボックス列（`div`）で囲み、その下のリストに新しい Todo 項目を追加する UI 要素を追加
16. 追加 UI 要素をリストから視覚的に分離するため、 [`MudDivider`](https://www.mudblazor.com/components/divider) を追加
17. 追加 UI のコントロールを含む横のスタック（[`FluentStack`](https://www.fluentui-blazor.net/Stack)）を追加
18. 新しい Todo 項目のタイトル用にテキストボックス（[`MudTextField`](https://www.mudblazor.com/components/textfield)）を追加
19. 新しい Todo 項目の期限用の日付ピッカー（[`MudDatePicker`](https://www.mudblazor.com/components/datepicke)）を追加
20. 追加アクションを実行するための「Add Todo」ボタン（[`MudButton`](https://www.mudblazor.com/components/button)）を追加
    ```
    @page "/"
    @rendermode InteractiveServer
    @namespace FluentBlazorTodoList.Models
    
    <PageTitle>Todo</PageTitle>
    
    <h3>Todo</h3>
    
    <div style="display:flex;flex-direction:column;gap:24px;justify-content:center;align-items:start;">
        <MudList>
            @foreach (var todo in todos)
            {
                <MudListItem>
                    <MudCard Height="64px">
                        <div style="display:flex;flex-direction:row;gap:4px;justify-content:start;align-items:center;">
                            <MudCheckBox @bind-Value="@todo.IsDone" Label="Is Done?" />
                            <MudTextField T="string" @bind-Value="@todo.Title" Label="Title" Placeholder="title" />
                            <MudDatePicker Label="Due" @bind-Value="@todo.DueDate" />
                        </div>
                    </MudCard>
                </MudListItem>
            }
        </MudList>
        <MudDivider />
        <div style="display:flex;flex-direction:row;gap:4px;justify-content:start;align-items:center;">
            <MudTextField T="string" Label="New Todo" Placeholder="Something todo"></MudTextField>
            <MudDatePicker Label="Due" />
            <MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Outlined.AddCircleOutline">Add todo</MudButton>
        </div>
    </div>
    
    @code {
        private List<Models.TodoItem> todos = new();
    }
    ```
現時点では、ボタンにイベント・ハンドラーがアタッチされていないため、「Add Todo」ボタン（[`MudButton`](https://www.mudblazor.com/components/button)）を押しても何も起こりません。
21. Todo アイテムの追加アクションを処理するには、**@code** ブロックに AddTodo メソッドを追加

    @code {
        private List<Models.TodoItem> todos = new();
        private void AddTodo()
        {
            // // Todo: todo アイテムを追加
        }
    }
22 . 次に、@onclick 属性を使用して、AddTodo メソッドを「Add Todo」ボタンのクリック・イベント ハンドラーとして登録

    <MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Outlined.AddCircleOutline" OnClick="AddTodo">Add todo</MudButton>

23 . 新しい Todo アイテムのタイトルを取得するため、**@code** ブロックの先頭に `newTodoTitle` **String** フィールドを追加
24 . 新しい Todo アイテムの期限を取得するため、`newTodoDueDate` **DateTime** フィールドを **@code** ブロックの先頭に追加
25 . `AddTodo` メソッドを更新して、指定されたタイトルと期限を持つ `TodoItem` をリストに追加
26 . `newTodoTitle` を空の文字列に設定し、`newTodoDueDate` を **null** に設定して、テキストボックス（[`MudTextField`](https://www.mudblazor.com/components/textfield)）と日付ピッカー（[`MudDatePicker`](https://www.mudblazor.com/components/datepicke)）の値をクリア

    @code {
        string? newTodoTitle;
        DateTime? newTodoDueDate;
        private List<Models.TodoItem> todos = new();
        private void AddTodo()
        {
            if (!string.IsNullOrWhiteSpace(newTodoTitle))
            {
                todos.Add(new TodoItem { Title = newTodoTitle, DueDate = newTodoDueDate });
                newTodoTitle = string.Empty;
                newTodoDueDate = null;
            }
        }
    }
27 . **@bind-Value** 属性を使用して、`newTodoTime` を テキストボックス（[`MudTextField`](https://www.mudblazor.com/components/textfield)）要素にバインド
28 .   **@bind-Value** 属性を使用して、`newTodoDueDate` を日付ピッカー（[`MudDatePicker`](https://www.mudblazor.com/components/datepicker)）要素にバインド

    <MudTextField T="string" Label="New Todo" Placeholder="Something todo" @bind-Value=newTodoTitle></MudTextField>
    <MudDatePicker Label="Due" @bind-Value=newTodoDueDate />
29 . Todo リストの前にカウンター・バッジ（[`MudBadge`](https://www.mudblazor.com/components/badge)）を追加して、完了していない Todo 項目の数を表示する (IsDone は false)。 Blazor がコンポーネントを再レンダリングするたびにその Razor 式が評価される

    <MudBadge Content="@todos.Count(todo => !todo.IsDone)" Color="Color.Primary" Overlap="true" Class="mx-6 my-4">
        <MudIcon Icon="@Icons.Material.Filled.Email" Color="Color.Secondary" />
    </MudBadge>
    <MudDivider />
#### 残りの問題
日付ピッカー（[`MudDatePicker`](https://www.mudblazor.com/components/datepicker)）のフィールドのアイコンが無効みたいで、クリックしてピッカーのポップアップが表示されませんので日付選択できません。
#### 完成
    ```
    @page "/"
    @rendermode InteractiveServer
    @namespace MudBlazorTodoList.Models
    
    <PageTitle>Todo</PageTitle>
    
    <h3>Todo</h3>
    
    <div style="display:flex;flex-direction:column;gap:24px;justify-content:center;align-items:start;">
        <MudBadge Content="@todos.Count(todo => !todo.IsDone)" Color="Color.Primary" Overlap="true" Class="mx-6 my-4">
            <MudIcon Icon="@Icons.Material.Filled.Email" Color="Color.Secondary" />
        </MudBadge>
        <MudDivider />
        <MudList>
            @foreach (var todo in todos)
            {
                <MudListItem>
                    <MudCard Height="64px">
                        <div style="display:flex;flex-direction:row;gap:4px;justify-content:start;align-items:center;">
                            <MudCheckBox @bind-Value="@todo.IsDone" Label="Is Done?" />
                            <MudTextField T="string" @bind-Value="@todo.Title" Label="Title" Placeholder="title />
                            <MudDatePicker Label="Due" @bind-Value="@todo.DueDate" />
                            </div>
                    </MudCard>
                </MudListItem>
            }
        </MudList>
        <MudDivider />
        <div style="display:flex;flex-direction:row;gap:4px;justify-content:start;align-items:center;">
            <MudTextField T="string" Label="New Todo" Placeholder="Something todo" @bind-Value=newTodoTitle></MudTextField>
            <MudDatePicker Label="Due" @bind-Value=newTodoDueDate />
            <MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Outlined.AddCircleOutline" OnClick="AddTodo">Add todo</MudButton>
        </div>
    </div>
    
    @code {
        string? newTodoTitle;
        DateTime? newTodoDueDate;
        private List<Models.TodoItem> todos = new();
        private void AddTodo()
        {
            if (!string.IsNullOrWhiteSpace(newTodoTitle))
            {
                todos.Add(new TodoItem { Title = newTodoTitle, DueDate = newTodoDueDate });
                newTodoTitle = string.Empty;
                newTodoDueDate = null;
            }
        }
    }
    ```
![mbb_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/5a61319e-fc7f-b254-8a9f-24151e0ae3d3.png)

### About ページ追加
1. `Components/Pages`フォルダーを右クリックすると表示されるコンテキストメニューで Add ＞ New Component を選択
![mbb_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/15013541-1398-6dad-635f-974eee843d29.png)
2. 「About.razor」を名付けて
3. 「追加」を押下
![mbb_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/20daeb2d-b96f-0f05-83b4-f68b71a429c3.png)
4. `Components/Pages/About.razor`を開いて
5. 次のコードを挿入して「About」のページを実装してください
    ```
    @page "/about"
    @rendermode InteractiveServer
    
    <PageTitle>About</PageTitle>
    
    <MudPaper>
        <h3>About</h3>

        <MudBadge Color="Color.Success" Overlap="true" Bordered="true">
            <MudAvatar>
                <MudImage Src="https://avatars.githubusercontent.com/u/3477572?v=4" Alt="Meister 619" />
            </MudAvatar>
        </MudBadge>
        <MudText Typo="Typo.h6">Meister 619</MudText>

        <MudText Typo="Typo.body1"> ... blah blah blah ...</MudText>

    </MudPaper>
    @code {
    
    }
    ```
#### 完成
![mbb_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531993/ba189556-3746-7a7d-b463-123a56e55e5c.png)

## 参考
* MicrosoftのLearn/.NET/ASP.NET Core/[Blazor Todo リスト アプリ](https://learn.microsoft.com/ja-jp/aspnet/core/blazor/tutorials/build-a-blazor-app?view=aspnetcore-8.0)のチュートリアル
* [MudBlazor コンポネント ライブラリー](https://www.mudblazor.com/)
* [MudBlazor](https://github.com/MudBlazor/MudBlazor/) の GitHub レポジトリー