導覽

提示

本內容節錄自《Enterprise Application Patterns Using .NET MAUI》電子書,可以從 .NET Docs 取得,也可以免費下載 PDF 離線閱讀。

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

.NET MAUI 支援頁面導覽,這通常是因為使用者與 UI 的互動,或因內部邏輯驅動狀態變更而從應用程式本身產生。 不過,在使用 Model-View-ViewModel (MVVM) 模式的應用程式中實作導覽可能相當複雜,因為必須符合下列挑戰:

  • 識別要導覽至的檢視,其使用的方法不會在檢視之間產生緊密結合程度和相依性。
  • 依要導覽至之檢視協調流程會具現化和初始化。 使用 MVVM 時,必須透過檢視的繫結內容,將檢視和檢視模型具現化並彼此關聯。 應用程式使用相依性插入容器時,將檢視和檢視模型具現化可能需要特定建構機制。
  • 要執行檢視優先導覽,還是檢視模型優先導覽。 使用檢視優先導覽時,要導覽至的頁面會參考檢視類型的名稱。 在導覽期間,會將指定的檢視具現化,以及其相應的檢視模型和其他相依服務。 另一個方法是使用檢視模型導覽,其中要導覽至的頁面會參考檢視模型類型的名稱。
  • 決定如何在檢視和檢視模型之間清楚分開應用程式的導覽行為。 MVVM 模式會分開應用程式的 UI 及其展示和商務邏輯,但不會提供直接機制將它們繫結在一起。 但應用程式的導覽行為通常會涵蓋應用程式的 UI 和展示部分。 使用者通常會從檢視開始導覽,而檢視會因導覽遭取代。 不過,導覽通常也可能需要從檢視模型內開始或協調。
  • 決定如何在導覽期間傳遞參數以進行初始化。 舉例來說,如果使用者導覽至檢視以更新訂單詳細資料,訂單資料必須傳遞至檢視,才能顯示正確的資料。
  • 協調導覽以確保遵守特定商務規則。 例如,在離開檢視之前,可能會提示使用者以便更正任何不正確的資料,或提示使用者提交或捨棄在檢視內所做的任何資料變更。

本章藉由呈現名為 MauiNavigationService 的導覽服務類別,用來執行檢視模型優先導覽,以解決這些挑戰。

注意

應用程式使用的 MauiNavigationService 簡單,且未涵蓋所有可能的導覽類型。 應用程式所需導覽類型可能需要額外的功能。

導覽邏輯可位於檢視的程式碼後置或資料繫結檢視模型中。 雖然在檢視中設定導覽邏輯可能是最簡單的方法,但無法透過單元測試輕鬆測試。 將導覽邏輯放在檢視模型類別中,表示可透過單元測試來驗證邏輯。 此外,檢視模型接著可以實作邏輯來控制導覽,確保強制執行特定商務規則。 例如,應用程式可能不允許使用者在沒有先確保輸入的資料有效的情況下,離開頁面。

導覽服務通常會從檢視模型叫用,以提升可測試性。 不過,從檢視模型導覽至檢視時,需要檢視模型以參考檢視,特別是未與使用中檢視模型相關聯的檢視,不建議這麼做。 因此,此處顯示的 MauiNavigationService 會將檢視模型類型指定為要導覽至的目標。

eShopOnContainers 多平台應用程式會使用 MauiNavigationService 類別,提供檢視模型優先導覽。 這個類別會實作 INavigationService 介面,如以下程式碼範例所示:

public interface INavigationService
{
    Task InitializeAsync();

    Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null);

    Task PopAsync();
}

此介面指會定實作類別必須提供下列方法:

方法 目的
InitializeAsync 在啟動應用程式時,導覽至兩個頁面的其中一個。
NavigateToAsync(string route, IDictionary<string, object> routeParameters = null) 使用已註冊的導覽路由,對指定的頁面執行階層式導覽。 可以選擇性傳遞具名路由參數,用於在目的地頁面上進行處理
PopAsync 從導覽堆疊中移除目前的頁面。

注意

INavigationService 介面也經常會指定 GoBackAsync 方法,用來以程式設計方式返回導覽堆疊中的上一頁。 但 eShopOnContainers 多平台應用程式沒有這個方法,因為並非必要。

