Compartir vía


Navegación

Sugerencia

Este contenido es un extracto del libro electrónico "Patrones de aplicaciones empresariales con .NET MAUI", disponible en Documentación de .NET o como un PDF descargable y gratuito que se puede leer sin conexión.

Miniatura de la portada del libro electrónico

.NET MAUI incluye compatibilidad con la navegación por páginas, que normalmente surge a partir de la interacción del usuario con la UI o de la propia aplicación, como resultado de los cambios de estado internos causados por la lógica. Sin embargo, la navegación puede ser compleja de implementar en aplicaciones que usan el patrón Modelo-Vista-Modelo de vista (MVVM), ya que se deben superar los siguientes desafíos:

  • Identificar la vista a la que se va a navegar mediante un enfoque que no introduce acoplamientos y dependencias estrictos entre las vistas.
  • Coordinar el proceso por el que se crea y se inicializa una instancia de la vista a la que se va a navegar. Al usar MVVM, es necesario crear instancias del modelo de vista y de la vista y asociarlas entre sí mediante el contexto de enlace de la vista. Cuando una aplicación usa un contenedor de inserción de dependencias, la creación de instancias de vistas y modelos de vistas podría requerir un mecanismo de construcción específico.
  • Saber si se va a realizar la navegación con prioridad para la vista o para el modelo de vista. Si se prioriza la vista, la página a la que se navega hace referencia al nombre del tipo de vista. Durante la navegación, se crea una instancia de la vista especificada, junto con su modelo de vista correspondiente y otros servicios dependientes. Un enfoque alternativo consiste en priorizar el modelo de vista. Así, la página a la que se navega hace referencia al nombre del tipo de modelo de vista.
  • Determinar cómo separar limpiamente el comportamiento de navegación de la aplicación entre las vistas y los modelos de vistas. El patrón MVVM separa la UI de la aplicación, su presentación y su lógica de negocios, pero no proporciona un mecanismo directo para unirlas. Sin embargo, el comportamiento de navegación de una aplicación suele abarcar la UI y las partes de presentación de la aplicación. El usuario suele iniciar la navegación desde una vista y la vista se reemplazará como resultado de la navegación. Sin embargo, es posible que. con cierta asiduidad, la navegación también tenga que iniciarse o coordinarse desde dentro del modelo de vista.
  • Determinar cómo pasar parámetros durante la navegación con fines de inicialización. Por ejemplo, si el usuario navega a una vista para actualizar los detalles de una orden, los datos de la orden tendrán que pasarse a la vista para que pueda mostrar los datos correctos.
  • Coordinar la navegación para garantizar que se cumplen las reglas específicas del negocio. Por ejemplo, es posible que, antes de salir de una vista, se solicite a los usuarios que corrijan datos no válidos o que envíen o descarten los cambios de datos realizados en la vista.

En este capítulo se abordan estos desafíos mediante la presentación de una clase de servicio de navegación denominada MauiNavigationService, que se usa para realizar la navegación de la página con prioridad para el modelo de vista.

Nota:

La clase MauiNavigationService que utiliza la aplicación es simplista y no abarca todos los tipos de navegación posibles. Los tipos de navegación necesarios para la aplicación pueden requerir funcionalidades adicionales.

La lógica de navegación puede residir en el código subyacente de una vista o en un modelo de vista enlazado a datos. Aunque colocar la lógica de navegación en una vista podría ser el enfoque más directo, no se puede probar fácilmente con pruebas unitarias. Si se coloca la lógica de navegación en las clases de modelo de vista, la lógica se puede comprobar mediante pruebas unitarias. Además, el modelo de vista puede implementar lógica para controlar la navegación y así asegurar que se aplican determinadas reglas del negocio. Por ejemplo, es posible que una aplicación no permita que el usuario salga de una página sin asegurarse primero de que los datos especificados sean válidos.

Normalmente, se invoca un servicio de navegación a partir de modelos de vistas, con el fin de promover la capacidad de realizar pruebas con ellos. Sin embargo, navegar a las vistas desde los modelos de vistas requeriría que los modelos de vistas hagan referencia a las vistas y, particularmente, a las vistas con las que el modelo de vista activo no está asociado, algo que no es recomendable. Por lo tanto, el elemento MauiNavigationService que se presenta aquí especifica el tipo de modelo de vista como destino al que navegar.

La aplicación multiplataforma eShop usa la clase MauiNavigationService para proporcionar navegación con prioridad para el modelo de vista. Esta clase implementa la interfaz INavigationService, que se muestra en el siguiente código de ejemplo:

