Enterprise Application Patterns Using .NET MAUI
Enterprise Application Patterns Using .NET MAUI
EDICIÓN v1.0
PUBLICADO POR
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.
Todas las demás marcas y logotipos son propiedad de sus respectivos dueños.
Autores:
Revisores:
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.
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/mauisamples repositorio.
Machine Translated by Google
Contenido
Objetivo................................................. ................................................. ................................ 1
i Contenido
Machine Translated by Google
ii Contenido
Machine Translated by Google
III Contenido
Machine Translated by Google
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.
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.
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.
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
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:
•
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 ModeloVistaVerModelo.
•
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 ModelView
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.
• 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.
• 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.
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.
• XAML
• Control S
•
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
Además, se proporcionan pruebas unitarias para algunas de las clases en la multiplataforma eShopOnContainers.
aplicación.
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
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
Carpeta Descripción
Desencadenantes Contiene el desencadenador BeginAnimation, que se usa para invocar una animación en XAML.
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
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.
CAPÍTULO 3
ModeloVistaVerModelo
(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.
•
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
Consejo
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
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.
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
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
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.
Las clases de modelo se utilizan normalmente junto con servicios o repositorios que encapsulan el acceso a datos y el almacenamiento
en caché.
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
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
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
Las siguientes secciones analizan los principales enfoques para conectar modelos de vista con vistas.
<ContentPage xmlns:local="clrnamespace:eShop">
<ContentPage.BindingContext>
<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.
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.
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
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:
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:
{
_isLogin = valor;
RaisePropertyChanged(() => IsLogin);
}
}
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
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
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
•
Kit de herramientas MVVM de la comunidad .NET
• UI reactiva
•
Biblioteca Prisma
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,
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
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.
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 | ModeloVistaVerModelo (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:
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:
...
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.
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.
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.
/// <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);
}
void RegistroEvento()
{
devolver;
}
ArgumentNullException.ThrowIfNull(eventInfo.EventHandlerType);
ArgumentNullException.ThrowIfNull(eventHandlerMethodInfo);
void UnregisterEvent() {
comando.Ejecutar(parámetro);
}
}
}
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
LoginView usa EventToCommandBehavior para ejecutar ValidateCommand cuando el usuario cambia el valor de su contraseña, como se
<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
Para obtener más información sobre comportamientos, consulte Comportamientos. en el Centro de desarrolladores de .NET MAUI.
Resumen
El patrón ModelViewViewModel (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
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.
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.
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.
• Un contenedor elimina la necesidad de que una clase ubique sus dependencias y administre su vida útil.
• 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.
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.
Nota
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:
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.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.
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:
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.
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.
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:
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:
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:
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. .
CAPÍTULO 5
Comunicación entre
componentes
débilmente acoplados
El patrón de publicaciónsuscripció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ónsuscripció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.
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.
Advertencia
.NET MAUI contiene una clase MessagingCenter incorporada cuyo uso ya no se recomienda y debe
migrarse a MVVM Toolkit Messenger.
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.
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:
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:
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.
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.
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.
Resumen
La interfaz IMessenger de MVVM Toolkit describe el patrón de publicaciónsuscripció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.
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 ModeloVista
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
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.
Tarea PopAsync();
}
Esta interfaz especifica que una clase de implementación debe proporcionar los siguientes métodos:
Método Objetivo
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
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:
Esto devuelve una referencia al objeto MauiNavigationService que está almacenado en el contenedor de inyección de
dependencia.
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 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.
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.
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
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.
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:
espere NavigationService.NavigateToAsync(
"Detalle de la orden",
36 CAPÍTULO 6 | Navegación
Machine Translated by Google
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.
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.
<VistaWeb>
<WebView.Comportamientos>
<comportamientos:EventToCommandBehavior
EventName="Navegando"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Comando="{Binding NavigateCommand}" />
</WebView.Comportamientos>
</WebView>
37 CAPÍTULO 6 | Navegación
Machine Translated by Google
_settingsService.AuthAccessToken = accessToken;
_settingsService.AuthIdToken = authResponse.IdentityToken;
await NavigationService.NavigateToAsync("//Main/Catalog");
}
}
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.
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>:
} Valor T público {
} público ValidableObject() {
_isValid = verdadero;
_errors = Enumerable.Empty<cadena>();
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.
40 CAPÍTULO 7 | Validación
Machine Translated by Google
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:
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:
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
41 CAPÍTULO 7 | Validación
Machine Translated by Google
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:
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.
42 CAPÍTULO 7 | Validación
Machine Translated by Google
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>:
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.
43 CAPÍTULO 7 | Validación
Machine Translated by Google
44 CAPÍTULO 7 | Validación
Machine Translated by Google
Propiedad Descripción
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.
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
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
45 CAPÍTULO 7 | Validación
Machine Translated by Google
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
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
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
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
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:
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:
Configuración públicaViewModel (
ILocationService servicio de ubicación, IAppEnvironmentService appEnvironmentService,
IDialogService dialogService, INavigationService servicio de navegación, ISettingsService settingsService)
_identityEndpoint = _settingsService.IdentityEndpointBase;
}
{
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:
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.
CAPÍTULO 9
En contenedores
Microservicios
El desarrollo de aplicaciones clienteservidor 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 frontend, 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.
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.
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.
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.
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 HyperV que se ejecutan dentro de una máquina virtual
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.
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.
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.
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 backend en contenedores, como
se ilustra en el siguiente diagrama.
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.
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
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.
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.
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.
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.
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.
CAPÍTULO 10
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.
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.
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 ContentType 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.
La siguiente imagen muestra la interacción de clases que leen datos del catálogo del microservicio de catálogo para mostrarlos en
CatalogView.
¿ devolver catálogo?.Datos;
}
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.
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.
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.
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
[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();
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.
método RegisterAppServices de la clase MauiProgram, la clase BasketService se registra como una asignación de tipo con el tipo IBasketService
Luego, cuando se crea una instancia de la clase BasketViewModel, su constructor acepta una
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.
espere _basketService.UpdateBasketAsync(
nueva cesta de clientes
{
CompradorId = userInfo.UserId,
Artículos = BasketItems.ToList()
},
token de autenticació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:
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.
[publicación http]
Tarea asíncrona pública <IActionResult> Publicación ([FromBody] Valor de CustomerBasket)
{
var cesta = await _repository.UpdateBasketAsync(valor);
devolver Ok(cesta);
}
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.
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:
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:
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.
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
Cuando el método DeleteAsync de la clase RequestProvider llama a HttpClient.DeleteAsync, se invoca el método Delete de la clase
[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.
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
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é.
Las aplicaciones distribuidas, como la aplicación de referencia eShopOnContainers, deben proporcionar uno o ambos de los siguientes cachés:
• 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
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.
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
Consejo
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
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
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.
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.
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.
Consejo
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.
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.
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.
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.
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.
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.
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.
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.
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:
• 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
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:
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
},
};
}
Propiedad Descripción
Nombre del cliente El nombre para mostrar del cliente, que se utiliza para el
Propiedad Descripción
Secretos del cliente Especifica las credenciales secretas del cliente que se utilizan
al solicitar tokens desde el punto final del token.
PublicarCerrar sesiónRedireccionarUris Especifica los URI permitidos a los que redirigir después de
cerrar sesión.
Á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.
Client.AllowedGrantTypes. Las especificaciones OpenID Connect y OAuth 2.0 definen varios flujos de autenticación, que incluyen:
permitido.
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.
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:
espere IsBusyFor
( async () => {
LoginUrl = _identityService.CreateAuthorizationRequest();
Es válido = verdadero;
IsLogin = verdadero;
});
}
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
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:
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.
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:
si (!string.IsNullOrEmpty(logoutRequest))
{
// Cerrar sesión
URL de inicio de sesión = solicitud de cierre de sesión;
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:
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.
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:
si (unescapedUrl.Equals(GlobalSetting.Instance.LogoutCallback,
StringComparison.OrdinalIgnoreCase))
{
_settingsService.AuthAccessToken = cadena.Empty;
_settingsService.AuthIdToken = cadena.Empty;
IsLogin = falso;
LoginUrl = _identityService.CreateAuthorizationRequest();
}
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
{
// 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.
acceso a sus datos. Para obtener información sobre cómo configurar IdentityServer para proteger las API, consulte
Configuración de recursos de API.
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 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:
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:
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.
CAPITULO 12
El patrón ModelViewViewModel (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:
campodealmacenamiento = nuevoValor;
RaisePropertyChanged(nombre de propiedad);
}
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:
[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:
}
}
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.
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.
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:
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:
{
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.
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:
[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:
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.
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.
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.
Paso Descripción
como se esperaba.
Este patrón garantiza que las pruebas unitarias sean legibles, autodescriptivas y coherentes.
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;
}
}
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.
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.
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:
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.
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.
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.
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);
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
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);
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.
[Hecho]
público vacío InvalidEventNameShouldThrowArgumentExceptionText()
{
comportamiento var = nuevo MockEventToCommandBehavior
{
Nombre del evento = "OnItemTapped"
};
var listView = nuevo ListView();
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()
{
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";
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.