Enterprise-App-Navigation

Hinweis

Dieses eBook wurde im Frühjahr 2017 veröffentlicht und wurde seitdem nicht aktualisiert. Es gibt viel in dem Buch, das wertvoll bleibt, aber einige der Materialien sind veraltet.

Xamarin.Forms enthält Unterstützung für die Seitennavigation, die in der Regel aus der Interaktion des Benutzers mit der Benutzeroberfläche oder aus der App selbst infolge von internen logikgesteuerten Zustandsänderungen resultiert. Die Implementierung der Navigation in Apps, die das Model-View-ViewModel-Muster (MVVM) verwenden, kann jedoch komplex sein, da die folgenden Herausforderungen zu bewältigen sind:

  • Ermitteln der zu navigierenden Ansicht mithilfe eines Ansatzes, bei dem keine enge Kopplung und Abhängigkeiten zwischen Ansichten eingeführt werden.
  • Koordinieren des Prozesses, zu dem die zu navigierende Ansicht instanziiert und initialisiert wird. Bei Verwendung von MVVM muss das Ansichts- und Ansichtsmodell über den Bindungskontext der Ansicht instanziiert und miteinander verknüpft werden. Wenn eine App einen Container zum Einfügen von Abhängigkeiten verwendet, ist möglicherweise die Instanziierung von Ansichten und Ansichtsmodellen ein bestimmter Konstruktionsmechanismus erforderlich.
  • Unabhängig davon, ob die Ansichts-first-Navigation oder die Erste Ansichtsmodellnavigation durchgeführt werden soll. Wenn Ansichtsnavigation zuerst verwendet wird, verweist die Seite, zu der navigiert werden soll, auf den Namen des Ansichtstyps. Während der Navigation wird die angegebene Ansicht instanziiert, zusammen mit dem entsprechenden Ansichtsmodell und anderen abhängigen Diensten. Eine alternative Methode besteht darin, die Ansichtsmodell-erste Navigation zu verwenden, bei der die Seite, zu der navigiert werden soll, auf den Namen des Ansichtsmodelltyps verweist.
  • Wie Sie das Navigationsverhalten der App bereinigt über die Ansichten und Ansichtsmodelle trennen. Das MVVM-Muster bietet eine Trennung zwischen der Benutzeroberfläche der App und der zugehörigen Präsentations- und Geschäftslogik. Das Navigationsverhalten einer App umfasst jedoch häufig die Ui- und Präsentationsteile der App. Der Benutzer initiiert die Navigation häufig über eine Ansicht, und die Ansicht wird als Ergebnis der Navigation ersetzt. Die Navigation muss jedoch häufig auch innerhalb des Ansichtsmodells initiiert oder koordiniert werden.
  • Übergeben von Parametern während der Navigation für Initialisierungszwecke. Wenn der Benutzer beispielsweise zu einer Ansicht navigiert, um Bestelldetails zu aktualisieren, müssen die Bestelldaten an die Ansicht übergeben werden, damit sie die richtigen Daten anzeigen kann.
  • Wie Sie die Navigation koordinieren, um sicherzustellen, dass bestimmte Geschäftsregeln eingehalten werden. So können Benutzer beispielsweise aufgefordert werden, ungültige Daten zu korrigieren, bevor sie eine Ansicht verlassen, oder sie werden aufgefordert, Datenänderungen, die in der Ansicht vorgenommen wurden, zu übermitteln oder zu verwerfen.

In diesem Kapitel werden diese Herausforderungen behandelt, indem eine NavigationService Klasse vorgestellt wird, die zum Ausführen der Ansichtsmodell-first-Seitennavigation verwendet wird.

Hinweis

Die NavigationService von der App verwendete App dient nur zum Ausführen der hierarchischen Navigation zwischen ContentPage-Instanzen. Die Verwendung des Diensts zum Navigieren zwischen anderen Seitentypen kann zu unerwartetem Verhalten führen.

Navigationslogik kann sich im CodeBehind einer Ansicht oder in einem datengebundenen Ansichtsmodell befinden. Während das Platzieren von Navigationslogik in einer Ansicht möglicherweise der einfachste Ansatz ist, ist sie nicht einfach durch Komponententests zu testen. Das Platzieren von Navigationslogik in Ansichtsmodellklassen bedeutet, dass die Logik über Komponententests ausgeübt werden kann. Darüber hinaus kann das Ansichtsmodell logik implementieren, um die Navigation zu steuern, um sicherzustellen, dass bestimmte Geschäftsregeln erzwungen werden. Beispielsweise erlaubt eine App dem Benutzer möglicherweise nicht, von einer Seite zu navigieren, ohne vorher sicherzustellen, dass die eingegebenen Daten gültig sind.

