MAUIを中心に勉強するブログ

MAUIを習得するブログです

【.NET MAUI】MagicOnion と連携してみる

.NET MAUI のプロジェクトを作るとサンプルでクリック回数をカウントするアプリが作成されますが、 アプリ内でカウントしている回数をサーバーでカウントするようにします。 (クライアントではカウントしない)

サーバーは gRPC が簡単に扱えるMagicOnion を使います。

参考: Unity+MagicOnion4.1.xを試す 環境構築&サービスでの通信編

MagicOnion の導入方法が分かりやすく説明されています。

新しいプロジェクトの作成

Visual Studio 2022 Preview > 新しいプロジェクトの作成 > .NET MAUIアプリ

共通ライブラリの実装

ソリューション > 追加 > 新しいプロジェクト > クラスライブラリ > フレームワークは[.NET 5]を指定して作成します。 Nuget で magiconion を検索して MagicOnion.Abstractions をインストールします。 Sharedフォルダを作成して配下に IMyFirstService.cs を作成 プロジェクト > クラスの追加 IMyFirstService.cs

using MagicOnion;

namespace ClassLibrary1.Shared
{
    public interface IMyFirstService : IService<IMyFirstService>
    {
        UnaryResult<int> CounterAsync();
    }
}

サーバーサイドの実装

新しいプロジェクトの追加 > ASP.NET Core gRPCサービス > フレームワークは[.NET 5]を指定して作成します。

以降作成した ASP.NET Core gRPCサービスのプロジェクトで操作します。

Protos、Servicesのフォルダを削除します。 プロジェクト > プロジェクト参照の追加 で共通ライブラリのプロジェクトを追加します。 Nuget で magiconion を検索して MagicOnion.Server をインストールします。

Startup.cs の内容を2つ修正します

① services.AddGrpc(); のしたに services.AddMagicOnion(); を追加します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc();
    services.AddMagicOnion();
}

② endpoints.MapGrpcService(); を endpoints.MapMagicOnionService(); に書き換えます

app.UseEndpoints(endpoints =>
{
    endpoints.MapMagicOnionService();

    endpoints.MapGet("/", async context =>
    {
        await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
    });
});

Serviceフォルダを作成して配下に MyFirstService.cs を作成

using System;
using GrpcService1.Shared;
using MagicOnion;
using MagicOnion.Server;

namespace GrpcService1.Service
{
    public class MyFirstService : ServiceBase<IMyFirstService>, IMyFirstService
    {
        static int count = 0;
        
        public async UnaryResult<int> CounterAsync()
        {
            Console.WriteLine($"Received");
            return ++count;
        }
    }
}

クライアントサイドの実装

以下から MAUIプロジェクトを操作します。

Nuget で magiconion を検索して MagicOnion.Client をインストールします。

プロジェクト > プロジェクト参照の追加 で共通ライブラリのプロジェクトを追加します。

サーバーを呼び出すコードを実装します。

using ClassLibrary1.Shared;
using Grpc.Net.Client;
using MagicOnion.Client;

namespace MauiApp16;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private async void OnCounterClicked(object sender, EventArgs e)
    {
        var channel = GrpcChannel.ForAddress("https://localhost:5001");
        var client = MagicOnionClient.Create<IMyFirstService>(channel);
        var result = await client.CounterAsync();
        CounterBtn.Text = $"サーバーで {result} 回カウント";

        SemanticScreenReader.Announce(CounterBtn.Text);
    }
}

実行

プロジェクト > スタートアッププロジェクトの設定 > マルチスタートアッププロジェクト を設定するとプロジェクトを同時に実行できます。 ボタンを押すごとにサーバー通信が発生し、サーバーサイドでクリックした回数がカウントされます。

【.NET MAUI】入力画面で入力した内容を遷移先の画面で表示する

ListView を表示する画面

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="MauiApp14.MainPage">
             
    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0" 
            VerticalOptions="Start">

            <ListView x:Name="TodoListView" SeparatorVisibility="Default">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextCell Text="{Binding Title}" Detail="{Binding Detail}" />
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            
            <Button 
                x:Name="MoveBtn"
                Text="追加"
                Clicked="OnMoveClicked"
                HorizontalOptions="End" />
            
        </VerticalStackLayout>
    </ScrollView>
 </ContentPage>

