[go: up one dir, main page]

0% encontró este documento útil (0 votos)
203 vistas108 páginas

Enterprise Application Patterns Using .NET MAUI

Cargado por

luissalli
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
203 vistas108 páginas

Enterprise Application Patterns Using .NET MAUI

Cargado por

luissalli
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 108

Machine Translated by Google

Machine Translated by Google

DESCARGAR disponible en: https://aka.ms/maui­ebook

EDICIÓN v1.0

PUBLICADO POR

Equipos de productos de la División de Desarrolladores de Microsoft, .NET y Visual Studio

Una división de Microsoft Corporation

Una forma de Microsoft

Redmond, Washington 98052­6399

Copyright © 2022 por Microsoft Corporation

Reservados todos los derechos. Ninguna parte del contenido de este libro puede reproducirse o transmitirse de ninguna forma
ni por ningún medio sin el permiso por escrito del editor.

Este libro se proporciona "tal cual" y expresa los puntos de vista y opiniones del autor. Los puntos de vista, opiniones e información
expresada en este libro, incluida la URL y otras referencias a sitios web de Internet, pueden cambiar sin previo aviso.

Algunos ejemplos aquí representados se proporcionan únicamente con fines ilustrativos y son ficticios. No se pretende ni debe inferirse
ninguna asociación o conexión real.

Microsoft y las marcas comerciales enumeradas en https://www.microsoft.com en la página web “Marcas comerciales” son marcas
comerciales del grupo de empresas Microsoft.

Mac y macOS son marcas comerciales de Apple Inc.

Todas las demás marcas y logotipos son propiedad de sus respectivos dueños.

Autores:

Michael Stonis, Arquitecto de software móvil, Eight­Bot

Revisores:

James Montemagno, Gerente principal de programas, Microsoft Corp.

David Pino, Relaciones con desarrolladores, Microsoft Corp.

Expresiones de gratitud
Este libro se originó a partir del excelente libro electrónico Patrones de aplicaciones empresariales que utilizan Xamarin.Forms de David
Britch. y Javier Suárez Ruiz. Sin su arduo trabajo, información detallada y excelentes ejemplos, este libro no sería posible.

Introducción
Las aplicaciones empresariales enfrentan una serie de problemas difíciles de resolver, incluidos los requisitos comerciales en
constante cambio, la necesidad de tiempos de respuesta rápidos, soporte para múltiples plataformas e integración con múltiples
sistemas. Debido a la naturaleza variable de estos problemas, es importante que la arquitectura de nuestra aplicación
permita que sea modular, modificable y extensible en el tiempo.
Machine Translated by Google

Este libro proporciona soluciones del mundo real para abordar estos problemas al crear una aplicación empresarial utilizando .NET
MAUI. Este libro utiliza una aplicación .NET MAUI prediseñada que sirve como interfaz de una aplicación de comercio
electrónico en línea como referencia y guía para patrones de diseño empresarial comunes. Este libro cubre temas como el
patrón MVVM, la inyección de dependencias, la navegación, la configuración, el acoplamiento flexible de componentes y
preocupaciones empresariales adicionales. El contenido de este libro es útil para cualquiera que busque crear una nueva aplicación
para este negocio o resolver los problemas de las aplicaciones que evolucionan con el tiempo.

¿Quién debería usar el libro?


Este libro está dirigido a desarrolladores de .NET MAUI que ya están familiarizados con el marco, pero que buscan orientación
sobre arquitectura e implementación al crear aplicaciones empresariales. Este libro puede ayudar a los desarrolladores a resolver
problemas comunes utilizando patrones probados y verdaderos.

Cómo utilizar el libro


Este libro se centra en la creación de aplicaciones empresariales multiplataforma utilizando .NET MAUI. Como tal, debe leerse en
su totalidad para proporcionar una base para comprender dichas aplicaciones y sus consideraciones técnicas. El libro,
junto con su aplicación de muestra, también puede servir como punto de partida o referencia para crear una nueva aplicación
empresarial. Utilice la aplicación de muestra asociada como plantilla para la nueva aplicación o para ver cómo organizar los
componentes de una aplicación. Luego, consulte esta guía para obtener orientación arquitectónica.
Puede encontrar la aplicación de muestra en GitHub.

Lo que este libro no cubre


Este libro está dirigido a lectores que ya están familiarizados con .NET MAUI. Cubre algunos conceptos de .NET MAUI para ayudar
a ilustrar mejor el tema, pero no cubre la mayoría de los controles y conceptos en detalle. Para obtener orientación general sobre
cómo crear una nueva aplicación .NET MAUI, consulte Creación de su primera aplicación. guía en la documentación de .NET MAUI

Recursos adicionales
Para obtener contenido oficial de .NET MAUI, consulte los documentos de .NET MAUI. .NET MAUI se desarrolla como un
proyecto de código abierto y está disponible en GitHub en dotnet/maui. Para ver ejemplos de código desarrollados con .NET MAUI,
consulte dotnet/maui­samples repositorio.
Machine Translated by Google

Contenido
Objetivo................................................. ................................................. ................................ 1

Lo que queda fuera del alcance de esta guía.................................... ................................................. ...................................1

Quién debería usar esta guía ............................................. ................................................. ................................................. .1

Como usar esta guia............................................. ................................................. ................................................. ...........2

Introducción a .NET MAUI ................................................ ................................................. ..... 3

Aplicación de muestra................................................ ................................................. ................................................. ..............4

Arquitectura de aplicación de ejemplo ................................................. ................................................. ........................................5

Aplicación multiplataforma................................................ ................................................. ................................................. ................6

Solución de aplicación multiplataforma.................................... ................................................. ................................................7

Proyecto eShopOnContainers................................................ ................................................. ................................................7

Resumen ................................................. ................................................. ................................................. ................................8

Modelo­Vista­VerModelo (MVVM).................................... ................................................ 9

El patrón MVVM................................................ ................................................. ................................................. ...............9

Vista ................................................. ................................................. ................................................. ................................ 10

Modelo de vista................................................. ................................................. ................................................. ................. 11

Modelo................................................. ................................................. ................................................. ................................ 11

Conexión de modelos de vista a vistas.................................... ................................................. ................................... 12

Crear un modelo de vista de forma declarativa ................................. ................................................. ................................ 12

Crear un modelo de vista mediante programación ................................. ................................................. ................... 13

Actualización de vistas en respuesta a cambios en el modelo o modelo de vista subyacente.................... 13

Marcos MVVM................................................ ................................................. ................................................. .......... 15

Interacción de la interfaz de usuario mediante comandos y comportamientos ................................. ................................................. .......... 15

Implementación de comandos ................................................. ................................................. ............................................... dieciséis

Invocar comandos desde una vista ......................................... ................................................. ................................ 17

Implementar comportamientos ................................................ ................................................. ................................................. 17

Invocar comportamientos desde una vista................................................ ................................................. ........................................ 20

Resumen ................................................. ................................................. ................................................. ................................ 20

Inyección de dependencia ................................................ ................................................. ......... 21

i Contenido
Machine Translated by Google

Introducción a la inyección de dependencia................................................ ................................................. ........................ 21

Registro................................................. ................................................. ................................................. ........................ 23

Resolución................................................. ................................................. ................................................. ................................ 25

Resumen ................................................. ................................................. ................................................. ................................ 26

Comunicación entre componentes débilmente acoplados ......................................... ...... 27


Introducción a MVVM Toolkit Messenger.................................... ................................................. ................. 27

Definiendo un mensaje................................................. ................................................. ................................................. .......... 29

Publicar un mensaje................................................ ................................................. ................................................. ...... 29

Suscribirse a un mensaje................................................ ................................................. ................................................. 30

Darse de baja de un mensaje................................................ ................................................. ........................................ 30

Resumen ................................................. ................................................. ................................................. ........................ 31

Navegación................................................. ................................................. ................................ 32


Navegar entre páginas ................................................ ................................................. ................................................ 33

Creando la instancia de MauiNavigationService ................................................. ................................................. 34

Manejo de solicitudes de navegación................................................ ................................................. ........................................ 34

Navegar cuando se inicia la aplicación................................... ................................................. ................................ 36

Pasar parámetros durante la navegación................................................ ................................................. ........................ 36

Invocar la navegación usando comportamientos ................................................ ................................................. ................................ 37

Confirmar o cancelar la navegación................................................ ................................................. ................................ 38

Resumen ................................................. ................................................. ................................................. ................................ 38

Validación................................................. ................................................. ................................ 39

Especificación de reglas de validación ................................................ ................................................. ................................................ 40

Agregar reglas de validación a una propiedad ......................................... ................................................. ................................ 42

Activación de la validación ................................................ ................................................. ................................................. ....... 42

Activar la validación manualmente................................................ ................................................. ................................ 42

Activar la validación cuando las propiedades cambian ................................. ................................................. ..... 43

Mostrando errores de validación ................................................ ................................................. ........................................ 44

Resaltar un control que contiene datos no válidos................................. ................................................. ... 44

Visualización de mensajes de error ................................................ ................................................. ........................................ 45

Resumen ................................................. ................................................. ................................................. ................................ 45

Gestión de configuración................................................ ................................................ 47

ii Contenido
Machine Translated by Google

Crear una interfaz de configuración.................................... ................................................. ........................................ 47

Agregar configuraciones ................................................. ................................................. ................................................. ................. 48

Vinculación de datos a la configuración del usuario................................. ................................................. ................................................ 49

Resumen ................................................. ................................................. ................................................. ................................ 50

Microservicios en contenedores ................................................ ................................................ 51


Microservicios ................................................. ................................................. ................................................. ................. 52

Contenedorización................................................. ................................................. ................................................. ................. 54

Comunicación entre cliente y microservicios ................................................. ................................................. .58

Comunicación entre microservicios................................................ ................................................. ................. 58

Resumen ................................................. ................................................. ................................................. ........................ 61

Acceso a datos remotos................................................. ................................................. ......... 62


Introducción a la transferencia de estado representacional................................................ ................................................. ....... 62

Consumir API RESTful................................................. ................................................. ................................................. .63

Realización de solicitudes web ................................................. ................................................. ................................................. ....... 63

Realizar una solicitud GET................................................ ................................................. ................................................. ....... 63

Realizar una solicitud POST................................................ ................................................. ................................................. .... 66

Realizar una solicitud BORRAR................................................ ................................................. ................................................. 69

Almacenamiento en caché de datos................................. ................................................. ................................................. ........................ 70

Gestionar la caducidad de los datos.................................. ................................................. ................................................ 71

Almacenamiento en caché de imágenes .................................. ................................................. ................................................. ................. 72

Aumento de la resiliencia................................................ ................................................. ................................................. ......... 72

Patrón de reintento................................................ ................................................. ................................................. ........................ 72

Patrón de disyuntor................................................ ................................................. ................................................. ..... 73

Resumen ................................................. ................................................. ................................................. ................................. 74

Autenticacion y autorizacion............................................... ........................................ 75

Autenticación................................................. ................................................. ................................................. ................... 75

Emitir tokens al portador utilizando IdentityServer 4................................. ................................................. 76

Agregar IdentityServer a una aplicación web................................... ................................................. ............ 77

Configuración del servidor de identidad................................................ ................................................. ........................................ 77

Configuración de recursos API ................................................ ................................................. .......................................... 78

Configuración de recursos de identidad ................................................ ................................................. ................................ 78

III Contenido
Machine Translated by Google

Configuración de clientes................................................ ................................................. ................................................. ...... 79

Configurar el flujo de autenticación................................................ ................................................. ........................ 80

Realización de autenticación................................................. ................................................. ........................................ 81

Iniciando sesión............................................... ................................................. ................................................. ........................ 82

Cerrando sesión............................................... ................................................. ................................................. ........................ 85

Autorización................................................. ................................................. ................................................. ................. 86

Configuración de IdentityServer para realizar autorización ................................................ ................................................ 87

Realizar solicitudes de acceso a las API................................................ ................................................. ........................................ 88

Resumen ................................................. ................................................. ................................................. ................................ 89

Características del kit de herramientas MVVM ................................. ................................................. ....... 90


Kit de herramientas MVVM ................................................ ................................................. ................................................. ................... 90

Objeto observable................................................. ................................................. ................................................. ............ 91

RelayCommand y AsyncRelayCommand ................................................. ................................................. ................ 92

Generadores de fuentes ................................................ ................................................. ................................................. ............ 93

Resumen ................................................. ................................................. ................................................. ................................ 95

Examen de la unidad ................................................ ................................................. ........................ 96


Inyección de dependencia y pruebas unitarias................................................. ................................................. ........................ 97

Prueba de aplicaciones MVVM................................................ ................................................. ........................................ 97

Prueba de funcionalidad asincrónica................................................ ................................................. ................................ 98

Probando implementaciones de INotifyPropertyChanged ................................. ................................................ 99

Prueba de comunicación basada en mensajes................................................ ................................................. ........................ 99

Probando el manejo de excepciones ................................................. ................................................. ..........................................100

Validación de pruebas................................................ ................................................. ................................................. ............100

Resumen ................................................. ................................................. ................................................. ..........................101

IV Contenido
Machine Translated by Google

CAPÍTULO 1

Objetivo
Este libro electrónico proporciona orientación sobre cómo crear aplicaciones empresariales multiplataforma utilizando .NET
MAUI. .NET MAUI es un conjunto de herramientas de interfaz de usuario multiplataforma que permite a los desarrolladores crear
fácilmente diseños de interfaz de usuario nativos que se pueden compartir entre plataformas, incluidas iOS, macOS, Android y
Windows 10/11. Proporciona una solución integral para aplicaciones de empresa a empleado (B2E), empresa a empresa
(B2B) y empresa a consumidor (B2C), brindando la capacidad de compartir código en todas las plataformas de destino y
ayudando a reducir el costo total de propiedad (TCO). ).

La guía proporciona orientación arquitectónica para desarrollar aplicaciones empresariales .NET MAUI adaptables,
mantenibles y comprobables. Se proporciona orientación sobre cómo implementar MVVM, inyección de
dependencias, navegación, validación y gestión de configuración, manteniendo al mismo tiempo un acoplamiento
flexible. Además, también hay orientación sobre cómo realizar autenticación y autorización con IdentityServer,
acceder a datos de microservicios en contenedores y pruebas unitarias.

La guía viene con el código fuente de la aplicación multiplataforma eShopOnContainers. y código fuente para la
aplicación de referencia eShopOnContainers. La aplicación multiplataforma eShopOnContainers es una aplicación
empresarial multiplataforma desarrollada con .NET MAUI, que se conecta a una serie de microservicios en
contenedores conocidos como la aplicación de referencia eShopOnContainers. Sin embargo, la aplicación
multiplataforma eShopOnContainers se puede configurar para consumir datos de servicios simulados para aquellos
que deseen evitar la implementación de microservicios en contenedores.

Lo que queda fuera del alcance de esta guía


Esta guía está dirigida a lectores que ya están familiarizados con .NET MAUI. Para obtener una introducción detallada
a .NET MAUI, consulte la documentación de .NET MAUI. en el Centro de desarrolladores de Microsoft y Creación
de aplicaciones multiplataforma con .NET MAUI.

La guía es complementaria a Microservicios .NET: Arquitectura para aplicaciones .NET en contenedores, que se
centra en el desarrollo e implementación de microservicios en contenedores. Otras guías que vale la pena leer
incluyen Arquitectura y desarrollo de aplicaciones web modernas con ASP.NET Core y Microsoft Azure. Ciclo
de vida de aplicaciones Docker en contenedores con plataforma y herramientas de Microsoft, y plataforma y
herramientas de Microsoft para el desarrollo de aplicaciones móviles.

Quién debería usar esta guía


La audiencia de esta guía son principalmente desarrolladores y arquitectos que desean aprender a
diseñar e implementar aplicaciones empresariales multiplataforma utilizando .NET MAUI.

1 CAPITULO 1 | Objetivo
Machine Translated by Google

Una audiencia secundaria son los tomadores de decisiones técnicas que desean recibir una descripción general de
la arquitectura y la tecnología antes de decidir qué enfoque seleccionar para el desarrollo de aplicaciones empresariales
multiplataforma utilizando .NET MAUI.

Como usar esta guia


Esta guía se centra en la creación de aplicaciones empresariales multiplataforma utilizando .NET MAUI. Como tal, debe
leerse en su totalidad para proporcionar una base para comprender dichas aplicaciones y sus consideraciones
técnicas. La guía y su aplicación de muestra también pueden servir como punto de partida o referencia para crear
una nueva aplicación empresarial. Utilice la aplicación de muestra asociada como plantilla para la nueva aplicación o
vea cómo organizar los componentes de una aplicación. Luego, consulte esta guía para obtener orientación arquitectónica.

No dude en enviar esta guía a los miembros del equipo para ayudar a garantizar una comprensión común del desarrollo
de aplicaciones empresariales multiplataforma utilizando .NET MAUI. Tener a todos trabajando a partir de un conjunto
común de terminologías y principios subyacentes ayudará a garantizar una aplicación consistente de patrones y prácticas
arquitectónicos.

2 CAPITULO 1 | Objetivo
Machine Translated by Google

CAPITULO 2

Introducción a .NET MAUI


Independientemente de la plataforma, los desarrolladores de aplicaciones empresariales enfrentan varios desafíos:

• Requisitos de la aplicación que pueden cambiar con el tiempo.

• Nuevas oportunidades y desafíos de negocio.



Comentarios continuos durante el desarrollo que pueden afectar significativamente el alcance y los
requisitos de la aplicación.

Teniendo esto en cuenta, es importante crear aplicaciones que puedan modificarse o ampliarse fácilmente con el tiempo.
Diseñar para tal adaptabilidad puede ser difícil, ya que requiere una arquitectura que permita que partes individuales de la
aplicación se desarrollen y prueben de forma independiente y aislada sin afectar el resto de la aplicación.

Muchas aplicaciones empresariales son lo suficientemente complejas como para requerir más de un desarrollador. Puede
ser un desafío importante decidir cómo diseñar una aplicación para que varios desarrolladores puedan trabajar de manera efectiva
en diferentes partes de la aplicación de forma independiente, al mismo tiempo que se garantiza que las piezas se unan sin problemas
cuando se integren en la aplicación.

El enfoque tradicional para diseñar y crear una aplicación da como resultado lo que se conoce como una aplicación
monolítica , donde los componentes están estrechamente acoplados sin una separación clara entre ellos.
Por lo general, este enfoque monolítico conduce a aplicaciones que son difíciles e ineficientes de mantener, porque puede ser difícil
resolver errores sin dañar otros componentes de la aplicación, y puede resultar difícil agregar nuevas funciones o
reemplazar funciones existentes.

Un remedio eficaz para estos desafíos es dividir una aplicación en componentes discretos y poco acoplados que se
puedan integrar fácilmente en una aplicación. Este enfoque ofrece varios beneficios:


Permite que diferentes personas o equipos desarrollen, prueben, amplíen y mantengan funciones individuales.


Promueve la reutilización y una clara separación de preocupaciones entre las capacidades horizontales de
la aplicación, como la autenticación y el acceso a datos, y las capacidades verticales, como la funcionalidad empresarial
específica de la aplicación. Esto permite gestionar más fácilmente las dependencias e interacciones entre los
componentes de la aplicación.

Ayuda a mantener una separación de roles al permitir que diferentes personas o equipos se concentren en una tarea o
funcionalidad específica según su experiencia. En particular, proporciona una separación más clara entre la interfaz de
usuario y la lógica empresarial de la aplicación.

Sin embargo, hay muchos problemas que deben resolverse al dividir una aplicación en componentes discretos y
poco acoplados. Éstas incluyen:

3 CAPITULO 2 | Introducción a .NET MAUI


Machine Translated by Google


Decidir cómo proporcionar una separación clara de preocupaciones entre los controles de la interfaz de usuario y su
lógica. Una de las decisiones más importantes al crear una aplicación empresarial .NET MAUI es si colocar la lógica
empresarial en archivos de código subyacente o si crear una separación clara de preocupaciones entre los
controles de la interfaz de usuario y su lógica, para que la aplicación más mantenible y comprobable. Para obtener
más información, consulte Modelo­Vista­VerModelo.

Determinar si se debe utilizar un contenedor de inyección de dependencia. Los contenedores de inyección
de dependencias reducen el acoplamiento de dependencias entre objetos al proporcionar una función para
construir instancias de clases con sus dependencias inyectadas y administrar su vida útil en función de la
configuración del contenedor. Para obtener más información, consulte Inyección de dependencia.

Elegir entre eventos proporcionados por la plataforma y comunicación basada en mensajes débilmente
acoplados entre componentes que son inconvenientes de vincular por referencias de tipo y objeto. Para obtener
más información, consulte Introducción a la comunicación entre componentes débilmente acoplados.


Decidir cómo navegar entre páginas, incluido cómo invocar la navegación y dónde debe residir la lógica de
navegación. Para obtener más información, consulte Navegación.

Determinar cómo validar la corrección de la entrada del usuario. La decisión debe incluir cómo validar la entrada
del usuario y cómo notificarle sobre los errores de validación. Para obtener más información, consulte Validación.


Decidir cómo realizar la autenticación y cómo proteger los recursos con autorización. Para obtener más información,
consulte Autenticación y autorización.

Determinar cómo acceder a datos remotos desde servicios web, incluido cómo recuperar datos de manera confiable y
cómo almacenarlos en caché. Para obtener más información, consulte Acceso a datos remotos.

Decidir cómo probar la aplicación. Para obtener más información, consulte Pruebas unitarias.

Esta guía proporciona orientación sobre estos temas y se centra en los patrones y la arquitectura principales para crear una
aplicación empresarial multiplataforma utilizando .NET MAUI. La guía tiene como objetivo ayudar a producir código adaptable,
mantenible y comprobable, abordando escenarios comunes de desarrollo de aplicaciones empresariales .NET MAUI y
separando las preocupaciones de presentación, lógica de presentación y entidades a través del soporte para Model­View­
ViewModel (MVVM). ) patrón.

Aplicación de muestra
Esta guía incluye una aplicación de muestra, eShopOnContainers, que es una tienda en línea que incluye las siguientes funciones:


Autenticar y autorizar contra un servicio backend.

Navegar por un catálogo de camisetas, tazas de café y otros artículos de marketing.

Filtrando el catálogo.

Realizar pedidos de artículos del catálogo.

Ver el historial de pedidos del usuario.

Configuración de ajustes.

4 CAPITULO 2 | Introducción a .NET MAUI


Machine Translated by Google

Arquitectura de aplicación de muestra


A continuación se muestra una descripción general de alto nivel de la arquitectura de la aplicación de muestra.

La aplicación de muestra viene con tres aplicaciones cliente:

• Una aplicación MVC desarrollada con ASP.NET Core.

• Una Aplicación de Página Única (SPA) desarrollada con Angular 2 y Typecript. Este enfoque para
Las aplicaciones web evitan realizar un viaje de ida y vuelta al servidor con cada operación.

• Una aplicación multiplataforma desarrollada con .NET MAUI, compatible con iOS, Android, macOS a través de Mac
Catalyst y Windows 10/11.

Para obtener información sobre las aplicaciones web, consulte Diseño y desarrollo de aplicaciones web modernas
con ASP.NET Core y Microsoft Azure.

La aplicación de ejemplo incluye los siguientes servicios de backend:

• Un microservicio de identidad, que utiliza ASP.NET Core Identity e IdentityServer.

• Un microservicio de catálogo, que es un servicio de creación, lectura, actualización y eliminación (CRUD) basado en datos que
consume una base de datos de SQL Server usando EntityFramework Core.

• Un microservicio de pedidos, que es un servicio controlado por dominio que utiliza un diseño controlado por dominio.
patrones.

• Un microservicio de cesta, que es un servicio CRUD basado en datos que utiliza Redis Cache.

Estos servicios backend se implementan como microservicios utilizando ASP.NET Core MVC y se implementan como
contenedores únicos dentro de un único host Docker. En conjunto, estos servicios backend se conocen como la aplicación de
referencia de eShopOnContainers. Las aplicaciones cliente se comunican con los servicios backend a través de una
interfaz web de Transferencia de Estado Representacional (REST). Para obtener más información sobre los
microservicios y Docker, consulte Microservicios en contenedores.

5 CAPITULO 2 | Introducción a .NET MAUI


Machine Translated by Google

Para obtener información sobre la implementación de los servicios backend, consulte Microservicios .NET:
arquitectura para aplicaciones .NET en contenedores.

Aplicación multiplataforma
Esta guía se centra en la creación de aplicaciones empresariales multiplataforma utilizando .NET MAUI y
utiliza la aplicación multiplataforma eShopOnContainers como ejemplo. La siguiente imagen muestra las páginas
de la aplicación multiplataforma eShopOnContainers que brindan la funcionalidad descrita anteriormente.

La aplicación multiplataforma consume los servicios backend proporcionados por la aplicación de referencia
eShopOnContainers. Sin embargo, se puede configurar para consumir datos de servicios simulados para aquellos
que deseen evitar la implementación de servicios backend.

La aplicación multiplataforma eShopOnContainers ejerce la siguiente funcionalidad .NET MAUI:

• XAML
• Control S

6 CAPITULO 2 | Introducción a .NET MAUI


Machine Translated by Google


Fijaciones
• Convertidores


Estilos
• animaciones

• Comandos

• Comportamientos


Desencadenantes

• Efectos

• Controles personalizados

Para obtener más información sobre esta funcionalidad, consulte la documentación de .NET MAUI. en el Centro de desarrolladores de

Microsoft y Creación de aplicaciones multiplataforma con .NET MAUI.

Además, se proporcionan pruebas unitarias para algunas de las clases en la multiplataforma eShopOnContainers.

aplicación.

Solución de aplicación multiplataforma


La solución de aplicación multiplataforma eShopOnContainers organiza el código fuente y otros recursos en múltiples proyectos. Todos los

componentes móviles principales están contenidos en un proyecto singular llamado eShopContainers. Esta es una característica introducida

con .NET 6 que permite que un proyecto apunte a múltiples resultados, lo que ayuda a eliminar la necesidad de proyectos de múltiples

plataformas que habríamos usado en Xamarin.Forms y versiones anteriores de .NET. Se incluye un proyecto adicional para pruebas

unitarias.

Si bien este proyecto tiene todos sus componentes almacenados en un proyecto singular, vale la pena considerar separarlo en varios

proyectos según sus necesidades. Por ejemplo, si tiene múltiples implementaciones de proveedores de servicios basadas en un

servicio con sus propias dependencias, puede tener sentido dividir esas implementaciones de proveedores de servicios en su propio proyecto

separado. Los buenos candidatos para la separación de proyectos incluyen modelos compartidos, implementaciones de servicios,

componentes de cliente API, bases de datos o capas de almacenamiento en caché. Cualquier lugar donde crea que la empresa

podría reutilizar un componente en otro proyecto es un candidato potencial para la separación. Estos proyectos luego se pueden empaquetar

a través de NuGet para facilitar la distribución y el control de versiones.

Todos los proyectos utilizan carpetas para organizar el código fuente y otros recursos en categorías. Las clases de la aplicación

multiplataforma eShopOnContainers se pueden reutilizar en cualquier aplicación .NET MAUI con poca o ninguna modificación.

Proyecto eShopOnContainers
El proyecto eShopOnContainers contiene las siguientes carpetas:

Carpeta Descripción

Animaciones Contiene clases que permiten consumir animaciones en XAML.

Comportamientos Contiene comportamientos que están expuestos a clases de visualización.

7 CAPITULO 2 | Introducción a .NET MAUI


Machine Translated by Google

Carpeta Descripción

Control S Contiene controles personalizados utilizados por la aplicación.

Convertidores Contiene convertidores de valores que aplican lógica personalizada a un enlace.

Excepciones Contiene la excepción ServiceAuthenticationException personalizada.

Extensiones Contiene métodos de extensión para las clases VisualElement e IEnumerable<T>.

Ayudantes Contiene clases de ayuda para la aplicación.

Modelos Contiene las clases de modelo para la aplicación.

Propiedades Contiene AssemblyInfo.cs, un archivo de metadatos de ensamblaje .NET.