Eine NavigationService Klasse wird in der Regel aus Ansichtsmodellen aufgerufen, um die Testbarkeit zu fördern. Das Navigieren zu Ansichten aus Ansichtsmodellen erfordert jedoch, dass die Ansichtsmodelle auf Ansichten verweisen, und insbesondere Ansichten, denen das aktive Ansichtsmodell nicht zugeordnet ist, was nicht empfohlen wird. Daher gibt die NavigationService hier dargestellte Darstellung den Ansichtsmodelltyp als Ziel an, zu dem navigiert werden soll.

Die mobile eShopOnContainers-App verwendet die NavigationService Klasse zum Bereitstellen der Ansichtsmodell-first-Navigation. Diese Klasse implementiert die INavigationService-Schnittstelle, die im folgenden Codebeispiel gezeigt wird:

public interface INavigationService  
{  
    ViewModelBase PreviousPageViewModel { get; }  
    Task InitializeAsync();  
    Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase;  
    Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase;  
    Task RemoveLastFromBackStackAsync();  
    Task RemoveBackStackAsync();  
}

Diese Schnittstelle gibt an, dass eine implementierende Klasse die folgenden Methoden bereitstellen muss:

Methode Zweck
InitializeAsync Führt beim Starten der App die Navigation zu einer von zwei Seiten aus.
NavigateToAsync Führt eine hierarchische Navigation zu einer angegebenen Seite aus.
NavigateToAsync(parameter) Führt eine hierarchische Navigation zu einer angegebenen Seite durch, wobei ein Parameter übergeben wird.
RemoveLastFromBackStackAsync Entfernt die vorherige Seite aus dem Navigationsstapel.
RemoveBackStackAsync Entfernt alle vorherigen Seiten aus dem Navigationsstapel.

Darüber hinaus gibt die INavigationService Schnittstelle an, dass eine implementierungsklasse eine PreviousPageViewModel Eigenschaft bereitstellen muss. Diese Eigenschaft gibt den Ansichtsmodelltyp zurück, der der vorherigen Seite im Navigationsstapel zugeordnet ist.

Hinweis

Eine INavigationService-Schnittstelle gibt normalerweise auch eine GoBackAsync-Methode an, die verwendet wird, um programmgesteuert zur vorherigen Seite im Navigationsstapel zurückzukehren. Diese Methode fehlt jedoch in der mobilen eShopOnContainers-App, da sie nicht erforderlich ist.

Erstellen der NavigationService-Instanz

Die NavigationService Klasse, die die INavigationService Schnittstelle implementiert, wird als Singleton mit dem Container für die Autofac-Abhängigkeitseinfügung registriert, wie im folgenden Codebeispiel veranschaulicht:

builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();

Die INavigationService Schnittstelle wird im ViewModelBase Klassenkonstruktor aufgelöst, wie im folgenden Codebeispiel veranschaulicht:

NavigationService = ViewModelLocator.Resolve<INavigationService>();

Dadurch wird ein Verweis auf das NavigationService Objekt zurückgegeben, das im Autofac-Abhängigkeitseinfügungscontainer gespeichert ist, der von der Methode in der App InitNavigation Klasse erstellt wird. Weitere Informationen finden Sie unter "Navigieren beim Starten der App".

Die ViewModelBase-Klasse speichert die NavigationService-Instanz in einer NavigationService-Eigenschaft vom Typ INavigationService. Daher können alle Ansichtsmodellklassen, die von der ViewModelBase Klasse abgeleitet werden, die NavigationService Eigenschaft verwenden, um auf die von der INavigationService Schnittstelle angegebenen Methoden zuzugreifen. Dadurch wird verhindert, dass das NavigationService Objekt aus dem Autofac-Abhängigkeitseinfügungscontainer in jede Ansichtsmodellklasse eingefügt wird.

Behandeln von Navigationsanforderungen

Xamarin.Forms stellt die NavigationPage Klasse bereit, die eine hierarchische Navigationsoberfläche implementiert, in der der Benutzer nach Bedarf durch Seiten, Vorwärts und Rückwärts navigieren kann. Weitere Informationen zur hierarchischen Navigation finden Sie unter : Hierarchische Navigation.

Anstatt die NavigationPage Klasse direkt zu verwenden, umschließt die eShopOnContainers-App die NavigationPage Klasse in der CustomNavigationView Klasse, wie im folgenden Codebeispiel gezeigt:

