.NET MAUI＋Blazor を始めてみた

# はじめに

Windows のデスクトップアプリを作るのに自分は、以前は Delphi を使っていて、仕事は WinForms＋VB.NET を使っていて、趣味は Electron も使っています。新たにアプリを作り始めるのに、別の開発手段も試してみようと思いました。

# .NET MAUI とは

.NET Multi-Platform App UI (. NET MAUI) は、C# と XAML を使用して、ネイティブのモバイル アプリやデスクトップ アプリを作成するための、クロスプラットフォーム フレームワークです。
（[.NET MAUI とは - .NET MAUI | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/maui/what-is-maui)）

チュートリアルのソースコードの一部を見てみます。

```MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="●●.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">

            <Button
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked"
                HorizontalOptions="Center" />

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>
```
```MainPage.xaml.cs
public partial class MainPage : ContentPage
{
    int count = 0;

    public MainPage()
    {
        InitializeComponent();
    }

    private void OnCounterClicked(object sender, EventArgs e)
    {
        count++;

        if (count == 1)
            CounterBtn.Text = $"Clicked {count} time";
        else
            CounterBtn.Text = $"Clicked {count} times";
    }
}
```

XAML で画面の定義して C# でロジックを実装します。いわゆるウェブアプリが HTML で画面の定義して JavaScript でロジックの実装するのと似ています。

# .NET MAUI を始めてみた

## Visual Studio を使う

参考：[Visual Studio 2022 をインストールする - .NET MAUI | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/maui/get-started/installation?view=net-maui-7.0)

## プロジェクトを新規作成する

参考：[初めての .NET MAUI アプリをビルドする - .NET MAUI | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/maui/get-started/first-app?pivots=devices-windows&view=net-maui-7.0)

## 新規プロジェクトの内容を確認する

アプリのベースになるプロジェクトは簡単に作成できましたが、ファイルが幾つもあります。どのファイルを変更すればいいのか。それを知るために、プロジェクトに含まれるファイルを調べてみました。

### MauiProgram.cs

エントリポイントは、```MauiProgram.cs``` のようです。

```MauiProgram.cs
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>();

        return builder.Build();
    }
}
```

```builder.UseMauiApp<App>();``` で指定されている ```App``` の定義を探します。

### App.xaml および App.xaml.cs

```App.xaml.cs``` に ```App``` というクラスの定義がありました。

```App.xaml.cs
public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        MainPage = new AppShell();
    }
}
```

```App.xaml
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:●●"
             x:Class="●●.App">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Resources/Styles/Colors.xaml" />
                <ResourceDictionary Source="Resources/Styles/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
```

```App.xaml.cs``` で new されている ```AppShell``` の定義を探します。

### AppShell.xaml および AppShell.xaml.cs

```AppShell.xaml``` と ```AppShell.xaml.cs``` がありました。

```AppShell.xaml.cs
public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
    }
}
```

```AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="●●.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:●●"
    Shell.FlyoutBehavior="Disabled">

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />

</Shell>
```

```ContentTemplate="{DataTemplate local:MainPage}"``` および ```Route="MainPage"``` で指定されている ```MainPage``` の定義を探します。

### MainPage.xaml および MainPage.xaml.cs

```MainPage.xaml``` と ```MainPage.xaml.cs ``` がありました。ようやく画面を定義しているファイルに辿り着きました。

```MainPage.xaml.cs
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }
}
```

```MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="●●.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Spacing="25"
            Padding="30,0"
            VerticalOptions="Center">
        
        （中略）

        </VerticalStackLayout>
    </ScrollView>

</ContentPage>
```

このファイルを修正すればいいようです。

このファイルを ```MauiProgram.cs``` で指定すればいいように思いましたが、```AppShell``` クラスでルーティング機能つまりページ遷移を実装しています。

# .NET MAUI を始めてみたが

```●●.xaml.cs``` は ```●●.xaml``` とセットになっています。WinForms＋C# のアプリで ```●●.cs``` と ```●●.Designer.cs``` がセットになるのと同じでしょうか。
WinForms アプリを Visual Studio で開発するときはビジュアルデザイナが使えるので ```●●.Designer.cs``` は直接編集することがありませんが、.NET MAUI アプリは Visual Studio でも ```●●.xaml``` を直接編集しないといけないようです。

