XAML Más Sencillo en .NET MAUI 10: Nombres de Espacio Implícitos y Globales

Construir interfaces de usuario en .NET MAUI con XAML sigue siendo el enfoque más popular, ofreciendo una estructura de UI clara, hot reload y un potente flujo de estado con enlace de datos. Sin embargo, su principal desventaja es la verbosidad. Cada página requiere declarar los espacios de nombres para cualquier tipo utilizado, proporcionarles prefijos y, por supuesto, usarlos. Esta práctica a menudo conduce a inconsistencias y a un código más desordenado de lo necesario, ya que es común usar diferentes prefijos para el mismo espacio de nombres en distintos archivos.

De manera similar a cómo .NET 6 introdujo los «usings» globales e implícitos para C# para reducir las declaraciones al inicio de los archivos, .NET 10 (a partir de la Preview 5) introduce la misma funcionalidad para XAML. Ahora podrás declarar tus espacios de nombres y prefijos en un solo archivo y utilizarlos en toda tu aplicación. ¡De hecho, en muchos casos, podrás omitir el uso de prefijos por completo!

Nombres de Espacio Implícitos

Esta actualización comienza cambiando el espacio de nombres global que todos los archivos XAML utilizaban en .NET MAUI. Se migra de xmlns="http://schemas.microsoft.com/dotnet/2021/maui" a xmlns="http://schemas.microsoft.com/dotnet/maui/global". Este nuevo espacio de nombres es verdaderamente global y único para tu aplicación, actuando como un contenedor para agrupar otros espacios de nombres que se utilizarán en todo el código base.

Para optar por los espacios de nombres implícitos, simplemente necesitas añadir la siguiente configuración a tu archivo de proyecto (.csproj):

<PropertyGroup>
    <DefineConstants>$(DefineConstants);MauiAllowImplicitXmlnsDeclaration</DefineConstants>
    <EnablePreviewFeatures>true</EnablePreviewFeatures>
</PropertyGroup>

Una vez configurado, tu proyecto incluirá implícitamente los dos espacios de nombres principales que tradicionalmente has visto en cada archivo XAML desde el lanzamiento de .NET MAUI:

xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

Es importante destacar que el prefijo x: seguirá siendo necesario, ya que es utilizado internamente por el inflador de XAML. No obstante, con este cambio, tu XAML para una vista básica se vuelve considerablemente más conciso.

Antes:

<ContentPage 
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="MyApp.Pages.MyContentPage">
</ContentPage>

Después:

<ContentPage x:Class="MyApp.Pages.MyContentPage">
</ContentPage>

Esta simplificación contribuye a un código más limpio y fácil de leer.

Nombres de Espacio Globales

A medida que tu aplicación crece y empiezas a incluir tus propias clases y paquetes NuGet útiles para .NET MAUI, la lista de declaraciones xmlns en la cabecera de tus archivos XAML puede volverse extremadamente larga. El autor del artículo proporciona un ejemplo de la MainPage de su aplicación Telepathy, que ilustra esta verbosidad:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:pageModels="clr-namespace:Telepathic.PageModels"
             xmlns:viewModels="clr-namespace:Telepathic.ViewModels"
             xmlns:models="clr-namespace:Telepathic.Models"
             xmlns:converters="clr-namespace:Telepathic.Converters"
             xmlns:controls="clr-namespace:Telepathic.Pages.Controls"
             xmlns:sf="clr-namespace:Syncfusion.Maui.Toolkit.TextInputLayout;assembly=Syncfusion.Maui.Toolkit"
             xmlns:cards="clr-namespace:Syncfusion.Maui.Toolkit.Cards;assembly=Syncfusion.Maui.Toolkit"
             xmlns:b="clr-namespace:Syncfusion.Maui.Toolkit.Buttons;assembly=Syncfusion.Maui.Toolkit"
             xmlns:pullToRefresh="clr-namespace:Syncfusion.Maui.Toolkit.PullToRefresh;assembly=Syncfusion.Maui.Toolkit"
             xmlns:bottomSheet="clr-namespace:Syncfusion.Maui.Toolkit.BottomSheet;assembly=Syncfusion.Maui.Toolkit"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:aloha="clr-namespace:AlohaKit.Animations;assembly=AlohaKit.Animations"
             xmlns:effectsView="http://schemas.syncfusion.com/maui/toolkit"
             x:Class="Telepathic.Pages.MainPage"
             x:DataType="pageModels:MainPageModel"
             Title="{Binding Today}">