public partial class CustomNavigationView : NavigationPage  
{  
    public CustomNavigationView() : base()  
    {  
        InitializeComponent();  
    }  

    public CustomNavigationView(Page root) : base(root)  
    {  
        InitializeComponent();  
    }  
}

Der Zweck dieses Umbruchs ist die Einfache Formatierung der NavigationPage Instanz innerhalb der XAML-Datei für die Klasse.

Die Navigation erfolgt innerhalb von Ansichtsmodellklassen, indem sie eine der NavigateToAsync Methoden aufrufen und den Ansichtsmodelltyp für die Seite angeben, zu der navigiert wird, wie im folgenden Codebeispiel gezeigt:

await NavigationService.NavigateToAsync<MainViewModel>();

Das folgende Codebeispiel zeigt die NavigateToAsync von der NavigationService Klasse bereitgestellten Methoden:

public Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase  
{  
    return InternalNavigateToAsync(typeof(TViewModel), null);  
}  

public Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase  
{  
    return InternalNavigateToAsync(typeof(TViewModel), parameter);  
}

Jede Methode ermöglicht jede Ansichtsmodellklasse, die von der ViewModelBase Klasse abgeleitet wird, die hierarchische Navigation durch Aufrufen der InternalNavigateToAsync Methode auszuführen. Darüber hinaus ermöglicht die zweite NavigateToAsync Methode die Angabe von Navigationsdaten als Argument, das an das Ansichtsmodell übergeben wird, zu dem navigiert wird, wo sie normalerweise zum Initialisieren verwendet wird. Weitere Informationen finden Sie unter Übergeben von Parametern während der Navigation.

Die InternalNavigateToAsync Methode führt die Navigationsanforderung aus und wird im folgenden Codebeispiel gezeigt:

private async Task InternalNavigateToAsync(Type viewModelType, object parameter)  
{  
    Page page = CreatePage(viewModelType, parameter);  

    if (page is LoginView)  
    {  
        Application.Current.MainPage = new CustomNavigationView(page);  
    }  
    else  
    {  
        var navigationPage = Application.Current.MainPage as CustomNavigationView;  
        if (navigationPage != null)  
        {  
            await navigationPage.PushAsync(page);  
        }  
        else  
        {  
            Application.Current.MainPage = new CustomNavigationView(page);  
        }  
    }  

    await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);  
}  

private Type GetPageTypeForViewModel(Type viewModelType)  
{  
    var viewName = viewModelType.FullName.Replace("Model", string.Empty);  
    var viewModelAssemblyName = viewModelType.GetTypeInfo().Assembly.FullName;  
    var viewAssemblyName = string.Format(  
                CultureInfo.InvariantCulture, "{0}, {1}", viewName, viewModelAssemblyName);  
    var viewType = Type.GetType(viewAssemblyName);  
    return viewType;  
}  

private Page CreatePage(Type viewModelType, object parameter)  
{  
    Type pageType = GetPageTypeForViewModel(viewModelType);  
    if (pageType == null)  
    {  
        throw new Exception($"Cannot locate page type for {viewModelType}");  
    }  

    Page page = Activator.CreateInstance(pageType) as Page;  
    return page;  
}

Die InternalNavigateToAsync Methode führt die Navigation zu einem Ansichtsmodell aus, indem zuerst die CreatePage Methode aufgerufen wird. Diese Methode sucht die Ansicht, die dem angegebenen Ansichtsmodelltyp entspricht, und erstellt und gibt eine Instanz dieses Ansichtstyps zurück. Das Auffinden der Ansicht, die dem Ansichtsmodelltyp entspricht, verwendet einen konventionsbasierten Ansatz. Dabei wird davon ausgegangen, dass:

  • Ansichten befinden sich in derselben Assembly wie Ansichtsmodelltypen.
  • Ansichten befinden sich in einem . Zeigt den untergeordneten Namespace an.
  • Ansichtsmodelle befinden sich in einem . ViewModels untergeordneter Namespace.
  • Ansichtsnamen entsprechen Ansichtsmodellnamen, wobei "Modell" entfernt wurde.

Wenn eine Ansicht instanziiert wird, wird sie dem entsprechenden Ansichtsmodell zugeordnet. Weitere Informationen dazu, wie dies geschieht, finden Sie unter "Automatisches Erstellen eines Ansichtsmodells mit einem Ansichtsmodelllocator".