Servicios Contiene interfaces y clases que implementan servicios que se proporcionan al


aplicación.

Desencadenantes Contiene el desencadenador BeginAnimation, que se usa para invocar una animación en XAML.

Validaciones Contiene clases involucradas en la validación de la entrada de datos.

ViewModels Contiene la lógica de la aplicación que está expuesta a las páginas.

Puntos de vista
Contiene las páginas de la aplicación.

Resumen
Las herramientas y plataformas de desarrollo de aplicaciones multiplataforma de Microsoft brindan una solución integral para

aplicaciones de clientes móviles B2E, B2B y B2C, brindando la capacidad de compartir código en todas las plataformas de destino (iOS, macOS,

Android y Windows) y ayudando a reducir el coste total de propiedad. Las aplicaciones pueden compartir su interfaz de usuario y código lógico de

aplicación, manteniendo al mismo tiempo la apariencia y el funcionamiento nativos de la plataforma.

Los desarrolladores de aplicaciones empresariales enfrentan varios desafíos que pueden alterar la arquitectura de la aplicación durante el desarrollo.

Por lo tanto, es importante crear una aplicación que pueda modificarse o ampliarse con el tiempo. Diseñar para tal adaptabilidad puede ser

difícil, pero generalmente implica dividir una aplicación en componentes discretos y poco acoplados que se pueden integrar fácilmente en una

aplicación.

8 CAPITULO 2 | Introducción a .NET MAUI


Machine Translated by Google

CAPÍTULO 3

Modelo­Vista­VerModelo
(MVVM)
La experiencia del desarrollador de .NET MAUI normalmente implica crear una interfaz de usuario en XAML y luego agregar código
subyacente que opere en la interfaz de usuario. Pueden surgir problemas de mantenimiento complejos a medida que las
aplicaciones se modifican y crecen en tamaño y alcance. Estos problemas incluyen el estrecho acoplamiento entre los controles de la
interfaz de usuario y la lógica empresarial, lo que aumenta el costo de realizar modificaciones en la interfaz de usuario, y la dificultad de
realizar pruebas unitarias de dicho código.

El patrón MVVM ayuda a separar claramente la lógica empresarial y de presentación de una aplicación de su interfaz de
usuario (UI). Mantener una separación clara entre la lógica de la aplicación y la interfaz de usuario ayuda a abordar
numerosos problemas de desarrollo y hace que una aplicación sea más fácil de probar, mantener y evolucionar. También
puede mejorar significativamente las oportunidades de reutilización de código y permite a los desarrolladores y
diseñadores de UI colaborar más fácilmente al desarrollar sus respectivas partes de una aplicación.

El patrón MVVM
Hay tres componentes principales en el patrón MVVM: el modelo, la vista y el modelo de vista.
Cada uno tiene un propósito distinto. El siguiente diagrama muestra las relaciones entre los tres
componentes.

Además de comprender las responsabilidades de cada componente, también es importante


comprender cómo interactúan. En un nivel alto, la vista "conoce" el modelo de vista y el modelo de
vista "conoce" el modelo, pero el modelo desconoce el modelo de vista y el modelo de vista
desconoce la vista. Por lo tanto, el modelo de vista aísla la vista del modelo y permite que el
modelo evolucione independientemente de la vista.

Los beneficios de utilizar el patrón MVVM son los siguientes:

9 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google


Si la implementación de un modelo existente encapsula la lógica empresarial existente, cambiarla puede resultar difícil o arriesgado.
En este escenario, el modelo de vista actúa como un adaptador para las clases de modelo y le impide realizar cambios
importantes en el código del modelo.

Los desarrolladores pueden crear pruebas unitarias para el modelo de vista y el modelo, sin utilizar la vista.
Las pruebas unitarias para el modelo de vista pueden ejercer exactamente la misma funcionalidad que utiliza la vista.

• La interfaz de usuario de la aplicación se puede rediseñar sin tocar el modelo de vista y el código del modelo, siempre que la vista se
implemente completamente en XAML o C#. Por lo tanto, una nueva versión de la vista debería funcionar con el modelo de vista
existente.

Los diseñadores y desarrolladores pueden trabajar de forma independiente y simultánea en sus componentes durante el
desarrollo. Los diseñadores pueden centrarse en la vista, mientras que los desarrolladores pueden trabajar en el modelo de vista y
los componentes del modelo.

La clave para utilizar MVVM de forma eficaz radica en comprender cómo factorizar el código de la aplicación en las clases correctas y

cómo interactúan las clases. Las siguientes secciones analizan las responsabilidades de cada una de las clases en el patrón MVVM.

Vista
La vista es responsable de definir la estructura, el diseño y la apariencia de lo que el usuario ve en la pantalla. Idealmente, cada vista se

define en XAML, con un código subyacente limitado que no contiene lógica empresarial. Sin embargo, en algunos casos, el código

subyacente puede contener lógica de interfaz de usuario que implementa un comportamiento visual que es difícil de expresar en XAML,

como animaciones.

En una aplicación .NET MAUI, una vista suele ser una clase derivada de ContentPage o ContentView.

Sin embargo, las vistas también se pueden representar mediante una plantilla de datos, que especifica los elementos de la interfaz de

usuario que se utilizarán para representar visualmente un objeto cuando se muestra. Una plantilla de datos como vista no tiene ningún código

subyacente y está diseñada para vincularse a un tipo de modelo de vista específico.

Consejo

Evite habilitar y deshabilitar elementos de la interfaz de usuario en el código subyacente.

Asegúrese de que los modelos de vista sean responsables de definir cambios de estado lógicos que afecten algunos aspectos de la

visualización de la vista, como si un comando está disponible o una indicación de que una operación está pendiente. Por lo tanto,

habilite y deshabilite los elementos de la interfaz de usuario vinculándolos a las propiedades del modelo de vista, en lugar de habilitarlos y

deshabilitarlos en el código subyacente.

Hay varias opciones para ejecutar código en el modelo de vista en respuesta a interacciones en la vista, como hacer clic en un botón o

seleccionar un elemento. Si un control admite comandos, la propiedad Command del control puede vincularse a una propiedad ICommand en el

modelo de vista. Cuando se invoca el comando del control, se ejecutará el código en el modelo de vista. Además de los comandos, se
pueden adjuntar comportamientos a un objeto en la vista y pueden escuchar si se invoca un comando o si se genera un evento. En

respuesta, el comportamiento puede invocar un ICommand en el modelo de vista o un método en el modelo de vista.

10 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

Ver modelo
El modelo de vista implementa propiedades y comandos a los que la vista puede vincular datos y notifica a la vista cualquier
cambio de estado a través de eventos de notificación de cambios. Las propiedades y comandos que proporciona el modelo
de vista definen la funcionalidad que ofrecerá la interfaz de usuario, pero la vista determina cómo se mostrará esa funcionalidad.

Consejo

Mantenga la interfaz de usuario receptiva con operaciones asincrónicas.

Las aplicaciones multiplataforma deben mantener el hilo de la interfaz de usuario desbloqueado para mejorar la percepción
del rendimiento del usuario. Por lo tanto, en el modelo de vista, utilice métodos asincrónicos para operaciones de E/S y genere
eventos para notificar asincrónicamente a las vistas sobre cambios de propiedades.

El modelo de vista también es responsable de coordinar las interacciones de la vista con cualquier clase de modelo que sea necesaria.
Normalmente existe una relación de uno a muchos entre el modelo de vista y las clases del modelo. El modelo de vista puede
optar por exponer las clases del modelo directamente a la vista para que los controles de la vista puedan vincular datos directamente
a ellas. En este caso, las clases de modelo deberán diseñarse para admitir el enlace de datos y los eventos de notificación de
cambios.

Cada modelo de vista proporciona datos de un modelo en un formato que la vista puede consumir fácilmente. Para lograr
esto, el modelo de vista a veces realiza una conversión de datos. Colocar esta conversión de datos en el modelo de vista es una buena
idea porque proporciona propiedades a las que se puede vincular la vista. Por ejemplo, el modelo de vista podría combinar los valores
de dos propiedades para que la vista lo muestre más fácilmente.

Consejo

Centralice las conversiones de datos en una capa de conversión.

También es posible utilizar convertidores como una capa de conversión de datos separada que se encuentra entre el modelo de
vista y la vista. Esto puede ser necesario, por ejemplo, cuando los datos requieren un formato especial que el modelo de vista no
proporciona.

Para que el modelo de vista participe en el enlace de datos bidireccional con la vista, sus propiedades deben generar el evento
PropertyChanged. Los modelos de vista satisfacen este requisito implementando la interfaz INotifyPropertyChanged y
generando el evento PropertyChanged cuando se cambia una propiedad.

Para las colecciones, se proporciona ObservableCollection<T>, fácil de ver. Esta colección implementa la notificación de cambio
de colección, lo que evita que el desarrollador tenga que implementar la interfaz INotifyCollectionChanged en las
colecciones.

Modelo
Las clases de modelo son clases no visuales que encapsulan los datos de la aplicación. Por lo tanto, se puede considerar que el
modelo representa el modelo de dominio de la aplicación, que generalmente incluye un modelo de datos junto con lógica comercial y
de validación. Ejemplos de objetos modelo incluyen objetos de transferencia de datos (DTO), objetos CLR antiguos (POCO) y
objetos de entidad y proxy generados.

11 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

Las clases de modelo se utilizan normalmente junto con servicios o repositorios que encapsulan el acceso a datos y el almacenamiento

en caché.

Conexión de modelos de vista a vistas


Los modelos de vista se pueden conectar a vistas utilizando las capacidades de enlace de datos de .NET MAUI. Hay muchos enfoques

que se pueden utilizar para construir vistas y modelos de vista y asociarlos en tiempo de ejecución. Estos enfoques se dividen en dos

categorías, conocidas como composición de vista primero y composición de primer modelo de vista. Elegir entre la composición de la vista

primero y la composición del modelo de vista primero es una cuestión de preferencia y complejidad. Sin embargo, todos los enfoques

comparten el mismo objetivo, que es que la vista tenga un modelo de vista asignado a su propiedad BindingContext.

Con la primera composición de vista, la aplicación se compone conceptualmente de vistas que se conectan a los modelos de vista de

los que dependen. El principal beneficio de este enfoque es que facilita la construcción de aplicaciones comprobables por unidad y

débilmente acopladas porque los modelos de vista no dependen de las vistas mismas. También es fácil comprender la estructura de

la aplicación siguiendo su estructura visual, en lugar de tener que realizar un seguimiento de la ejecución del código para comprender

cómo se crean y asocian las clases. Además, la primera construcción de la vista se alinea con el sistema de navegación de Microsoft Maui,

que es responsable de construir páginas cuando se produce la navegación, lo que hace que la primera composición del modelo de vista sea

compleja y desalineada con la plataforma.

Con la primera composición del modelo de vista, la aplicación se compone conceptualmente de modelos de vista, con un servicio

responsable de ubicar la vista de un modelo de vista. La primera composición del modelo de vista parece más natural para algunos

desarrolladores, ya que la creación de la vista se puede abstraer, lo que les permite centrarse en la estructura lógica no UI de la

aplicación. Además, permite que otros modelos de vista creen modelos de vista. Sin embargo, este enfoque suele ser complejo y puede

resultar difícil comprender cómo se crean y asocian las distintas partes de la aplicación.

Consejo

Mantenga los modelos de vista y las vistas independientes.

La vinculación de vistas a una propiedad en una fuente de datos debe ser la dependencia principal de la vista de su modelo de vista

correspondiente. Específicamente, no haga referencia a tipos de vista, como Button y ListView, desde los modelos de vista. Siguiendo los

principios descritos aquí, los modelos de vista se pueden probar de forma aislada, lo que reduce la probabilidad de defectos de

software al limitar el alcance.

Las siguientes secciones analizan los principales enfoques para conectar modelos de vista con vistas.

Crear un modelo de vista de forma declarativa


El enfoque más simple es que la vista cree una instancia declarativa de su modelo de vista correspondiente en XAML. Cuando se

construye la vista, también se construirá el objeto de modelo de vista correspondiente.

Este enfoque se demuestra en el siguiente ejemplo de código:

<ContentPage xmlns:local="clr­namespace:eShop">
<ContentPage.BindingContext>

12 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

<local:LoginViewModel/>
</ContentPage.BindingContext>
<!­­ Omitido por brevedad... ­­>
</Página de contenido>

Cuando se crea ContentPage, se construye automáticamente una instancia de LoginViewModel y se establece como
BindingContext de la vista.

Esta construcción declarativa y asignación del modelo de vista por parte de la vista tiene la ventaja de que es simple, pero
tiene la desventaja de que requiere un constructor predeterminado (sin parámetros) en el modelo de vista.

Crear un modelo de vista mediante programación


Una vista puede tener código en el archivo de código subyacente, lo que da como resultado que el modelo de vista se
asigne a su propiedad BindingContext. Esto normalmente se logra en el constructor de la vista, como se muestra en
el siguiente ejemplo de código:

Vista de inicio de sesión pública ()


{
InicializarComponente();
BindingContext = nuevo LoginViewModel (servicio de navegación);
}

La construcción y asignación programática del modelo de vista dentro del código subyacente de la vista tiene la ventaja de que
es simple. Sin embargo, la principal desventaja de este enfoque es que la vista debe proporcionar al modelo de vista las
dependencias necesarias. El uso de un contenedor de inyección de dependencia puede ayudar a mantener un
acoplamiento flexible entre la vista y el modelo de vista. Para obtener más información, consulte Inyección de
dependencia.

Actualización de vistas en respuesta a cambios en el


modelo o modelo de vista subyacente
Todos los modelos de vista y clases de modelo a los que puede acceder una vista deben implementar
[INotifyPropertyChanged interfaz. La implementación de esta interfaz en un modelo de vista o clase de modelo permite que la
clase proporcione notificaciones de cambios a cualquier control vinculado a datos en la vista cuando cambia el valor de la
propiedad subyacente.

Las aplicaciones deben estar diseñadas para el uso correcto de la notificación de cambio de propiedad, cumpliendo
con los siguientes requisitos:


Siempre generando un evento PropertyChanged si el valor de una propiedad pública cambia. No asuma que se
puede ignorar el evento PropertyChanged debido al conocimiento de cómo se produce el enlace XAML.


Siempre generando un evento PropertyChanged para cualquier propiedad calculada cuyos valores sean utilizados
por otras propiedades en el modelo de vista o modelo.

Siempre generando el evento PropertyChanged al final del método que realiza un cambio de propiedad, o cuando
se sabe que el objeto está en un estado seguro. Provocar el evento interrumpe el

13 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

operación invocando los controladores del evento sincrónicamente. Si esto sucede en medio de una operación,
podría exponer el objeto a funciones de devolución de llamada cuando se encuentre en un estado inseguro
y parcialmente actualizado. Además, es posible que los eventos PropertyChanged activen
cambios en cascada. Los cambios en cascada generalmente requieren que se completen las actualizaciones
antes de que sea seguro ejecutar el cambio en cascada.

Nunca generar un evento PropertyChanged si la propiedad no cambia. Esto significa que debe comparar los
valores antiguos y nuevos antes de generar el evento PropertyChanged.

Nunca genere el evento PropertyChanged durante el constructor de un modelo de vista si está
inicializando una propiedad. Los controles vinculados a datos en la vista no se habrán suscrito para recibir
notificaciones de cambios en este momento.

Nunca generar más de un evento PropertyChanged con el mismo argumento de nombre de propiedad dentro
de una única invocación sincrónica de un método público de una clase. Por ejemplo, dada una propiedad
NumberOfItems cuyo almacén de respaldo es el campo _numberOfItems, si un método incrementa
_numberOfItems cincuenta veces durante la ejecución de un bucle, solo debería generar una notificación
de cambio de propiedad en la propiedad NumberOfItems una vez, después de que se complete todo el
trabajo. Para métodos asincrónicos, genere el evento PropertyChanged para un nombre de propiedad
determinado en cada segmento sincrónico de una cadena de continuación asincrónica.

Una forma sencilla de proporcionar esta funcionalidad sería crear una extensión de la clase BindableObject.
En este ejemplo, la clase ExtendedBindableObject proporciona notificaciones de cambios, como se muestra en el
siguiente ejemplo de código:

clase abstracta pública ExtendedBindableObject : BindableObject


{
public void RaisePropertyChanged<T>(Expresión<Func<T>> propiedad)
{
var nombre = GetMemberInfo(propiedad).Nombre;
OnPropertyChanged(nombre);
}

MemberInfo privada GetMemberInfo ( expresión de expresión)


{
// Omitido por brevedad...
}
}

La clase BindableObject de .NET MAUI implementa la interfaz INotifyPropertyChanged y proporciona un método


OnPropertyChanged. La clase ExtendedBindableObject proporciona el método RaisePropertyChanged para invocar la
notificación de cambio de propiedad y, al hacerlo, utiliza la funcionalidad proporcionada por la clase BindableObject.

Las clases de modelo de vista pueden derivar de la clase ExtendedBindableObject. Por lo tanto, cada clase de
modelo de vista utiliza el método RaisePropertyChanged en la clase ExtendedBindableObject para proporcionar una
notificación de cambio de propiedad. El siguiente ejemplo de código muestra cómo la aplicación multiplataforma
eShopOnContainers invoca una notificación de cambio de propiedad mediante una expresión lambda:

bool público IsLogin


{
obtener => _isLogin;
colocar

{
_isLogin = valor;
RaisePropertyChanged(() => IsLogin);

14 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

}
}

Usar una expresión lambda de esta manera implica un pequeño costo de rendimiento porque la expresión lambda debe evaluarse

para cada llamada. Aunque el costo de rendimiento es pequeño y normalmente no afectaría a una aplicación, los costos pueden acumularse

cuando hay muchas notificaciones de cambios. Sin embargo, la ventaja de este enfoque es que proporciona seguridad de tipos en tiempo de

compilación y soporte de refactorización al cambiar el nombre de las propiedades.

Marcos MVVM
El patrón MVVM está bien establecido en .NET y la comunidad ha creado muchos marcos que ayudan a facilitar este desarrollo. Cada

marco proporciona un conjunto diferente de características, pero es estándar que proporcionen un modelo de vista común con una

implementación de la interfaz INotifyPropertyChanged. Las características adicionales de los marcos MVVM incluyen

comandos personalizados, ayudas de navegación, componentes de localización de servicios/inyección de dependencias e

integración de la plataforma UI. Si bien no es necesario utilizar estos marcos, pueden acelerar y estandarizar su desarrollo. La aplicación

multiplataforma eShopOnContainers utiliza el kit de herramientas .NET Community MVVM . Al elegir un marco, debes considerar las

necesidades de tu aplicación y las fortalezas de tu equipo. La siguiente lista incluye algunos de los marcos MVVM más comunes

para .NET MAUI.


Kit de herramientas MVVM de la comunidad .NET
• UI reactiva


Biblioteca Prisma

Interacción de la interfaz de usuario mediante comandos y comportamientos

En aplicaciones multiplataforma, las acciones generalmente se invocan en respuesta a una acción del usuario, como hacer clic en un

botón, que se puede implementar creando un controlador de eventos en el archivo de código subyacente. Sin embargo, en el patrón MVVM,

la responsabilidad de implementar la acción recae en el modelo de vista y se debe evitar colocar código en el código subyacente.

Los comandos proporcionan una manera conveniente de representar acciones que pueden vincularse a controles en la interfaz de usuario.

Encapsulan el código que implementa la acción y ayudan a mantenerlo desacoplado de su representación visual en la vista. De esta manera,

sus modelos de vista se vuelven más portátiles para nuevas plataformas, ya que no dependen directamente de los eventos proporcionados

por el marco de interfaz de usuario de la plataforma. .NET MAUI incluye controles que se pueden conectar mediante declaración a un comando,

y estos controles invocarán el comando cuando el usuario interactúe con el control.

Los comportamientos también permiten que los controles se conecten de forma declarativa a un comando. Sin embargo, los comportamientos

se pueden usar para invocar una acción asociada con una variedad de eventos generados por un control. Por lo tanto, los

comportamientos abordan muchos de los mismos escenarios que los controles habilitados por comandos, al tiempo que brindan un

mayor grado de flexibilidad y control. Además, los comportamientos también se pueden utilizar para asociar objetos o métodos de comando con

controles que no fueron diseñados específicamente para interactuar con comandos.

15 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

Implementando comandos
Los modelos de vista normalmente exponen propiedades públicas, para vincularse desde la vista, que
implementan la interfaz ICommand. Muchos controles y gestos de .NET MAUI proporcionan una propiedad Command,
que puede ser datos vinculados a un objeto ICommand proporcionado por el modelo de vista. El control de botón es
uno de los controles más utilizados y proporciona una propiedad de comando que se ejecuta cuando se hace
clic en el botón.

Nota

Si bien es posible exponer la implementación real de la interfaz ICommand que utiliza su modelo de vista (por
ejemplo, Command<T> o RelayCommand), se recomienda exponer sus comandos públicamente como
ICommand. De esta manera, si alguna vez necesita cambiar la implementación en una fecha posterior, podrá
cambiarla fácilmente.

La interfaz ICommand define un método Execute, que encapsula la operación en sí, un método CanExecute,
que indica si el comando se puede invocar y un evento CanExecuteChanged que ocurre
cuando ocurren cambios que afectan si el comando debe ejecutarse. En la mayoría de los casos, solo
proporcionaremos el método Ejecutar para nuestros comandos. Para obtener una descripción más detallada
de ICommand, consulte la sección Comando documentación para .NET MAUI.

Con .NET MAUI se proporcionan las clases Command y Command<T> que implementan la interfaz
ICommand, donde T es el tipo de argumentos para Execute y CanExecute. Command y Command<T> son
implementaciones básicas que proporcionan el conjunto mínimo de funcionalidades necesarias para la interfaz
ICommand.

Nota

Muchos marcos MVVM ofrecen implementaciones más ricas en funciones de la interfaz ICommand.

El constructor Command o Command<T> requiere un objeto de devolución de llamada Action que se llama cuando
se invoca el método ICommand.Execute. El método CanExecute es un parámetro de constructor opcional y es un
Func que devuelve un bool.

La aplicación multiplataforma eShopOnContainers utiliza RelayCommand y AsyncRelayCommand. El principal


beneficio para las aplicaciones modernas es que AsyncRelayCommand proporciona una mejor funcionalidad para
operaciones asincrónicas.

El siguiente código muestra cómo se construye una instancia de Comando, que representa un comando de
registro, especificando un delegado al método del modelo de vista de Registro:
public ICommand RegisterCommand { get; }

El comando se expone a la vista a través de una propiedad que devuelve una referencia a un ICommand.
Cuando se llama al método Execute en el objeto Command, simplemente reenvía la llamada al método en el modelo
de vista a través del delegado que se especificó en el constructor Command. Un comando puede invocar un método
asincrónico utilizando las palabras clave async y await al especificar el delegado de ejecución del comando. Esto
indica que la devolución de llamada es una tarea y se debe esperar. Para

dieciséis
CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)
Machine Translated by Google

Por ejemplo, el siguiente código muestra cómo se construye una instancia de ICommand, que representa un comando
de inicio de sesión, especificando un delegado para el método de modelo de vista SignInAsync:

public ICommand SignInCommand { obtener; }


...
SignInCommand = nuevo AsyncRelayCommand(async () => espera SignInAsync());

Los parámetros se pueden pasar a las acciones Execute y CanExecute mediante la clase
AsyncRelayCommand<T> para crear una instancia del comando. Por ejemplo, el siguiente código muestra cómo se
usa una instancia de AsyncRelayCommand<T> para indicar que el método NavigateAsync requerirá un argumento
de tipo cadena:

ICommand público NavigateCommand { get; }

...
NavigateCommand = nuevo AsyncRelayCommand<string>(NavigateAsync);

Tanto en las clases RelayCommand como RelayCommand<T>, el delegado al método CanExecute en cada constructor
es opcional. Si no se especifica un delegado, el comando devolverá verdadero para CanExecute. Sin
embargo, el modelo de vista puede indicar un cambio en el estado CanExecute del comando llamando al método
ChangeCanExecute en el objeto Command. Esto hace que se genere el evento CanExecuteChanged. Cualquier control
de UI vinculado al comando actualizará su estado habilitado para reflejar la disponibilidad del comando vinculado a
datos.

Invocar comandos desde una vista


El siguiente ejemplo de código muestra cómo una cuadrícula en LoginView se vincula con RegisterCommand en el
Clase LoginViewModel utilizando una instancia de TapGestureRecognizer:

<Grid Grid.Column="1" HorizontalOptions="Centro">


<Label Text="REGISTRAR" TextColor="Gris"/>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding RegisterCommand}" NumberOfTapsRequired="1"
/>
</Grid.GestureRecognizers>
</Grid>

Opcionalmente, también se puede definir un parámetro de comando utilizando la propiedad CommandParameter. El


tipo de argumento esperado se especifica en los métodos de destino Execute y CanExecute.
TapGestureRecognizer invocará automáticamente el comando de destino cuando el usuario interactúe con el control
adjunto. El CommandParameter, si se proporciona, se pasará como argumento al delegado de ejecución del
comando.

Implementar comportamientos
Los comportamientos permiten agregar funcionalidad a los controles de la interfaz de usuario sin tener que
subclasificarlos. En cambio, la funcionalidad se implementa en una clase de comportamiento y se adjunta al control
como si fuera parte del control mismo. Los comportamientos le permiten implementar código que normalmente tendría
que escribir como código subyacente, porque interactúa directamente con la API del control, de tal manera que puede ser conciso.

17 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

adjunto al control y empaquetado para su reutilización en más de una vista o aplicación. En el contexto de MVVM, los
comportamientos son un enfoque útil para conectar controles a comandos.

Un comportamiento que está asociado a un control a través de propiedades adjuntas se conoce como comportamiento adjunto.
Luego, el comportamiento puede usar la API expuesta del elemento al que está adjunto para agregar funcionalidad a ese
control, u otros controles, en el árbol visual de la vista.

Un comportamiento .NET MAUI es una clase que deriva de la clase Behavior o Behavior<T>, donde T es el tipo de control
al que se debe aplicar el comportamiento. Estas clases proporcionan los métodos OnAttachedTo y OnDetachingFrom,
que deben anularse para proporcionar la lógica que se ejecutará cuando el comportamiento se adjunte y se separe de los
controles.

En la aplicación multiplataforma eShopOnContainers, la clase BindableBehavior<T> deriva de la clase Behavior<T>.


El propósito de la clase BindableBehavior<T> es proporcionar una clase base para comportamientos .NET MAUI que
requieren que el BindingContext del comportamiento se establezca en el control adjunto.

La clase BindableBehavior<T> proporciona un método OnAttachedTo reemplazable que establece el BindingContext


del comportamiento y un método OnDetachingFrom reemplazable que limpia BindingContext.

La aplicación multiplataforma eShopOnContainers incluye un EventToCommandBehavior clase proporcionada por el kit


de herramientas de la comunidad MAUI. EventToCommandBehavior ejecuta un comando en respuesta a un evento
que ocurre. Esta clase deriva de la clase BaseBehavior<View> para que el comportamiento pueda vincularse y ejecutar
un ICommand especificado por una propiedad Command cuando se consume el comportamiento. El siguiente ejemplo de
código muestra la clase EventToCommandBehavior:

/// <resumen>
/// El <see cref="EventToCommandBehavior"/> es un comportamiento que permite al usuario invocar un <see cref="ICommand"/
> a través de un evento. Está diseñado para asociar comandos a eventos expuestos por controles que no fueron
diseñados para admitir comandos. Le permite asignar cualquier evento arbitrario en un control a un Comando.