```MainPage.xaml``` に手を加え始めましたが、思ったように書けそうにありません。自分は以前に WPF アプリを作ろうとして、このときも XAML の編集に馴染めなくて挫折していました。

.NET MAUI の記事を読み漁っていると、Blazor で開発するという記事を見つけました。

# .NET Blazor とは

Blazor は、開発者が C＃ と HTML を使用してウェブアプリを作成できるようにする、無料のオープンソースのフレームワークです。これは Microsoft によって開発されています。
（[Blazor - Wikipedia](https://en.wikipedia.org/wiki/Blazor)）

チュートリアルのソースコードの一部を見てみます。

```Counter.razor
@page "/counter"

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
    {
        currentCount++;
    }
}
```

前半は HTML そのもので、後半は C# のコードです。XAML より書きやすそうです。

# .NET Blazor を始めてみた

## Visual Studio を使う

参考：[Blazor チュートリアル | ダウンロードしてインストールする](https://dotnet.microsoft.com/ja-jp/learn/aspnet/blazor-tutorial/install)

## プロジェクトを新規作成する

参考：[Blazor チュートリアル | 最初の Blazor アプリをビルドする](https://dotnet.microsoft.com/ja-jp/learn/aspnet/blazor-tutorial/create)

## Blazor サーバアプリと Blazor クライアントアプリ

Blazor アプリは、

* サーバで実行されて HTML がレンダリングされるタイプ「Blazor Server アプリ」
* クライアントのブラウザ上で実行されるタイプ「Blazor WebAssembly アプリ」

参考：[ASP.NET Core Blazor | Microsoft Learn](https://learn.microsoft.com/ja-jp/aspnet/core/blazor/)

# サーバサイド Blazor を始めてみた

まず、サーバサイド Blazor アプリを作ってみました。

## プロジェクトの内容を確認する

アプリのベースになるプロジェクトは簡単にできましたが、ファイルが幾つもあります。どのファイルを変更すればいいのか。それを知るために、プロジェクトに含まれるファイルを調べてみました。

### Program.cs

エントリポイントは、```Program.cs``` のようです。

```Program.cs
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
```

```app.MapFallbackToPage("/_Host");``` で指定されている ```_Host``` の定義を探します。

### Pages/_Host.cshtml

```Pages```フォルダに ```_Host.cshtml``` がありました。

```_Host.cshtml
@page "/"
@using Microsoft.AspNetCore.Components.Web
@namespace ●●.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <base href="~/" />
    <link href="css/site.css" rel="stylesheet" />
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
    <component type="typeof(App)" render-mode="ServerPrerendered" />

    <script src="_framework/blazor.server.js"></script>
</body>
</html>
```

```<component type="typeof(App)"``` で指定されている ```App``` の定義を探します。

### App.razor

```App.razor``` がありました。

```App.razor
<Router AppAssembly="@typeof(Program).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <p>Sorry, there's nothing at this address.</p>
    </NotFound>
</Router>
```

```<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"``` で指定されている ```MainLayout``` の定義を探します。

### MainLayout.razor

```MainLayout.razor``` がありました。

```MainLayout.razor
@inherits LayoutComponentBase

<main>
    @Body
</main>
```

```@Body``` に何か埋込されるようです。

参考：[ASP.NET Core Blazor レイアウト | Microsoft Learn](https://learn.microsoft.com/ja-jp/aspnet/core/blazor/components/layouts)
参考：[ASP.NET Core の Blazor ルーティングとナビゲーション | Microsoft Learn](https://learn.microsoft.com/ja-jp/aspnet/core/blazor/fundamentals/routing)

起動時には ```"/"``` が指定されたページが埋込されます。

### Pages/Index.razor

```Pages```フォルダに ```Index.razor``` がありました。

```Index.razor
@page "/"

<h1>Hello, world!</h1>
```

このファイルを修正すればいいようです。ようやく画面を定義しているファイルに辿り着きました。

このファイルを ```_Host.cshtml``` で指定すればいいように思いましたが、```App.razor``` で ```Router``` コンポーネントを使ってルーティング機能つまりページ遷移を実装しています。

# クライアントサイド Blazor を始めてみた

続いて、クライアントサイド Blazor アプリを作ってみました。

## 新規プロジェクトの内容を確認する

アプリのベースになるプロジェクトは簡単に作成できましたが、ファイルが幾つもあります。どのファイルを変更すればいいのか。それを知るために、プロジェクトに含まれるファイルを調べてみました。

### Program.cs

エントリポイントは、```Program.cs``` のようです。サーバサイドのアプリと違っています。

```Program.cs
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

await builder.Build().RunAsync();
```

```builder.RootComponents.Add<App>("#app");``` で指定されている ```#app``` と ```App``` の定義を探します。

### wwwroot/index.html

```wwwroot```フォルダの ```index.html``` に ```<div id="app">``` がありました。

```index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <base href="/" />
    <link href="css/app.css" rel="stylesheet" />
</head>
<body>
    <div id="app">Loading...</div>

    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>
```

```<div id="app">``` にコンテンツが埋込されるようです。

### App.razor

```App.razor``` がありました。ここからサーバサイド Blazor アプリと同じです。

# .NET MAUI＋Blazor を始めてみた

## プロジェクトを新規作成する

参考：[BlazorWebView を使用して .NET MAUI アプリで Blazor Web アプリをホストする - .NET MAUI | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/maui/user-interface/controls/blazorwebview) 

## 新規プロジェクトの内容を確認する

アプリのベースになるプロジェクトは簡単に作成できましたが、ファイルが幾つもあります。これのどこを変更すればいいのか。それを知るために、プロジェクトに含まれるファイルを調べてみました。

### MauiProgram.cs

エントリポイントは、```MauiProgram.cs``` のようです。XAML を使った MAUI アプリと同じです。

```MauiProgram.cs
public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>();

        builder.Services.AddMauiBlazorWebView();
        builder.Services.AddSingleton<WeatherForecastService>();

        return builder.Build();
    }
}
```

```builder.Services.AddMauiBlazorWebView();``` と ```builder.Services.AddSingleton<WeatherForecastService>();``` が加わっています。

```builder.UseMauiApp<App>();``` で指定されている ```App``` の定義を探します。

### App.xaml および App.xaml.cs

```App.xaml.cs
public partial class App : Application
{
    public App()
    {
        InitializeComponent();

        MainPage = new MainPage();
    }
}
```

```App.xaml
<?xml version="1.0" encoding="UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:●●"
             x:Class="●●.App">
    <Application.Resources>
        <ResourceDictionary>
            （中略）
        </ResourceDictionary>
    </Application.Resources>
</Application>
```

```App.xaml.cs``` で new されている ```MainPage``` の定義を探します。

### MainPage.xaml および MainPage.xaml.cs

```MainPage.xaml.cs
public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }
}
```

```MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:●●"
             x:Class="●●.MainPage"
             BackgroundColor="{DynamicResource PageBackgroundColor}">

    <BlazorWebView HostPage="wwwroot/index.html">
        <BlazorWebView.RootComponents>
            <RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
        </BlazorWebView.RootComponents>
    </BlazorWebView>

</ContentPage>
```

XAML を使った MAUI アプリは、このファイルで画面の定義していましたが、Blazor を使った MAUI アプリは、 ```<BlazorWebView>``` が配置されていて、```wwwroot/index.html``` が埋込されているようです。
```<RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />``` で指定されている ```#app``` と ```Main``` の定義を探します。

### wwwroot/index.html

```wwwroot```フォルダに ```index.html``` ファイルがあります。クライアントサイド Blazor アプリと同じです。

### Main.razor

Blazor アプリの ```App.razor``` と同じです。ここから Blazor アプリと同じです。```App.razor``` でないのは ```App.xaml``` があるからでしょうか。

# .NET MAUI＋Blazor を始めてみたが

.NET MAUI＋Blazor アプリは、実行ファイルを作るのは MAUI のフレームワークだが、アプリの機能を実装するのは基本的に Blazor になります。
エントリポイントから Blazor を実行するまでは、変更することはないでしょう。アプリの機能を実装するには、```MainLayout.razor``` あるいは ```Pages/Index.razor``` 辺りを編集します。
Blazor は、HTML＋CSS＋JavaScript の代わりに HTML＋CSS＋C# で書くので、XAML＋C# を新たに覚えて使うより楽かも知れません。