Wenn es sich bei der erstellten Ansicht um eine LoginViewumschlossene Ansicht handelt, wird sie in eine neue Instanz der CustomNavigationView Klasse eingeschlossen und der Application.Current.MainPage Eigenschaft zugewiesen. Andernfalls wird die CustomNavigationView Instanz abgerufen und vorausgesetzt, dass sie nicht NULL ist, wird die PushAsync Methode aufgerufen, um die ansicht zu übertragen, die auf dem Navigationsstapel erstellt wird. Wenn die abgerufene CustomNavigationView Instanz jedoch lautet null, wird die erstellte Ansicht in eine neue Instanz der CustomNavigationView Klasse umschlossen und der Application.Current.MainPage Eigenschaft zugewiesen. Mit diesem Mechanismus wird sichergestellt, dass Seiten während der Navigation sowohl bei leerer Als auch bei der Datenzuführung dem Navigationsstapel korrekt hinzugefügt werden.

Tipp

Erwägen Sie das Zwischenspeichern von Seiten. Das Zwischenspeichern von Seiten führt zu einer Speicherauslastung für Ansichten, die derzeit nicht angezeigt werden. Ohne Seitenzwischenspeicherung bedeutet dies jedoch, dass die XAML-Analyse und -Konstruktion der Seite und des Ansichtsmodells jedes Mal auftreten, wenn eine neue Seite navigiert wird. Dies kann sich auf eine komplexe Seite auswirken. Für eine gut gestaltete Seite, die keine übermäßige Anzahl von Steuerelementen verwendet, sollte die Leistung ausreichen. Das Zwischenspeichern von Seiten kann jedoch hilfreich sein, wenn langsame Seitenladezeiten auftreten.

Nachdem die Ansicht erstellt und navigiert wurde, wird die InitializeAsync Methode des zugeordneten Ansichtsmodells der Ansicht ausgeführt. Weitere Informationen finden Sie unter Übergeben von Parametern während der Navigation.

Wenn die App gestartet wird, wird die InitNavigation Methode in der App Klasse aufgerufen. Im folgenden Codebeispiel wird diese Methode veranschaulicht:

private Task InitNavigation()  
{  
    var navigationService = ViewModelLocator.Resolve<INavigationService>();  
    return navigationService.InitializeAsync();  
}

Die Methode erstellt ein neues NavigationService Objekt im Autofac-Abhängigkeitseinfügungscontainer und gibt einen Verweis darauf zurück, bevor sie die InitializeAsync Methode aufrufen.

Hinweis

Wenn die INavigationService Schnittstelle von der ViewModelBase Klasse aufgelöst wird, gibt der Container einen Verweis auf das NavigationService Objekt zurück, das beim Aufrufen der InitNavigation-Methode erstellt wurde.

Das folgende Codebeispiel zeigt die NavigationService InitializeAsync Methode:

public Task InitializeAsync()  
{  
    if (string.IsNullOrEmpty(Settings.AuthAccessToken))  
        return NavigateToAsync<LoginViewModel>();  
    else  
        return NavigateToAsync<MainViewModel>();  
}

Die MainView Navigation erfolgt, wenn die App über ein zwischengespeichertes Zugriffstoken verfügt, das für die Authentifizierung verwendet wird. Andernfalls wird die LoginView Navigation erfolgt.

Weitere Informationen zum Autofac-Abhängigkeitseinfügungscontainer finden Sie in der Einführung in Abhängigkeitseinfügung.

Übergeben von Parametern während der Navigation

Eine der NavigateToAsync methoden, die von der INavigationService Schnittstelle angegeben werden, ermöglicht die Angabe von Navigationsdaten als Argument, das an das Ansichtsmodell übergeben wird, zu dem navigiert wird, wo es in der Regel zum Initialisieren verwendet wird.

Beispielsweise enthält die ProfileViewModel-Klasse einen OrderDetailCommand, der ausgeführt wird, wenn der Benutzer eine Bestellung auf der ProfileView-Seite auswählt. Dadurch wird wiederum die OrderDetailAsync-Methode ausgeführt, wie im folgenden Codebeispiel gezeigt:

private async Task OrderDetailAsync(Order order)  
{  
    await NavigationService.NavigateToAsync<OrderDetailViewModel>(order);  
}

Mit dieser Methode wird die Navigation auf der OrderDetailViewModelSeite aufgerufen, wobei eine Order Instanz übergeben wird, die die Reihenfolge darstellt, die der Benutzer auf der ProfileView Seite ausgewählt hat. Wenn die NavigationService Klasse erstellt OrderDetailViewwird, wird die OrderDetailViewModel Klasse instanziiert und der Ansicht BindingContextzugewiesen. Nach dem OrderDetailViewNavigieren zur Methode wird die InternalNavigateToAsync InitializeAsync Methode des zugeordneten Ansichtsmodells der Ansicht ausgeführt.

