Condividi tramite


Navigazione

Suggerimento

Questo contenuto è un estratto dell'eBook, Enterprise Application Patterns Using .NETMAUI, disponibile in .NET Docs o come PDF scaricabile gratuitamente che può essere letto offline.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

.NET MAUI include il supporto per la navigazione delle pagine, che in genere deriva dall'interazione dell'utente con l'interfaccia utente o dall'app stessa come risultato di cambiamenti di stato interni basati sulla logica. Tuttavia, la navigazione può essere difficile da implementare nelle app che usano il modello Model-View-ViewModel (MVVM), perché è necessario affrontare i problemi seguenti:

  • Identificare la vista verso cui navigare usando un approccio che non introduca accoppiamenti e dipendenze strette tra le viste.
  • Coordinare il processo in base al quale creare un'istanza e inizializzare la vista da esplorare. Quando si usa MVVM, è necessario creare un'istanza e associare tra loro la vista e il modello della vista tramite il relativo contesto di associazione. Quando un'app usa un contenitore di inserimento di dipendenze, la creazione di un'istanza di viste e view-model potrebbe richiedere un meccanismo di costruzione specifico.
  • Se eseguire la navigazione view-first o la navigazione view-model-first. Con la navigazione view-first, la pagina da esplorare si riferisce al nome del tipo di vista. Durante la navigazione, viene creare un'istanza della vista specificata insieme al modello di vista corrispondente e ad altri servizi dipendenti. Un approccio alternativo consiste nell'usare la navigazione view-model-first, in cui la pagina da esplorare fa riferimento al nome del tipo di view-model.
  • Determinare come separare in modo pulito il comportamento di navigazione dell'app tra le viste e i view-model. Il modello MVVM separa l'interfaccia utente dell'app dalla presentazione e dalla logica di business, ma non fornisce un meccanismo diretto per collegarle tra loro. Tuttavia, il comportamento di navigazione di un'app spesso si estende alle parti dell'interfaccia utente e della presentazione dell'app. Spesso l'utente avvia la navigazione da una vista e questa viene sostituita come risultato della navigazione. Tuttavia, spesso la navigazione deve essere avviata o coordinata anche dall'interno del modello di vista.
  • Determinare come passare i parametri durante la navigazione a scopo di inizializzazione. Ad esempio, se l'utente naviga in una vista per aggiornare i dettagli dell'ordine, i dati dell'ordine dovranno essere passati alla vista, in modo che possa visualizzare i dati corretti.
  • Coordinare la navigazione per garantire il rispetto di specifiche regole aziendali. Ad esempio, agli utenti potrebbe essere richiesta una conferma prima di uscire da una vista, in modo da correggere eventuali dati non validi, oppure una richiesta a inviare o scartare le modifiche apportate ai dati all'interno della vista.

Questo capitolo affronta queste problemi presentando una classe di servizio di navigazione denominata MauiNavigationService, usata per eseguire la navigazione view-model-first della pagina.

Nota

Il MauiNavigationService usato dall'app è semplicistico e non riguarda tutti i tipi di navigazione possibili. I tipi di navigazione richiesti dall'applicazione possono richiedere funzionalità aggiuntive.

La logica di navigazione può risiedere nel code-behind di una vista o in un modello di vista associato ai dati. Anche se l'inserimento della logica di navigazione in una vista può essere l'approccio più semplice, non è facilmente testabile tramite unit test. L'inserimento della logica di navigazione nelle classi del modello di vista significa che la logica può essere verificata tramite unit test. Inoltre, il modello della vista può implementare una logica per controllare la navigazione per garantire l'applicazione di determinate regole aziendali. Ad esempio, un'app potrebbe non consentire all'utente di spostarsi da una pagina senza prima assicurarsi che i dati immessi siano validi.

Un servizio di navigazione viene in genere richiamato dai view-model, per favorire la testabilità. Tuttavia, la navigazione verso le viste dai view-model richiederebbe che i view-model facessero riferimento alle viste, in particolare a quelle a cui il view-model attivo non è associato, il che non è consigliabile. Di conseguenza, il MauiNavigationService presentato qui specifica il tipo di modello di vista come destinazione a cui passare.

L'app multipiattaforma eShopOnContainers usa la classe MauiNavigationService per fornire una navigazione view-model-first. Questa classe implementa l'interfaccia INavigationService, illustrata nell'esempio di codice seguente:

public interface INavigationService
{
    Task InitializeAsync();

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

    Task PopAsync();
}

Questa interfaccia specifica che una classe di implementazione deve fornire i metodi seguenti:

metodo Scopo
InitializeAsync Esegue la navigazione su una delle due pagine all'avvio dell'app.
NavigateToAsync(string route, IDictionary<string, object> routeParameters = null) Esegue la navigazione gerarchica su una pagina specificata usando un percorso di navigazione registrato. Facoltativamente è possibile passare parametri di percorso denominati da usare per l'elaborazione nella pagina di destinazione
PopAsync Rimuove la pagina corrente dallo stack di navigazione.