コード側に入力画面からパラメータを受け取れるように QueryProperty属性を付ける。

using System.Collections.ObjectModel;

namespace MauiApp14;

// 最初の引数はデータを受け取るプロパティの名前を指定し、2番目の引数はパラメーターID を指定
[QueryProperty(nameof(AddTodo), "Todo")]
public partial class MainPage : ContentPage
{
    static ObservableCollection<TodoItem> TodoList = new();
    List<TodoItem> TodoItems = new();
    public MainPage()
    {
        InitializeComponent();

        TodoListView.ItemsSource = TodoList;
    }

    private async void OnMoveClicked(object sender, EventArgs e)
    {
        await Shell.Current.GoToAsync(nameof(NewPage1));
    }

    public TodoItem AddTodo
    {
        set
        {
            TodoList.Add(value);
        }
    }
}

public sealed class TodoItem
{
    public TodoItem(string title, string detail)
    {
        Title = title;
        Detail = detail;
    }
    
    public string Title { get; set; }
    public string Detail { get; set; }
}

入力画面

参考: docs.microsoft.com

NewPage1.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="MauiApp14.NewPage1"
             Title="TODO入力">
    <ScrollView>
        <VerticalStackLayout VerticalOptions="Center">
            <Entry x:Name="TitleEntry" Placeholder="タイトル" />
            <Entry x:Name="DetailEntry" Placeholder="詳細" />
            <Button Text="追加" Clicked="OnAddClicked" />
        </VerticalStackLayout>
    </ScrollView>
</ContentPage>
namespace MauiApp14;

public partial class NewPage1 : ContentPage
{
    public NewPage1()
    {
        InitializeComponent();
    }

    private async void OnAddClicked(object sender, EventArgs e)
    {
        var todo = new TodoItem(TitleEntry.Text, DetailEntry.Text);
        var navigationParameter = new Dictionary<string, object>
        {
            { "Todo", todo }
        };
        await Shell.Current.GoToAsync($"//{nameof(MainPage)}", navigationParameter);
    }
}

Todo というキーで MainPage に入力内容のデータを渡す。

画面遷移の設定

AppShell.xaml

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

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

</Shell>
namespace MauiApp14;

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        Routing.RegisterRoute(nameof(NewPage1), typeof(NewPage1));
    }
}

実行

最初は何もない

入力画面

追加ボタンを押すと最初の画面に戻ってListView に入力内容を反映する

.NET MAUI ListViiew を使う

公式ドキュメントはこちら

ListView - .NET MAUI | Microsoft Docs

画面遷移の記事のアプリに ListView を配置してみる。

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="MauiApp14.MainPage">
             
    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0" 
            VerticalOptions="Start">

            <ListView x:Name="TodoListView" SeparatorVisibility="Default">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <TextCell Text="{Binding Title}" Detail="{Binding Detail}" />
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            
            <Button 
                x:Name="MoveBtn"
                Text="追加"
                Clicked="OnMoveClicked"
                HorizontalOptions="End" />
            
        </VerticalStackLayout>
    </ScrollView>
 </ContentPage>

MainPage.xaml.cs

namespace MauiApp14;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        var items = new List<TodoItem>();
        items.Add(new TodoItem("牛乳買う", "おいしい牛乳を希望"));
        items.Add(new TodoItem("お茶買う", "おいしいお茶を希望"));

        TodoListView.ItemsSource = items;
    }

    private async void OnMoveClicked(object sender, EventArgs e)
    {
        await Shell.Current.GoToAsync(nameof(NewPage1));
    }
}

public sealed class TodoItem
{
    public TodoItem(string title, string detail)
    {
        Title = title;
        Detail = detail;
    }
    
    public string Title { get; set; }
    public string Detail { get; set; }
}

実行

デフォルトのままだとリストは質素な感じ…。 マウスオーバーでリストの背景がグレーになったり クリックすると選択されたりする。

次回

リストのデザインを何とかしたけど、入力画面の作り込みをやります。

.NET MAUI プロジェクト作成~画面遷移を作る

プロジェクトの作成~新しいページの追加

Visual Studio 2022 Preview で新しいプロジェクトを作成します。 テンプレートは検索ボックスに maui と入力すると .NET MAUIアプリが出てくるので選択して次へ → 作成。