/// </summary>
clase pública EventToCommandBehavior : BaseBehavior<VisualElement>
{
// Omitido por brevedad...

/// <heredardoc/>
anulación protegida nula OnAttachedTo (VisualElement vinculable)
{
base.OnAttachedTo(enlazable);
RegistrarEvento();
}

/// <heredardoc/>
anulación protegida nula OnDetachingFrom (VisualElement vinculable)
{
Anular el registro de evento();
base.OnDetachingFrom(enlazable);
}

vacío estático OnEventNamePropertyChanged (BindableObject vinculable, objeto oldValue, objeto


nuevo valor)
=> ((EventToCommandBehavior)bindable).RegisterEvent();

void RegistroEvento()
{

18 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

Anular el registro de evento();

var nombreEvento = nombreEvento;


if (La vista es nula || string.IsNullOrWhiteSpace(eventName)) {

devolver;
}

eventInfo = View.GetType()?.GetRuntimeEvent(eventName) ?? throw new


ArgumentException($"{nameof(EventToCommandBehavior)}: No se pudo resolver el evento.",
nameof(EventName));

ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);

eventHandler = eventHandlerMethodInfo.CreateDelegate(eventInfo.EventHandlerType, esto) ?

lanzar nueva ArgumentException ($"{nameof(EventToCommandBehavior)}: No se pudo crear


controlador de eventos.", nombre de (Nombre del evento));

eventInfo.AddEventHandler (Ver, eventHandler);


}

void UnregisterEvent() {

si (eventInfo no es nulo && eventHandler no es nulo) {

eventInfo.RemoveEventHandler (Ver, eventHandler);


}

información del evento =


nulo; manejador de eventos = nulo;
}

/// <summary> ///


Método virtual que se ejecuta cuando se invoca un comando /// </summary> ///
<param
name="sender"></param> /// <param
name="eventArgs" ></param>
[Microsoft.Maui.Controls.Internals.Preserve(Conditional = true)] vacío virtual protegido
OnTriggerHandled(objeto? remitente = nulo, objeto? eventArgs = nulo) {

parámetro var = parámetro de comando


?? EventArgsConverter?.Convert(eventArgs, tipo de (objeto), nulo, nulo);

comando var = comando; si


(comando?.CanExecute(parámetro) ?? falso) {

comando.Ejecutar(parámetro);
}
}
}

Los métodos OnAttachedTo y OnDetachingFrom se utilizan para registrar y cancelar el registro de un


controlador de eventos para el evento definido en la propiedad EventName. Luego, cuando se
activa el evento, se invoca el método OnTriggerHandled, que ejecuta el comando.

19 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

La ventaja de utilizar EventToCommandBehavior para ejecutar un comando cuando se activa un evento es que los comandos se pueden asociar

con controles que no fueron diseñados para interactuar con comandos. Además, esto mueve el código de manejo de eventos a modelos de

visualización, donde se puede probar unitariamente.

Invocar comportamientos desde una vista


EventToCommandBehavior es particularmente útil para adjuntar un comando a un control que no admite comandos. Por ejemplo,

LoginView usa EventToCommandBehavior para ejecutar ValidateCommand cuando el usuario cambia el valor de su contraseña, como se

muestra en el siguiente código:

<Entrada
EsContraseña="Verdadero"
Text="{Contraseña de enlace.Valor, Modo=Bidireccional}">
<!­­ Omitido por brevedad... ­­>
<Entrada.Comportamientos>
<mct:EventToCommandBehavior
NombreEvento="TextoCambiado"
Comando="{Binding ValidateCommand}" />
</Entry.Behaviors>
<!­­ Omitido por brevedad... ­­>
</Entrada>

En tiempo de ejecución, EventToCommandBehavior responderá a la interacción con la entrada. Cuando un usuario escribe en el campo

Entrada, se activará el evento TextChanged, que ejecutará ValidateCommand en LoginViewModel. De forma predeterminada, los argumentos del

evento se pasan al comando. Si es necesario, la propiedad EventArgsConverter se puede utilizar para convertir los EventArgs proporcionados

por el evento en un valor que el comando espera como entrada.

Para obtener más información sobre comportamientos, consulte Comportamientos. en el Centro de desarrolladores de .NET MAUI.

Resumen
El patrón Model­View­ViewModel (MVVM) ayuda a separar claramente la lógica empresarial y de presentación de una aplicación de su interfaz

de usuario (UI). Mantener una separación clara entre la lógica de la aplicación y la interfaz de usuario ayuda a abordar numerosos problemas

de desarrollo y hace que una aplicación sea más fácil de probar, mantener y evolucionar. También puede mejorar significativamente las

oportunidades de reutilización de código y permite a los desarrolladores y diseñadores de UI colaborar más fácilmente al desarrollar

sus respectivas partes de un

aplicación.

Usando el patrón MVVM, la interfaz de usuario de la aplicación y la presentación subyacente y la lógica de negocios se separan en tres clases

separadas: la vista, que encapsula la interfaz de usuario y la lógica de la interfaz de usuario; el modelo de vista, que encapsula la lógica y

el estado de la presentación; y el modelo, que encapsula la lógica empresarial y los datos de la aplicación.

20 CAPÍTULO 3 | Modelo­Vista­VerModelo (MVVM)


Machine Translated by Google

CAPÍTULO 4

Inyección de dependencia
Normalmente, se invoca un constructor de clase al crear una instancia de un objeto, y cualquier valor que el
objeto necesite se pasa como argumento al constructor. Este es un ejemplo de inyección de dependencia
conocida como inyección de constructor. Las dependencias que necesita el objeto se inyectan en el constructor.

Al especificar dependencias como tipos de interfaz, la inyección de dependencias permite desacoplar los
tipos concretos del código que depende de estos tipos. Generalmente utiliza un contenedor que
contiene una lista de registros y asignaciones entre interfaces y tipos abstractos, y los tipos
concretos que implementan o amplían estos tipos.

También existen otros tipos de inyección de dependencia, como la inyección de establecimiento de propiedades y la inyección
de llamadas a métodos, pero se ven con menos frecuencia. Por lo tanto, este capítulo se centrará únicamente en realizar
la inyección de constructor con un contenedor de inyección de dependencia.

Introducción a la inyección de dependencia.


La inyección de dependencia es una versión especializada del patrón de Inversión de Control (IoC), donde la
preocupación que se invierte es el proceso de obtener la dependencia requerida. Con la inyección de
dependencias, otra clase es responsable de inyectar dependencias en un objeto en tiempo de ejecución.
El siguiente ejemplo de código muestra cómo se estructura la clase ProfileViewModel cuando se utiliza la inyección
de dependencia:

ISettingsService privado de solo lectura _settingsService;


privado de solo lectura IAppEnvironmentService _appEnvironmentService;

Modelo de vista de perfil público (


IAppEnvironmentService aplicaciónEnvironmentService,
IDialogService servicio de diálogo,
INavigationService servicio de navegación,
ISettingsService configuraciónServicio)
: base (servicio de diálogo, servicio de navegación, servicio de configuración)
{
_appEnvironmentService = appEnvironmentService;
_settingsService = servicio de configuración;

// Omitido por brevedad


}

El constructor ProfileViewModel recibe múltiples instancias de objetos de interfaz como argumentos


inyectados por otra clase. La única dependencia en la clase ProfileViewModel está en los tipos de interfaz.
Por lo tanto, la clase ProfileViewModel no tiene ningún conocimiento de la clase responsable de crear
instancias de los objetos de la interfaz. La clase responsable de crear instancias de los objetos de la
interfaz e insertarlos en la clase ProfileViewModel se conoce como contenedor de inyección de dependencia.

21 CAPITULO 4 | Inyección de dependencia


Machine Translated by Google

Los contenedores de inyección de dependencia reducen el acoplamiento entre objetos al proporcionar una función para
crear instancias de clases y administrar su vida útil en función de la configuración del contenedor.
Durante la creación del objeto, el contenedor inyecta en él cualquier dependencia que el objeto requiera. Si esas dependencias aún
no se han creado, el contenedor crea y resuelve sus dependencias primero.

Existen varias ventajas al utilizar un contenedor de inyección de dependencia:

• Un contenedor elimina la necesidad de que una clase ubique sus dependencias y administre su vida útil.

• Un contenedor permite el mapeo de dependencias implementadas sin afectar la clase.

• Un contenedor facilita la capacidad de prueba al permitir burlarse de las dependencias.

• Un contenedor aumenta la capacidad de mantenimiento al permitir que se agreguen fácilmente nuevas clases a la aplicación.

En el contexto de una aplicación .NET MAUI que usa MVVM, normalmente se usará un contenedor de inyección de dependencia
para registrar y resolver vistas, registrar y resolver modelos de vista y para registrar servicios e inyectarlos en modelos de vista.

Hay muchos contenedores de inyección de dependencias disponibles en .NET; la aplicación multiplataforma eShopOnContainers
utiliza Microsoft.Extensions.DependencyInjection para administrar la creación de instancias de vistas, modelos de vista y clases
de servicio en la aplicación. Microsoft.Extensions.DependencyInjection facilita la creación de aplicaciones ligeramente
acopladas y proporciona todas las características que se encuentran comúnmente en los contenedores de inyección de
dependencias, incluidos métodos para registrar asignaciones de tipos e instancias de objetos, resolver objetos, administrar
la duración de los objetos e inyectar objetos dependientes en constructores de objetos que se resuelve. Para obtener más
información sobre Microsoft.Extensions.DependencyInjection, consulte Inyección de dependencia en .NET.

En .NET MAUI, la clase MauiProgram llamará al método CreateMauiApp para crear un objeto MauiAppBuilder. El
objeto MauiAppBuilder tiene una propiedad Servicios de tipo IServiceCollection, que proporciona un lugar para registrar nuestros
componentes, como vistas, modelos de vista y servicios para inyección de dependencia. Cualquier componente registrado
con la propiedad Servicios se proporcionará al contenedor de inyección de dependencia cuando se llame al método
MauiAppBuilder.Build.

En tiempo de ejecución, el contenedor debe saber qué implementación de los servicios se están solicitando para poder crear
instancias para los objetos solicitados. En la aplicación multiplataforma eShopOnContainers, las interfaces IAppEnvironmentService,
IDialogService, INavigationService e ISettingsService deben resolverse antes de poder crear una instancia de un objeto
ProfileViewModel. Esto implica que el contenedor realice las siguientes acciones:


Decidir cómo crear una instancia de un objeto que implemente la interfaz. Esto se conoce como registro.


Crear una instancia del objeto que implementa la interfaz requerida y el objeto ProfileViewModel. Esto se conoce
como resolución.

Finalmente, la aplicación terminará de usar el objeto ProfileViewModel y estará disponible para la recolección de basura. En
este punto, el recolector de basura debe deshacerse de cualquier implementación de interfaz de corta duración si otras clases
no comparten la misma instancia.

22 CAPITULO 4 | Inyección de dependencia


Machine Translated by Google

Registro
Antes de poder inyectar dependencias en un objeto, primero se deben registrar los tipos de dependencias
en el contenedor. Registrar un tipo implica pasar al contenedor una interfaz y un tipo concreto que implementa
la interfaz.

Hay dos formas de registrar tipos y objetos en el contenedor mediante código:


• Registre un tipo o mapeo con el contenedor. Esto se conoce como registro transitorio. Cuando sea
necesario, el contenedor creará una instancia del tipo especificado.
• Registre un objeto existente en el contenedor como singleton. Cuando sea necesario, el contenedor
devolverá una referencia al objeto existente.

Nota

Los contenedores de inyección de dependencia no siempre son adecuados. La inyección de dependencia


introduce complejidad y requisitos adicionales que podrían no ser apropiados o útiles para aplicaciones pequeñas.
Si una clase no tiene dependencias, o no es una dependencia para otros tipos, puede que no tenga sentido
colocarla en el contenedor. Además, si una clase tiene un único conjunto de dependencias que son parte integral
del tipo y nunca cambiarán, puede que no tenga sentido colocarla en el contenedor.

El registro de tipos que requieren inyección de dependencia debe realizarse con un único método en una
aplicación. Este método debe invocarse al principio del ciclo de vida de la aplicación para garantizar que
conozca las dependencias entre sus clases. La aplicación multiplataforma eShopOnContainers realiza esto
mediante el método MauiProgram.CreateMauiApp. El siguiente ejemplo de código muestra
cómo la aplicación multiplataforma eShopOnContainers declara CreateMauiApp en la clase MauiProgram:

clase estática pública MauiProgram


{
MauiApp estática pública CreateMauiApp()
=> MauiApp.CreateBuilder()
.UseMauiApp<Aplicación>()
// Omitido por
brevedad .RegisterAppServices()
.RegisterViewModels()
.RegisterViews()
.Construir();
}

El método MauiApp.CreateBuilder crea un objeto MauiAppBuilder que podemos usar para registrar nuestras
dependencias. Es necesario registrar muchas dependencias en la aplicación multiplataforma
eShopOnContainers, por lo que se crearon los métodos de extensión RegisterAppServices, RegisterViewModels
y RegisterViews para ayudar a proporcionar un flujo de trabajo de registro organizado y fácil de mantener. El
siguiente código muestra el método RegisterViewModels:

MauiAppBuilder estático público RegisterViewModels (este MauiAppBuilder mauiAppBuilder)


{
mauiAppBuilder.Services.AddSingleton<ViewModels.MainViewModel>();
mauiAppBuilder.Services.AddSingleton<ViewModels.LoginViewModel>();
mauiAppBuilder.Services.AddSingleton<ViewModels.BasketViewModel>();
mauiAppBuilder.Services.AddSingleton<ViewModels.CatalogViewModel>();
mauiAppBuilder.Services.AddSingleton<ViewModels.ProfileViewModel>();

23 CAPITULO 4 | Inyección de dependencia


Machine Translated by Google

mauiAppBuilder.Services.AddTransient<ViewModels.CheckoutViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.OrderDetailViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.SettingsViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.CampaignViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.CampaignDetailsViewModel>();

devolver mauiAppBuilder;
}

Este método recibe una instancia de MauiAppBuilder y podemos usar la propiedad Servicios para registrar nuestros modelos
de vista. Dependiendo de las necesidades de su aplicación, es posible que necesite agregar servicios con diferentes
duraciones. La siguiente tabla proporciona información sobre cuándo es posible que desee elegir estas diferentes duraciones
de registro:

Método Descripción

Agregar Singleton<T> Creará una única instancia del objeto que permanecerá
durante toda la vida útil de la aplicación.

AgregarTransiente<T> Creará una nueva instancia del objeto cuando se solicite


durante la resolución. Los objetos transitorios no tienen
una vida útil predefinida, pero normalmente seguirán
la vida útil de su host.

Nota

Los modelos de vista no heredan de una interfaz, por lo que solo necesitan que se les proporcione su tipo concreto a los
métodos AddSingleton<T> y AddTransient<T>.

CatalogViewModel se usa cerca de la raíz de la aplicación y siempre debe estar disponible, por lo que es
beneficioso registrarlo con AddSingleton<T>. A otros modelos de vista, como CheckoutViewModel y OrderDetailViewModel,
se navega según la situación o se utilizan más adelante en la aplicación. Supongamos que sabe que tiene un componente
que quizás no siempre se utilice. En ese caso, si requiere mucha memoria o computación o requiere datos justo a tiempo,
puede ser un mejor candidato para el registro AddTransient<T>.

Otra forma común de agregar servicios es utilizar los métodos AddSingleton<TService, TImplementation> y
AddTransient<TService, TImplementation>. Estos métodos toman dos tipos de entrada: la definición de interfaz y la
implementación concreta. Este tipo de registro es mejor para los casos en los que implementa servicios basados en interfaces.
En el siguiente ejemplo de código, registramos nuestra interfaz ISettingsService utilizando la implementación de
SettingsService:

MauiAppBuilder estático público RegisterAppServices (este MauiAppBuilder mauiAppBuilder)


{
mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
// Omitido por brevedad...
}

Una vez que se hayan registrado todos los servicios, se debe llamar al método MauiAppBuilder.Build para crear nuestra
MauiApp y completar nuestro contenedor de inyección de dependencia con todos los servicios registrados.

24 CAPITULO 4 | Inyección de dependencia


Machine Translated by Google

Importante

Una vez que se haya llamado al método Build, los servicios registrados con el contenedor de inyección de
dependencia serán inmutables y ya no podrán actualizarse ni modificarse.

Resolución
Una vez registrado un tipo, se puede resolver o inyectar como una dependencia. Cuando se resuelve un tipo
y el contenedor necesita crear una nueva instancia, inyecta todas las dependencias en la instancia.

Generalmente, cuando se resuelve un tipo, sucede una de tres cosas:

1. Si el tipo no se ha registrado, el contenedor genera una excepción.


2. Si el tipo se ha registrado como singleton, el contenedor devuelve la instancia singleton. Si es la primera
vez que se solicita el tipo, el contenedor lo crea si es necesario y mantiene una referencia a él.

3. Si el tipo se ha registrado como transitorio, el contenedor devuelve una nueva instancia y no mantiene una
referencia a ella.

.NET MAUI ofrece varias formas de resolver componentes registrados según sus necesidades. La forma más directa
de obtener acceso al contenedor de inyección de dependencia es desde un elemento utilizando
Handler.MauiContext.Services. Un ejemplo de esto se muestra a continuación:

var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();

Esto puede resultar útil si necesita resolver un servicio desde dentro de un Elemento o desde fuera del constructor
de su Elemento.

Precaución

Existe la posibilidad de que la propiedad Handler de su Elemento sea nula, así que tenga en cuenta que es posible que
deba manejar esas situaciones. Para obtener más información, consulte Ciclo de vida del controlador. en el
Centro de documentación de Microsoft.

Si usa el control Shell para .NET MAUI, implícitamente llamará al contenedor de inyección de dependencia para
crear nuestros objetos durante la navegación. Al configurar nuestro control Shell, el método Routing.RegisterRoute
vinculará una ruta de ruta a una Vista como se muestra en el siguiente ejemplo:

Routing.RegisterRoute("Filtro", tipo de(FiltersView));

Durante la navegación del Shell, buscará registros de FiltersView y, si encuentra alguno, creará esa vista e
inyectará las dependencias en el constructor. Como se muestra en el ejemplo de código siguiente,
CatalogViewModel se inyectará en FiltersView:

espacio de nombres eShopOnContainers.Views;

FiltersView de clase parcial pública : ContentPage


{
FiltersView público (CatalogViewModel viewModel)
{

25 CAPITULO 4 | Inyección de dependencia


Machine Translated by Google

BindingContext = verModelo;

InicializarComponente();
}
}

Consejo

El contenedor de inyección de dependencia es excelente para crear instancias de modelo de vista. Si un modelo de vista tiene
dependencias, se encargará de la creación e inyección de cualquier servicio requerido. Solo asegúrese de registrar sus modelos
de vista y cualquier dependencia que puedan tener con el método CreateMauiApp en la clase MauiProgram.

Resumen
La inyección de dependencia permite desacoplar tipos concretos del código que depende de estos tipos. Normalmente utiliza un
contenedor que contiene una lista de registros y asignaciones entre interfaces y tipos abstractos, y los tipos concretos que
implementan o amplían estos tipos.

Microsoft.Extensions.DependencyInjection facilita la creación de aplicaciones poco acopladas y proporciona todas las características
que se encuentran comúnmente en los contenedores de inyección de dependencias, incluidos métodos para registrar asignaciones
de tipos e instancias de objetos, resolver objetos, administrar la duración de los objetos e inyectar objetos dependientes en los
constructores de los objetos que resuelve. .

26 CAPITULO 4 | Inyección de dependencia


Machine Translated by Google

CAPÍTULO 5

Comunicación entre
componentes
débilmente acoplados
El patrón de publicación­suscripción es un patrón de mensajería en el que los editores envían mensajes sin
conocer a ningún receptor, conocido como suscriptores. De manera similar, los suscriptores escuchan
mensajes específicos, sin conocer a ningún editor.

Los eventos en .NET implementan el patrón de publicación­suscripción y son el enfoque más simple para
una capa de comunicación entre componentes si no se requiere un acoplamiento flexible, como un control y la
página que lo contiene. Sin embargo, las duraciones del editor y del suscriptor están unidas por
referencias de objetos entre sí, y el tipo de suscriptor debe tener una referencia al tipo de editor. Esto puede
crear problemas de administración de memoria, especialmente cuando hay objetos de corta duración que se
suscriben a un evento de un objeto estático o de larga duración. Si no se elimina el controlador de eventos,
el suscriptor se mantendrá vivo mediante la referencia a él en el editor, y esto evitará o retrasará la recolección
de basura del suscriptor.

Introducción a MVVM Toolkit Messenger


La interfaz IMessenger de MVVM Toolkit describe el patrón de publicación­suscripción, lo que permite la
comunicación basada en mensajes entre componentes que son incómodos de vincular por objeto y
referencias de tipo. Este mecanismo permite a los editores y suscriptores comunicarse sin tener una referencia
directa entre sí, lo que ayuda a reducir las dependencias entre componentes y, al mismo tiempo, permite
que los componentes se desarrollen y prueben de forma independiente.

Nota

MVVM Toolkit Messenger es parte del paquete CommunityToolkit.Mvvm. Para obtener información sobre cómo
agregar el paquete a su proyecto, consulte Introducción al kit de herramientas MVVM. en el Centro de
desarrolladores de Microsoft.

27 CAPÍTULO 5 | Comunicación entre componentes débilmente acoplados


Machine Translated by Google

Advertencia

.NET MAUI contiene una clase MessagingCenter incorporada cuyo uso ya no se recomienda y debe
migrarse a MVVM Toolkit Messenger.

La interfaz IMessenger permite la funcionalidad de publicación y suscripción de multidifusión. Esto significa


que puede haber varios editores que publiquen un solo mensaje y puede haber varios suscriptores
escuchando el mismo mensaje. La siguiente imagen ilustra esta relación:

Hay dos implementaciones de la interfaz IMessenger que vienen con el paquete


CommunityToolkit.Mvvm. WeakReferenceMessenger utiliza referencias débiles que pueden resultar en una
limpieza más fácil para los suscriptores de mensajes. Esta es una buena opción si tus suscriptores no tienen
un ciclo de vida claramente definido. StrongReferenceMessenger utiliza referencias sólidas que pueden dar
como resultado un mejor rendimiento y una vida útil de la suscripción controlada más claramente. Si tiene un
flujo de trabajo con una vida útil muy controlada (por ejemplo, una suscripción vinculada a los métodos
OnAppearing y OnDisappearing de una página), StrongReferenceManager puede ser una mejor opción, si el
rendimiento es un problema. Ambas implementaciones están disponibles con implementaciones
predeterminadas listas para usar haciendo referencia a WeakReferenceMessenger.Default o StrongReferenceMessenger.Default.

Nota

Si bien la interfaz IMessenger permite la comunicación entre clases débilmente acopladas, no ofrece la única
solución arquitectónica a este problema. Por ejemplo, la comunicación entre un modelo de vista y una vista
también se puede lograr mediante el motor de enlace y mediante notificaciones de cambio de propiedad.
Además, la comunicación entre dos modelos de vista también se puede lograr pasando datos durante la
navegación.

La aplicación multiplataforma eShopOnContainers utiliza la clase WeakReferenceMessenger para comunicarse


entre componentes débilmente acoplados. La aplicación define un único mensaje llamado AddProductMessage.
AddProductMessage lo publica la clase CatalogViewModel cuando se agrega un artículo a la cesta de la
compra. A cambio, la clase CatalogView se suscribe al mensaje y lo utiliza para resaltar el producto agregado
con animación en respuesta.

En la aplicación multiplataforma eShopOnContainers, WeakReferenceMessenger se utiliza para actualizar la


interfaz de usuario en respuesta a una acción que ocurre en otra clase. Por lo tanto, los mensajes se publican
desde el hilo en el que se ejecuta la clase y los suscriptores reciben el mensaje en el mismo hilo.

28 CAPÍTULO 5 | Comunicación entre componentes débilmente acoplados


Machine Translated by Google

Consejo

Dirige la interfaz de usuario o el hilo principal al realizar actualizaciones de la interfaz de usuario. Si no se realizan
actualizaciones de las interfaces de usuario en este hilo, la aplicación puede fallar o volverse inestable.

Si se requiere un mensaje enviado desde un subproceso en segundo plano para actualizar la interfaz de usuario, procese el
mensaje en el subproceso de la interfaz de usuario en el suscriptor invocando el método MainThread.BeginInvokeOnMainThread.

Para obtener más información sobre Messenger, consulte Messenger. en el Centro de desarrolladores de Microsoft.

Definiendo un mensaje
Los mensajes de IMessenger son objetos personalizados que proporcionan cargas útiles personalizadas. El siguiente ejemplo
de código muestra el mensaje AddProductMessage definido dentro de la aplicación multiplataforma eShopOnContainers:

clase pública AddProductMessage : ValueChangedMessage<int>


{
AddProductMessage público ( recuento int) : base (recuento)
{
}
}

La clase base se define usando ValueChangedMessage<T> donde T puede ser de cualquier tipo necesario para pasar
datos. Tanto los publicadores de mensajes como los suscriptores pueden esperar mensajes de un tipo específico (por
ejemplo, AddProductMessage). Esto puede ayudar a garantizar que ambas partes hayan acordado un contrato de mensajería
y que los datos proporcionados con ese contrato sean coherentes. Además, este enfoque proporciona soporte de refactorización
y seguridad de tipos en tiempo de compilación.

Publicar un mensaje
Para publicar un mensaje, necesitaremos utilizar el método IMessenger.Send. Se puede acceder a esto más comúnmente a
través de WeakReferenceMessenger.Default.Send o
StrongReferenceMessenger.Default.Send. El mensaje enviado puede ser de cualquier tipo de objeto. El siguiente ejemplo de
código demuestra la publicación del mensaje AddProduct:

WeakReferenceMessenger.Default.Send (nuevos mensajes.AddProductMessage (BadgeCount));

En este ejemplo, el método Send especifica proporciona una nueva instancia del objeto AddProductMessage para que la reciban
los suscriptores posteriores. Se puede agregar un segundo parámetro de token adicional para usarlo cuando varios
suscriptores diferentes necesitan recibir mensajes del mismo tipo sin recibir el
mensaje equivocado.

El método Enviar publicará el mensaje y los datos de su carga útil utilizando un enfoque de disparar y olvidar.
Por lo tanto, el mensaje se envía incluso si no hay suscriptores registrados para recibir el mensaje. En esta situación, el
mensaje enviado se ignora.

29 CAPÍTULO 5 | Comunicación entre componentes débilmente acoplados


Machine Translated by Google

Suscribirse a un mensaje
Los suscriptores pueden registrarse para recibir un mensaje usando una de las sobrecargas IMessenger.Register<T>.
El siguiente ejemplo de código demuestra cómo la aplicación multiplataforma eShopOnContainers se suscribe y
procesa el mensaje AddProductMessage:

WeakReferenceMessenger.Default
.Register<CatalogView, Mensajes.AddProductMessage>(
este,
asíncrono (destinatario, mensaje) =>
{
espera destinatario.Dispatcher.DispatchAsync(
asíncrono () =>
{
aguarde destinatario.badge.ScaleTo(1.2);
espere destinatario.badge.ScaleTo(1.0);
});
});

En el ejemplo anterior, el método Register se suscribe al mensaje AddProductMessage y ejecuta un delegado de devolución
de llamada en respuesta a la recepción del mensaje. Este delegado de devolución de llamada, especificado como una expresión
lambda, ejecuta código que actualiza la interfaz de usuario.

Nota

Evite el uso de esto dentro de su delegado de devolución de llamada para evitar capturar ese objeto dentro del delegado.
Esto puede ayudar a mejorar el rendimiento. En su lugar, utilice el parámetro de destinatario.

Si se proporcionan datos de carga útil, no intente modificarlos desde un delegado de devolución de llamada porque varios
subprocesos podrían estar accediendo a los datos recibidos simultáneamente. En este escenario, los datos de la carga útil
deben ser inmutables para evitar errores de simultaneidad.

Darse de baja de un mensaje


Los suscriptores pueden darse de baja de los mensajes que ya no desean recibir. Esto se logra con una de las sobrecargas de
IMessenger.Unregister, como se demuestra en el siguiente ejemplo de código:

WeakReferenceMessenger.Default.Unregister<Messages.AddProductMessage>(este);

Nota

En este ejemplo, no es completamente necesario llamar a Unregister ya que WeakReferenceMessenger permitirá que los
objetos no utilizados se recolecten como basura. Si se utilizara StrongReferenceMessenger, se recomendaría llamar a
Anular registro para cualquier suscripción que ya no esté en uso.