Nota

Un'interfaccia INavigationService di solito specifica anche un metodo GoBackAsync, che viene usato per tornare a livello di programmazione alla pagina precedente nello stack di navigazione. Tuttavia, questo metodo non è presente nell'app multipiattaforma eShopOnContainers perché non è obbligatorio.

Creazione dell'istanza di MauiNavigationService

La classe MauiNavigationService, che implementa l'interfaccia INavigationService, viene registrata come singleton nel contenitore di inserimento delle dipendenze nel metodo MauiProgram.CreateMauiApp(), come illustrato nell'esempio di codice seguente:

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

L'interfaccia INavigationService può quindi essere risolta aggiungendola al costruttore delle viste e dei view-model, come illustrato nell'esempio di codice seguente:

public AppShell(INavigationService navigationService)

Restituisce un riferimento all'oggetto MauiNavigationService, archiviato nel contenitore di inserimento delle dipendenze.

La classe ViewModelBase archivia l'istanza di MauiNavigationService in una proprietà NavigationService di tipo INavigationService. Pertanto, tutte le classi view-model che derivano dalla classe ViewModelBase possono usare la proprietà NavigationService per accedere ai metodi specificati dall'interfaccia INavigationService.

Gestione delle richieste di navigazione

.NET MAUI offre diversi modi per navigare all'interno di un'applicazione. Il modo tradizionale di navigare avviene con la classe NavigationPage, che implementa un'esperienza di navigazione gerarchica in cui l'utente può spostarsi tra le pagine, avanti e indietro, come desidera. L'app eShopOnContainers usa il componente Shell come contenitore radice dell'applicazione e come host di navigazione. Per altre informazioni sullo spostamento della shell, vedere Spostamento della shellin Centro per sviluppatori Microsoft.

La navigazione viene eseguita all'interno delle classi view-model richiamando uno dei metodi NavigateToAsync, specificando il percorso di route per la pagina a cui passare, come illustrato nell'esempio di codice seguente:

await NavigationService.NavigateToAsync("//Main");

L'esempio di codice seguente illustra il metodo NavigateToAsync fornito dalla classe MauiNavigationService:

public Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null)
{
    return
        routeParameters != null
            ? Shell.Current.GoToAsync(route, routeParameters)
            : Shell.Current.GoToAsync(route);
}

Il controllo .NET MAUIShell ha già familiarità con la navigazione basata su route, quindi il metodo NavigateToAsync funziona per mascherare questa funzionalità. Il metodo NavigateToAsync consente di specificare i dati di navigazione come argomento da passare al modello di vista a cui passare, in cui viene in genere usato per eseguire l'inizializzazione. Per altre informazioni, vedere Passaggio di parametri durante la navigazione.

Importante

Esistono diversi modi per eseguire la navigazione in .NET MAUI. MauiNavigationService è stato creato specificamente per essere usato con Shell. Se si usa NavigationPage o TabbedPage o un meccanismo di navigazione diverso, questo servizio di routing dovrà essere aggiornato per essere usato con tali componenti.

Per registrare le route per MauiNavigationService è necessario fornire le informazioni di route da XAML o nel code-behind. L'esempio seguente mostra la registrazione delle route tramite 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>

In questo esempio, gli oggetti dell'interfaccia utente ShellContent e TabBar impostano la proprietà Route. Questo è il metodo preferito per registrare le route per gli oggetti dell'interfaccia utente controllati da un Shell.

Se sono presenti oggetti che verranno aggiunti allo stack di navigazione in un secondo momento, sarà necessario aggiungerli tramite code-behind. L'esempio seguente mostra la registrazione delle rotte nel code-behind.

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

Nel code-behind, verrà chiamato il metodo Routing.RegisterRoute che prende un nome di route come primo parametro e un tipo di vista come secondo parametro. Quando un modello di vista usa la proprietà NavigationService per spostarsi, l'oggetto Shell dell'applicazione cerca le route registrate e le inserisce nello stack di navigazione.

Dopo che la vista è stata creata ed esplorata, vengono eseguiti i metodi ApplyQueryAttributes e InitializeAsync del modello di visualizzazione associato alla vista. Per altre informazioni, vedere Passaggio di parametri durante la navigazione.

Quando l'app viene avviata, un oggetto Shell viene impostato come vista principale dell'applicazione. Una volta impostato, Shell verrà usato per controllare la registrazione della route e sarà presente nella radice dell'applicazione in futuro. Dopo aver creato Shell, è possibile attendere che venga collegato all'applicazione usando il metodo OnParentSet per inizializzare la route di navigazione. L'esempio di codice seguente illustra il metodo:

protected override async void OnParentSet()
{
    base.OnParentSet();

    if (Parent is not null)
    {
        await _navigationService.InitializeAsync();
    }
}

Il metodo usa un'istanza di INavigationService, di cui è stato fornito il costruttore dall'inserimento di dipendenze, e invoca il relativo metodo InitializeAsync.