</ContentPage>

Es evidente que muchos archivos XAML utilizan los mismos espacios de nombres repetidamente (como pageModels, models, converters, controls, etc.). Para globalizarlos, se puede crear un archivo C# (por ejemplo, GlobalXmlns.cs) donde se registran estos espacios de nombres utilizando el atributo XmlnsDefinition, vinculándolos al nuevo espacio de nombres http://schemas.microsoft.com/dotnet/maui/global.

Por ejemplo, para espacios de nombres propios de la aplicación:

[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Telepathic.PageModels")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Telepathic.Models")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Telepathic.Converters")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Telepathic.Pages.Controls")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Telepathic.ViewModels")]

Este mismo método es aplicable para globalizar controles y herramientas de terceros, como los del Syncfusion Toolkit para .NET MAUI, el Community Toolkit para .NET MAUI o AlohaKit Animations:

[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Syncfusion.Maui.Toolkit.TextInputLayout", AssemblyName = "Syncfusion.Maui.Toolkit")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Syncfusion.Maui.Toolkit.Cards", AssemblyName = "Syncfusion.Maui.Toolkit")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Syncfusion.Maui.Toolkit.Buttons", AssemblyName = "Syncfusion.Maui.Toolkit")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Syncfusion.Maui.Toolkit.PullToRefresh", AssemblyName = "Syncfusion.Maui.Toolkit")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "Syncfusion.Maui.Toolkit.BottomSheet", AssemblyName = "Syncfusion.Maui.Toolkit")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "http://schemas.syncfusion.com/maui/toolkit")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "http://schemas.microsoft.com/dotnet/2022/maui/toolkit")]
[assembly: XmlnsDefinition(
    "http://schemas.microsoft.com/dotnet/maui/global",
    "AlohaKit.Animations", AssemblyName = "AlohaKit.Animations")]

Tras estas definiciones, las engorrosas declaraciones xmlns pueden ser eliminadas de la cabecera de tus archivos XAML, lo que resulta en un código significativamente más limpio y legible:

<ContentPage x:Class="Telepathic.Pages.MainPage"
             x:DataType="MainPageModel"
             Title="{Binding Today}">
</ContentPage>

Prefijos de Espacio de Nombres

Una vez que los `xmlns` se han globalizado, surge la pregunta sobre los prefijos que se usaban para referenciar controles. ¡La excelente noticia es que en muchos casos ya no son necesarios! Observa en el ejemplo anterior cómo x:DataType="MainPageModel" ahora omite el prefijo pageModels: que antes era obligatorio.

Veamos otro ejemplo para ilustrar esta simplificación en el código:

Antes:

<BindableLayout.ItemTemplate>
    <DataTemplate x:DataType="models:ProjectTask">
        <controls:TaskView 
            TaskCompletedCommand="{Binding CompletedCommand, 
                                    Source={RelativeSource AncestorType={x:Type pageModels:MainPageModel}}, 
                                    x:DataType=pageModels:MainPageModel}"/>
    </DataTemplate>
</BindableLayout.ItemTemplate>

Después:

<BindableLayout.ItemTemplate>
    <DataTemplate x:DataType="ProjectTask">
        <TaskView 
            TaskCompletedCommand="{Binding CompletedCommand, 
                                    Source={RelativeSource AncestorType={x:Type MainPageModel}}, 
                                    x:DataType=MainPageModel}"/>
    </DataTemplate>