En este ejemplo, la sintaxis del método Unsubscribe especifica el tipo de argumento del mensaje y el objeto destinatario que
escucha los mensajes.

30 CAPÍTULO 5 | Comunicación entre componentes débilmente acoplados


Machine Translated by Google

Resumen
La interfaz IMessenger de MVVM Toolkit describe el patrón de publicación­suscripción, lo que permite la
comunicación basada en mensajes entre componentes que son incómodos de vincular por objeto y
referencias de tipo. Este mecanismo permite a los editores y suscriptores comunicarse sin tener una
referencia entre sí, lo que ayuda a reducir las dependencias entre componentes y, al mismo tiempo, permite
que los componentes se desarrollen y prueben de forma independiente.

31 CAPÍTULO 5 | Comunicación entre componentes débilmente acoplados


Machine Translated by Google

CAPÍTULO 6

Navegación
.NET MAUI incluye soporte para la navegación de páginas, que generalmente resulta de la interacción del usuario con la
interfaz de usuario o de la aplicación misma como resultado de cambios de estado internos impulsados por la lógica.
Sin embargo, la navegación puede ser compleja de implementar en aplicaciones que utilizan el patrón Modelo­Vista­
VerModelo (MVVM), ya que se deben superar los siguientes desafíos:


Identificar la vista a la que se navegará utilizando un enfoque que no introduzca un acoplamiento estrecho ni
dependencias entre vistas.

Coordinar el proceso mediante el cual se crea una instancia e inicializa la vista a la que se va a navegar.
Cuando se utiliza MVVM, es necesario crear una instancia de la vista y el modelo de vista y asociarlos entre sí a
través del contexto vinculante de la vista. Cuando una aplicación utiliza un contenedor de inyección de
dependencia, la creación de instancias de vistas y modelos de vista puede requerir un mecanismo de construcción
específico.

• Si se debe realizar una navegación de visualización primero o una navegación de visualización primero del modelo. Con vista primero
navegación, la página a la que navegar se refiere 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 es utilizar la navegación primero en el modelo de vista, donde la página a la
que navegar hace referencia al nombre del tipo de modelo de vista.

Determinar cómo separar claramente el comportamiento de navegación de la aplicación entre las vistas y los
modelos de vista. El patrón MVVM separa la interfaz de usuario de la aplicación y su presentación y lógica de negocios,
pero no proporciona un mecanismo directo para unirlos. Sin embargo, el comportamiento de navegación de
una aplicación a menudo abarcará la interfaz de usuario y las partes de presentación de la aplicación. El usuario a
menudo iniciará la navegación desde una vista y la vista será reemplazada como resultado de la navegación. Sin
embargo, es posible que a menudo también sea necesario iniciar o coordinar la navegación 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 del pedido, los datos del pedido deberán pasarse a la vista para
que pueda mostrar los datos correctos.

Coordinar la navegación para garantizar que se obedezcan las reglas comerciales específicas. Por ejemplo, es posible
que se les solicite a los usuarios antes de salir de una vista para que puedan corregir cualquier dato no válido o que
se les solicite que envíen o descarten cualquier cambio de datos que se haya realizado dentro de la vista.

Este capítulo aborda estos desafíos presentando una clase de servicio de navegación denominada
MauiNavigationService que se utiliza para realizar la navegación de la primera página del modelo de vista.

Nota

El MauiNavigationService utilizado por la aplicación es simplista y no cubre todos los tipos de navegación posibles. Los tipos
de navegación que necesita su aplicación pueden requerir funcionalidad adicional.

32 CAPÍTULO 6 | Navegación
Machine Translated by Google

Navegando entre páginas


La lógica de navegación puede residir en el código subyacente de una vista o en un modelo de vista vinculado a datos.
Si bien colocar la lógica de navegación en una vista puede ser el enfoque más sencillo, no se puede probar
fácilmente mediante pruebas unitarias. Poner la lógica de navegación en clases de modelo de vista significa que la lógica se
puede verificar mediante pruebas unitarias. Además, el modelo de vista puede implementar lógica para controlar la
navegación y garantizar que se cumplan ciertas reglas comerciales. Por ejemplo, es posible que una aplicación no
permita al usuario salir de una página sin asegurarse primero de que los datos ingresados sean válidos.

Por lo general, se invoca un servicio de navegación desde modelos de vista para promover la capacidad de prueba. Sin
embargo, navegar a vistas desde modelos de vista requeriría que los modelos de vista hicieran referencia a vistas
y, en particular, a vistas con las que el modelo de vista activo no está asociado, lo cual no se recomienda.
Por lo tanto, el MauiNavigationService presentado aquí especifica el tipo de modelo de vista como destino al que navegar.

La aplicación multiplataforma eShopOnContainers utiliza la clase MauiNavigationService para proporcionar navegación


basada en el modelo de vista. Esta clase implementa la interfaz INavigationService, que se muestra en el siguiente ejemplo
de código:

interfaz pública INavigationService


{
Tarea InicializarAsync();

Tarea NavigateToAsync( ruta de cadena, IDictionary<cadena, objeto> routeParameters = null);

Tarea PopAsync();
}

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

Método Objetivo

InicializarAsync Realiza la navegación a una de dos páginas cuando se


inicia la aplicación.

NavigateToAsync(ruta de cadena, Realiza navegación jerárquica a una página específica


IDictionary<cadena, objeto> routeParameters = nulo) utilizando una ruta de navegación registrada.
Opcionalmente, puede pasar parámetros de ruta con
nombre para usarlos en el procesamiento en la página de destino.

PopAsync Elimina la página actual de la pila de navegación.

Nota

Una interfaz INavigationService normalmente también especificaría un método GoBackAsync, que se usa para regresar
mediante programación a la página anterior en la pila de navegación. Sin embargo, este método falta en la aplicación
multiplataforma eShopOnContainers porque no es necesario.

33 CAPÍTULO 6 | Navegación
Machine Translated by Google

Creando la instancia de MauiNavigationService


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

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

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

AppShell público (INavigationService servicio de navegación)

Esto devuelve una referencia al objeto MauiNavigationService que está almacenado en el contenedor de inyección de
dependencia.

La clase ViewModelBase almacena la instancia de MauiNavigationService en una propiedad NavigationService, de tipo


INavigationService. Por lo tanto, todas las clases de modelo de vista, que derivan de la clase ViewModelBase, pueden
usar la propiedad NavigationService para acceder a los métodos especificados por la interfaz
INavigationService.

Manejo de solicitudes de navegación


.NET MAUI proporciona múltiples 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 las páginas, hacia adelante y hacia atrás, según lo desee. La aplicación eShopOnContainers utiliza el
componente Shell como contenedor raíz de la aplicación y como host de navegación. Para obtener más información
sobre la navegación del Shell, consulte Navegación del Shell. en el Centro de desarrolladores de Microsoft.

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

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

El siguiente ejemplo de código muestra el método NavigateToAsync proporcionado por


Clase MauiNavigationService:

Tarea pública NavigateToAsync( ruta de cadena, IDictionary<cadena, objeto> routeParameters =


nulo)
{
devolver
parámetros de ruta ! = nulo
? Shell.Current.GoToAsync (ruta, parámetros de ruta)
: Shell.Current.GoToAsync(ruta);
}

El control .NET MAUI Shell 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 obtener más información, consulte Pasar parámetros durante la navegación.

34 CAPÍTULO 6 | Navegación
Machine Translated by Google

Importante

Hay varias formas de realizar la navegación en .NET MAUI. MauiNavigationService está diseñado específicamente
para funcionar con Shell. Si está utilizando NavigationPage o TabbedPage o un mecanismo de navegación
diferente, este servicio de enrutamiento deberá actualizarse para que funcione con esos componentes.

Para registrar rutas para MauiNavigationService, necesitamos proporcionar información de ruta desde XAML o
en el código subyacente. El siguiente ejemplo muestra el registro de rutas a través de XAML.

<?xml versión="1.0" codificación="UTF­8" ?>


<Concha
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">

<!­­ Omitido por brevedad ­­>

<Elemento desplegable >


<ShellContent x:Name="login" ContentTemplate="{Vistas de DataTemplate:LoginView}"
Ruta="Iniciar sesión" />
</FlyoutItem>

<TabBar x:Nombre="principal" Ruta="Principal">


<ShellContent Title="CATALOG" Route="Catalogo" Icon="{StaticResource
CatalogIconImageSource}" ContentTemplate="{Vistas de DataTemplate:CatalogView}" />
<ShellContent Título="PERFIL" Ruta="Perfil" Icono="{StaticResource
ProfileIconImageSource}" ContentTemplate="{Vistas de DataTemplate:ProfileView}" />
</TabBar>
</Shell>

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

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

Routing.RegisterRoute("Filtro", tipo de(FiltersView));


Routing.RegisterRoute("Cesta", tipo de(BasketView));

En el código subyacente, llamaremos al método Routing.RegisterRoute que toma 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 creada la vista y navegada hacia ella, se ejecutan los métodos ApplyQueryAttributes e InitializeAsync
del modelo de vista asociado a la vista. Para obtener más información, consulte Pasar parámetros durante la
navegación.

35 CAPÍTULO 6 | Navegación
Machine Translated by Google

Navegar 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 configurado,
el Shell se utilizará para controlar el registro de rutas y estará presente en la raíz de nuestra aplicación en el
futuro. Una vez creado el Shell podemos esperar a que se adjunte 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:

anulación protegida asíncrona nula OnParentSet()


{
base.OnParentSet();

si (el padre no es nulo)


{
espere _navigationService.InitializeAsync();
}
}

El método utiliza una instancia de INavigationService que se proporciona al constructor a partir de la


inyección de dependencia e invoca su método InitializeAsync.

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

Tarea pública InicializarAsync()


{
devolver NavigateToAsync(string.IsNullOrEmpty(_settingsService.AuthAccessToken)
? "//Acceso"
: "//Principal/Catálogo");
}

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

Pasar parámetros durante la navegación


El método NavigateToAsync, especificado por la interfaz INavigationService, permite especificar los datos de
navegación como un 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. A su vez, esto ejecuta el método OrderDetailAsync, que se
muestra en el siguiente ejemplo de código:

Orden de tarea asíncrona privada OrderDetailAsync (orden de pedido)


{
si (el pedido es nulo)
{
devolver;
}

espere NavigationService.NavigateToAsync(
"Detalle de la orden",

36 CAPÍTULO 6 | Navegación
Machine Translated by Google

nuevo Diccionario<cadena, objeto>{ { "Número de pedido", pedido.Número de pedido } });


}

Este método invoca la navegación a la ruta OrderDetail, pasando la información del número de pedido que el usuario
seleccionó. Cuando el marco de inyección de dependencia 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 ejemplo de código
siguiente.

[Propiedad de consulta (nombre de (Número de pedido), "Número de pedido")]


clase pública OrderDetailViewModel : ViewModelBase
{
public int Número de pedido { get; colocar; }
}

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

Invocar la navegación usando comportamientos


La navegación generalmente se activa desde una vista mediante la interacción del usuario. Por ejemplo, LoginView
realiza la navegación después de una autenticación exitosa. El siguiente ejemplo de código muestra cómo un
comportamiento invoca la navegación:

<VistaWeb>
<WebView.Comportamientos>
<comportamientos:EventToCommandBehavior
EventName="Navegando"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Comando="{Binding NavigateCommand}" />
</WebView.Comportamientos>
</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 de navegación, que ejecutará NavigateCommand en
LoginViewModel. De forma predeterminada, los argumentos del evento se pasan al comando. Estos datos se
convierten a medida que pasan entre el origen y el destino mediante el convertidor especificado en la propiedad
EventArgsConverter, que devuelve la URL de WebNavigatingEventArgs. Por lo tanto,
cuando se ejecuta NavigationCommand, la URL de la página web se pasa como parámetro a la Acción registrada.

A su vez, NavigationCommand ejecuta el método NavigateAsync, que se muestra en el siguiente ejemplo


de código:

Task NavigateAsync asíncrono privado ( URL de cadena)


{
// Omitido por brevedad.
si (!string.IsNullOrWhiteSpace(accessToken))
{

37 CAPÍTULO 6 | Navegación
Machine Translated by Google

_settingsService.AuthAccessToken = accessToken;
_settingsService.AuthIdToken = authResponse.IdentityToken;
await NavigationService.NavigateToAsync("//Main/Catalog");
}
}

Este método invoca NavigationService y enruta la aplicación a la ruta //Principal/Catalog.

Confirmar o cancelar la navegación


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

Resumen
.NET MAUI incluye soporte para la navegación de páginas, que generalmente resulta de la interacción del usuario con la interfaz
de usuario, o de la aplicación misma, como resultado de cambios de estado internos impulsados por la lógica. Sin embargo,
la navegación puede resultar compleja de implementar en aplicaciones que utilizan el patrón MVVM.

Este capítulo presentó una clase NavigationService, que se utiliza para realizar la navegación del modelo de vista primero
desde los modelos de vista. Colocar la lógica de navegación en clases de modelo de vista significa que la lógica se puede ejercitar
mediante pruebas automatizadas. Además, el modelo de vista puede implementar lógica para controlar la navegación y
garantizar que se cumplan ciertas reglas comerciales.

38 CAPÍTULO 6 | Navegación
Machine Translated by Google

CAPÍTULO 7

Validación
Cualquier aplicación que acepte aportaciones de los usuarios debe asegurarse de que sean válidas. Una aplicación
podría, por ejemplo, buscar entradas que contengan solo caracteres en un rango particular, tengan una longitud determinada
o coincidan con un formato particular. Sin validación, un usuario puede proporcionar datos que provoquen que la aplicación falle.
Una validación adecuada hace cumplir las reglas comerciales y podría ayudar a evitar que un atacante inyecte
datos maliciosos.

En el contexto del patrón Modelo­Vista­VistaModelo (MVVM), a menudo se requerirá un modelo de vista o un


modelo para realizar la validación de datos y señalar cualquier error de validación a la vista para que el usuario
pueda corregirlos. La aplicación multiplataforma eShopOnContainers realiza una validación sincrónica del lado del
cliente de las propiedades del modelo de vista y notifica al usuario sobre cualquier error de validación resaltando el
control que contiene los datos no válidos y mostrando mensajes de error que informan al usuario de por qué los
datos no son válidos. La siguiente imagen muestra las clases involucradas en la realización de
la validación en la aplicación multiplataforma eShopOnContainers.

Las propiedades del modelo de vista que requieren validación son del tipo ValidatableObject<T> y cada
instancia de ValidatableObject<T> tiene reglas de validación agregadas a su propiedad Validations. La validación es

39 CAPÍTULO 7 | Validación
Machine Translated by Google

se invoca desde el modelo de vista llamando al método Validate de la instancia ValidatableObject<T>, que recupera
las reglas de validación y las ejecuta en la propiedad ValidatableObject<T>.Value. Cualquier error de validación
se coloca en la propiedad Errors de la instancia ValidatableObject<T> y la propiedad IsValid de la instancia
ValidatableObject<T> se actualiza para indicar si la validación se realizó correctamente o falló. El siguiente
código muestra la implementación de ValidatableObject<T>:

usando CommunityToolkit.Mvvm.ComponentModel; espacio


de nombres eShopOnContainers.Validations; clase
pública ValidatableObject<T> : ObservableObject, IValidity {

privado IEnumerable<cadena> _errors; bool


privado _isValid; valor T
privado ; Lista pública
<IValidationRule<T>> Validaciones { get; } = nuevo(); Errores públicos
IEnumerable<cadena> {

obtener => _errores;


conjunto privado => SetProperty(ref _errors, valor);

} public bool Es válido {

obtener => _isValid;


conjunto privado => SetProperty(ref _isValid, valor);

} Valor T público {

obtener => _valor;


establecer => EstablecerProperty(ref _valor, valor);

} público ValidableObject() {

_isValid = verdadero;
_errors = Enumerable.Empty<cadena>();

} public bool Validar() {

Errores = Validaciones
?.Donde(v => !v.Check(Valor))
?.Select(v => v.ValidationMessage)
?.ToArray()
?? Enumerable.Empty<cadena>(); IsValid
= !Errors.Any(); devolver es
válido;
}
}

La clase ObservableObject proporciona la notificación de cambio de propiedad, por lo que un control Entry puede
vincularse a la propiedad IsValid de la instancia ValidatableObject<T> en la clase de modelo de vista para recibir una
notificación sobre si los datos ingresados son válidos o no.

Especificación de reglas de validación


Las reglas de validación se especifican mediante la creación de una clase que deriva de la interfaz IValidationRule<T>,
que se muestra en el siguiente ejemplo de código:

40 CAPÍTULO 7 | Validación
Machine Translated by Google

interfaz pública IValidationRule<T>


{
cadena Mensaje de Validación { get; colocar; }
bool Verificar ( valor T);
}

Esta interfaz especifica que una clase de regla de validación debe proporcionar un método Check booleano que se utiliza
para realizar la validación requerida y una propiedad ValidationMessage cuyo valor es el mensaje de error de validación
que se mostrará si la validación falla.

El siguiente ejemplo de código muestra la regla de validación IsNotNullOrEmptyRule<T>, que se usa para realizar la
validación del nombre de usuario y la contraseña ingresados por el usuario en LoginView cuando se usan servicios
simulados en la aplicación multiplataforma eShopOnContainers:

clase pública IsNotNullOrEmptyRule<T> : IValidationRule<T>


{
cadena pública ValidationMessage { get; colocar; }

control de bolo público ( valor T) =>


el valor es cadena str && !string.IsNullOrWhiteSpace(str);
}

El método Check devuelve un valor booleano que indica si el argumento del valor es nulo, está vacío o consta únicamente
de espacios en blanco.

Aunque no lo utiliza la aplicación multiplataforma eShopOnContainers, el siguiente ejemplo de código muestra una regla de
validación para validar direcciones de correo electrónico:

clase pública EmailRule<T> : IValidationRule<T>


{
expresión regular privada de solo lectura _regex = new(@"^([w.­]+)@([w­]+)((.(w){2,3})+)$");

cadena pública ValidationMessage { get; colocar; }

control de bolo público ( valor T) =>


el valor es cadena str && _regex.IsMatch(str);
}

El método Check devuelve un valor booleano que indica si el argumento del valor es una dirección de correo electrónico
válida o no. Esto se logra buscando en el argumento de valor la primera aparición del patrón de expresión regular
especificado en el constructor Regex. Se puede determinar si se ha encontrado el patrón de expresión regular en la
cadena de entrada comparando el valor con Regex.IsMatch.

Nota

La validación de propiedades a veces puede involucrar propiedades dependientes. Un ejemplo de propiedades


dependientes es cuando el conjunto de valores válidos para la propiedad A depende del valor particular que se ha
establecido en la propiedad B. Comprobar que el valor de la propiedad A es uno de los valores permitidos implicaría
recuperar el valor de la propiedad B. Además, cuando el valor de la propiedad B cambie, la propiedad A necesitaría
ser revalidada.

41 CAPÍTULO 7 | Validación
Machine Translated by Google

Agregar reglas de validación a una propiedad


En la aplicación multiplataforma eShopOnContainers, las propiedades del modelo de vista que requieren validación
se declaran de tipo ValidatableObject<T>, donde T es el tipo de datos que se van a validar. El siguiente ejemplo de
código muestra un ejemplo de dos de estas propiedades:

public ValidatableObject<cadena> Nombre de usuario { get; conjunto privado; }


public ValidatableObject<cadena> Contraseña { get; conjunto privado; }

Para que se produzca la validación, se deben agregar reglas de validación a la colección Validaciones de cada
ValidatableObject<T> instancia, como se demuestra en el siguiente ejemplo de código:

AddValidations vacío privado ()


{
UserName.Validations.Add(new IsNotNullOrEmptyRule<cadena>
{
ValidationMessage = "Se requiere un nombre de usuario."
});

Contraseña.Validaciones.Agregar (nueva IsNotNullOrEmptyRule<cadena>


{
ValidationMessage = "Se requiere una contraseña."
});
}

Este método agrega la regla de validación IsNotNullOrEmptyRule<T> a la colección Validations de cada instancia de
ValidatableObject<T>, especificando valores para la propiedad ValidationMessage de la regla de validación, que especifica
el mensaje de error de validación que se mostrará si la validación falla.

Validación de activación
El enfoque de validación utilizado en la aplicación multiplataforma eShopOnContainers puede activar manualmente la
validación de una propiedad y activar automáticamente la validación cuando una propiedad cambia.

Activar la validación manualmente


La validación se puede activar manualmente para una propiedad del modelo de vista. Por ejemplo, esto ocurre en
la aplicación multiplataforma eShopOnContainers cuando el usuario toca el botón Iniciar sesión en LoginView, cuando
utiliza servicios simulados. El delegado del comando llama al método MockSignInAsync en
LoginViewModel, que invoca la validación ejecutando el método Validate, que se muestra en el siguiente ejemplo de
código:

bool privado Validar()


{
bool isValidUser = ValidateUserName();
bool isValidPassword = ValidarContraseña();
return esValidUser && isValidPassword;
}

bool privado ValidateUserName()