L'esempio di codice seguente illustra l'implementazione del metodo MauiNavigationService.InitializeAsync:

public Task InitializeAsync()
{
    return NavigateToAsync(string.IsNullOrEmpty(_settingsService.AuthAccessToken)
        ? "//Login"
        : "//Main/Catalog");
}

La route //Main/Catalog viene visualizzata se l'app dispone di un token di accesso memorizzato nella cache, che viene usato per l'autenticazione. In caso contrario, viene visualizzata la route //Login.

Passaggio di parametri durante la navigazione

Il metodo NavigateToAsync, specificato dall'interfaccia INavigationService, consente di specificare i dati di navigazione come IDictionary<string, object> di dati che vengono passati al modello di vista a cui passare, in cui viene in genere usato per eseguire l'inizializzazione.

Ad esempio, la classe ProfileViewModel contiene un OrderDetailCommand eseguito quando l'utente seleziona un ordine nella pagina ProfileView. A sua volta, esegue il metodo OrderDetailAsync, illustrato nell'esempio di codice seguente:

private async Task OrderDetailAsync(Order order)
{
    if (order is null)
    {
        return;
    }

    await NavigationService.NavigateToAsync(
        "OrderDetail",
        new Dictionary<string, object>{ { "OrderNumber", order.OrderNumber } });
}

Questo metodo richiama la navigazione alla route OrderDetail, passando le informazioni sul numero dell'ordine selezionato dall'utente. Quando il framework di inserimento delle dipendenze crea il OrderDetailView per la route OrderDetail insieme alla classe OrderDetailViewModel assegnata alla vista BindingContext. A OrderDetailViewModel è stato aggiunto un attributo che consente di ricevere dati dal servizio di navigazione, come illustrato nell'esempio di codice seguente.

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

L'attributo QueryProperty consente di fornire un parametro per una proprietà di cui eseguire il mapping dei valori e una chiave per trovare i valori dal dizionario dei parametri query. In questo esempio, la chiave "OrderNumber" e il valore del numero d'ordine sono stati specificati durante la chiamata NavigateToAsync. Il view-model ha trovato la chiave "OrderNumber" e ha eseguito il mapping del valore alla proprietà OrderNumber. La proprietà OrderNumber può quindi essere usata in un secondo momento per recuperare i dettagli completi dell'ordine dall'istanza OrderService.

Richiamo della navigazione tramite comportamenti

La navigazione viene in genere attivata da una vista tramite un'interazione utente. Ad esempio, LoginView esegue la navigazione dopo un'autenticazione riuscita. L'esempio di codice seguente mostra come la navigazione viene chiamata da un comportamento:

<WebView>
    <WebView.Behaviors>
        <behaviors:EventToCommandBehavior
            EventName="Navigating"
            EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
            Command="{Binding NavigateCommand}" />
    </WebView.Behaviors>
</WebView>

In fase di esecuzione, EventToCommandBehavior risponderà all'interazione con WebView. Quando WebView passa a una pagina Web, viene generato l'evento Navigating che esegue NavigateCommand in LoginViewModel. Per impostazione predefinita, gli argomenti dell'evento vengono passati al comando. Questi dati vengono convertiti durante il passaggio tra l'origine e la destinazione dal convertitore specificato nella proprietà EventArgsConverter, che restituisce Url da WebNavigatingEventArgs. Pertanto, quando viene eseguito NavigationCommand, il Url della pagina Web viene passato come parametro all'azione registrata.

A sua volta, NavigationCommand esegue il metodo NavigateAsync, illustrato nell'esempio di codice seguente:

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");
    }
}

Questo metodo richiama la route NavigationService dell'applicazione alla route //Main/Catalog.

Conferma o annullamento della navigazione

Un'app potrebbe avere bisogno di interagire con l'utente durante un'operazione di navigazione, in modo che l'utente possa confermare o annullare la navigazione. Potrebbe essere necessario, ad esempio, quando l'utente tenta di spostarsi prima di aver completato una pagina di inserimento dati. In questo caso, un'app dovrebbe fornire una notifica che consenta all'utente di uscire dalla pagina o di annullare l'operazione di navigazione prima che avvenga. Ciò può essere ottenuto in una classe del modello di visualizzazione usando la risposta di una notifica per controllare se la navigazione viene richiamata o meno.

Riepilogo

.NET MAUI include il supporto per la navigazione delle pagine, che in genere deriva dall'interazione dell'utente con l'interfaccia utente o dall'app stessa, come risultato di cambiamenti di stato interni basati sulla logica. Tuttavia, la navigazione può essere complessa da implementare nelle applicazioni che usano il modello MVVM.

In questo capitolo è stata presentata la classe NavigationService, usata per eseguire la navigazione view-model-first dai view-model. L'inserimento della logica di navigazione nelle classi del view-model indica che la logica può essere esercitata attraverso test automatizzati. Inoltre, il modello della vista può implementare una logica per controllare la navigazione per garantire l'applicazione di determinate regole aziendali.