Die InitializeAsync Methode wird in der ViewModelBase Klasse als Methode definiert, die überschrieben werden kann. Diese Methode gibt ein object Argument an, das die Daten darstellt, die während eines Navigationsvorgangs an ein Ansichtsmodell übergeben werden sollen. Daher stellen Ansichtsmodellklassen, die Daten aus einem Navigationsvorgang empfangen möchten, eine eigene Implementierung der InitializeAsync Methode bereit, um die erforderliche Initialisierung durchzuführen. Im folgenden Codebeispiel wird die InitializeAsync-Methode aus der OrderDetailViewModel-Klasse gezeigt:

public override async Task InitializeAsync(object navigationData)  
{  
    if (navigationData is Order)  
    {  
        ...  
        Order = await _ordersService.GetOrderAsync(  
                        Convert.ToInt32(order.OrderNumber), authToken);  
        ...  
    }  
}

Diese Methode ruft die Order Instanz ab, die während des Navigationsvorgangs an das Ansichtsmodell übergeben wurde, und verwendet sie, um die vollständigen Bestelldetails aus der OrderService Instanz abzurufen.

Aufrufen der Navigation mithilfe von Verhalten

Die Navigation wird normalerweise von einer Ansicht aus durch eine Benutzerinteraktion ausgelöst. Beispielsweise führt die LoginView Navigation nach erfolgreicher Authentifizierung aus. Das folgende Codebeispiel zeigt, wie die Navigation durch Verhalten aufgerufen wird:

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

Zur Laufzeit reagiert das EventToCommandBehavior auf die Interaktion mit der WebView. Wenn die WebView Navigation zu einer Webseite erfolgt, wird das Navigating Ereignis ausgelöst, das in der NavigateCommand LoginViewModelDatei ausgeführt wird. Die Ereignisargumente für das Ereignis werden standardmäßig an den Befehl übergeben. Diese Daten werden konvertiert, während sie von dem in der EventArgsConverter-Eigenschaft angegebenen Konverter zwischen Quelle und Ziel übergeben werden, der die Url aus den WebNavigatingEventArgs zurückgibt. Wenn die Webseite ausgeführt wird, wird die NavigationCommand URL der Webseite daher als Parameter an die registrierte ActionÜbergeben.

Dadurch führt NavigationCommand wiederum die NavigateAsync-Methode aus, wie im folgenden Codebeispiel gezeigt:

private async Task NavigateAsync(string url)  
{  
    ...          
    await NavigationService.NavigateToAsync<MainViewModel>();  
    await NavigationService.RemoveLastFromBackStackAsync();  
    ...  
}

Diese Methode ruft die Navigation auf, MainViewModelund die folgende Navigation entfernt die LoginView Seite aus dem Navigationsstapel.

Bestätigen oder Abbrechen der Navigation

Eine App muss möglicherweise während eines Navigationsvorgangs mit dem Benutzer interagieren, damit der Benutzer die Navigation bestätigen oder abbrechen kann. Dies kann beispielsweise erforderlich dann sein, wenn der Benutzer versucht, zu navigieren, bevor er eine Dateneingabeseite vollständig abgeschlossen hat. Unter diesen Umständen sollte eine App eine Benachrichtigung bereitstellen, die dem Benutzer erlaubt, von der Seite zu navigieren oder den Navigationsvorgang abzubrechen, bevor er auftritt. Dies kann in einer Ansichtsmodellklasse mithilfe der Antwort einer Benachrichtigung erreicht werden, um zu steuern, ob die Navigation aufgerufen wird.

Zusammenfassung

Xamarin.Forms enthält Unterstützung für die Seitennavigation, die in der Regel aus der Interaktion des Benutzers mit der Benutzeroberfläche oder aus der App selbst resultiert, aufgrund von internen logikgesteuerten Zustandsänderungen. Allerdings kann die Navigation in Anwendungen, die das MVVM-Muster verwenden, kompliziert zu implementieren sein.

In diesem Kapitel wurde eine NavigationService Klasse vorgestellt, die zum Ausführen der Ansichtsmodell-ersten Navigation aus Ansichtsmodellen verwendet wird. Das Platzieren von Navigationslogik in Ansichtsmodellklassen bedeutet, dass die Logik über automatisierte Tests ausgeübt werden kann. Darüber hinaus kann das Ansichtsmodell logik implementieren, um die Navigation zu steuern, um sicherzustellen, dass bestimmte Geschäftsregeln erzwungen werden.