public interface INavigationService
{
    Task InitializeAsync();

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

    Task PopAsync();
}

Esta interfaz especifica que una clase de implementación debe proporcionar los siguientes métodos:

Método Propósito
InitializeAsync Realiza la navegación a una de las dos páginas cuando se inicia la aplicación.
NavigateToAsync(string route, IDictionary<string, object> routeParameters = null) Realiza la navegación jerárquica a una página especificada mediante una ruta de navegación registrada. Opcionalmente, puede pasar parámetros de ruta con nombre, que se usarán para su procesamiento en la página de destino.
PopAsync Quita la página actual de la pila de navegación.

Nota:

Normalmente, una interfaz INavigationService también especificaría un método GoBackAsync, que se usa para volver mediante programación a la página anterior de la pila de navegación. Sin embargo, este método no está en la aplicación multiplataforma eShop porque no es necesario.

Creación de la instancia de MauiNavigationService

La clase MauiNavigationService, que implementa la interfaz INavigationService, se registra como singleton con el contenedor de inserción de dependencias en el método MauiProgram.CreateMauiApp(), como se muestra en el siguiente código de ejemplo:

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

La interfaz INavigationService se puede resolver agregándola al constructor de nuestras vistas y modelos de vistas, como se muestra en el siguiente código de ejemplo:

public AppShell(INavigationService navigationService)

Esto devuelve una referencia al objeto MauiNavigationService almacenado en el contenedor de inserción de dependencias.

La clase ViewModelBase almacena la instancia MauiNavigationService en una propiedad NavigationService, de tipo INavigationService. Por lo tanto, todas las clases de modelos de vistas, que se derivan de la clase ViewModelBase, pueden usar la propiedad NavigationService para tener acceso a los métodos especificados por la interfaz INavigationService.

Control de solicitudes de navegación

.NET MAUI ofrece varias formas de navegar dentro de una aplicación. La forma tradicional de navegar es con la clase NavigationPage, que implementa una experiencia de navegación jerárquica en la que el usuario puede navegar por páginas, hacia delante y hacia atrás, según considere. La aplicación eShop usa el componente Shell como contenedor raíz para la aplicación y como host de navegación. Para más información sobre la navegación de Shell, consulte Navegación de Shell en el Centro para desarrolladores de Microsoft.

La navegación se realiza dentro de las clases de modelos de vistas invocando uno de los métodos NavigateToAsync, especificando la ruta de acceso de la página a la que se navega, como se muestra en el siguiente código de ejemplo:

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

En el siguiente código de ejemplo se muestra el método NavigateToAsync proporcionado por la clase MauiNavigationService:

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

El control .NET MAUIShell ya está familiarizado con la navegación basada en rutas, por lo que el método NavigateToAsync funciona para enmascarar esta funcionalidad. El método NavigateToAsync permite especificar datos de navegación como un argumento que se pasa al modelo de vista al que se navega, donde normalmente se usa para realizar la inicialización. Para más información, consulte Pasar parámetros durante la navegación.

Importante

Hay varias formas de realizar la navegación en .NET MAUI. MauiNavigationService se compila específicamente para trabajar con Shell. Si usa NavigationPage, TabbedPage o un mecanismo de navegación diferente, este servicio de enrutamiento tendría que actualizarse para que funcione con esos componentes.

Para registrar rutas para MauiNavigationService tenemos que proporcionar información de ruta desde XAML o en el código subyacente. En el ejemplo siguiente se muestra el registro de rutas a través de 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>

En este ejemplo, los objetos de interfaz de usuario ShellContent y TabBar establecen su propiedad Route. Este es el método preferido para registrar rutas para objetos de interfaz de usuario controlados por Shell.

Si tenemos objetos que se agregarán a la pila de navegación más adelante, deberá agregarlos mediante el código subyacente. En el ejemplo siguiente se muestra el registro de rutas en el código subyacente.

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

En el código subyacente, llamaremos al método Routing.RegisterRoute, que tiene un nombre de ruta como primer parámetro y un tipo de vista como segundo parámetro. Cuando un modelo de vista usa la propiedad NavigationService para navegar, el objeto Shell de la aplicación buscará rutas registradas y las insertará en la pila de navegación.

Una vez se haya creado la vista y se acceda a ella, se ejecutan los métodos ApplyQueryAttributes y InitializeAsync del modelo de vista asociado a la vista. Para más información, consulte Pasar parámetros durante la navegación.