</BindableLayout.ItemTemplate>

La eliminación de prefijos como models: y controls: simplifica drásticamente la lectura y escritura del código XAML, haciendo que sea mucho más intuitivo.

Desambiguación de Tipos

Habrá ocasiones en las que tengas tipos personalizados que colisionen con tipos existentes de .NET MAUI. El autor del artículo se encontró con esto en una aplicación donde tenía un control personalizado llamado FlyoutItem en su espacio de nombres ControlGallery.Views. Originalmente, se referenciaba en el XAML como <views:FlyoutItem />. Al añadir ControlGallery.Views al espacio de nombres global y eliminar el prefijo views:, se produjo una colisión porque .NET MAUI ya tiene un tipo FlyoutItem nativo.

Una forma de resolver esto sería usar la ruta completa del espacio de nombres en el XAML, como <ControlGallery.Views.FlyoutItem />. Sin embargo, esta solución es bastante larga y, a menudo, no es la preferida por los desarrolladores.

En su lugar, existe un atributo más elegante que se puede usar junto con XmlnsDefinition: el atributo XmlnsPrefix. En tu archivo GlobalXmlns.cs, puedes añadir un prefijo específico para ese espacio de nombres particular, el cual será utilizable globalmente en tu aplicación.

[assembly: XmlnsPrefix(
    "clr-namespace:ControlGallery.Views",
    "views")]

De esta forma, puedes volver a utilizar el prefijo views: en tu XAML para referenciar tu control personalizado, como <views:FlyoutItem />, manteniendo la claridad y evitando eficazmente las ambigüedades.

Nota Importante: Estado de la Característica (Preview)

Es crucial recordar que todas estas características se encuentran en una fase Preview y aún hay aspectos importantes por mejorar. Actualmente, existen algunos problemas conocidos que los desarrolladores deben tener en cuenta:

  • Las compilaciones en modo depuración (Debug) son actualmente más lentas al iniciar las aplicaciones.
  • La inflación de vistas en tiempo de ejecución también es más lenta.
  • Los editores XAML pueden reportar incorrectamente tipos desconocidos con «líneas rojas» (por ejemplo, errores como XLS0414), ya que el Servicio de Lenguaje XAML aún no es completamente consciente de estos cambios.
  • Al declarar prefijos para espacios de nombres en el archivo de proyecto, es necesario incluir explícitamente clr-namespace:.

El equipo de Microsoft está trabajando activamente en solucionar estos problemas y mejorar tanto el rendimiento como la experiencia del desarrollador. Por lo tanto, se recomienda precaución al considerar el uso de estas características en proyectos de producción y se agradece encarecidamente la retroalimentación de la comunidad.

Recursos Adicionales

Problemas Conocidos

  • Las compilaciones de depuración son actualmente más lentas al iniciar.
  • La inflación de vistas en tiempo de ejecución es más lenta.
  • Los editores XAML informan incorrectamente tipos desconocidos con «líneas rojas» (ej. XLS0414).
  • Declarar prefijos para espacios de nombres en el proyecto requiere incluir clr-namespace:.

Feedback

Para empezar a explorar estas nuevas funcionalidades, puedes instalar .NET 10 Preview 5 con la carga de trabajo de .NET MAUI o Visual Studio 17.14 Preview, y luego crear un nuevo proyecto utilizando el siguiente comando:

dotnet new maui -n LessXamlPlease

¡Tu feedback es extremadamente valioso para el equipo de desarrollo! Te animamos a probar esta funcionalidad y a compartir tus impresiones abriendo un issue en GitHub o enviando una nota directamente a david.ortinau@microsoft.com.

Estas mejoras prometen transformar la experiencia de desarrollo XAML en .NET MAUI, haciéndola significativamente más limpia, legible y productiva en el futuro cercano.

Author: Enagora

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *