Simpler XAML in .NET MAUI 10

Building UI in .NET MAUI with XAML continues to be the most popular approach. It’s easy to see the nesting structure of your UI, works for most use cases with hot reload, and it supports powerful state flow with data binding. One of the downsides is how verbose it can become. Every page requires you to declare the namespaces for any types used, provide prefixes for them, and of course use them. You’re likely a better developer than I am, but I very often use a different prefix for the same namespace in different files making quite a mess.

.NET 6 introduced global and implicit usings for C# which greatly reduced the using statements at the head of many C# files. Now in .NET 10 starting with Preview 5 we are introducing the same for XAML so you can declare your namespaces and prefixes in a single file and use them throughout. In fact, you can now omit the use of prefixes altogether!

Implicit Namespaces

This update begins by switching the global namespace that all XAML files used in .NET MAUI from xmlns="http://schemas.microsoft.com/dotnet/2021/maui" to xmlns="http://schemas.microsoft.com/dotnet/maui/global". Now there is a truly global namespace unique to your application where we can pack other namespaces for use throughout the codebase.

Opt-in to the implicit namespaces by adding this configuration to your project file.

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

Now your project will implicitly include these 2 namespaces which you’ve been accustomed to seeing in every XAML file since .NET MAUI first shipped.

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

Because x: is used by the XAML inflator, you still need to use that prefix. With this change alone, your XAML for a view gets much tighter.

Before

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

After

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

Global Namespaces

As you start to include classes of your own and from any of the many useful NuGet packages for .NET MAUI, the stack of xmlns in your XAML starts to grow like a layer cake. This is the MainPage from my app Telepathy

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

Yikes! Many of my XAML files use the same namespaces over and over such as pageModels, models, converters, controls, and so on. To globalize these, I created a GlobalXmlns.cs file where I can register these namespaces using XmlnsDefinition.

[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")]

I can also use the same to globalize third-party controls like the Syncfusion Toolkit for .NET MAUI, the Community Toolkit for .NET MAUI, and AlohaKit Animations used in this file.

[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")]

After this I can remove all those declarations from the head of the file, resulting in a much cleaner read.

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

Namespace Prefixes

Now that the xmlns are all gone, you may be wondering about the prefixes that had been defined in order to reference those controls in XAML. Well, you no longer need them! Notice in the previous example the x:DataType omits the pageModels: prefix previously required.

Let’s look at another example from this same page.

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

After

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

Disambiguating Types

There will be times when you have types that collide and you’ll need to disambiguate them. I ran into this in another application where I have a custom control called FlyoutItem in my namespace ControlGallery.Views. In the XAML file it was being reference like <views:FlyoutItem /> and all was well. When I added ControlGallery.Views to the global namespace and removed the views: prefix, I encountered a collision because .NET MAUI already has a type FlyoutItem!

One way I could solve this would be to use the full path with namespace in the XAML like <ControlGallery.Views.FlyoutItem />. That is pretty long and not my preference.

Instead, there’s another attribute I can use alongside XmlnsDefinition and that’s XmlnsPrefix. Back in the GlobalXmlns.cs I can add a prefix for this namespace that’ll be usable globally in my application.

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

Now once again in XAML I can use that prefix like <views:FlyoutItem />.

Note

Before you go all in on this, be aware it’s a preview and there are issues to be ironed out. For example, the XAML Language Service needs some love to make it aware of what’s happening so you don’t get red squiggles everywhere. Additionally, work needs to be done to address the negative startup and runtime performance impact.

Resources

Known Issues

Feedback

You can get started by installing .NET 10 Preview 5 with the .NET MAUI workload or Visual Studio 17.14 Preview, and then creating a new project.

dotnet new maui -n LessXamlPlease

We need your feedback! Give this a go and let us know what you think by opening an issue on GitHub or sending me a note directly at [email protected].

The post Simpler XAML in .NET MAUI 10 appeared first on .NET Blog.