建立 MauiNavigationService 執行個體

實作 INavigationService 介面的 MauiNavigationService 類別,會向 MauiProgram.CreateMauiApp() 方法中的相依性插入容器註冊為 singleton,如以下程式碼範例所示:

mauiAppBuilder.Services.AddSingleton<INavigationService, MauiNavigationService>();;

您接著可以將 INavigationService 介面新增至檢視和檢視模型的建構函式,來解析介面,如以下程式碼範例所示:

public AppShell(INavigationService navigationService)

這會傳回儲存在相依性插入容器中的 MauiNavigationService 物件參考。

ViewModelBase 類別會將 MauiNavigationService 執行個體儲存在型別為 INavigationServiceNavigationService。 因此,衍生自 ViewModelBase 類別的所有檢視模型類別,都可以使用 NavigationService 屬性來存取 INavigationService 介面指定的方法。

處理導覽要求

.NET MAUI 提供多種方式在應用程式內導覽。 傳統導覽方式是使用 NavigationPage 類別,其會實作階層式導覽體驗,讓使用者可以視需要向前和向後瀏覽頁面。 eShopOnContainers 應用程式會將 Shell 元件當作應用程式的根容器,以及當作導覽主機。 如需 Shell 導覽的詳細資訊,請參閱 Microsoft 開發人員中心的 Shell 導覽 (機器翻譯)

導覽是透過叫用其中一種 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 使用而建置。 如果您使用 NavigationPageTabbedPage 或其他導覽機制,則必須更新此路由服務,才能使用這些元件運作。

為了註冊 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:eShopOnContainers.Views"
    x:Class="eShopOnContainers.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>

在此範例中,ShellContentTabBar 使用者介面物件正在設定其 Route 屬性。 這是註冊使用者介面物件路由的慣用方法,這些物件由 Shell 控制。

如果有稍後將新增至導覽堆疊的物件,則必須透過程式碼後置新增這些物件。 下列範例顯示在程式碼後置中註冊路由。

Routing.RegisterRoute("Filter", typeof(FiltersView));
Routing.RegisterRoute("Basket", typeof(BasketView));

在程式碼後置中,我們會呼叫 Routing.RegisterRoute 方法,該方法會將路由名稱當作第一個參數,並將檢視類型當作第二個參數。 檢視模型使用 NavigationService 屬性來導覽時,應用程式的 Shell 物件會尋找已註冊的路由,並將其推送至導覽堆疊。

建立並導覽至檢視之後,會執行檢視相關聯檢視模型的 ApplyQueryAttributesInitializeAsync 方法。 如需詳細資訊,請參閱在導覽期間傳遞參數 (機器翻譯)

啟動應用程式時,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 路由,傳遞訂單號碼資訊給使用者選取的訂單。 相依性插入架構建立 OrderDetail 路由的 OrderDetailView,以及指派給檢視 BindingContextOrderDetailViewModel 類別時。 OrderDetailViewModel 已將屬性新增至其中,允許它從導覽服務接收資料,如下列程式碼範例所示。

[QueryProperty(nameof(OrderNumber), "OrderNumber")]
public class OrderDetailViewModel : ViewModelBase
{
    public int OrderNumber { get; set; }
}

QueryProperty 屬性 (attribute) 可讓我們提供參數,可將值對應至屬性 (property),以及可從查詢參數字典中尋找值的索引鍵。 在此範例中,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 導覽至網頁時,會引發 Navigating 事件,這會在 LoginViewModel 中執行 NavigateCommand。 預設會將事件的事件引數傳遞給命令。 資料因其在來源和目標之間傳遞,由 EventArgsConverter 屬性中指定的轉換器轉換,這會從 WebNavigatingEventArgs 傳回 Url。 因此,執行 NavigationCommand 時,網頁的 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 類別,用來從檢視模型執行檢視模型優先導覽。 在檢視模型類別中設定導覽邏輯,表示可以透過自動化的測試來運用邏輯。 此外,檢視模型接著可以實作邏輯來控制導覽,確保強制執行特定商務規則。