「ナビゲーション」
ヒント
この内容は電子ブック『.NET MAUI を使用したエンタープライズ アプリケーション パターン』からの抜粋です。これは .NET Docs で閲覧することも、無料の PDF をダウンロードしてオフラインで読むこともできます。
.NET MAUI では、ページ ナビゲーションがサポートされています。これは通常、ユーザーの UI 操作によって、または内部ロジックによって状態が変わる結果としてアプリ自体から生じます。 ところが、Model-View-ViewModel (MVVM) パターンを使用するアプリで実装するナビゲーションは、次の課題を満たす必要があるため、複雑になる場合があります。
- ナビゲート先のビューを、ビュー間の緊密な結合と依存関係を導入しないアプローチを使用して特定する。
- ナビゲート先のビューがインスタンス化および初期化されるプロセスを調整する。 MVVM を使用する場合、ビューとビュー モデルをインスタンス化し、ビューのバインド コンテキストを介して相互に関連付ける必要があります。 アプリで依存関係挿入コンテナーを使用している場合、ビューとビュー モデルのインスタンス化で特定の構築メカニズムが必要になる可能性があります。
- ビュー優先ナビゲーションを実行するのか、ビューモデル優先ナビゲーションを実行するのか。 ビュー優先ナビゲーションでは、ナビゲート先のページから、ビューの種類の名前を参照します。 ナビゲーション中に、指定したビューは、対応するビューモデルやその他の依存サービスと共にインスタンス化されます。 別の方法として、ビューモデル優先ナビゲーションを使用します。このとき、ナビゲート先のページから、ビューモデルの種類の名前を参照します。
- ビューとビューモデル全体でアプリのナビゲーション動作を明確に分離する方法を決定する。 MVVM パターンによって、アプリの UI とアプリのプレゼンテーションおよびビジネス ロジックが分離されますが、それらを結び付けるための直接のメカニズムは用意されていません。 ところが、アプリのナビゲーション動作は、多くの場合、アプリの UI 部分とプレゼンテーション部分に及びます。 ユーザーがビューからナビゲーションを開始することが多く、ナビゲーションの結果としてビューが置き換えられます。 一方、ビューモデル内からナビゲーションを開始または調整する必要があることも多くあります。
- 初期化のためにナビゲーション中にパラメーターを渡す方法を決定する。 たとえば、ユーザーが注文の詳細を更新するためにあるビューに移動する場合、正しいデータを表示できるように、そのビューに注文データを渡す必要があります。
- ナビゲーションを特定のビジネス ルールに確実に従うように調整する。 たとえば、ユーザーがビューから移動する前にメッセージを表示して、無効なデータを修正する、または、そのビュー内で行われたデータの変更を送信または破棄するように求めることができるようにする場合があります。
この章では、ビューモデル優先のページ ナビゲーションを実行するために使用される MauiNavigationService
という名前のナビゲーション サービス クラスを示して、これらの課題に対処します。
注意
このアプリで使用される MauiNavigationService
は単純化されており、可能なナビゲーションの種類のすべては網羅していません。 ご自分のアプリケーションで必要なナビゲーションの種類によっては、追加機能が必要です。
ページ間のナビゲーション
ナビゲーション ロジックは、ビューのコードビハインドまたはデータ バインドされたビューモデル内に存在できます。 ビューにナビゲーション ロジックを配置するのが最も単純な方法ですが、単体テストでテストするのは簡単ではありません。 ビューモデル クラスにナビゲーション ロジックを配置することは、単体テストを通じてロジックを検証できることを意味します。 さらに、ビューモデルでは、特定のビジネス ルールが確実に適用されるようにナビゲーションを制御するロジックを実装できます。 たとえば、アプリによっては、入力したデータが有効であることが保証されない限り、ユーザーはページから移動できません。
ナビゲーション サービスは、通常、テスト容易性を高めるために、ビューモデルから呼び出されます。 ただし、ビューモデルからビューにナビゲートするには、ビューモデルから、ビュー、特にアクティブなビューモデルが関連付けられていないビューを参照する必要がありますが、これは推奨されません。 そのため、ここに示す MauiNavigationService
は、ナビゲート先としてビューモデルの種類を指定します。
eShop マルチプラットフォーム アプリは、MauiNavigationService
クラスを使ってビュー モデル優先ナビゲーションを提供します。 このクラスを使用して、次のコード例に示される INavigationService
インターフェイスを実装します。
public interface INavigationService
{
Task InitializeAsync();
Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null);
Task PopAsync();
}
このインターフェイスで、実装クラスで次のメソッドを提供する必要があることを指定します。
メソッド | 目的 |
---|---|
InitializeAsync |
アプリの起動時に、2 つのページのいずれかへのナビゲーションを実行します。 |
NavigateToAsync(string route, IDictionary<string, object> routeParameters = null) |
登録済みのナビゲーション ルートを使用して、指定されたページへの階層ナビゲーションを実行します。 必要に応じて、ナビゲーション先ページ上の処理に使用する名前付きルート パラメーターを渡すことができます |
PopAsync |
ナビゲーション スタックから現在のページを削除します。 |
注意
INavigationService
インターフェイスでは、通常、GoBackAsync
メソッドも指定します。これは、ナビゲーション スタック内の前のページに戻るためにプログラムで使用します。 ただし、このメソッドは必要ないため、eShop マルチプラットフォーム アプリにはありません。
MauiNavigationService インスタンスの作成
次のコード例に示すように、MauiNavigationService
クラスは、INavigationService
インターフェイスを実装し、MauiProgram.CreateMauiApp()
メソッドで、依存関係挿入コンテナーを持つシングルトンとして登録されます。
mauiAppBuilder.Services.AddSingleton<INavigationService, MauiNavigationService>();;
INavigationService
インターフェイスは、次のコード例に示すように、ビューとビューモデルのコンストラクターに追加することで解決できます。
public AppShell(INavigationService navigationService)
これにより、依存関係挿入コンテナーに格納されている MauiNavigationService
オブジェクトへの参照が返されます。
ViewModelBase
クラスによって、種類が INavigationService
の NavigationService
プロパティに MauiNavigationService
インスタンスが格納されます。 そのため、すべてのビューモデル クラスが、ViewModelBase
クラスから派生し、NavigationService
プロパティを使用して、INavigationService
インターフェイスで指定されたメソッドにアクセスできます。
ナビゲーション要求の処理
.NET MAUI には、アプリケーション内でナビゲートするための複数の方法が用意されています。 従来のナビゲーションは、NavigationPage
クラスを使用する方法で、ユーザーが必要に応じて前後にページを移動できる階層ナビゲーション エクスペリエンスを実装します。 eShop アプリは、アプリケーションのルート コンテナー、およびナビゲーション ホストとして、Shell
コンポーネントを使います。 シェル ナビゲーションの詳細については、Microsoft デベロッパー センターの シェル ナビゲーションに関するページを参照してください。
ナビゲーションは、次のコード例に示すように、ナビゲーション先のページのルート パスを指定して、NavigateToAsync
メソッドのいずれかを呼び出すと、ビューモデル クラス内で実行されます。
await NavigationService.NavigateToAsync("//Main");
次のコード例は、MauiNavigationService
クラスから提供される NavigateToAsync
メソッドを示しています。
public Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null)
{
return
routeParameters != null
? Shell.Current.GoToAsync(route, routeParameters)
: Shell.Current.GoToAsync(route);
}
.NET MAUIShell
コントロールはルート ベースのナビゲーションを既に熟知しているため、NavigateToAsync
メソッドは、この機能をマスクするように動作します。 NavigateToAsync
メソッドで、ナビゲート先のビューモデルに渡す引数としてナビゲーション データを指定でき、これは通常、初期化の実行に使用されます。 詳細については、「ナビゲーション中にパラメーターを渡す」を参照してください。
重要
.NET MAUI でナビゲーションを実行するには、複数の方法があります。 MauiNavigationService
は、Shell
で動作するように特別にビルドされています。 NavigationPage
や TabbedPage
、または別のナビゲーション メカニズムを使用している場合、これらのコンポーネントを使用して機能するように、このルーティング サービスを更新する必要があります。
MauiNavigationService
のルートを登録するには、XAML またはコードビハインドでルート情報を指定する必要があります。 次の例は、XAML を介したルートの登録を示しています。
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:eShop.Views"
x:Class="eShop.AppShell">
<!-- Omitted for brevity -->
<FlyoutItem >
<ShellContent x:Name="login" ContentTemplate="{DataTemplate views:LoginView}" Route="Login" />
</FlyoutItem>
<TabBar x:Name="main" Route="Main">
<ShellContent Title="CATALOG" Route="Catalog" Icon="{StaticResource CatalogIconImageSource}" ContentTemplate="{DataTemplate views:CatalogView}" />
<ShellContent Title="PROFILE" Route="Profile" Icon="{StaticResource ProfileIconImageSource}" ContentTemplate="{DataTemplate views:ProfileView}" />
</TabBar>
</Shell>
この例では、ShellContent
および TabBar
ユーザー インターフェイス オブジェクトで Route
プロパティを設定しています。 Shell
によって制御されるユーザー インターフェイス オブジェクトのルートを登録する場合は、この方法をお勧めします。
後でナビゲーション スタックに追加されるオブジェクトがある場合は、コードビハインドを介してそれらを追加する必要があります。 次の例は、コードビハインドのルートの登録を示しています。
Routing.RegisterRoute("Filter", typeof(FiltersView));
Routing.RegisterRoute("Basket", typeof(BasketView));
コードビハインドでは、ルート名を第 1 パラメーター、ビューの種類を第 2 パラメーターとして受け取る Routing.RegisterRoute
メソッドを呼び出します。 ビューモデルで NavigationService
プロパティを使用してナビゲートする場合は、アプリケーションの Shell
オブジェクトで、登録済みのルートを探してナビゲーション スタックにプッシュします。
ビューが作成されてナビゲートされると、ビューの関連付けられたビューモデルの ApplyQueryAttributes
メソッドと InitializeAsync
メソッドが実行されます。 詳細については、「ナビゲーション中にパラメーターを渡す」を参照してください。
アプリの起動時のナビゲーション
アプリを起動すると、Shell
オブジェクトがアプリケーションのルート ビューとして設定されます。 設定されたら、Shell
がルート登録を制御するために使用され、今後、アプリケーションのルートに存在するようになります。 Shell
が作成されたら、OnParentSet
メソッドを使用してナビゲーション ルートを初期化することで、アプリケーションにアタッチされるのを待つことができます。 以下のコード例はこのメソッドを示しています。
protected override async void OnParentSet()
{
base.OnParentSet();
if (Parent is not null)
{
await _navigationService.InitializeAsync();
}
}
このメソッドでは、依存関係挿入からコンストラクターが提供される INavigationService
のインスタンスを使用し、その InitializeAsync
メソッドを呼び出します。
次のコード例は、MauiNavigationService.InitializeAsync
メソッドの実装を示しています。
public Task InitializeAsync()
{
return NavigateToAsync(string.IsNullOrEmpty(_settingsService.AuthAccessToken)
? "//Login"
: "//Main/Catalog");
}
アプリに、キャッシュされたアクセス トークンがあり、これを認証に使用する場合は、//Main/Catalog
ルートにナビゲートされます。 それ以外の場合は、//Login
ルートにナビゲートされます。
ナビゲーション中にパラメーターを渡す
INavigationService
インターフェイスで指定する NavigateToAsync
メソッドでは、ナビゲーション先のビューモデルに渡されるデータの IDictionary<string, object>
としてナビゲーション データを指定でき、これは通常、初期化の実行に使用されます。
たとえば、ProfileViewModel
クラスには、ユーザーが ProfileView
ページで注文を選択したときに実行される OrderDetailCommand
が含まれています。 そして、次のコード例に示すように、OrderDetailAsync
メソッドが実行されます。
private async Task OrderDetailAsync(Order order)
{
if (order is null)
{
return;
}
await NavigationService.NavigateToAsync(
"OrderDetail",
new Dictionary<string, object>{ { "OrderNumber", order.OrderNumber } });
}
このメソッドから、OrderDetail
ルートへのナビゲーションが呼び出され、ユーザーが選択した注文の注文番号情報が渡されます。 依存関係挿入フレームワークによって、ビューの BindingContext
に割り当てられている OrderDetailViewModel
クラスと共に OrderDetail
ルートの OrderDetailView
が作成されます。 OrderDetailViewModel
には、次のコード例に示すように、ナビゲーション サービスからデータを受信できるようにする属性が追加されています。
[QueryProperty(nameof(OrderNumber), "OrderNumber")]
public class OrderDetailViewModel : ViewModelBase
{
public int OrderNumber { get; set; }
}
QueryProperty
属性を使用すると、値をマップするプロパティのパラメーターと、クエリ パラメーター ディクショナリから値を検索するためのキーを指定できます。 この例では、NavigateToAsync
呼び出し中にキー "OrderNumber" と注文番号の値が指定されました。 ビューモデルで、"OrderNumber" キーが見つかり、その値が OrderNumber
プロパティにマップされました。 OrderNumber
プロパティを後で使用して、OrderService
インスタンスから完全な注文の詳細を取得できます。
動作を使用したナビゲーションの呼び出し
ナビゲーションは通常、ユーザー操作によってビューからトリガーされます。 たとえば、LoginView
では、認証成功後にナビゲーションが実行されます。 次のコード例は、動作によってナビゲーションがどのように呼び出されるかを示しています。
<WebView>
<WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</WebView.Behaviors>
</WebView>
実行時に、EventToCommandBehavior
によって、WebView
とのやりとりとの応答が行われます。 WebView
で、Web ページにナビゲートすると、Navigating
イベントが発生し、LoginViewMode
l の NavigateCommand
が実行されます。 既定では、このイベントのイベント引数がコマンドに渡されます。 このデータは、ソースとターゲットの間で渡される際に、EventArgsConverter
プロパティで指定されたコンバーターによって変換され、WebNavigatingEventArgs
から Url
が返されます。 そのため、NavigationCommand
が実行されると、Web ページの Url
がパラメーターとして登録済みアクションに渡されます。
そして、次のコード例に示すように、NavigationCommand
で NavigateAsync
メソッドを実行します。
private async Task NavigateAsync(string url)
{
// Omitted for brevity.
if (!string.IsNullOrWhiteSpace(accessToken))
{
_settingsService.AuthAccessToken = accessToken;
_settingsService.AuthIdToken = authResponse.IdentityToken;
await NavigationService.NavigateToAsync("//Main/Catalog");
}
}
このメソッドにより、NavigationService
アプリケーションが //Main/Catalog
ルートにルーティングされます。
ナビゲーションの確認または取り消し
場合によって、アプリで、ナビゲーション操作中にユーザーと対話し、ユーザーがナビゲーションを確認または取り消すことができるようにする必要があります。 これが必要になるのは、たとえば、ユーザーがデータ入力ページを完了する前に移動しようとしたときです。 この場合、ユーザーが、そのページから移動する、またはナビゲーション操作が発生する前に取り消すことができるようにアプリから通知する必要があります。 これは、ビューモデル クラスで、通知からの応答を使用してナビゲーションを呼び出すかどうかを制御することで実現できます。
まとめ
.NET MAUI では、ページ ナビゲーションがサポートされています。これは通常、ユーザーの UI 操作によって、または内部ロジックによって状態が変わる結果としてアプリ自体から生じます。 ところが、MVVM パターンを使用するアプリで実装するナビゲーションは複雑になる場合があります。
この章では、ビューモデルからビューモデル優先ナビゲーションを実行するために使用される NavigationService クラスについて説明しました。 ビューモデル クラスにナビゲーション ロジックを配置することは、自動テストを通じてロジックを実行できることを意味します。 さらに、ビューモデルでは、特定のビジネス ルールが確実に適用されるようにナビゲーションを制御するロジックを実装できます。
.NET