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.
.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.
Navegación entre las páginas
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.
Navegación cuando se inicia la aplicació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.