{
return _userName.Validate();

42 CAPÍTULO 7 | Validación
Machine Translated by Google

bool privado Validar contraseña()


{
return _contraseña.Validate();
}

El método Validate realiza la validación del nombre de usuario y la contraseña ingresados por el usuario en LoginView,
invocando el método Validate en cada instancia de ValidatableObject<T>. El siguiente ejemplo de código muestra el
método Validate de la clase ValidatableObject<T>:

bool público Validar()


{
Errores = _validaciones
?.Donde(v => !v.Check(Valor))
?.Select(v => v.ValidationMessage)
?.ToArray()
?? Enumerable.Empty<cadena>();

IsValid = !Errors.Any();

devolver es válido;
}

Este método recupera las reglas de validación que se agregaron a la colección Validaciones del objeto. Se ejecuta el
método Check para cada regla de validación recuperada y el valor de la propiedad ValidationMessage para
cualquier regla de validación que no pueda validar los datos se agrega a la colección Errors de la instancia
ValidatableObject<T>. Finalmente, se establece la propiedad IsValid y su valor se devuelve al método de llamada,
indicando si la validación fue exitosa o fallida.

Activar la validación cuando las propiedades cambian


La validación también se activa automáticamente cada vez que cambia una propiedad vinculada. Por ejemplo, cuando
un enlace bidireccional en LoginView establece la propiedad Nombre de usuario o Contraseña, se activa la validación.
El siguiente ejemplo de código demuestra cómo ocurre esto:

< Texto de entrada="{Nombre de usuario vinculante.Valor, modo=Bidireccional}">


<Entrada.Comportamientos>
<comportamientos:EventToCommandBehavior
NombreEvento="TextoCambiado"
Comando="{Enlace ValidateUserNameCommand}" />
</Entry.Behaviors>
</Entrada>

El control Entry se vincula a la propiedad UserName.Value de la instancia ValidatableObject<T> y a la colección


Behaviors del control se le agrega una instancia EventToCommandBehavior. Este
El comportamiento ejecuta ValidateUserNameCommand en respuesta al evento TextChanged que se activa en la
entrada, que se genera cuando cambia el texto de la entrada. A su vez, el delegado ValidateUserNameCommand ejecuta
el método ValidateUserName, que ejecuta el método Validate en la instancia ValidatableObject<T>. Por lo tanto,
cada vez que el usuario ingresa un carácter en el control de Entrada del nombre de usuario, se realiza la validación de
los datos ingresados.

43 CAPÍTULO 7 | Validación
Machine Translated by Google

Mostrando errores de validación


La aplicación multiplataforma eShopOnContainers notifica al usuario sobre cualquier error de validación resaltando
el control que contiene los datos no válidos con un fondo rojo y mostrando un mensaje de error que informa al usuario
por qué los datos no son válidos debajo del control que contiene los datos no válidos. Cuando se corrigen los datos
no válidos, el fondo vuelve al estado predeterminado y se elimina el mensaje de error. La siguiente imagen muestra
LoginView en la aplicación multiplataforma eShopOnContainers cuando hay errores de validación.

Resaltar un control que contiene datos no válidos


.NET MAUI ofrece varias formas de presentar información de validación a los usuarios finales, pero una de las
formas más sencillas es mediante el uso de activadores. Los desencadenadores nos brindan una manera de
cambiar el estado de nuestros controles, generalmente la apariencia, en función de un evento o cambio de datos
que ocurre para un control. Para la validación, usaremos un DataTrigger que escuchará los cambios generados desde
una propiedad vinculada y responderá a los cambios. Los controles de entrada en LoginView se configuran
utilizando el siguiente código:

< Texto de entrada="{Nombre de usuario vinculante.Valor, modo=Bidireccional}">


<Estilo.de.entrada>
<OnPlatform x:TypeArguments="Estilo">
<En Plataforma="iOS, Android" Valor="{StaticResource EntryStyle}" />
<En Plataforma="WinUI" Valor="{StaticResource WinUIEntryStyle}" />
</OnPlataforma>
</Entry.Style>
<Entrada.Comportamientos>
<mct:EventToCommandBehavior
NombreEvento="TextoCambiado"
Comando="{Binding ValidateCommand}" />
</Entry.Behaviors>
<Entrada.Disparadores>
<Disparador de datos
TargetType="Entrada"
Vinculación="{Nombre de usuario vinculante.IsValid}"
Valor="Falso">
<Setter Property="BackgroundColor" Value="{StaticResource ErrorColor}" />
</DataTrigger>
</Entry.Triggers>
</Entrada>

44 CAPÍTULO 7 | Validación
Machine Translated by Google

El DataTrigger especifica las siguientes propiedades:

Propiedad Descripción

Tipo de objetivo El tipo de control al que pertenece el desencadenador.

Vinculante El marcado de enlace de datos que proporcionará notificaciones


de cambios y valor para la condición de activación.

Valor El valor de datos para especificar cuándo se ha cumplido la


condición del desencadenador.

Para esta entrada, estaremos atentos a los cambios en la propiedad LoginViewModel.UserName.IsValid.

Cada vez que esta propiedad genera un cambio, el valor se comparará con la propiedad Valor establecida en DataTrigger. Si los valores son

iguales, entonces se cumplirá la condición de activación y se ejecutará cualquier objeto Setter proporcionado al DataTrigger. Este control tiene

un único objeto Setter que actualiza la propiedad BackgroundColor a un color personalizado definido mediante el marcado StaticResource.

Cuando ya no se cumple una condición de activación, el control revertirá las propiedades establecidas por el objeto Setter a su estado

anterior. Para obtener más información sobre los desencadenadores, consulte Documentos de .NET MAUI: Desencadenadores.

Mostrando mensajes de error


La interfaz de usuario muestra mensajes de error de validación en los controles de etiqueta debajo de cada control cuyos datos no

superaron la validación. El siguiente ejemplo de código muestra la etiqueta que muestra un mensaje de error de validación, si el usuario no ha
ingresado un nombre de usuario válido:

<Etiqueta

Text="{Binding UserName.Errors, Converter={StaticResource FirstValidationErrorConverter}"


Estilo="{StaticResource ValidationErrorLabelStyle}" />

Cada etiqueta se vincula a la propiedad Errores del objeto del modelo de vista que se está validando. La propiedad Errors la proporciona la

clase ValidatableObject<T> y es de tipo IEnumerable<string>. Debido a que la propiedad Errors puede contener varios errores de validación, la

instancia FirstValidationErrorConverter se utiliza para recuperar el primer error de la colección para mostrarlo.

Resumen
La aplicación multiplataforma eShopOnContainers realiza una validación sincrónica del lado del cliente de las propiedades del modelo de

vista y notifica al usuario sobre cualquier error de validación resaltando el control que contiene los datos no válidos y mostrando

mensajes de error que informan al usuario por qué los datos no son válidos.

Las propiedades del modelo de vista que requieren validación son del tipo ValidatableObject<T> y cada instancia de

ValidatableObject<T> tiene reglas de validación agregadas a su propiedad Validations. La validación se invoca desde el modelo de vista

llamando al método Validate de la instancia ValidatableObject<T>, que recupera las reglas de validación y las ejecuta en la propiedad

ValidatableObject<T> Value. Cualquier error de validación se coloca en la propiedad Errores de ValidatableObject<T>.

45 CAPÍTULO 7 | Validación
Machine Translated by Google

instancia, y la propiedad IsValid de la instancia ValidatableObject<T> se actualiza para indicar si la


validación se realizó correctamente o falló.

46 CAPÍTULO 7 | Validación
Machine Translated by Google

CAPÍTULO 8

Gestión de
configuración
La configuración permite separar los datos que configuran el comportamiento de una aplicación del código, lo que permite
cambiar el comportamiento sin reconstruir la aplicación. Hay dos tipos de configuraciones: configuraciones de aplicaciones y
configuraciones de usuario.

La configuración de la aplicación son datos que una aplicación crea y administra. Puede incluir datos como puntos finales de servicios web

fijos, claves API y estado de tiempo de ejecución. La configuración de la aplicación está vinculada a la funcionalidad principal y solo

tiene significado para esa aplicación.

La configuración de usuario es la configuración personalizable de una aplicación que afecta el comportamiento de la


aplicación y no requiere reajustes frecuentes. Por ejemplo, una aplicación podría permitir al usuario especificar dónde
recuperar datos y cómo mostrarlos en la pantalla.

Crear una interfaz de configuración


Si bien el administrador de preferencias se puede utilizar directamente en su aplicación, tiene el inconveniente
de hacer que su aplicación esté estrechamente acoplada a la implementación del administrador de preferencias.
Este acoplamiento significa que la creación de pruebas unitarias o la ampliación de la funcionalidad de
gestión de preferencias será limitada ya que su aplicación no tendrá una forma directa de interceptar el comportamiento.
Para abordar esta preocupación, se puede crear una interfaz que funcione como un proxy para la gestión de preferencias.
La interfaz nos permitirá suministrar una implementación que se ajuste a nuestras necesidades. Por ejemplo, al escribir una
prueba unitaria, es posible que queramos establecer configuraciones específicas y la interfaz nos brindará una
manera fácil de configurar estos datos de manera consistente para la prueba. El siguiente ejemplo de código muestra la interfaz
ISettingsService en la aplicación multiplataforma eShopOnContainers:

espacio de nombres eShopOnContainers.Services.Settings;

interfaz pública ISettingsService


{
cadena AuthAccessToken { obtener; colocar; }
cadena AuthIdToken { obtener; colocar; }
bool UseMocks { obtener; colocar; }
cadena IdentityEndpointBase { obtener; colocar; }
cadena GatewayShoppingEndpointBase { get; colocar; }
cadena GatewayMarketingEndpointBase { obtener; colocar; }
bool UseFakeLocation { get; colocar; }
cadena Latitud { get; colocar; }
cadena Longitud { obtener; colocar; }

47 CAPITULO 8 | Gestión de configuración


Machine Translated by Google

bool AllowGpsLocation { get; colocar; }


}

Agregar configuraciones
.NET MAUI incluye un administrador de preferencias que proporciona una forma de almacenar la configuración de tiempo de ejecución para un usuario.

Se puede acceder a esta función desde cualquier lugar dentro de su aplicación utilizando la clase

Microsoft.Maui.Storage.Preferences. El administrador de preferencias proporciona un enfoque multiplataforma consistente y con seguridad de escritura

para conservar y recuperar configuraciones de aplicaciones y usuarios, mientras utiliza la administración de configuraciones nativa proporcionada por

cada plataforma. Además, es sencillo utilizar el enlace de datos para acceder a los datos de configuración expuestos por la biblioteca. Para obtener más

información, consulte las Preferencias. en el Centro de desarrolladores de Microsoft.

Consejo

Las preferencias están destinadas a almacenar datos relativamente pequeños. Si necesita almacenar datos más grandes o más complejos,

considere usar una base de datos o un sistema de archivos local para almacenar los datos.

Nuestra aplicación utilizará la clase Preferencias necesaria para implementar la interfaz ISettingsService. El siguiente código muestra cómo el servicio

de configuración de la aplicación multiplataforma eShopOnContainers implementa las propiedades AuthTokenAccess y UseMocks:

Servicio de configuración de clase pública sellada : ISettingsService


{
cadena constante privada AccessToken = "access_token";
cadena constante privada AccessTokenDefault = cadena.Empty;

cadena constante privada IdUseMocks = "use_mocks";


privado const bool UseMocksDefault = verdadero;

cadena pública AuthAccessToken


{
obtener => Preferencias.Get(AccessToken, AccessTokenDefault);
establecer => Preferencias.Set(AccessToken, valor);
}

bool público UseMocks


{
get => Preferencias.Get(IdUseMocks, UseMocksDefault);
establecer => Preferencias.Set(IdUseMocks, valor);
}
}

Cada configuración consta de una clave privada, un valor predeterminado privado y una propiedad pública. La clave es siempre una cadena constante

que define un nombre único, siendo el valor predeterminado para la configuración un valor estático de solo lectura o constante del tipo requerido.

Proporcionar un valor predeterminado garantiza que haya un valor válido disponible si se recupera una configuración no configurada. Esta

implementación de servicio se puede proporcionar mediante inyección de dependencia a nuestra aplicación para su uso en modelos de vista u otros

servicios en toda la aplicación.

48 CAPITULO 8 | Gestión de configuración


Machine Translated by Google

Enlace de datos a la configuración del usuario


En la aplicación multiplataforma eShopOnContainers, SettingsView expone múltiples configuraciones que el usuario puede
configurar en tiempo de ejecución. Estas configuraciones incluyen permitir la configuración de si la aplicación debe
recuperar datos de microservicios implementados como contenedores Docker o si la aplicación debe recuperar datos de
servicios simulados que no requieren una conexión a Internet. Al recuperar datos de microservicios en contenedores,
se debe especificar una URL de punto final base para los microservicios. La siguiente imagen muestra SettingsView cuando
el usuario ha elegido recuperar datos de microservicios en contenedores.

El enlace de datos se puede utilizar para recuperar y establecer configuraciones expuestas por la interfaz ISettingService.
Esto se logra mediante controles en la vista que se vinculan a las propiedades del modelo de vista que, a su vez, acceden
a las propiedades en la interfaz ISettingService y generan una notificación de cambio de propiedad si el valor ha cambiado.

El siguiente ejemplo de código muestra el control de entrada de SettingsView que permite al usuario ingresar una URL
de punto final de identidad base para los microservicios en contenedores:

< Texto de entrada="{Punto final de identidad vinculante, modo=Bidireccional}" />

Este control Entry se vincula a la propiedad IdentityEndpoint de la clase SettingsViewModel mediante un enlace bidireccional.
El siguiente ejemplo de código muestra la propiedad IdentityEndpoint:

ISettingsService privado de solo lectura _settingsService;

cadena privada _identityEndpoint;

Configuración públicaViewModel (
ILocationService servicio de ubicación, IAppEnvironmentService appEnvironmentService,
IDialogService dialogService, INavigationService servicio de navegación, ISettingsService settingsService)

: base (servicio de diálogo, servicio de navegación, servicio de configuración)


{
_settingsService = servicio de configuración;

_identityEndpoint = _settingsService.IdentityEndpointBase;
}

49 CAPITULO 8 | Gestión de configuración


Machine Translated by Google

cadena pública IdentityEndpoint


{
obtener => _identityEndpoint;
colocar

{
SetProperty(ref _identityEndpoint, valor);

si (!cadena.IsNullOrWhiteSpace(valor))
{
UpdateIdentityEndpoint();
}
}
}

Cuando se establece la propiedad IdentityEndpoint, se llama al método UpdateIdentityEndpoint, siempre que el valor
proporcionado sea válido. El siguiente ejemplo de código muestra el método UpdateIdentityEndpoint:

vacío privado UpdateIdentityEndpoint()


{
_settingsService.IdentityEndpointBase = _identityEndpoint;
}

Este método actualiza la propiedad IdentityEndpointBase en la implementación de la interfaz ISettingService


con el valor de la URL del punto final base ingresado por el usuario. Si la clase SettingsService se proporciona como
implementación de _settingsService, el valor persistirá en el almacenamiento específico de la plataforma.

Resumen
La configuración permite separar los datos que configuran el comportamiento de una aplicación del código, lo que permite
cambiar el comportamiento sin reconstruir la aplicación. La configuración de la aplicación son datos que una aplicación crea y
administra, y la configuración del usuario son las configuraciones personalizables de una aplicación que afectan el comportamiento
de la aplicación y no requieren reajustes frecuentes.

La clase Microsoft.Maui.Storage.Preferences proporciona un enfoque multiplataforma, coherente y con seguridad de


tipos para conservar y recuperar la configuración de aplicaciones y usuarios.

50 CAPITULO 8 | Gestión de configuración


Machine Translated by Google

CAPÍTULO 9

En contenedores
Microservicios
El desarrollo de aplicaciones cliente­servidor ha resultado en un enfoque en la creación de aplicaciones por niveles que
utilizan tecnologías específicas en cada nivel. Estas aplicaciones a menudo se denominan monolíticas y están
empaquetadas en hardware preescalado para cargas máximas. Los principales inconvenientes de este enfoque
de desarrollo son el estrecho acoplamiento entre los componentes dentro de cada nivel, el hecho de que los componentes
individuales no se pueden escalar fácilmente y el costo de las pruebas. Una simple actualización puede tener efectos
imprevistos en el resto del nivel, por lo que un cambio en un componente de la aplicación requiere volver a probar
e implementar todo su nivel.

Particularmente preocupante, en la era de la nube, es que los componentes individuales no se pueden escalar
fácilmente. Una aplicación monolítica contiene funcionalidades específicas de un dominio y normalmente está
dividida en capas funcionales como front­end, lógica empresarial y almacenamiento de datos. La
siguiente imagen ilustra cómo se escala una aplicación monolítica clonando toda la aplicación en varias máquinas.

51 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

Microservicios
Los microservicios ofrecen un enfoque diferente para el desarrollo y la implementación de aplicaciones, un enfoque que se adapta
a los requisitos de agilidad, escala y confiabilidad de las aplicaciones en la nube modernas. Una aplicación de microservicios
se divide en componentes independientes que trabajan juntos para ofrecer la funcionalidad general de la aplicación. El término
microservicio enfatiza que las aplicaciones deben estar compuestas de servicios lo suficientemente pequeños como para
reflejar preocupaciones particulares, de modo que cada microservicio implemente una única función. Además, cada microservicio
tiene contratos bien definidos con los que otros microservicios se comunican y comparten datos. Los ejemplos típicos
de microservicios incluyen carritos de compras, procesamiento de inventario, subsistemas de compra y procesamiento de pagos.

Los microservicios pueden escalar de forma independiente en comparación con aplicaciones monolíticas gigantes que escalan juntas.
Esto significa que un área funcional específica que requiere más potencia de procesamiento o ancho de banda de red para soportar
la demanda se puede escalar en lugar de escalar innecesariamente otras áreas de aplicaciones. La siguiente imagen ilustra este
enfoque, donde los microservicios se implementan y escalan de forma independiente, creando instancias de servicios en todas las
máquinas.

52 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

La ampliación horizontal de los microservicios puede ser casi instantánea, lo que permite que una aplicación se adapte a las
cargas cambiantes. Por ejemplo, un único microservicio en la funcionalidad web de una aplicación podría ser el único
microservicio que necesita escalarse para manejar tráfico entrante adicional.

El modelo clásico de escalabilidad de aplicaciones es tener un nivel sin estado y con carga equilibrada con un almacén de
datos externo compartido para almacenar datos persistentes. Los microservicios con estado administran sus propios datos
persistentes, generalmente almacenándolos localmente en los servidores en los que están ubicados, para evitar la sobrecarga
del acceso a la red y la complejidad de las operaciones entre servicios. Esto permite el procesamiento de datos más rápido posible
y puede eliminar la necesidad de sistemas de almacenamiento en caché. Además, los microservicios con estado escalables
generalmente dividen los datos entre sus instancias para administrar el tamaño de los datos y transferir el rendimiento más allá
del que un solo servidor puede soportar.

Los microservicios también admiten actualizaciones independientes. Este débil acoplamiento entre microservicios proporciona una
evolución rápida y confiable de las aplicaciones. Su naturaleza independiente y distribuida ayuda a las actualizaciones continuas,
donde solo se actualizará un subconjunto de instancias de un único microservicio en un momento dado. Por lo tanto, si se detecta
un problema, se puede revertir una actualización defectuosa antes de que todas las instancias se actualicen con el código o la
configuración defectuosos. De manera similar, los microservicios suelen utilizar control de versiones de esquema, de modo que
los clientes vean una versión coherente cuando se aplican las actualizaciones, independientemente de con qué instancia de
microservicio se esté comunicando.

Por tanto, las aplicaciones de microservicios tienen muchas ventajas sobre las aplicaciones monolíticas:


Cada microservicio es relativamente pequeño, fácil de gestionar y evolucionar.

Cada microservicio se puede desarrollar e implementar independientemente de otros servicios.

Cada microservicio se puede escalar de forma independiente. Por ejemplo, es posible que sea necesario
ampliar un servicio de catálogo o un servicio de cesta de compras más que un servicio de pedidos.

53 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

Por lo tanto, la infraestructura resultante consumirá recursos de manera más eficiente durante el escalamiento horizontal.


Cada microservicio aísla cualquier problema. Por ejemplo, si hay un problema en un servicio, solo afectará a ese
servicio. Los demás servicios pueden seguir atendiendo solicitudes.

Cada microservicio puede utilizar las últimas tecnologías. Debido a que los microservicios son autónomos y se ejecutan en
paralelo, se pueden utilizar las últimas tecnologías y marcos, en lugar de verse obligados a utilizar un marco más
antiguo que podría ser utilizado por una aplicación monolítica.

Sin embargo, una solución basada en microservicios también tiene posibles inconvenientes:


Elegir cómo dividir una aplicación en microservicios puede ser un desafío, ya que cada microservicio debe ser
completamente autónomo, de extremo a extremo, incluida la responsabilidad de sus fuentes de datos.


Los desarrolladores deben implementar comunicación entre servicios, lo que agrega complejidad y latencia a la aplicación.


Las transacciones atómicas entre múltiples microservicios generalmente no son posibles. Por lo tanto, los
requisitos comerciales deben abarcar una eventual coherencia entre los microservicios.

En producción, existe una complejidad operativa al implementar y administrar un sistema comprometido por
muchos servicios independientes.
• La comunicación directa entre el cliente y el microservicio puede dificultar la refactorización de los contratos de

microservicios. Por ejemplo, con el tiempo es posible que sea necesario cambiar la forma en que se divide el sistema en
servicios. Un único servicio puede dividirse en dos o más servicios y dos servicios pueden fusionarse. Cuando los
clientes se comunican directamente con los microservicios, este trabajo de refactorización puede romper la compatibilidad
con las aplicaciones del cliente.

Contenedorización
La contenedorización es un enfoque para el desarrollo de software en el que una aplicación y su conjunto versionado de dependencias,
además de su configuración de entorno abstraída como archivos de manifiesto de implementación, se empaquetan juntos como
una imagen de contenedor, se prueban como una unidad y se implementan en un sistema operativo host.

Un contenedor es un entorno operativo portátil, aislado y controlado por recursos, donde una aplicación puede ejecutarse
sin tocar los recursos de otros contenedores o el host. Por lo tanto, un contenedor se ve y actúa como una computadora física
o una máquina virtual recién instalada.

Existen muchas similitudes entre contenedores y máquinas virtuales, como se ilustra a continuación.

54 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

Un contenedor ejecuta un sistema operativo, tiene un sistema de archivos y se puede acceder a él a través de una red como si fuera una

máquina física o virtual. Sin embargo, la tecnología y los conceptos utilizados por los contenedores son muy diferentes a los de las máquinas
virtuales. Las máquinas virtuales incluyen las aplicaciones, las dependencias necesarias y un sistema operativo invitado completo. Los

contenedores incluyen la aplicación y sus dependencias, pero comparten el sistema operativo con otros contenedores, ejecutándose como

procesos aislados en el sistema operativo host (aparte de los contenedores Hyper­V que se ejecutan dentro de una máquina virtual

especial por contenedor).

Por lo tanto, los contenedores comparten recursos y normalmente requieren menos recursos que las máquinas virtuales.

La ventaja de un enfoque de desarrollo e implementación orientado a contenedores es que elimina la mayoría de los problemas que

surgen de configuraciones de entorno inconsistentes y los problemas que las acompañan. Además, los contenedores permiten una rápida

funcionalidad de ampliación de aplicaciones al crear instancias de nuevos contenedores según sea necesario.

Los conceptos clave a la hora de crear y trabajar con contenedores son:

Concepto Descripción

Anfitrión del contenedor La máquina física o virtual configurada para alojar contenedores.
El host del contenedor ejecutará uno
o más contenedores.

Imagen del contenedor Una imagen consiste en una unión de sistemas de


archivos en capas apilados uno encima del otro y es la base de
un contenedor. Una imagen no tiene estado y nunca cambia
cuando se implementa en diferentes entornos.

Envase Un contenedor es una instancia en tiempo de ejecución de una imagen.

55 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

Concepto Descripción

Imagen del sistema operativo del contenedor Los contenedores se implementan a partir de imágenes.
La imagen del sistema operativo del contenedor es la
primera capa de potencialmente muchas capas de imágenes que
componen un contenedor. Un sistema operativo de contenedor
es inmutable y no se puede modificar.

Repositorio de contenedores Cada vez que se crea una imagen de contenedor, la


imagen y sus dependencias se almacenan en un repositorio
local. Estas imágenes se pueden reutilizar muchas veces en
el host del contenedor. El contenedor

Las imágenes también se pueden almacenar en un registro


público o privado, como Docker Hub, para que puedan usarse
en diferentes hosts de contenedores.

Las empresas adoptan cada vez más contenedores al implementar aplicaciones basadas en microservicios, y Docker se ha convertido en
la implementación de contenedores estándar adoptada por la mayoría de las plataformas de software y proveedores de nube.

La aplicación de referencia eShopOnContainers utiliza Docker para alojar cuatro microservicios back­end en contenedores, como
se ilustra en el siguiente diagrama.

56 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

La arquitectura de los servicios back­end en la aplicación de referencia se descompone en múltiples subsistemas


autónomos en forma de microservicios y contenedores colaborativos. Cada microservicio proporciona un área única de
funcionalidad: un servicio de identidad, un servicio de catálogo, un servicio de pedidos y un servicio de cesta.

Cada microservicio tiene su propia base de datos, lo que le permite estar completamente desacoplado
de los demás microservicios. Cuando sea necesario, la coherencia entre bases de datos de diferentes
microservicios se logra mediante eventos a nivel de aplicación. Para obtener más información, consulte
Comunicación entre microservicios.

Para obtener más información sobre la aplicación de referencia, consulte Microservicios .NET: arquitectura para
aplicaciones .NET en contenedores.

57 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

Comunicación entre cliente y microservicios.


La aplicación multiplataforma eShopOnContainers se comunica con los microservicios back­end en contenedores
mediante comunicación directa de cliente a microservicio , como se muestra a continuación.

Con comunicación directa de cliente a microservicio, la aplicación multiplataforma realiza solicitudes a cada microservicio
directamente a través de su punto final público, con un puerto TCP diferente por microservicio. En producción, el punto
final normalmente se asignaría al equilibrador de carga del microservicio, que distribuye las solicitudes entre las instancias
disponibles.

Consejo

Considere la posibilidad de utilizar la comunicación de puerta de enlace API.

La comunicación directa entre el cliente y el microservicio puede tener inconvenientes al crear una aplicación grande y compleja
basada en microservicios, pero es más que adecuada para una aplicación pequeña. Considere la posibilidad de utilizar la
comunicación de puerta de enlace API al diseñar una aplicación grande basada en microservicios con decenas de
microservicios. Para obtener más información, consulte Microservicios .NET: arquitectura para aplicaciones .NET en
contenedores.

Comunicación entre microservicios.


Una aplicación basada en microservicios es un sistema distribuido que potencialmente se ejecuta en varias máquinas.
Cada instancia de servicio suele ser un proceso. Por lo tanto, los servicios deben interactuar utilizando un protocolo de
comunicación entre procesos, como HTTP, TCP, Protocolo avanzado de cola de mensajes (AMQP) o protocolos binarios, según
la naturaleza de cada servicio.

Los dos enfoques comunes para la comunicación de microservicio a microservicio son la comunicación REST basada en HTTP
cuando se consultan datos y la mensajería asincrónica ligera cuando se comunican actualizaciones entre
múltiples microservicios.

58 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

La comunicación asincrónica basada en mensajes basada en eventos es fundamental cuando se propagan cambios en
múltiples microservicios. Con este enfoque, un microservicio publica un evento cuando sucede algo notable, por ejemplo,
cuando actualiza una entidad comercial. Otros microservicios se suscriben a estos eventos. Luego, cuando un
microservicio recibe un evento, actualiza sus propias entidades comerciales, lo que, a su vez, podría dar lugar a que se
publiquen más eventos. Esta funcionalidad de publicación y suscripción generalmente se logra con un bus de eventos.

Un bus de eventos permite la comunicación de publicación y suscripción entre microservicios sin necesidad de que los
componentes se conozcan explícitamente entre sí, como se muestra a continuación.

Desde la perspectiva de una aplicación, el bus de eventos es simplemente un canal de publicación y suscripción expuesto
a través de una interfaz. Sin embargo, la forma en que se implementa el bus de eventos puede variar. Por ejemplo,
una implementación de bus de eventos podría usar RabbitMQ, Azure Service Bus u otros buses de servicios como
NServiceBus y MassTransit. El siguiente diagrama muestra cómo se utiliza un bus de eventos en la aplicación de
referencia de eShopOnContainers.

59 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

El bus de eventos eShopOnContainers, implementado con RabbitMQ, proporciona una funcionalidad de


publicación y suscripción asíncrona de uno a muchos. Esto significa que después de publicar un evento, puede haber
varios suscriptores escuchando el mismo evento. El siguiente diagrama ilustra esta relación.

Este enfoque de comunicación uno a muchos utiliza eventos para implementar transacciones comerciales que abarcan
múltiples servicios, asegurando la coherencia final entre los servicios. Una transacción eventualmente consistente
consta de una serie de pasos distribuidos. Por lo tanto, cuando el microservicio de perfil de usuario recibe el
comando UpdateUser, actualiza los detalles del usuario en su base de datos y publica el evento UserUpdated en el
bus de eventos. Tanto el microservicio de cesta como el microservicio de pedidos se han suscrito para recibir este evento
y, en respuesta, actualizan la información de su comprador en sus respectivas bases de datos.

Nota

El bus de eventos eShopOnContainers, implementado con RabbitMQ, está diseñado para usarse únicamente como
prueba de concepto. Para los sistemas de producción, se deben considerar implementaciones alternativas de
bus de eventos.

60 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

Para obtener más información sobre la implementación del bus de eventos, consulte Microservicios .NET: arquitectura
para aplicaciones .NET en contenedores.

Resumen
Los microservicios ofrecen un enfoque para el desarrollo y la implementación de aplicaciones que se adapta a los
requisitos de agilidad, escala y confiabilidad de las aplicaciones en la nube modernas. Una de las principales ventajas de
los microservicios es que se pueden escalar de forma independiente, lo que significa que se puede escalar un área
funcional específica que requiere más potencia de procesamiento o ancho de banda de red para soportar la demanda
sin escalar innecesariamente áreas de la aplicación que no están experimentando un aumento. demanda.

Un contenedor es un entorno operativo portátil, aislado y controlado por recursos donde una aplicación puede
ejecutarse sin tocar los recursos de otros contenedores o del host. Las empresas adoptan cada vez más contenedores
al implementar aplicaciones basadas en microservicios, y Docker se ha convertido en la implementación de contenedores
estándar que han adoptado la mayoría de las plataformas de software y proveedores de nube.

61 CAPITULO 9 | Microservicios en contenedores


Machine Translated by Google

CAPÍTULO 10

Acceder a datos remotos


Muchas soluciones modernas basadas en web utilizan servicios web, alojados en servidores web, para proporcionar
funcionalidad para aplicaciones de clientes remotos. Las operaciones que expone un servicio web constituyen una
API web.

Las aplicaciones cliente deberían poder utilizar la API web sin saber cómo se implementan los datos u
operaciones que expone la API. Esto requiere que la API cumpla con estándares comunes que permitan
que una aplicación cliente y un servicio web acuerden qué formatos de datos usar y la estructura de los datos
que se intercambian entre las aplicaciones cliente y el servicio web.

Introducción a la transferencia de estado representacional


La transferencia de estado representacional (REST) es un estilo arquitectónico para construir sistemas distribuidos
basados en hipermedia. Una ventaja principal del modelo REST es que se basa en estándares abiertos y no
vincula la implementación del modelo ni las aplicaciones cliente que acceden a él a ninguna implementación
específica. Por lo tanto, se podría implementar un servicio web REST utilizando Microsoft ASP.NET Core, y las
aplicaciones cliente podrían desarrollarse utilizando cualquier lenguaje y conjunto de herramientas que pueda generar
solicitudes HTTP y analizar respuestas HTTP.

El modelo REST utiliza un esquema de navegación para representar objetos y servicios en una red,
denominados recursos. Los sistemas que implementan REST suelen utilizar el protocolo HTTP para
transmitir solicitudes de acceso a estos recursos. En dichos sistemas, una aplicación cliente envía una
solicitud en forma de URI que identifica un recurso y un método HTTP (como GET, POST, PUT o
DELETE) que indica la operación que se realizará en ese recurso. El cuerpo de la solicitud HTTP contiene
todos los datos necesarios para realizar la operación.

Nota

REST define un modelo de solicitud sin estado. Por lo tanto, las solicitudes HTTP deben ser independientes y pueden
ocurrir en cualquier orden.

La respuesta de una solicitud REST utiliza códigos de estado HTTP estándar. Por ejemplo, una solicitud que
devuelve datos válidos debe incluir el código de respuesta HTTP 200 (OK), mientras que una solicitud que no
logra encontrar o eliminar un recurso específico debe devolver una respuesta que incluye el código de estado
HTTP 404 (No encontrado).

Una API web RESTful expone un conjunto de recursos conectados y proporciona las operaciones
principales que permiten que una aplicación manipule esos recursos y navegue fácilmente entre ellos. Por esta
razón, los URI que constituyen una API web RESTful típica están orientados hacia los datos que expone y
utilizan las funciones proporcionadas por HTTP para operar con estos datos.

62 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Los datos incluidos por una aplicación cliente en una solicitud HTTP y los correspondientes mensajes de respuesta del servidor web
podrían presentarse en una variedad de formatos, conocidos como tipos de medios. Cuando una aplicación cliente envía una
solicitud que devuelve datos en el cuerpo de un mensaje, puede especificar los tipos de medios que puede manejar en el
encabezado Aceptar de la solicitud. Si el servidor web admite este tipo de medio, puede responder con una respuesta que incluya
el encabezado Content­Type que especifica el formato de los datos en el cuerpo del mensaje. Luego, es responsabilidad de la
aplicación cliente analizar el mensaje de respuesta e interpretar adecuadamente los resultados en el cuerpo del mensaje.

Para obtener más información sobre REST, consulte Diseño de API. e implementación de API en Documentos de Microsoft.

Consumir API RESTful


La aplicación multiplataforma eShopOnContainers utiliza el patrón Model­View­ViewModel (MVVM), y los elementos del modelo del
patrón representan las entidades de dominio utilizadas en la aplicación. Las clases de controlador y repositorio en la aplicación de
referencia eShopOnContainers aceptan y devuelven muchos de estos objetos modelo. Por lo tanto, se utilizan como objetos de
transferencia de datos (DTO) que contienen todos los datos que se pasan entre la aplicación y los microservicios en contenedores.
El principal beneficio de utilizar DTO para pasar y recibir datos de un servicio web es que al transmitir más datos en una sola llamada
remota, la aplicación puede reducir la cantidad de llamadas remotas que deben realizarse.

Realizar solicitudes web


La aplicación multiplataforma eShopOnContainers utiliza la clase HttpClient para realizar solicitudes a través de HTTP, utilizando
JSON como tipo de medio. Esta clase proporciona funcionalidad para enviar solicitudes HTTP de forma asincrónica y recibir respuestas
HTTP de un recurso identificado por URI. La clase HttpResponseMessage representa un mensaje de
respuesta HTTP recibido de una API REST después de realizar una solicitud HTTP. Contiene información sobre la respuesta, incluido
el código de estado, los encabezados y cualquier cuerpo. La clase HttpContent representa el cuerpo HTTP y los encabezados de
contenido, como Content­Type y Content­Encoding. El contenido se puede leer utilizando cualquiera de los métodos ReadAs, como
ReadAsStringAsync y ReadAsByteArrayAsync, según el formato de los datos.

Realizar una solicitud GET


La clase CatalogService se utiliza para gestionar el proceso de recuperación de datos del microservicio de catálogo.
En el método RegisterViewModels de la clase MauiProgram, la clase CatalogService se registra como una asignación de tipos con el
tipo ICatalogService con el contenedor de inyección de dependencia. Luego, cuando se crea una instancia de la clase CatalogViewModel,
su constructor acepta un tipo ICatalogService, que el contenedor de inyección de dependencia resuelve y devuelve una instancia de
la clase CatalogService.
Para obtener más información sobre la inyección de dependencia, consulte Inyección de dependencia.

La siguiente imagen muestra la interacción de clases que leen datos del catálogo del microservicio de catálogo para mostrarlos en
CatalogView.

63 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Cuando se navega a CatalogView, se llama al método OnInitialize en la clase CatalogViewModel.


Este método recupera datos del catálogo del microservicio de catálogo, como se demuestra en el
siguiente ejemplo de código:

anulación pública de la tarea asíncrona InicializarAsync()


{
Productos = await _productsService.GetCatalogAsync();
}

Este método llama al método GetCatalogAsync de la instancia de CatalogService que el contenedor de


inyección de dependencias inyectó en CatalogViewModel. El siguiente ejemplo de código muestra el
método GetCatalogAsync:

Tarea asíncrona pública <ObservableCollection<CatalogItem>> GetCatalogAsync()


{
Constructor UriBuilder = nuevo UriBuilder(GlobalSetting.Instance.CatalogEndpoint);
builder.Path = "api/v1/catalog/items";
cadena uri = constructor.ToString();

¿Raíz del catálogo? catálogo = await _requestProvider.GetAsync<CatalogRoot>(uri);

¿ devolver catálogo?.Datos;
}

64 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Este método crea el URI que identifica el recurso al que se enviará la solicitud y utiliza la clase
RequestProvider para invocar el método GET HTTP en el recurso, antes de devolver los resultados a
CatalogViewModel. La clase RequestProvider contiene una funcionalidad que envía una solicitud en forma
de URI que identifica un recurso, un método HTTP que indica la operación que se realizará en ese recurso
y un cuerpo que contiene los datos necesarios para realizar la operación. Para obtener
información sobre cómo se inyecta la clase RequestProvider en la clase CatalogService, consulte
Inyección de dependencia.

El siguiente ejemplo de código muestra el método GetAsync en la clase RequestProvider:

Tarea asíncrona pública <TResult> GetAsync<TResult>(cadena uri, cadena token = "")


{
HttpClient httpClient = GetOrCreateHttpClient(token);
Respuesta HttpResponseMessage = espera httpClient.GetAsync(uri);

espere HandleResponse (respuesta);


Resultado TResult = espera respuesta.Content.ReadFromJsonAsync<TResult>();

resultado de devolución ;
}

Este método llama al método GetOrCreateHttpClient, que devuelve una instancia de la clase HttpClient con
los encabezados apropiados establecidos. Luego envía una solicitud GET asincrónica al recurso
identificado por el URI, y la respuesta se almacena en la instancia HttpResponseMessage. Luego se
invoca el método HandleResponse, que genera una excepción si la respuesta no incluye un código de
estado HTTP exitoso. Luego, la respuesta se lee como una cadena, se convierte de JSON a
un objeto CatalogRoot y se devuelve a CatalogService.

El método GetOrCreateHttpClient se muestra en el siguiente ejemplo de código:

privado de solo lectura Lazy<HttpClient> _httpClient =


nuevo Lazy<HttpClient>(
() =>
{
var httpClient = nuevo HttpClient();
httpClient.DefaultRequestHeaders.Accept.Add(nuevo
MediaTypeWithQualityHeaderValue("aplicación/json"));
devolver httpCliente;
},
LazyThreadSafetyMode.ExecutionAndPublication);

HttpClient privado GetOrCreateHttpClient ( token de cadena = "")


{
var httpClient = _httpClient.Value;

si (!cadena.IsNullOrEmpty(token))
{
httpClient.DefaultRequestHeaders.Authorization = nuevo
AuthenticationHeaderValue("Portador", token);
}
demás
{
httpClient.DefaultRequestHeaders.Authorization = nulo;
}

sesenta y cinco
CAPÍTULO 10 | Acceder a datos remotos
Machine Translated by Google

devolver httpCliente;
}

Este método crea una nueva instancia o recupera una instancia almacenada en caché de la clase HttpClient y establece el encabezado Aceptar

de cualquier solicitud realizada por la instancia de HttpClient en application/json, lo que indica que espera que el contenido de cualquier

respuesta se formatee usando JSON. . Luego, si se pasó un token de acceso como argumento al método GetOrCreateHttpClient, se agrega al

encabezado de Autorización de cualquier solicitud realizada por la instancia de HttpClient, con el prefijo Bearer.

Para obtener más información sobre la autorización, consulte Autorización.

Consejo

Se recomienda encarecidamente almacenar en caché y reutilizar instancias de HttpClient para mejorar el rendimiento de la aplicación. La

creación de un nuevo HttpClient para cada operación puede provocar un problema de agotamiento de sockets.

Para obtener más información, consulte Creación de instancias de HttpClient. en el Centro de desarrolladores de Microsoft.

Cuando el método GetAsync de la clase RequestProvider llama a HttpClient.GetAsync, se invoca el método Items de la clase CatalogController del

proyecto Catalog.API, como se muestra en el siguiente ejemplo de código:

[httpGet]
[Ruta("[acción]")]
Tarea pública asíncrona <IActionResult> Elementos (
[FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0)
{
var totalItems = espera _catalogContext.CatalogItems
.LongCountAsync();

var itemsOnPage = espera _catalogContext.CatalogItems


.OrderBy(c => c.Nombre)
.Omitir(tamaño de página * índice de página)
.Take(tamaño de página)
.ToListAsync();

elementos en la página = ComposePicUri (elementos en la página);


modelo var = nuevo PaginatedItemsViewModel<CatalogItem>(
pageIndex, pageSize, totalItems, itemsOnPage);

devolver Ok(modelo);
}

Este método recupera los datos del catálogo de la base de datos SQL utilizando EntityFramework, y lo devuelve como un mensaje de respuesta

que incluye un código de estado HTTP exitoso y una colección de instancias de CatalogItem con formato JSON.

Realizar una solicitud POST


La clase BasketService se utiliza para gestionar el proceso de recuperación y actualización de datos con el microservicio de cesta. En el

método RegisterAppServices de la clase MauiProgram, la clase BasketService se registra como una asignación de tipo con el tipo IBasketService

con el contenedor de inyección de dependencia.

Luego, cuando se crea una instancia de la clase BasketViewModel, su constructor acepta una

66 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Tipo IBasketService, que resuelve el contenedor de inyección de dependencias, devolviendo una instancia de la clase
BasketService. Para obtener más información sobre la inyección de dependencia, consulte Inyección de dependencia.

La siguiente imagen muestra la interacción de las clases que envían los datos de la cesta mostrados por
BasketView al microservicio de la cesta.

Cuando se agrega un artículo a la cesta de la compra, se llama al método ReCalculateTotalAsync en la


clase BasketViewModel. Este método actualiza el valor total de los artículos de la cesta y envía los datos de la cesta al
microservicio de la cesta, como se demuestra en el siguiente ejemplo de código:

Tarea asíncrona privada ReCalculateTotalAsync()


{
// Omitido por brevedad...

espere _basketService.UpdateBasketAsync(
nueva cesta de clientes
{
CompradorId = userInfo.UserId,
Artículos = BasketItems.ToList()
},
token de autenticación);

67 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Este método llama al método UpdateBasketAsync de la instancia de BasketService que el contenedor de


inyección de dependencias inyectó en BasketViewModel. El siguiente método muestra el método
UpdateBasketAsync:

Tarea asíncrona pública <Cesta de clientes> UpdateBasketAsync(


CustomerBasket customerBasket, token de cadena )
{
Constructor UriBuilder = nuevo UriBuilder(GlobalSetting.Instance.BasketEndpoint);
cadena uri = constructor.ToString();
resultado var = await _requestProvider.PostAsync(uri, customerBasket, token);
resultado de devolución ;
}

Este método crea el URI que identifica el recurso al que se enviará la solicitud y utiliza la clase
RequestProvider para invocar el método POST HTTP en el recurso, antes de devolver los resultados a
BasketViewModel. Tenga en cuenta que se requiere un token de acceso, obtenido de IdentityServer
durante el proceso de autenticación, para autorizar las solicitudes al microservicio de la cesta. Para
obtener más información sobre la autorización, consulte Autorización.

El siguiente ejemplo de código muestra uno de los métodos PostAsync en la clase RequestProvider:

Tarea asíncrona pública <TResult> PostAsync<TResult>(


uri de cadena , datos TResult, token de cadena = "", encabezado de cadena = "")
{
HttpClient httpClient = GetOrCreateHttpClient(token);

contenido var = new StringContent(JsonSerializer.Serialize(data));


content.Headers.ContentType = nuevo MediaTypeHeaderValue("aplicación/json");
Respuesta HttpResponseMessage = espera httpClient.PostAsync(uri, contenido);

espere HandleResponse (respuesta);


Resultado TResult = espera respuesta.Content.ReadFromJsonAsync<TResult>();

resultado de devolución ;
}

Este método llama al método GetOrCreateHttpClient, que devuelve una instancia de la clase HttpClient con los
encabezados apropiados establecidos. Luego envía una solicitud POST asincrónica al recurso
identificado por el URI, con los datos de la cesta serializada que se envían en formato JSON y la
respuesta se almacena en la instancia HttpResponseMessage. Luego se invoca el método HandleResponse,
que genera una excepción si la respuesta no incluye un código de estado HTTP exitoso. Luego, la
respuesta se lee como una cadena, se convierte de JSON a un objeto CustomerBasket y se devuelve a
BasketService. Para obtener más información sobre el método GetOrCreateHttpClient, consulte Realizar
una solicitud GET.

Cuando el método PostAsync de la clase RequestProvider llama a HttpClient.PostAsync, se invoca el método


Post de la clase BasketController del proyecto Basket.API, como se muestra en el siguiente ejemplo de código:

[publicación http]
Tarea asíncrona pública <IActionResult> Publicación ([FromBody] Valor de CustomerBasket)
{
var cesta = await _repository.UpdateBasketAsync(valor);
devolver Ok(cesta);
}

68 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Este método utiliza una instancia de la clase RedisBasketRepository para conservar los datos de la cesta en la
caché de Redis y los devuelve como un mensaje de respuesta que incluye un código de estado HTTP exitoso y
una instancia de CustomerBasket con formato JSON.

Realizar una solicitud ELIMINAR


La siguiente imagen muestra las interacciones de las clases que eliminan datos de la cesta del
microservicio de la cesta, para CheckoutView.

Cuando se invoca el proceso de pago, se llama al método CheckoutAsync en la clase CheckoutViewModel. Este
método crea un nuevo pedido antes de vaciar la cesta de la compra, como se demuestra en el siguiente ejemplo de
código:

tarea asíncrona privada CheckoutAsync()


{
// Omitido por brevedad...

await _basketService.ClearBasketAsync(
_shippingAddress.Id.ToString(), authToken);
}

Este método llama al método ClearBasketAsync de la instancia de BasketService que el contenedor de inyección
de dependencias inyectó en CheckoutViewModel. El siguiente método muestra el método ClearBasketAsync:

Tarea asíncrona pública ClearBasketAsync (cadena guidUser, token de cadena )


{
Constructor UriBuilder = new(GlobalSetting.Instance.BasketEndpoint);
constructor.Path = guidUser;
cadena uri = constructor.ToString();
espere _requestProvider.DeleteAsync(uri, token);
}

69 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Este método crea el URI que identifica el recurso al que se enviará la solicitud y utiliza la clase RequestProvider para invocar el método DELETE

HTTP en el recurso. Tenga en cuenta que se requiere un token de acceso, obtenido de IdentityServer durante el proceso de autenticación, para

autorizar las solicitudes al microservicio de la cesta. Para obtener más información sobre la autorización, consulte Autorización.

El siguiente ejemplo de código muestra el método DeleteAsync en la clase RequestProvider:

Tarea asíncrona pública DeleteAsync ( uri de cadena, token de cadena = "")


{
HttpClient httpClient = GetOrCreateHttpClient(token);
espere httpClient.DeleteAsync(uri);
}

Este método llama al método GetOrCreateHttpClient, que devuelve una instancia de la clase HttpClient con los encabezados apropiados

establecidos. Luego envía una solicitud DELETE asincrónica al recurso identificado por el URI. Para obtener más información sobre el método

GetOrCreateHttpClient, consulte Realizar una solicitud GET.

Cuando el método DeleteAsync de la clase RequestProvider llama a HttpClient.DeleteAsync, se invoca el método Delete de la clase

BasketController del proyecto Basket.API, como se muestra en el siguiente ejemplo de código:

[HttpDelete("{id}")]
eliminación pública vacía ( identificación de cadena) =>
_repository.DeleteBasketAsync(id);

Este método utiliza una instancia de la clase RedisBasketRepository para eliminar los datos de la cesta del caché de Redis.

Almacenamiento en caché de datos

El rendimiento de una aplicación se puede mejorar almacenando en caché los datos a los que se accede con frecuencia en un

almacenamiento rápido ubicado cerca de la aplicación. Si el almacenamiento rápido está ubicado más cerca de la aplicación que de la fuente

original, entonces el almacenamiento en caché puede mejorar significativamente los tiempos de respuesta al recuperar datos.

La forma más común de almacenamiento en caché es el almacenamiento en caché de lectura, en el que una aplicación recupera

datos haciendo referencia al caché. Si los datos no están en la caché, se recuperan del almacén de datos y se agregan a la caché. Las aplicaciones

pueden implementar el almacenamiento en caché de lectura directa con el patrón de caché aparte. Este patrón determina si el

elemento está actualmente en la caché. Si el elemento no está en la caché, se lee del almacén de datos y se agrega a la caché. Para obtener

más información, consulte Caché aparte. patrón en Microsoft Docs.

Consejo

Datos en caché que se leen con frecuencia y cambian con poca frecuencia.

Estos datos se pueden agregar al caché a pedido la primera vez que una aplicación los recupera. Esto significa que la aplicación necesita

recuperar los datos solo una vez del almacén de datos y que el acceso posterior se puede realizar mediante el uso de la memoria caché.

70 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Las aplicaciones distribuidas, como la aplicación de referencia eShopOnContainers, deben proporcionar uno o ambos de los siguientes cachés:

• Una caché compartida, a la que pueden acceder múltiples procesos o máquinas.

• Un caché privado, donde los datos se guardan localmente en el dispositivo que ejecuta la aplicación.

La aplicación multiplataforma eShopOnContainers utiliza un caché privado, donde los datos se guardan localmente en el dispositivo que

ejecuta una instancia de la aplicación. Para obtener información sobre el caché utilizado por la aplicación de referencia

eShopOnContainers, consulte Microservicios .NET: arquitectura para aplicaciones .NET en contenedores.

Consejo

Piense en la caché como un almacén de datos transitorio que podría desaparecer en cualquier momento.

Asegúrese de que los datos se mantengan en el almacén de datos original y en la caché. Las posibilidades de perder datos se minimizan si
el caché deja de estar disponible.

Gestionar la caducidad de los datos


No es práctico esperar que los datos almacenados en caché siempre sean coherentes con los datos originales. Los datos del almacén de datos

original pueden cambiar después de almacenarlos en caché, lo que hace que los datos almacenados en caché queden obsoletos.

Por lo tanto, las aplicaciones deben implementar una estrategia que ayude a garantizar que los datos en el caché estén lo más actualizados

posible, pero que también puedan detectar y manejar situaciones que surgen cuando los datos en el caché se vuelven obsoletos. La mayoría

de los mecanismos de almacenamiento en caché permiten configurar el caché para que caduquen los datos y, por lo tanto, reduzcan el

período durante el cual los datos pueden estar desactualizados.

Consejo

Establezca un tiempo de vencimiento predeterminado al configurar un caché.

Muchos cachés implementan la caducidad, lo que invalida los datos y los elimina del caché si no se accede a ellos durante un período

específico. Sin embargo, hay que tener cuidado a la hora de elegir el plazo de caducidad. Si es demasiado corto, los datos caducarán demasiado

rápido y se reducirán los beneficios del almacenamiento en caché. Si se prolonga demasiado, los datos corren el riesgo de volverse

obsoletos. Por lo tanto, el tiempo de vencimiento debe coincidir con el patrón de acceso de las aplicaciones que utilizan los datos.

Cuando los datos almacenados en caché caducan, se deben eliminar del caché y la aplicación debe recuperar los datos del almacén de datos

original y volver a colocarlos en el caché.

También es posible que un caché se llene si se permite que los datos permanezcan durante un período demasiado largo. Por lo tanto, es posible

que se requieran solicitudes para agregar nuevos elementos a la caché para eliminar algunos elementos en un proceso conocido como desalojo.

Los servicios de almacenamiento en caché normalmente expulsan los datos según el uso menos reciente. Sin embargo, existen otras

políticas de desalojo, incluidas las de uso más reciente y las de primero en entrar, primero en salir. Para obtener más información, consulte Guía

de almacenamiento en caché. en Documentos de Microsoft.

71 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Almacenamiento en caché de imágenes

La aplicación multiplataforma eShopOnContainers consume imágenes de productos remotos que se benefician del almacenamiento
en caché. Estas imágenes se muestran mediante el control Imagen. El control de imagen .NET MAUI admite el almacenamiento
en caché de imágenes descargadas, que tiene el almacenamiento en caché habilitado de forma predeterminada y almacenará la
imagen localmente durante 24 horas. Además, el tiempo de vencimiento se puede configurar con la propiedad CacheValidity. Para
obtener más información, consulte Almacenamiento en caché de imágenes descargadas. en el Centro de desarrolladores de Microsoft.

Aumento de la resiliencia

Todas las aplicaciones que se comunican con servicios y recursos remotos deben ser sensibles a fallas transitorias.
Las fallas transitorias incluyen la pérdida momentánea de la conectividad de la red a los servicios, la indisponibilidad temporal
de un servicio o los tiempos de espera que surgen cuando un servicio está ocupado. Estas fallas a menudo se corrigen solas y, si
la acción se repite después de un retraso adecuado, es probable que tenga éxito.

Las fallas transitorias pueden tener un gran impacto en la calidad percibida de una aplicación, incluso si ha sido probada
exhaustivamente en todas las circunstancias previsibles. Para garantizar que una aplicación que se comunica con servicios remotos
funcione de manera confiable, debe poder hacer todo lo siguiente:


Detecte fallas cuando ocurran y determine si es probable que sean transitorias.

Vuelva a intentar la operación si determina que es probable que la falla sea transitoria y realice un seguimiento del número de
veces que se reintentó la operación.

Utilice una estrategia de reintento adecuada, que especifique el número de reintentos, el retraso entre cada intento y las
acciones a realizar después de un intento fallido.

Este manejo de fallas transitorias se puede lograr envolviendo todos los intentos de acceder a un servicio remoto en un código que
implemente el patrón de reintento.

Patrón de reintento

Si una aplicación detecta una falla cuando intenta enviar una solicitud a un servicio remoto, puede manejar la falla de cualquiera de las
siguientes maneras:


Reintentando la operación. La aplicación podría volver a intentar la solicitud fallida inmediatamente.

Reintentando la operación después de un retraso. La aplicación debe esperar un tiempo adecuado antes de volver a intentar
la solicitud.

Cancelando la operación. La aplicación debe cancelar la operación e informar una excepción.

La estrategia de reintento debe ajustarse para que coincida con los requisitos comerciales de la aplicación. Por ejemplo, es importante
optimizar el recuento de reintentos y el intervalo de reintentos para la operación que se intenta. Si la operación es parte de una
interacción del usuario, el intervalo de reintento debe ser corto y solo se deben intentar unos pocos reintentos para evitar que
los usuarios esperen una respuesta. Si la operación es parte de un flujo de trabajo de larga duración, donde cancelar o reiniciar
el flujo de trabajo es costoso o requiere mucho tiempo, es apropiado esperar más tiempo entre intentos y volver a intentarlo más veces.

72 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Nota

Una estrategia de reintento agresiva con un retraso mínimo entre intentos y una gran cantidad de reintentos podría
degradar un servicio remoto que se ejecuta cerca de su capacidad o al máximo de su capacidad. Además, dicha estrategia de
reintento también podría afectar la capacidad de respuesta de la aplicación si intenta continuamente realizar una operación fallida.

Si una solicitud aún falla después de varios reintentos, es mejor que la aplicación evite que más solicitudes vayan al mismo
recurso e informe una falla. Luego, después de un período determinado, la aplicación puede realizar una o más solicitudes
al recurso para ver si tienen éxito. Para obtener más información, consulte Patrón de disyuntor.

Consejo

Nunca implemente un mecanismo de reintento sin fin. En lugar de ello, prefiera un retroceso exponencial.

Utilice un número finito de reintentos o implemente el disyuntor patrón para permitir que un servicio se recupere.

La aplicación de referencia eShopOnContainers implementa el patrón de reintento. Para obtener más


información, incluida una discusión sobre cómo combinar el patrón de reintento con la clase HttpClient, consulte
Microservicios .NET: arquitectura para aplicaciones .NET en contenedores.

Para obtener más información sobre el patrón de reintento, consulte Reintentar patrón en Microsoft Docs.

Patrón de disyuntor
En algunas situaciones, pueden ocurrir fallas debido a eventos anticipados que tardan más en solucionarse. Estas fallas
pueden variar desde una pérdida parcial de conectividad hasta la falla total de un servicio. En estas situaciones, no tiene
sentido que una aplicación vuelva a intentar una operación que es poco probable que tenga éxito y, en su lugar, debería
aceptar que la operación falló y manejar esta falla en consecuencia.

El patrón de disyuntor puede evitar que una aplicación intente repetidamente ejecutar una operación que probablemente
falle, al mismo tiempo que permite que la aplicación detecte si la falla se ha resuelto.

Nota

El propósito del patrón de disyuntor es diferente del patrón de reintento. El patrón de reintento permite que una aplicación
reintente una operación con la expectativa de que tendrá éxito. El patrón de disyuntor impide que una aplicación realice una
operación que probablemente falle.

Un disyuntor actúa como proxy para operaciones que podrían fallar. El proxy debe monitorear la cantidad de fallas recientes
que han ocurrido y usar esta información para decidir si permite que la operación continúe o si devuelve una
excepción inmediatamente.

La aplicación multiplataforma eShopOnContainers actualmente no implementa el patrón de disyuntor.


Sin embargo, eShopOnContainers sí lo hace. Para obtener más información, consulte Microservicios .NET: arquitectura
para aplicaciones .NET en contenedores.

Consejo

Combine los patrones de reintento y disyuntor.

73 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

Una aplicación puede combinar los patrones de reintento y disyuntor mediante el uso del patrón de reintento para invocar una
operación a través de un disyuntor. Sin embargo, la lógica de reintento debe ser sensible a cualquier excepción devuelta por el
disyuntor y abandonar los reintentos si el disyuntor indica que una falla no es transitoria.

Para obtener más información sobre el patrón del disyuntor, consulte la sección Disyuntor. patrón en Microsoft Docs.

Resumen
Muchas soluciones modernas basadas en web utilizan servicios web, alojados en servidores web, para proporcionar
funcionalidad para aplicaciones de clientes remotos. Las operaciones que expone un servicio web constituyen una API web, y
las aplicaciones cliente deberían poder utilizar la API web sin saber cómo se implementan los datos u operaciones que
expone la API.

El rendimiento de una aplicación se puede mejorar almacenando en caché los datos a los que se accede con frecuencia en
un almacenamiento rápido ubicado cerca de la aplicación. Las aplicaciones pueden implementar el almacenamiento en
caché de lectura directa con el patrón de caché aparte. Este patrón determina si el elemento está actualmente en la caché. Si
el elemento no está en la caché, se lee del almacén de datos y se agrega a la caché.

Al comunicarse con las API web, las aplicaciones deben ser sensibles a fallas transitorias. Las fallas transitorias incluyen la
pérdida momentánea de la conectividad de la red a los servicios, la indisponibilidad temporal de un servicio o los tiempos de
espera que surgen cuando un servicio está ocupado. Estas fallas a menudo se corrigen solas y, si la acción se repite después de
un retraso adecuado, es probable que tenga éxito. Por lo tanto, las aplicaciones deben incluir todos los intentos de acceder a
una API web en un código que implemente un mecanismo de manejo de fallas transitorias.

74 CAPÍTULO 10 | Acceder a datos remotos


Machine Translated by Google

CAPÍTULO 11

Autenticación y
Autorización
La autenticación es el proceso de obtener credenciales de identificación, como el nombre y la contraseña,
de un usuario y validar esas credenciales frente a una autoridad. La entidad que envió las credenciales se
considera una identidad autenticada si las credenciales son válidas. Una vez que se ha establecido
una identidad, un proceso de autorización determina si esa identidad tiene acceso a un recurso determinado.

Existen muchos enfoques para integrar la autenticación y la autorización en una aplicación .NET MAUI que se
comunica con una aplicación web ASP.NET, incluido el uso de ASP.NET Core Identity, proveedores de
autenticación externos como Microsoft, Google, Facebook o Twitter, y middleware de autenticación. .
La aplicación multiplataforma eShopOnContainers realiza autenticación y autorización con un microservicio de
identidad en contenedores que utiliza IdentityServer 4. La aplicación solicita tokens de seguridad de IdentityServer
para autenticar a un usuario o acceder a un recurso. Para que IdentityServer emita tokens en nombre de un
usuario, el usuario debe iniciar sesión en IdentityServer. Sin embargo, IdentityServer no proporciona una
interfaz de usuario ni una base de datos para la autenticación. Por lo tanto, en la aplicación de referencia
de eShopOnContainers, se utiliza ASP.NET Core Identity para este propósito.

Autenticación
Se requiere autenticación cuando una aplicación necesita conocer la identidad del usuario actual. El mecanismo
principal de ASP.NET Core para identificar usuarios es el sistema de membresía ASP.NET Core Identity, que
almacena la información del usuario en un almacén de datos configurado por el desarrollador. Normalmente, este
almacén de datos será un almacén EntityFramework, aunque se pueden usar almacenes personalizados o paquetes de
terceros para almacenar información de identidad en Azure Storage, DocumentDB u otras ubicaciones.

Para escenarios de autenticación que utilizan un almacén de datos de usuario local y conservan información de identidad
entre solicitudes a través de cookies (como es típico en las aplicaciones web ASP.NET), ASP.NET Core Identity es
una solución adecuada. Sin embargo, las cookies no siempre son un medio natural para conservar y transmitir datos.
Por ejemplo, una aplicación web ASP.NET Core que expone puntos finales RESTful a los que se accede desde una
aplicación normalmente necesitará usar autenticación de token de portador, ya que las cookies no se pueden usar en este escenario.
Sin embargo, los tokens al portador se pueden recuperar e incluir fácilmente en el encabezado de autorización de las
solicitudes web realizadas desde la aplicación.

75 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Emitir tokens al portador utilizando IdentityServer 4

Servidor de identidad 4 es un marco OpenID Connect y OAuth 2.0 de código abierto para ASP.NET Core, que
se puede utilizar para muchos escenarios de autenticación y autorización, incluida la emisión de tokens de
seguridad para usuarios locales de ASP.NET Core Identity.

Nota

OpenID Connect y OAuth 2.0 son muy similares, aunque tienen responsabilidades diferentes.

OpenID Connect es una capa de autenticación además del protocolo OAuth 2.0. OAuth 2 es un protocolo que permite a las
aplicaciones solicitar tokens de acceso de un servicio de token de seguridad y utilizarlos para comunicarse con las API.
Esta delegación reduce la complejidad tanto en las aplicaciones cliente como en las API, ya que la autenticación y la autorización
se pueden centralizar.

OpenID Connect y OAuth 2.0 combinan las dos preocupaciones de seguridad fundamentales de la autenticación y el acceso
API, y IdentityServer 4 es una implementación de estos protocolos.

En aplicaciones que utilizan comunicación directa de cliente a microservicio, como la aplicación de referencia eShopOnContainers,
se puede utilizar un microservicio de autenticación dedicado que actúa como servicio de token de seguridad (STS) para autenticar a
los usuarios, como se muestra en el siguiente diagrama. Para obtener más información sobre la comunicación directa de cliente
a microservicio, consulte Microservicios.

La aplicación multiplataforma eShopOnContainers se comunica con el microservicio de identidad, que utiliza IdentityServer 4 para
realizar la autenticación y el control de acceso a las API. Por lo tanto, la aplicación multiplataforma solicita tokens a IdentityServer,
ya sea para autenticar a un usuario o para acceder a un recurso:


La autenticación de usuarios con IdentityServer se logra mediante la aplicación multiplataforma que solicita un token de
identidad , que representa el resultado de un proceso de autenticación. Como mínimo, contiene un identificador del
usuario e información sobre cómo y cuándo se autentica el usuario. También puede incluir datos de identidad

adicionales.

El acceso a un recurso con IdentityServer se logra mediante la aplicación multiplataforma que solicita un token de
acceso , que permite el acceso a un recurso API. Los clientes solicitan tokens de acceso y los reenvían a la API.
Los tokens de acceso contienen información sobre el cliente y el usuario, si están presentes. Luego, las API utilizan
esa información para autorizar el acceso a sus datos.

Nota

Un cliente debe estar registrado en IdentityServer antes de que pueda solicitar tokens correctamente. Para obtener más
información sobre cómo agregar clientes, consulte Definición de clientes.

76 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Agregar IdentityServer a una aplicación web


Para que una aplicación web ASP.NET Core utilice IdentityServer 4, se debe agregar a la solución Visual Studio de la aplicación
web. Para obtener más información, consulte Configuración y descripción general. en la documentación de IdentityServer.
Una vez que IdentityServer se incluye en la solución Visual Studio de la aplicación web, se debe agregar a su canal de procesamiento
de solicitudes HTTP para atender solicitudes a los puntos finales de OpenID Connect y OAuth 2.0. Esto se logra en el método
Configure en la clase Startup de la aplicación web, como se demuestra en el siguiente ejemplo de código:

Configuración pública vacía (aplicación IApplicationBuilder, entorno IHostingEnvironment, registro ILoggerFactory


erFábrica)
{
aplicación.UseIdentity();
}

El orden es importante en el proceso de procesamiento de solicitudes HTTP de la aplicación web. Por lo tanto, IdentityServer debe
agregarse a la canalización antes del marco de la interfaz de usuario que implementa la pantalla de inicio de sesión.

Configurar el servidor de identidad


IdentityServer se debe configurar en el método ConfigureServices en la clase Startup de la aplicación web llamando al método
services.AddIdentityServer, como se demuestra en el siguiente ejemplo de código de la aplicación de referencia
eShopOnContainers:

ConfigureServices públicos vacíos (servicios IServiceCollection)


{
servicios
.AddIdentityServer(x => x.IssuerUri = "nulo")
.AddSigningCredential(Certificado.Get())
.AddAspNetIdentity<UsuarioAplicación>()
.AddConfigurationStore(constructor =>
builder.UseSqlServer (cadena de conexión, opciones =>
opciones.MigrationsAssembly(migrationsAssembly)))
.AddOperationalStore(constructor =>
builder.UseSqlServer (cadena de conexión, opciones =>
opciones.MigrationsAssembly(migrationsAssembly)))
.Services.AddTransient<IProfileService, ProfileService>();
}

Después de llamar al método services.AddIdentityServer, se llaman API fluidas adicionales para configurar lo siguiente:


Credenciales utilizadas para firmar.

API y recursos de identidad a los que los usuarios pueden solicitar acceso.

Clientes que se conectarán para solicitar tokens.

Identidad central de ASP.NET.

Consejo

Cargue dinámicamente la configuración de IdentityServer 4. Las API de IdentityServer 4 permiten configurar IdentityServer a
partir de una lista en memoria de objetos de configuración. En la aplicación de referencia eShopOnContainers, estas colecciones en
memoria están codificadas en la aplicación. Sin embargo, en escenarios de producción se pueden cargar dinámicamente desde un
archivo de configuración o desde una base de datos.

77 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Para obtener información sobre cómo configurar IdentityServer para usar ASP.NET Core Identity, consulte Uso de ASP.NET
Core Identity. en la documentación de IdentityServer.

Configurar recursos API


Al configurar los recursos de API, el método AddInMemoryApiResources espera una colección
IEnumerable<ApiResource>. El siguiente ejemplo de código muestra el método GetApis que proporciona esta colección en la
aplicación de referencia de eShopOnContainers:

público estático IEnumerable<ApiResource> GetApis()


{
devolver nueva Lista<ApiResource>
{
nuevo ApiResource("pedidos", "Servicio de pedidos"),
nuevo ApiResource("cesta", "Servicio de cesta")

}

Este método especifica que IdentityServer debe proteger los pedidos y las API de la cesta. Por lo tanto, se necesitarán
tokens de acceso administrados por IdentityServer al realizar llamadas a estas API. Para obtener más información sobre el
tipo ApiResource, consulte Recurso API en la documentación de IdentityServer 4.

Configurar recursos de identidad


Al configurar recursos de identidad, el método AddInMemoryIdentityResources espera una colección
IEnumerable<IdentityResource>. Los recursos de identidad son datos como el ID de usuario, el nombre o la dirección de correo
electrónico. Cada recurso de identidad tiene un nombre único y se le pueden asignar tipos de reclamo arbitrarios, que se
incluirán en el token de identidad del usuario. El siguiente ejemplo de código muestra el método GetResources que
proporciona esta colección en la aplicación de referencia de eShopOnContainers:

público estático IEnumerable<IdentityResource> GetResources()


{
devolver nueva Lista<IdentityResource>
{
nuevos IdentityResources.OpenId(),
nuevo IdentityResources.Profile()

}

La especificación OpenID Connect especifica algunos recursos de identidad estándar. El requisito mínimo es que
se proporcione soporte para emitir una identificación única para los usuarios. Esto se logra exponiendo el recurso de
identidad IdentityResources.OpenId.

Nota

La clase IdentityResources admite todos los ámbitos definidos en la especificación OpenID Connect (openid, correo
electrónico, perfil, teléfono y dirección).

IdentityServer también admite la definición de recursos de identidad personalizados. Para obtener más información, consulte
Definición de recursos de identidad personalizados. en la documentación de IdentityServer. Para obtener más información
sobre el tipo IdentityResource, consulte Recurso de identidad en la documentación de IdentityServer 4.

78 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Configurando clientes
Los clientes son aplicaciones que pueden solicitar tokens de IdentityServer. Normalmente, se deben definir las siguientes configuraciones para cada
cliente como mínimo:

• Una identificación de cliente única.

• Las interacciones permitidas con el servicio de token (conocido como tipo de concesión). • La ubicación a la que se

envían los tokens de identidad y acceso (conocida como URI de redireccionamiento). • Una lista de recursos a los que el cliente tiene

acceso (conocidos como ámbitos).

Al configurar clientes, el método AddInMemoryClients espera una colección IEnumerable<Client>. El siguiente ejemplo de código muestra

la configuración de la aplicación multiplataforma eShopOnContainers en el método GetClients que proporciona esta colección en la aplicación de

referencia de eShopOnContainers:

público estático IEnumerable<Cliente> GetClients(Diccionario<cadena,cadena> clientUrl) {

devolver nueva Lista<Cliente>


{
// Omitido por brevedad nuevo
Cliente {

ClientId = "xamarin",
ClientName = "eShop Xamarin OpenId Client",
AllowedGrantTypes = GrantTypes.Hybrid,
ClientSecrets = {

nuevo Secreto("secreto".Sha256())
},
RedirectUris = { clientUrl["Xamarin"] },
Requerir consentimiento = falso,
RequerirPkce = verdadero,
PostLogoutRedirectUris = { $"{clientsUrl["Xamarin"]}/Cuenta/Redireccionamiento" },
AllowedCorsOrigins = { "http://eshopxamarin" },
AllowedScopes = nueva Lista<cadena> {

IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.OfflineAccess, "pedidos",
"cesta"

},
Permitir acceso sin conexión = verdadero,
AllowAccessTokensViaBrowser = verdadero
},

}

Esta configuración especifica datos para las siguientes propiedades:

Propiedad Descripción

Identificación del cliente


Una identificación única para el cliente.

Nombre del cliente El nombre para mostrar del cliente, que se utiliza para el

registro y la pantalla de consentimiento.

79 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Propiedad Descripción

Tipos de concesión permitidos Especifica cómo un cliente quiere interactuar con


IdentityServer. Para obtener más información, consulte
Configuración del flujo de autenticación.

Secretos del cliente Especifica las credenciales secretas del cliente que se utilizan
al solicitar tokens desde el punto final del token.

RedirigirUris Especifica los URI permitidos a los que devolver tokens o


códigos de autorización.

Requerir consentimiento Especifica si se requiere una pantalla de consentimiento.

RequerirPkce Especifica si los clientes que utilizan un código de autorización


deben enviar una clave de prueba.

PublicarCerrar sesiónRedireccionarUris Especifica los URI permitidos a los que redirigir después de
cerrar sesión.

PermitidoCorsOrigins Especifica el origen del cliente para que IdentityServer


pueda permitir llamadas entre orígenes desde el origen.

Ámbitos permitidos Especifica los recursos a los que tiene acceso el cliente.
De forma predeterminada, un cliente no tiene acceso a ningún
recursos.

Permitir acceso sin conexión Especifica si el cliente puede solicitar tokens de actualización.

Configurar el flujo de autenticación


El flujo de autenticación entre un cliente e IdentityServer se puede configurar especificando los tipos de concesión en la propiedad

Client.AllowedGrantTypes. Las especificaciones OpenID Connect y OAuth 2.0 definen varios flujos de autenticación, que incluyen:

Flujo de autenticación Descripción

Implícito Este flujo está optimizado para aplicaciones basadas en


navegador y debe usarse solo para autenticación de usuario o
para solicitudes de autenticación y token de acceso. Todos
los tokens se transmiten a través del navegador y,
por lo tanto, las funciones avanzadas como los tokens de
actualización no están disponibles.

permitido.

Código de Autorización Este flujo brinda la capacidad de recuperar tokens en un canal


posterior, a diferencia del canal frontal del navegador, y al mismo
tiempo admite la autenticación del cliente.

80 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Flujo de autenticación Descripción

Híbrido Este flujo es una combinación de los tipos de concesión


de código implícito y de autorización. El token de
identidad se transmite a través del canal del navegador

y contiene la respuesta del protocolo firmado y otros


artefactos como el código de autorización.

Después de validar con éxito la respuesta, se debe utilizar


el canal posterior para recuperar la
token de acceso y actualización.

Consejo

Considere utilizar el flujo de autenticación híbrido. El flujo de autenticación híbrido mitiga una serie de ataques que se aplican al
canal del navegador y es el flujo recomendado para aplicaciones nativas que desean recuperar tokens de acceso (y posiblemente
actualizar tokens).

Para obtener más información sobre los flujos de autenticación, consulte Tipos de concesión. en la documentación
de IdentityServer 4.

Realizar autenticación
Para que IdentityServer emita tokens en nombre de un usuario, el usuario debe iniciar sesión en IdentityServer. Sin embargo,
IdentityServer no proporciona una interfaz de usuario ni una base de datos para la autenticación. Por lo tanto, en la
aplicación de referencia de eShopOnContainers, se utiliza ASP.NET Core Identity para este propósito.

La aplicación multiplataforma eShopOnContainers se autentica con IdentityServer con el flujo de autenticación híbrido,
que se ilustra en el siguiente diagrama.

Se realiza una solicitud de inicio de sesión a <punto final base>:5105/connect/authorize. Después de una autenticación
exitosa, IdentityServer devuelve una respuesta de autenticación que contiene un código de autorización y un token de identidad.
El código de autorización se envía a <punto final base>:5105/connect/token, que responde con tokens de acceso, identidad y
actualización.

La aplicación multiplataforma eShopOnContainers cierra sesión en IdentityServer enviando una solicitud a <punto final base>:5105/
connect/endsession con parámetros adicionales. Después de cerrar sesión, IdentityServer responde enviando un URI de
redireccionamiento posterior al cierre de sesión a la aplicación multiplataforma. El siguiente diagrama ilustra este proceso.

81 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

En la aplicación multiplataforma eShopOnContainers, la comunicación con IdentityServer la realiza la clase


IdentityService, que implementa la interfaz IIdentityService. Esta interfaz especifica que la clase de implementación
debe proporcionar los métodos CreateAuthorizationRequest, CreateLogoutRequest y GetTokenAsync.

Iniciando sesión

Cuando el usuario toca el botón INICIAR SESIÓN en LoginView, se ejecuta SignInCommand en la clase
LoginViewModel, que a su vez ejecuta el método SignInAsync. El siguiente ejemplo de código muestra este
método:

Tarea asíncrona privada SignInAsync() {

espere IsBusyFor
( async () => {

LoginUrl = _identityService.CreateAuthorizationRequest();

Es válido = verdadero;
IsLogin = verdadero;
});
}

Este método invoca el método CreateAuthorizationRequest en la clase IdentityService, como se muestra en el


siguiente ejemplo de código:

cadena pública CreateAuthorizationRequest() {

// Crear URI para el punto final de autorización var


AuthorizeRequest = new AuthorizeRequest(GlobalSetting.Instance.IdentityEndpoint); // Diccionario con valores para
la solicitud de autorización var dic = new Dictionary<string, string>();
dic.Add("client_id", GlobalSetting.Instance.ClientId);
dic.Add("client_secret", GlobalSetting.Instance.ClientSecret);
dic.Add("tipo_respuesta", "código id_token"); dic.Add("scope", "openid perfil cesta
pedidos ubicaciones marketing offline_access");
dic.Add("redirect_uri", GlobalSetting.Instance.IdentityCallback); dic.Add("nonce", Guid.NewGuid().ToString("N"));
dic.Add("code_challenge", CreateCodeChallenge()); dic.Add("code_challenge_method",
"S256"); // Agregue un token CSRF para protegerse contra
ataques de falsificación de solicitudes entre sitios. var
currentCSRFToken = Guid.NewGuid().ToString("N");
dic.Add("estado", currentCSRFToken);

var AuthorizeUri = AuthorizeRequest.Create(dic); autorización de


devolución Uri;
}

82 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Este método crea el URI para el punto final de autorización de IdentityServer. con los parámetros requeridos.
El punto final de autorización está en /connect/authorize en el puerto 5105 del punto final base expuesto como una configuración
de usuario. Para obtener más información sobre la configuración del usuario, consulte Gestión de configuración.

Nota

La superficie de ataque de la aplicación multiplataforma eShopOnContainers se reduce mediante la implementación de la


extensión Proof Key for Code Exchange (PKCE) en OAuth. PKCE protege el código de autorización para que no se utilice si es
interceptado. Esto se logra cuando el cliente genera un verificador secreto, cuyo hash se pasa en la solicitud de autorización y que
se presenta sin hash al canjear el código de autorización. Para obtener más información sobre PKCE, consulte Clave
de prueba para el intercambio de código por parte de clientes públicos de OAuth. en el sitio web del Grupo de Trabajo de
Ingeniería de Internet.

El URI devuelto se almacena en la propiedad LoginUrl de la clase LoginViewModel. Cuando la propiedad IsLogin se vuelve
verdadera, WebView en LoginView se vuelve visible. Los datos de WebView vinculan su propiedad Source a la propiedad
LoginUrl de la clase LoginViewModel y una solicitud de inicio de sesión a IdentityServer cuando la propiedad LoginUrl se
establece en el punto final de autorización de IdentityServer. Cuando IdentityServer recibe esta solicitud y el usuario no está
autenticado, WebView será redirigido a la página de inicio de sesión configurada que se muestra en la imagen a continuación.

Una vez que se complete el inicio de sesión, WebView será redirigido a un URI de retorno. Esta navegación WebView hará que se
ejecute el método NavigateAsync en la clase LoginViewModel, como se muestra en el siguiente ejemplo de código:

83 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Task NavigateAsync asíncrono privado ( URL de cadena)


{
var unescapedUrl = System.Net.WebUtility.UrlDecode(url);

si (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback,
StringComparison.OrdinalIgnoreCase))
{
_settingsService.AuthAccessToken = cadena.Empty;
_settingsService.AuthIdToken = cadena.Empty;
IsLogin = falso;
LoginUrl = _identityService.CreateAuthorizationRequest();
}
de lo contrario, si (unescapedUrl.Contains(GlobalSetting.Instance.Callback,
StringComparison.OrdinalIgnoreCase))
{
var authResponse = nueva AuthorizeResponse(url);
si (!string.IsNullOrWhiteSpace(authResponse.Code))
{
var userToken = await _identityService.GetTokenAsync(authResponse.Code);
cadena accessToken = userToken.AccessToken;

si (!string.IsNullOrWhiteSpace(accessToken))
{
_settingsService.AuthAccessToken = accessToken;
_settingsService.AuthIdToken = authResponse.IdentityToken;
await NavigationService.NavigateToAsync("//Main/Catalog");
}
}
}
}

Este método analiza la respuesta de autenticación contenida en el URI de retorno y, siempre que esté presente un código de autorización
válido, realiza una solicitud al punto final del token de IdentityServer. pasando el código de autorización, el verificador secreto
PKCE y otros parámetros requeridos. El punto final del token está en /connect/token en el puerto 5105 del punto final base expuesto
como una configuración de usuario. Para obtener más información sobre la configuración de usuario, consulte Gestión de
configuración).

Consejo

Asegúrese de validar los URI de retorno. Aunque la aplicación multiplataforma eShopOnContainers no valida el URI de retorno,
la mejor práctica es validar que el URI de retorno haga referencia a una ubicación conocida para evitar ataques de redireccionamiento
abierto.

Si el punto final del token recibe un código de autorización válido y un verificador secreto PKCE, responde con un token de acceso, un
token de identidad y un token de actualización. El token de acceso (que permite el acceso a los recursos de la API) y el
token de identidad se almacenan como configuración de la aplicación y se realiza la navegación por la página.
Por lo tanto, el efecto general en la aplicación multiplataforma eShopOnContainers es el siguiente: siempre que los usuarios puedan
autenticarse exitosamente con IdentityServer, se les dirige a la ruta //Main/Catalog, que es una página con pestañas que muestra
CatalogView como su pestaña seleccionada. .

Para obtener información sobre la navegación de páginas, consulte Navegación. Para obtener información sobre cómo la
navegación WebView hace que se ejecute un método de modelo de vista, consulte Invocar la navegación mediante comportamientos.
Para obtener información sobre la configuración de la aplicación, consulte Gestión de configuración.

84 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

Nota

eShopOnContainers también permite un inicio de sesión simulado cuando la aplicación está configurada para usar servicios
simulados en SettingsView. En este modo, la aplicación no se comunica con IdentityServer, sino que permite al usuario
iniciar sesión con cualquier credencial.

Cerrando sesión
Cuando el usuario toca el botón CERRAR SESIÓN en ProfileView, se ejecuta LogoutCommand en la clase
ProfileViewModel, que ejecuta el método LogoutAsync. Este método realiza la navegación de la página a la página
LoginView, pasando una instancia LogoutParameter establecida en verdadero como parámetro.

Cuando se crea una vista y se navega a ella, se ejecuta el método InitializeAsync del modelo de vista asociado a la vista,
que luego ejecuta el método Logout de la clase LoginViewModel, que se muestra en el siguiente ejemplo de código:

cierre de sesión privado vacío ()


{
var authIdToken = Configuración.AuthIdToken;
var logoutRequest = _identityService.CreateLogoutRequest(authIdToken);

si (!string.IsNullOrEmpty(logoutRequest))
{
// Cerrar sesión
URL de inicio de sesión = solicitud de cierre de sesión;

// Omitido por brevedad


}

Este método invoca el método CreateLogoutRequest en la clase IdentityService y pasa el token de identidad
recuperado de la configuración de la aplicación como parámetro. Para obtener más información sobre la
configuración de la aplicación, consulte Gestión de configuración. El siguiente ejemplo de código muestra el
método CreateLogoutRequest:

cadena pública CreateLogoutRequest ( token de cadena)


{
// Omitido por brevedad

var (punto final, devolución de llamada) =


(GlobalSetting.Instance.LogoutEndpoint, GlobalSetting.Instance.LogoutCallback);

return $"{endpoint}?id_token_hint={token}&post_logout_redirect_uri={callback}";
}

Este método crea el URI para el punto final de la sesión de IdentityServer. con los parámetros requeridos.
El punto final de la sesión está en /connect/endsession en el puerto 5105 del punto final base expuesto como una
configuración de usuario.

El URI devuelto se almacena en la propiedad LoginUrl de la clase LoginViewModel. Si bien la propiedad IsLogin es
verdadera, WebView en LoginView es visible. Los datos de WebView vinculan su propiedad Source a la propiedad LoginUrl
de la clase LoginViewModel, realizando una solicitud de cierre de sesión a IdentityServer.

85 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

cuando la propiedad LoginUrl se establece en el punto final de sesión de IdentityServer. El cierre de sesión se produce cuando
IdentityServer recibe esta solicitud, siempre que el usuario haya iniciado sesión. La autenticación se realiza con una cookie
administrada por el middleware de autenticación de cookies de ASP.NET Core. Por lo tanto, cerrar sesión en IdentityServer elimina la
cookie de autenticación y envía un URI de redireccionamiento posterior al cierre de sesión al cliente.

WebView será redirigido al URI de redireccionamiento posterior al cierre de sesión en la aplicación multiplataforma. Esta
navegación WebView hará que se ejecute el método NavigateAsync en la clase LoginViewModel, que se muestra en el
siguiente ejemplo de código:

Task NavigateAsync asíncrono privado ( URL de cadena)


{
var unescapedUrl = System.Net.WebUtility.UrlDecode(url);

si (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback,
StringComparison.OrdinalIgnoreCase))
{
_settingsService.AuthAccessToken = cadena.Empty;
_settingsService.AuthIdToken = cadena.Empty;
IsLogin = falso;
LoginUrl = _identityService.CreateAuthorizationRequest();
}

// Omitido por brevedad


}

Este método borra tanto el token de identidad como el token de acceso de la configuración de la aplicación. Establece la propiedad
IsLogin en falso, lo que hace que WebView en la página LoginView se vuelva invisible.
Finalmente, la propiedad LoginUrl se establece en el URI del punto final de autorización de IdentityServer, con los parámetros
requeridos, en preparación para la próxima vez que el usuario inicie sesión.

Para obtener información sobre la navegación de páginas, consulte Navegación. Para obtener información sobre cómo la
navegación WebView hace que se ejecute un método de modelo de vista, consulte Invocar la navegación mediante comportamientos.
Para obtener información sobre la configuración de la aplicación, consulte Gestión de configuración.

Nota

eShopOnContainers también permite un cierre de sesión simulado cuando la aplicación está configurada para utilizar servicios simulados
en SettingsView. En este modo, la aplicación no se comunica con IdentityServer y, en cambio, borra los tokens almacenados de la
configuración de la aplicación.

Autorización
Después de la autenticación, las API web de ASP.NET Core a menudo necesitan autorizar el acceso, lo que permite que un servicio
hacer que las API estén disponibles para algunos usuarios autenticados, pero no para todos.

Se puede restringir el acceso a una ruta de ASP.NET Core aplicando un atributo Authorize a un controlador o acción, lo que limita el
acceso al controlador o acción a usuarios autenticados, como se muestra en el siguiente ejemplo de código:

[Autorizar]
Clase pública sellada BasketController : Controlador

86 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

{
// Omitido por brevedad
}

Si un usuario no autorizado intenta acceder a un controlador o acción marcada con el atributo Autorizar, el marco API
devuelve un código de estado HTTP 401 (no autorizado).

Nota

Se pueden especificar parámetros en el atributo Autorizar para restringir una API a usuarios específicos. Para obtener
más información, consulte ASP.NET Core Docs: Autorización.

IdentityServer se puede integrar en el flujo de trabajo de autorización para que los tokens de acceso que proporciona
controlen la autorización. Este enfoque se muestra en el siguiente diagrama.

La aplicación multiplataforma eShopOnContainers se comunica con el microservicio de identidad y solicita un token


de acceso como parte del proceso de autenticación. Luego, el token de acceso se reenvía a las API expuestas por los
microservicios de pedido y de cesta como parte de las solicitudes de acceso. Los tokens de acceso contienen
información sobre el cliente y el usuario. Las API luego usan esa información para autorizar

acceso a sus datos. Para obtener información sobre cómo configurar IdentityServer para proteger las API, consulte
Configuración de recursos de API.

Configuración de IdentityServer para realizar autorización


Para realizar la autorización con IdentityServer, su middleware de autorización debe agregarse a la canalización de solicitudes
HTTP de la aplicación web. El middleware se agrega en el método ConfigureAuth en la clase Startup de la aplicación
web, que se invoca desde el método Configure y se demuestra en el siguiente ejemplo de código de la aplicación de
referencia eShopOnContainers:

vacío virtual protegido ConfigureAuth (aplicación IApplicationBuilder)


{

87 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

var identidadUrl = Configuration.GetValue<cadena>("IdentidadUrl");


app.UseIdentityServerAuthentication (nuevas opciones de autenticación de IdentityServer
{
Autoridad = identidadUrl.ToString(),
NombreAlcance = "cesta",
RequireHttpsMetadata = falso,
});
}

Este método garantiza que solo se pueda acceder a la API con un token de acceso válido. El middleware valida el
token entrante para garantizar que se envíe desde un emisor confiable y valida que el token sea válido para usarse con la
API que lo recibe. Por lo tanto, navegar hasta el controlador de pedidos o de la cesta devolverá un código de
estado HTTP 401 (no autorizado), que indica que se requiere un token de acceso.

Nota

El middleware de autorización de IdentityServer se debe agregar a la canalización de solicitudes HTTP de la


aplicación web antes de agregar MVC con app.UseMvc() o app.UseMvcWithDefaultRoute().

Realizar solicitudes de acceso a las API


Al realizar solicitudes a los microservicios de pedidos y cesta, el token de acceso obtenido de
IdentityServer durante el proceso de autenticación debe incluirse en la solicitud, como se muestra en el siguiente
ejemplo de código:

var authToken = Configuración.AuthAccessToken;


Pedido = await _ordersService.GetOrderAsync(order.OrderNumber, authToken);

El token de acceso se almacena como una configuración de la aplicación, se recupera del almacenamiento específico
de la plataforma y se incluye en la llamada al método GetOrderAsync en la clase OrderService.

De manera similar, el token de acceso debe incluirse al enviar datos a una API protegida por IdentityServer, como se
muestra en el siguiente ejemplo de código:

var authToken = Configuración.AuthAccessToken;


espere _basketService.UpdateBasketAsync(
nueva cesta de clientes
{
CompradorId = userInfo.UserId,
Artículos = BasketItems.ToList()
},
token de autenticación);

El token de acceso se recupera de la configuración y se incluye en la llamada al método UpdateBasketAsync en la clase


BasketService.

La clase RequestProvider en la aplicación multiplataforma eShopOnContainers utiliza la clase HttpClient para realizar
solicitudes a las API RESTful expuestas por la aplicación de referencia de eShopOnContainers. Al realizar solicitudes
a las API de pedidos y cestas, que requieren autorización, se debe incluir un token de acceso válido con la solicitud.
Esto se logra agregando el token de acceso a los encabezados de la instancia de HttpClient, como se demuestra en el
siguiente ejemplo de código:

88 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Portador", para


conocido);

La propiedad DefaultRequestHeaders de la clase HttpClient expone los encabezados que se envían con cada solicitud
y el token de acceso se agrega al encabezado de Autorización con el prefijo de cadena Portador. Cuando la
solicitud se envía a una API RESTful, el valor del encabezado Autorización se extrae y se valida para garantizar que se
envía desde un emisor confiable y se utiliza para determinar si el usuario tiene permiso para invocar la API que la

recibe.

Para obtener más información sobre cómo la aplicación multiplataforma eShopOnContainers realiza solicitudes web,
consulte Acceso a datos remotos.

Resumen
Existen muchos enfoques para integrar la autenticación y la autorización en una aplicación .NET MAUI que se comunica
con una aplicación web ASP.NET. La aplicación multiplataforma eShopOnContainers realiza autenticación y
autorización con un microservicio de identidad en contenedores que utiliza IdentityServer 4. IdentityServer es un
marco OpenID Connect y OAuth 2.0 de código abierto para ASP.NET Core que se integra con ASP.NET Core
Identity para realizar la autenticación de token de portador.

La aplicación multiplataforma solicita tokens de seguridad de IdentityServer para autenticar a un usuario o acceder a un
recurso. Al acceder a un recurso, se debe incluir un token de acceso en la solicitud a las API que requieren
autorización. El middleware de IdentityServer valida los tokens de acceso entrantes para garantizar que se envíen
desde un emisor confiable y que sean válidos para usarse con la API que los recibe.

89 CAPÍTULO 11 | Autenticacion y autorizacion


Machine Translated by Google

CAPITULO 12

Características del kit de herramientas MVVM

Kit de herramientas MVVM

El patrón Model­View­ViewModel (MVVM) es una excelente base estructural para crear nuestras aplicaciones.
En este patrón, ViewModel se convierte en la columna vertebral de nuestra aplicación, ya que
proporciona comunicación a nuestra interfaz de usuario frontal y a los componentes de respaldo. Para proporcionar
integración con la interfaz de usuario, confiaremos en las propiedades y comandos de ViewModel. Como se detalla
en Actualización de vistas en respuesta a cambios en el modelo o modelo de vista subyacente, la interfaz
INotifyPropertyChanged en nuestro ViewModel permite cambios en nuestras propiedades para notificar cuando se cambia el valor.
Implementar todas estas características significa que nuestro ViewModel puede terminar volviéndose muy detallado.
Por ejemplo, el siguiente código muestra un ViewModel simple con propiedades que generan cambios:

clase pública SampleViewModel : INotifyPropertyChanged {

cadena privada _nombre;


privado int _valor;

evento público PropertyChangedEventHandler PropertyChanged;

Nombre de cadena pública


{
obtener =>
_nombre; establecer => SetPropertyValue(ref _nombre, valor);
}

Valor int público {

obtener => _valor;


establecer => SetPropertyValue(ref _value, valor);
}

void protegido SetPropertyValue<T>(ref T StorageField, T newValue, [CallerMemberName] string propertyName =


"")
{
if (Equals(storageField, newValue)) retorno;

campodealmacenamiento = nuevoValor;
RaisePropertyChanged(nombre de propiedad);
}

vacío virtual protegido RaisePropertyChanged ([CallerMemberName] cadena nombre de propiedad =


"")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

90 CAPÍTULO 12 | Características del kit de herramientas MVVM


Machine Translated by Google

Si bien se podrían realizar algunas optimizaciones con el tiempo, aún terminaremos con un conjunto de código bastante
detallado para definir nuestro ViewModel. Este código puede ser difícil de mantener y se vuelve propenso a errores.

El paquete NuGet CommunityToolkit.Mvvm (también conocido como MVVM Toolkit) se puede utilizar para ayudar a
abordar y simplificar estos patrones MVVM comunes. MVVM Toolkit, junto con características más nuevas del
lenguaje .NET, permite una lógica simplificada, una fácil adopción en un proyecto e independencia del tiempo de
ejecución. El siguiente ejemplo muestra el mismo ViewModel usando componentes que vienen con MVVM Toolkit:

clase parcial pública SampleViewModel : ObservableObject


{
[Propiedad observable]
cadena privada _nombre;

[Propiedad observable]
privado int _valor;
}

Nota

El kit de herramientas MVVM se proporciona con el paquete CommunityToolkit.Mvvm. Para obtener información sobre cómo
agregar el paquete a su proyecto, consulte Introducción al kit de herramientas MVVM. en el Centro de desarrolladores de
Microsoft.

En comparación con el ejemplo original, pudimos reducir drásticamente la complejidad general y simplificar la capacidad
de mantenimiento de nuestro ViewModel. MVVM Toolkit viene con muchos componentes y características comunes
prediseñados, como el ObservableObject que se muestra arriba, que simplifica y estandariza el código que
tenemos en toda la aplicación.

Objeto observable
El kit de herramientas MVVM proporciona ObservableObject, que está diseñado para usarse como base de
nuestros objetos ViewModel o cualquier objeto que necesite generar notificaciones de cambios. Implementa
INotifyPropertyChanged e INotifyPropertyChanging junto con métodos auxiliares para configurar propiedades y
generar cambios. A continuación se muestra un ejemplo de un ViewModel estándar que utiliza
ObservableObject:

clase pública SampleViewModel : ObservableObject


{
cadena privada _nombre;
privado int _valor;

Nombre de cadena pública


{
obtener => _nombre;
establecer => SetProperty(ref _nombre, valor);
}

valor int público


{
obtener => _valor;
establecer => EstablecerProperty(ref _valor, valor);

91 CAPÍTULO 12 | Características del kit de herramientas MVVM


Machine Translated by Google

}
}

ObservableObject maneja toda la lógica necesaria para generar notificaciones de cambios utilizando el método
SetProperty en su definidor de propiedades. Si tiene una propiedad que devuelve Task<T>, el método
SetPropertyAndNotifyOnCompletion se puede usar para retrasar la publicación de un cambio de propiedad hasta que se haya
completado la tarea. Los métodos OnPropertyChanged y OnPropertyChanging que también se pueden usar para generar
cambios de propiedad cuando sea necesario en su objeto.

Para obtener información más detallada sobre ObservableObject, consulte ObservableObject. en el Centro de
desarrolladores de MVVM Toolkit.

RelayCommand y AsyncRelayCommand
La interacción entre los controles .NET MAUI (por ejemplo, tocar un botón o seleccionar un elemento de una colección) y
ViewModel se realiza con la interfaz ICommand. .NET MAUI viene con una implementación predeterminada de ICommand con
el objeto Command. El comando de .NET MAUI es bastante básico y carece de soporte para funciones más avanzadas,
como la compatibilidad con el trabajo asincrónico y el estado de ejecución de comandos.

MVVM Toolkit viene con dos comandos, RelayCommand y AsyncRelayCommand.


RelayCommand está diseñado para situaciones en las que tiene código sincrónico para ejecutar y tiene una implementación
bastante similar al objeto .NET MAUI Command.

Nota

Aunque el comando .NET MAUI y RelayCommand son similares, el uso de RelayCommand permite desacoplar su ViewModel
de cualquier referencia directa a .NET MAUI. Esto significa que su ViewModel es más portátil, lo que facilita su
reutilización en todos los proyectos.

AsyncRelayCommand proporciona muchas funciones adicionales cuando se trabaja con flujos de trabajo asincrónicos.
Esto es bastante común en nuestro ViewModel, ya que normalmente nos comunicamos con repositorios, API, bases de
datos y otros sistemas que utilizan async/await. El constructor AsyncRelayCommand toma una tarea de ejecución definida
como Func<Task> o un delegado que devuelve Task como parte del constructor.
Mientras se ejecuta la tarea de ejecución, AsyncRelayCommand monitoreará el estado de la tarea y proporcionará
actualizaciones utilizando la propiedad IsRunning. La propiedad IsRunning se puede vincular a la interfaz de usuario, lo que
ayuda a administrar los estados de control, como mostrar la carga con un ActivityIndicator o deshabilitar/habilitar un control.
Mientras se ejecuta la tarea de ejecución, se puede llamar al método Cancelar para intentar cancelar la tarea de ejecución,
si es compatible.

De forma predeterminada, AsyncRelayCommand no permite la ejecución simultánea. Esto es muy útil en situaciones en las
que un usuario podría tocar un control varias veces sin querer para ejecutar una operación costosa o de larga duración.
Durante la ejecución de la tarea, AsyncRelayCommand llamará automáticamente al evento CanExecuteChanged. En .NET MAUI,
los controles que admiten las propiedades Command y CommandParameter, como Button, escucharán este evento y lo habilitarán
o deshabilitarán automáticamente durante la ejecución. Esta funcionalidad se puede anular mediante el uso de un
parámetro canExecute personalizado o configurando el indicador
AsyncRelayCommandOptions.AllowConcurrentExecutions en el constructor.

92 CAPÍTULO 12 | Características del kit de herramientas MVVM


Machine Translated by Google

Para obtener información más detallada sobre la implementación de comandos, consulte la sección Implementación de comandos .
en el capítulo MVVM. La información detallada para RelayCommand y AsyncRelayCommand está disponible en
Commanding del Centro de desarrolladores de MVVM Toolkit.

Generadores de fuentes
El uso de los componentes MVVM Toolkit listos para usar le permite simplificar enormemente nuestro ViewModel.
MVVM Toolkit le permite simplificar aún más los casos de uso de código común mediante el uso de generadores de código
fuente . Los generadores de código fuente de MVVM Toolkit buscan atributos específicos en nuestro código y pueden
generar contenedores para propiedades y comandos.

Importante

Los generadores de código fuente de MVVM Toolkit generan código que se suma a nuestros objetos existentes. Debido a esto,
cualquier objeto que aproveche un generador de fuente deberá marcarse como parcial.

El atributo ObservableProperty de MVVM Toolkit se puede aplicar a campos en objetos que heredan de ObservableObject y
envolverá un campo privado con una propiedad que genera cambios. El siguiente código muestra un ejemplo del uso del atributo
ObservableObject en el campo _name:

clase parcial pública SampleViewModel : ObservableObject


{
[Propiedad observable]
cadena privada _nombre;
}

Con el atributo ObservableProperty aplicado al campo _name, el generador de código fuente se ejecutará y generará otra clase
parcial con el siguiente código:

clase parcial SampleViewModel


{
Nombre de cadena pública
{
obtener => _nombre;
colocar

{
if (!EqualityComparer<cadena>.Default.Equals(_nombre, valor))
{
OnNameChanging(valor);
OnPropertyChanging("Nombre");
_nombre = valor;
OnNameChanged(valor);
OnPropertyChanged("Nombre");
}
}
}
}

El SampleViewModel generado utilizó el campo privado _name y generó una nueva propiedad Name que implementa toda
la lógica necesaria para generar notificaciones de cambios.

93 CAPÍTULO 12 | Características del kit de herramientas MVVM


Machine Translated by Google

El atributo RelayCommand de MVVM Toolkit se puede aplicar a métodos dentro de un ObservableObject y creará un RelayCommand o
AsyncRelayCommand correspondiente. El siguiente código muestra ejemplos del uso del atributo RelayCommand:

clase parcial pública SampleViewModel : ObservableObject {

servicio de navegación público INavigationService { get; colocar; }

[PropiedadObservable]
cadena privada _name;

[PropiedadObservable]
bool _isValid;

[RelayCommand]
Configuración de tarea privadaAsync
() {
return NavigationService.NavigateToAsync("Configuración");
}

[RelayCommand]
Validar vacío privado () {

IsValid = !string.IsNullOrEmpty(Nombre);
}
}

El RelayCommand aplicado al método Validate generará un RelayCommand validar ValidateCommand porque tiene un
retorno nulo y el método SettingsAsync generará un AsyncRelayCommand llamado SettingsCommand. El generador de código
fuente generará el siguiente código en otras clases parciales:

clase parcial SampleViewModel {

¿AsyncRelayCommand privado ? configuraciónComando;

SettingsCommand => settingsCommand ??= nuevo AsyncRelayCommand(SettingsAsync);


}

clase parcial SampleViewModel {

¿Comando de retransmisión privado ? validarComando;

public IRelayCommand ValidateCommand => validarCommand ??= nuevo RelayCommand(Validar);


}

Toda la complejidad de envolver los métodos de nuestro ViewModel con una implementación de ICommand ha sido manejada por el
generador de código fuente.

Para obtener información más detallada sobre los generadores de fuentes de MVVM Toolkit, consulte Generadores de fuentes de MVVM
en el Centro de desarrolladores de MVVM Toolkit.

94 CAPÍTULO 12 | Características del kit de herramientas MVVM


Machine Translated by Google

Resumen
MVVM Toolkit es una excelente manera de estandarizar y simplificar nuestro código ViewModel. El kit de herramientas
MVVM ofrece excelentes implementaciones de componentes MVVM estándar como ObservableObject y
Async/RelayCommand. Los generadores de código fuente ayudan a simplificar nuestras propiedades y comandos de
ViewModel generando todo el código repetitivo necesario para las interacciones de la interfaz de usuario. MVVM
Toolkit ofrece aún más funciones además de las que se han mostrado en este capítulo. Para obtener más
información sobre MVVM Toolkit, consulte Introducción a MVVM Toolkit. en el Centro de desarrolladores de MVVM Toolkit.

95 CAPÍTULO 12 | Características del kit de herramientas MVVM


Machine Translated by Google

CAPITULO 13

Examen de la unidad

Las aplicaciones multiplataforma experimentan problemas similares a las aplicaciones de escritorio y basadas en web. Los usuarios de
dispositivos móviles se diferenciarán por sus dispositivos, conectividad de red, disponibilidad de servicios y varios otros factores.
Por lo tanto, las aplicaciones multiplataforma deben probarse tal como se usarían en el mundo real para mejorar su calidad,
confiabilidad y rendimiento. Se deben realizar muchos tipos de pruebas en una aplicación, incluidas pruebas unitarias, pruebas
de integración y pruebas de interfaz de usuario. Las pruebas unitarias son la forma más común y esencial para crear aplicaciones de
alta calidad.

Una prueba unitaria toma una pequeña unidad de la aplicación, generalmente un método, la aísla del resto del código y verifica que se
comporta como se esperaba. Su objetivo es verificar que cada unidad de funcionalidad funcione como se espera, para que los errores
no se propaguen por toda la aplicación. Detectar un error donde ocurre es más eficiente que observar el efecto de un error
indirectamente en un punto secundario de falla.

Las pruebas unitarias tienen el efecto más significativo en la calidad del código cuando son una parte integral del flujo de trabajo de
desarrollo de software. Las pruebas unitarias pueden actuar como documentación de diseño y especificaciones funcionales para
una aplicación. Tan pronto como se ha escrito un método, se deben escribir pruebas unitarias que verifiquen el comportamiento
del método en respuesta a casos de datos de entrada estándar, de límites y incorrectos y verifiquen cualquier suposición explícita o
implícita hecha por el código. Alternativamente, con el desarrollo basado en pruebas, las pruebas unitarias se escriben antes del
código. Para obtener más información sobre el desarrollo basado en pruebas y cómo implementarlo, consulte Tutorial:
desarrollo basado en pruebas mediante Test Explorer.

Nota

Las pruebas unitarias son muy efectivas contra la regresión. Es decir, una funcionalidad que solía funcionar, pero que se vio
afectada por una actualización defectuosa.

Las pruebas unitarias suelen utilizar el patrón organizar­actuar­afirmar:

Paso Descripción

Arreglar Inicializa objetos y establece el valor de los datos que se


pasan al método bajo prueba.

Acto Invoca el método bajo prueba con los argumentos requeridos.

Afirmar Verifica que la acción del método bajo prueba se comporta

como se esperaba.

Este patrón garantiza que las pruebas unitarias sean legibles, autodescriptivas y coherentes.

96 CAPITULO 13 | Examen de la unidad


Machine Translated by Google

Inyección de dependencia y pruebas unitarias.


Una de las motivaciones para adoptar una arquitectura débilmente acoplada es que facilita las pruebas unitarias.
Uno de los tipos registrados en el servicio de inyección de dependencia es la interfaz IAppEnvironmentService. El
siguiente ejemplo de código muestra un esquema de esta clase:

clase pública OrderDetailViewModel : ViewModelBase


{
privado IAppEnvironmentService _appEnvironmentService;

Orden públicaDetailViewModel (
IAppEnvironmentService aplicaciónEnvironmentService,
IDialogService servicio de diálogo, INavigationService servicio de navegación,
ISettingsService configuraciónServicio)
: base (servicio de diálogo, servicio de navegación, servicio de configuración)
{
_appEnvironmentService = appEnvironmentService;
}
}

La clase OrderDetailViewModel depende del tipo IAppEnvironmentService, que el contenedor de inyección de


dependencia resuelve cuando crea una instancia de un objeto OrderDetailViewModel.
Sin embargo, en lugar de crear un objeto IAppEnvironmentService que utilice servidores, dispositivos y configuraciones
reales para realizar pruebas unitarias de la clase OrderDetailViewModel, reemplace el objeto
IAppEnvironmentService con un objeto simulado para los fines de las pruebas. Un objeto simulado es aquel que tiene la
misma firma de un objeto o una interfaz, pero se crea de una manera específica para ayudar con las pruebas unitarias.
A menudo se utiliza con inyección de dependencia para proporcionar implementaciones específicas de interfaces para
probar diferentes escenarios de flujo de trabajo y datos.

Este enfoque permite que el objeto IAppEnvironmentService se pase a la clase OrderDetailViewModel en tiempo de
ejecución y, en aras de la capacidad de prueba, permite pasar una clase simulada a la clase OrderDetailViewModel
en el momento de la prueba. La principal ventaja de este enfoque es que permite ejecutar pruebas unitarias sin requerir
recursos difíciles de manejar, como funciones de plataforma de ejecución, servicios web o bases de datos.

Prueba de aplicaciones MVVM


Probar modelos y ver modelos de aplicaciones MVVM es idéntico a probar cualquier otra clase y utiliza las mismas
herramientas y técnicas; esto incluye características como pruebas unitarias y burlas. Sin embargo, algunos patrones que
son típicos para modelar y ver clases de modelos pueden beneficiarse de técnicas de prueba unitarias específicas.

Consejo

Pruebe una cosa con cada prueba unitaria. A medida que aumenta la complejidad de una prueba, se hace más difícil
su verificación. Al limitar una prueba unitaria a una sola preocupación, podemos asegurarnos de que nuestras pruebas
sean más repetibles, aisladas y tengan un tiempo de ejecución menor. Ver

Mejores prácticas de pruebas unitarias con .NET Core y .NET Standard para obtener más prácticas recomendadas.

97 CAPITULO 13 | Examen de la unidad


Machine Translated by Google

No caiga en la tentación de hacer que una prueba unitaria ejercite más de un aspecto del comportamiento de la unidad. Hacerlo genera

pruebas que son difíciles de leer y actualizar. También puede generar confusión a la hora de interpretar un fallo.

La aplicación multiplataforma eShopOnContainers utiliza xUnit para realizar pruebas unitarias, que admiten dos tipos diferentes de pruebas

unitarias:

Atributo del tipo de prueba Descripción

Hechos Hecho Pruebas que siempre son verdaderas, que prueban condiciones invariantes.

Teorias Teoría Pruebas que sólo son verdaderas para un conjunto particular de datos.

Las pruebas unitarias incluidas con la aplicación multiplataforma eShopOnContainers son pruebas de hechos, por lo que cada método de
prueba unitaria está decorado con el atributo Fact.

Prueba de funcionalidad asincrónica


Al implementar el patrón MVVM, los modelos de vista generalmente invocan operaciones en servicios, a menudo de forma asincrónica.

Las pruebas de código que invocan estas operaciones suelen utilizar simulacros como reemplazo de los servicios reales. El siguiente ejemplo

de código demuestra la prueba de la funcionalidad asincrónica pasando un servicio simulado a un modelo de vista:

[Hecho]
Orden de tarea asíncrona públicaPropertyIsNotNullAfterViewModelInitializationTest ()
{
// Arreglar
var orderService = nuevo OrderMockService();
var orderViewModel = nuevo OrderDetailViewModel(orderService);

// Acto
orden var = espera ordenService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
espere ordenViewModel.InitializeAsync(orden);

// afirmar
Assert.NotNull(orderViewModel.Order);
}

Esta prueba unitaria comprueba que la propiedad Order de la instancia OrderDetailViewModel tendrá un valor después de que se haya

invocado el método InitializeAsync. El método InitializeAsync se invoca cuando se navega a la vista correspondiente del modelo de vista.

Para obtener más información sobre la navegación, consulte Navegación.

Cuando se crea la instancia de OrderDetailViewModel, espera que se especifique una instancia de IOrderService como argumento.

Sin embargo, OrderService recupera datos de un servicio web. Por lo tanto, se especifica una instancia de OrderMockService, una versión

simulada de la clase OrderService, como argumento del constructor OrderDetailViewModel. Luego, se recuperan datos simulados en lugar de

comunicarse con un servicio web cuando se invoca el método InitializeAsync del modelo de vista, que utiliza operaciones IOrderService.

98 CAPITULO 13 | Examen de la unidad


Machine Translated by Google

Prueba de implementaciones de INotifyPropertyChanged


La implementación de la interfaz INotifyPropertyChanged permite que las vistas reaccionen a los cambios que se originan en los modelos y

modelos de vista. Estos cambios no se limitan a los datos que se muestran en los controles: también se utilizan para controlar la vista, como los estados
del modelo de vista que hacen que se inicien animaciones o que los controles se activen.
estar inhabilitado.

Las propiedades que se pueden actualizar directamente mediante la prueba unitaria se pueden probar adjuntando un controlador de eventos al evento

PropertyChanged y verificando si el evento se genera después de establecer un nuevo valor para la propiedad. El siguiente ejemplo de código muestra

dicha prueba:

[Hecho]
Configuración de tarea asíncrona públicaOrderPropertyShouldRaisePropertyChanged()
{
var invocada = falsa;
var orderService = nuevo OrderMockService();
var orderViewModel = nuevo OrderDetailViewModel(orderService);

orderViewModel.PropertyChanged += (remitente, e) =>


{
if (e.PropertyName.Equals("Pedido"))
invocado = verdadero;

orden var = espera ordenService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
espere ordenViewModel.InitializeAsync(orden);

Assert.True(invocado);
}

Esta prueba unitaria invoca el método InitializeAsync de la clase OrderViewModel, lo que hace que se actualice su propiedad Order. La prueba unitaria

pasará, siempre que se genere el evento PropertyChanged para la propiedad Order.

Prueba de comunicación basada en mensajes


Los modelos de visualización que usan la clase MessagingCenter para comunicarse entre clases poco acopladas se pueden probar unitariamente

suscribiéndose al mensaje que envía el código bajo prueba, como se demuestra en el siguiente ejemplo de código:

[Hecho]
público vacío AddCatalogItemCommandSendsAddProductMessageTest()
{
var mensajeRecibido = falso;
var catalogService = nuevo CatalogMockService();
var catalogViewModel = nuevo CatalogViewModel(catalogService);

MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
esto, MessageKeys.AddProduct, (remitente, arg) =>
{
mensajerecibido = verdadero;
});
catalogViewModel.AddCatalogItemCommand.Execute(nulo);

99 CAPITULO 13 | Examen de la unidad


Machine Translated by Google

Assert.True(mensajerecibido);
}

Esta prueba unitaria verifica que CatalogViewModel publique el mensaje AddProduct en respuesta a la ejecución de
AddCatalogItemCommand. Debido a que la clase MessagingCenter admite suscripciones a mensajes de multidifusión, la prueba
unitaria puede suscribirse al mensaje AddProduct y ejecutar un delegado de devolución de llamada en respuesta a recibirlo. Este
delegado de devolución de llamada, especificado como una expresión lambda, establece un campo booleano que utiliza la
instrucción Assert para verificar el comportamiento de la prueba.

Probando el manejo de excepciones


También se pueden escribir pruebas unitarias que verifiquen que se lanzan excepciones específicas para acciones o entradas
no válidas, como se demuestra en el siguiente ejemplo de código:

[Hecho]
público vacío InvalidEventNameShouldThrowArgumentExceptionText()
{
comportamiento var = nuevo MockEventToCommandBehavior
{
Nombre del evento = "OnItemTapped"

var listView = nuevo ListView();

Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(comportamiento));


}

Esta prueba unitaria generará una excepción porque el control ListView no tiene un evento denominado OnItemTapped. El
método Assert.Throws<T> es un método genérico donde T es el tipo de excepción esperada. El argumento pasado al
método Assert.Throws<T> es una expresión lambda que generará la excepción. Por lo tanto, la prueba unitaria pasará siempre
que la expresión lambda arroje una ArgumentException.

Consejo

Evite escribir pruebas unitarias que examinen cadenas de mensajes de excepción. Las cadenas de mensajes de excepción
pueden cambiar con el tiempo, por lo que las pruebas unitarias que dependen de su presencia se consideran frágiles.

Validación de pruebas
Hay dos aspectos para probar la implementación de la validación: probar que las reglas de validación se implementen correctamente
y probar que la clase ValidatableObject<T> funciona como se esperaba.

La lógica de validación suele ser sencilla de probar, porque suele ser un proceso autónomo en el que la salida depende de la
entrada. Deben realizarse pruebas sobre los resultados de invocar el método Validate en cada propiedad que tenga al menos una
regla de validación asociada, como se demuestra en el siguiente ejemplo de código:

[Hecho]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{

100 CAPITULO 13 | Examen de la unidad


Machine Translated by Google

var MockViewModel = nuevo MockViewModel();


mockViewModel.Forename.Value = "Juan";
mockViewModel.Apellido.Value = "Smith";

var isValid = simuladoViewModel.Validate();

Afirmar.True(es válido);
}

Esta prueba unitaria verifica que la validación se realice correctamente cuando las dos propiedades ValidatableObject<T>
en la instancia de MockViewModel tienen datos.

Además de verificar que la validación sea exitosa, las pruebas unitarias de validación también deben verificar los valores
de las propiedades Value, IsValid y Errors de cada instancia de ValidatableObject<T>, para verificar que la clase
funcione como se esperaba. El siguiente ejemplo de código demuestra una prueba unitaria que hace esto:

[Hecho]
vacío público CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var MockViewModel = nuevo MockViewModel();
mockViewModel.Forename.Value = "Juan";

bool isValid = simuladoViewModel.Validate();

Afirmar.Falso (es válido);


Assert.NotNull(mockViewModel.Forename.Value);
Assert.Null(mockViewModel.Apellido.Value);
Assert.True(mockViewModel.Forename.IsValid);
Assert.False(mockViewModel.Apellido.IsValid);
Assert.Empty(mockViewModel.Forename.Errors);
Assert.NotEmpty(mockViewModel.Apellido.Errors);
}

Esta prueba unitaria comprueba que la validación falla cuando la propiedad Surname de MockViewModel no tiene ningún
dato y las propiedades Value, IsValid y Errors de cada instancia de ValidatableObject<T> están configuradas correctamente.

Resumen
Una prueba unitaria toma una pequeña unidad de la aplicación, generalmente un método, la aísla del resto del código y
verifica que se comporta como se esperaba. Su objetivo es verificar que cada unidad de funcionalidad funcione como se
espera, para que los errores no se propaguen por toda la aplicación.

El comportamiento de un objeto bajo prueba se puede aislar reemplazando los objetos dependientes con objetos
simulados que simulan el comportamiento de los objetos dependientes. Esto permite ejecutar pruebas unitarias sin
requerir recursos difíciles de manejar, como funciones de plataforma de tiempo de ejecución, servicios web o bases de datos.

Probar modelos y ver modelos de aplicaciones MVVM es idéntico a probar cualquier otra clase, y se pueden utilizar las
mismas herramientas y técnicas.

101 CAPITULO 13 | Examen de la unidad

También podría gustarte