Cuando se inicia la aplicación, se establece un objeto Shell como vista raíz de la aplicación. Una vez establecido, Shell se usará para controlar el registro de rutas y se mantendrá en la raíz de nuestra aplicación. Una vez creado, podemos esperar a que Shell se asocie a la aplicación mediante el método OnParentSet para inicializar nuestra ruta de navegación. El siguiente ejemplo de código muestra este método:

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

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

El método usa una instancia de INavigationService proporcionada por el constructor a partir de la inserción de dependencias e invoca su método InitializeAsync.

En el siguiente código de ejemplo se muestra la implementación del método MauiNavigationService.InitializeAsync:

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

Se navega a la ruta //Main/Catalog si la aplicación tiene un token de acceso almacenado en caché, que se usa para la autenticación. De lo contrario, se navega a la ruta //Login.

Pasar parámetros durante la navegación

El método NavigateToAsync, especificado por la interfaz INavigationService, permite especificar datos de navegación como un argumento IDictionary<string, object> de datos que se pasan al modelo de vista al que se navega, donde normalmente se usa para realizar la inicialización.

Por ejemplo, la clase ProfileViewModel contiene un OrderDetailCommand que se ejecuta cuando el usuario selecciona una orden en la página ProfileView. Después, esto sirve para ejecutar el método OrderDetailAsync, que se muestra en el siguiente código de ejemplo:

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

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

Este método invoca la navegación a la ruta OrderDetail, pasando la información del número de la orden a la orden seleccionada por el usuario. Cuando el marco de inserción de dependencias crea OrderDetailView para la ruta OrderDetail junto con la clase OrderDetailViewModel que se asigna al BindingContext de la vista. OrderDetailViewModel tiene un atributo agregado que le permite recibir datos del servicio de navegación, como se muestra en el siguiente código de ejemplo.

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

El atributo QueryProperty nos permite proporcionar un parámetro para que una propiedad asigne valores y una clave para buscar valores del diccionario de parámetros de consulta. En este ejemplo, se proporcionaron la clave "OrderNumber" y el valor del número de la orden durante la llamada de NavigateToAsync. El modelo de vista encontró la clave "OrderNumber" y asignó el valor a la propiedad OrderNumber. Así, la propiedad OrderNumber se puede usar más adelante para recuperar los detalles completos de la orden desde la instancia OrderService.

Invocación de la navegación mediante comportamientos

Normalmente, la interacción del usuario desencadena la navegación desde una vista. Por ejemplo, LoginView realiza la navegación después de autenticarse correctamente. En el siguiente código de ejemplo se muestra la forma en que un comportamiento invoca la navegación:

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

En tiempo de ejecución, EventToCommandBehavior responderá a la interacción con WebView. Cuando WebView navega a una página web, se activará el evento Navigating, que ejecutará NavigateCommand en LoginViewMode. De manera predeterminada, los argumentos de evento para el evento se pasarán al comando. Estos datos se convierten a medida que se pasan entre el origen y el destino mediante el convertidor especificado en la propiedad EventArgsConverter, que devuelve Url desde WebNavigatingEventArgs. Por lo tanto, cuando NavigationCommand se ejecuta, la propiedad Url de la página web se pasa como un parámetro a la Acción registrada.

Después, NavigationCommand ejecuta el método NavigateAsync, que se muestra en el siguiente código de ejemplo:

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

Este método invoca la ruta NavigationService de la aplicación a la ruta //Main/Catalog.

Confirmación o cancelación de la navegación

Es posible que una aplicación tenga que interactuar con el usuario durante una operación de navegación para que el usuario pueda confirmar o cancelar la navegación. Esto puede ser necesario, por ejemplo, cuando el usuario intenta navegar antes de haber completado del todo una página de entrada de datos. En esta situación, una aplicación debe enviar una notificación que permita al usuario salir de la página o cancelar la operación de navegación antes de que se produzca. Esto se puede lograr en una clase de modelo de vista, usando la respuesta de una notificación para controlar si se invoca o no la navegación.

Resumen

.NET MAUI incluye compatibilidad con la navegación por páginas, que normalmente surge a partir de la interacción del usuario con la UI o de la propia aplicación, como resultado de los cambios de estado internos causados por la lógica. Sin embargo, la navegación puede ser compleja de implementar en aplicaciones que usan el patrón MVVM.

En este capítulo se ha descrito una clase NavigationService, que se usa para realizar la navegación de la página con prioridad para el modelo de vista desde modelos de vistas. Si se coloca la lógica de navegación en las clases de modelo de vista, la lógica se puede revisar mediante pruebas unitarias. Además, el modelo de vista puede implementar lógica para controlar la navegación y así asegurar que se aplican determinadas reglas del negocio.