プロジェクト > 新しい項目の追加 > 新しい項目の追加画面の下部に .NET MAUI 関連が並んでいるので .NET MAUI ContentPage を選択して追加。

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="MauiApp14.MainPage">
    <ScrollView>
        <VerticalStackLayout 
            Spacing="25" 
            Padding="30,0" 
            VerticalOptions="Center">
            <Button 
                x:Name="MoveBtn"
                Text="画面遷移"
                Clicked="OnMoveClicked"
                HorizontalOptions="Center" />
        </VerticalStackLayout>
    </ScrollView>
 </ContentPage>

MainPage.cs にボタンクリックのイベント処理を入力します。

上記でボタンを押したときに OnMoveClicked を呼び出すようにしたので OnMoveClicked にナビゲーションを実行するコードを入力します。

namespace MauiApp14;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private async void OnMoveClicked(object sender, EventArgs e)
    {
        await Shell.Current.GoToAsync(nameof(NewPage1));
    }
}

AppShell.xaml にルートを登録します。

namespace MauiApp14;

public partial class AppShell : Shell
{
    public AppShell()
    {
        InitializeComponent();
        Routing.RegisterRoute(nameof(NewPage1), typeof(NewPage1));
    }
}

実行

ボタンを押すと画面遷移する。

左上にある「←」を押すと前の画面に戻れます。

次は画面の作り込みもしていきたいと思います。

祝!.NET MAUI 正式リリース!

Microsoft、「.NET MAUI」を正式リリース ~デスクトップ・モバイルのネイティブUIを単一コードで - 窓の杜

数日前にRC3を試したけど、なんと本日5/25に正式リリースされました! 半年ぐらいリリースが伸びたけど、今回は無事リリースされて良かった!

上記の記事にも書いてある通り製品版の Visual Studio 2022 で .NET MAUI がサポートされるのはまだなので 現状では VS2022 Preview1.1 をインストールする必要がある。

早速Visual Studio Installer から更新してみたけど、MVUパターンのホットリロードはまだできないっぽい?

.NET MAUI で MVUパターンはどうなった?

.NET MAUI が発表された時、MVUパターンが採用されたと聞いた覚えがあるけど、 RC3 の段階でプロジェクトを作ると XAMLを用いた MVVMパターンソースコードが生成される。

コードで UI画面を作れる MAUI に期待していた…はずだったけど、すっかり忘れてた。 というかサンプルがないのでどうやって MVUパターンで画面を作ればいいんだ…。

Visual Studio でプロジェクト→新しい項目の追加 にそれらしいものがありました。

public class NewPage1 : ContentPage
{
    public NewPage1()
    {
        Content = new StackLayout
        {
            Children = {
                new Label { Text = "Welcome to .NET MAUI!" }
            }
        };
    }
}

追加すると上記のコードが生成されてこれこれ!と思ったけど、 ホットリロードが効かない…?(Windows, Androidの実行で確認)

ネットで調べても MVUパターンでサンプルを公開している記事は見かけない。 まだ RC版だし、MVUパターンは正式リリースに期待…か?

.NET MAUI を久しぶりに触る(RC3)

.NET MAUI とは - .NET MAUI | Microsoft Docs

知らないうちに .NET MAUI RC3 がリリースされていました。 https://devblogs.microsoft.com/dotnet/dotnet-maui-rc-3/

Preview の時はインストールに手間取ったり、サンプルの実行に手間取ったり・・ いろいろありましたが RC3 も触ってみます。

Visual Studio Preview版のインストールから始めましたがなんと VIsual Studio のインストール時のワークロードにでかでかと表示されています。 分かりやすい・・!

インストール後、早速MAUIのプロジェクトを作ってビルド・・問題なく完了。

そして実行。まずは Windows用で。

Preview触ってたときは大抵実行でエラーが出てげんなりしていたが… 何事もなくアプリが起動できた。

ホットリロードも問題なく動くし素晴らしい! (ホットリロードはデバッグ実行じゃないと効かないっぽい?)

Android のビルドも無事にアプリ起動まで確認できた。

いつもはエラーの解決ができずに諦めていたけど、RCからは勉強を続けられそう。