Training
Module
Create a UI that uses data binding in .NET MAUI. - Training
Create a UI with data binding. Your UI automatically updates based on the latest data, while the data updates in response to changes in the UI.
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
The focus of .NET Multi-platform App UI (.NET MAUI) in .NET 9 is to improve product quality. This includes expanding test coverage, end to end scenario testing, and bug fixing. For more information about the product quality improvements in .NET MAUI 9, see the following release notes:
Important
Due to working with external dependencies, such as Xcode or Android SDK Tools, the .NET MAUI support policy differs from the .NET and .NET Core support policy. For more information, see .NET MAUI support policy.
Compatibility with Xcode 16, which includes SDK support for iOS 18, iPadOS 18, tvOS 18, and macOS 15, is required when building with .NET MAUI 9. Xcode 16 requires a Mac running macOS 14.5 or later.
In .NET 9, .NET MAUI ships as a .NET workload and multiple NuGet packages. The advantage of this approach is that it enables you to easily pin your projects to specific versions, while also enabling you to easily preview unreleased or experimental builds. When you create a new .NET MAUI project the required NuGet packages are automatically added to the project.
.NET MAUI 9 requires minimum deployment targets of iOS 12.2, and Mac Catalyst 15.0 (macOS 12.0). Android and Windows minimum deployment targets remain the same. For more information, see Supported platforms for .NET MAUI apps.
.NET MAUI 9 includes two new controls.
HybridWebView enables hosting arbitrary HTML/JS/CSS content in a web view, and enables communication between the code in the web view (JavaScript) and the code that hosts the web view (C#/.NET). For example, if you have an existing React JS app, you could host it in a cross-platform .NET MAUI native app, and build the back-end of the app using C# and .NET.
To build a .NET MAUI app with HybridWebView you need:
The entire app, including the web content, is packaged and runs locally on a device, and can be published to applicable app stores. The web content is hosted within a native web view control and runs within the context of the app. Any part of the app can access external web services, but isn't required to.
For more information, see HybridWebView.
The TitleBar control provides the ability to add a custom title bar to your app on Windows:
A TitleBar can be set as the value of the Window.TitleBar property on any TitleBar:
<Window.TitleBar>
<TitleBar x:Name="TeamsTitleBar"
Title="Hello World"
Icon="appicon.png"
HeightRequest="46">
<TitleBar.Content>
<SearchBar Placeholder="Search"
PlaceholderColor="White"
MaximumWidthRequest="300"
HorizontalOptions="Fill"
VerticalOptions="Center" />
</TitleBar.Content>
</TitleBar>
</Window.TitleBar>
An example of its use in C# is:
Window window = new Window
{
TitleBar = new TitleBar
{
Icon = "titlebar_icon.png"
Title = "My App",
Subtitle = "Demo"
Content = new SearchBar { ... }
}
};
A TitleBar is highly customizable through its Content, LeadingContent, and TrailingContent properties:
<TitleBar Title="My App"
BackgroundColor="#512BD4"
HeightRequest="48">
<TitleBar.Content>
<SearchBar Placeholder="Search"
MaximumWidthRequest="300"
HorizontalOptions="Fill"
VerticalOptions="Center" />
</TitleBar.Content>
<TitleBar.TrailingContent>
<ImageButton HeightRequest="36"
WidthRequest="36"
BorderWidth="0"
Background="Transparent">
<ImageButton.Source>
<FontImageSource Size="16"
Glyph=""
FontFamily="SegoeMDL2"/>
</ImageButton.Source>
</ImageButton>
</TitleBar.TrailingContent>
</TitleBar>
The following screenshot shows the resulting appearance:
Note
Mac Catalyst support for the TitleBar
control will be added in a future release.
For more information, see TitleBar.
.NET MAUI 9 includes control enhancements.
The binding mode for IsVisible
and IsEnabled
on a BackButtonBehavior in a Shell app is now BindingMode.OneWay
instead of BindingMode.OneTime
. This enables you to more easily control the behavior of the back button at runtime, with data bindings:
<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IsVisible="{Binding IsBackButtonVisible}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>
The default behavior for hosting content in a BlazorWebView has changed to 0.0.0.1
. The internal 0.0.0.0
address used to host content no longer works and results in the BlazorWebView not loading any content and rendering as an empty rectangle.
To opt into using the 0.0.0.0
address, add the following code to the CreateMauiApp
method in MauiProgram.cs:
// Set this switch to use the LEGACY behavior of always using 0.0.0.0 to host BlazorWebView
AppContext.SetSwitch("BlazorWebView.AppHostAddressAlways0000", true);
By default, BlazorWebView now fires and forgets the async disposal of the underlying WebViewManager
. This reduces the potential for disposal deadlocks to occur on Android.
Warning
This fire-and-forget default behavior means that disposal can return before all objects are disposed, which can cause behavioral changes in your app. The items that are disposed are partially Blazor's own internal types, but also app-defined types such as scoped services used within the BlazorWebView portion of your app.
To opt out of this behavior, you should configure your app to block on dispose via an AppContext switch in the CreateMauiApp
method in your MauiProgram
class:
AppContext.SetSwitch("BlazorWebView.AndroidFireAndForgetAsync", false);
If your app is configured to block on dispose via this switch, BlazorWebView performs async-over-sync disposal, which means that it blocks the thread until the async disposal is complete. However, this can cause deadlocks if the disposal needs to run code on the same thread (because the thread is blocked while waiting).
Button controls on iOS now respect spacing, padding, border width, and margins more accurately than in previous releases. A large image in a Button will now be resized to the maximum size, taking into account the spacing, padding, border width, and margins. However, if a Button contains text and an image it might not be possible to fit all the content inside the button, and so you should size your image manually to achieve your desired layout.
.NET MAUI 9 includes two optional new handlers on iOS and Mac Catalyst that bring performance and stability improvements to CollectionView
and CarouselView
. These handlers are based on UICollectionView
APIs.
To opt into using these handlers, add the following code to your MauiProgram
class:
#if IOS || MACCATALYST
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<Microsoft.Maui.Controls.CollectionView, Microsoft.Maui.Controls.Handlers.Items2.CollectionViewHandler2>();
handlers.AddHandler<Microsoft.Maui.Controls.CarouselView, Microsoft.Maui.Controls.Handlers.Items2.CarouselViewHandler2>();
});
#endif
In .NET MAUI 9, the HideSoftInputOnTapped property is also supported on Mac Catalyst, as well and Android and iOS.
.NET MAUI 9 adds new soft keyboard input support for Password
, Date
, and Time
. These can be enabled on Editor and Entry controls:
<Entry Keyboard="Date" />
The TextAlignment enumeration adds a Justify
member that can be used to align text in text controls. For example, you can horizontally align text in a Label with HorizontalTextAlignment.Justify
:
<Label Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. In facilisis nulla eu felis fringilla vulputate."
HorizontalTextAlignment="Justify"/>
TimePicker gains a TimeSelected event, which is raised when the selected time changes. The TimeChangedEventArgs object that accompanies the TimeSelected
event has NewTime
and OldTime
properties, which specify the new and old time, respectively.
WebView adds a ProcessTerminated
event that's raised when a WebView process ends unexpectedly. The WebViewProcessTerminatedEventArgs
object that accompanies this event defines platform-specific properties that indicate why the process failed.
Bindings written in code typically use string paths that are resolved at runtime with reflection, and the overhead of doing this varies from platform to platform. .NET MAUI 9 introduces an additional SetBinding extension method that defines bindings using a Func
argument instead of a string path:
// in .NET 8
MyLabel.SetBinding(Label.TextProperty, "Text");
// in .NET 9
MyLabel.SetBinding(Label.TextProperty, static (Entry entry) => entry.Text);
This compiled binding approach provides the following benefits:
Not all methods can be used to define a compiled binding. The expression must be a simple property access expression. The following examples show valid and invalid binding expressions:
// Valid: Property access
static (PersonViewModel vm) => vm.Name;
static (PersonViewModel vm) => vm.Address?.Street;
// Valid: Array and indexer access
static (PersonViewModel vm) => vm.PhoneNumbers[0];
static (PersonViewModel vm) => vm.Config["Font"];
// Valid: Casts
static (Label label) => (label.BindingContext as PersonViewModel).Name;
static (Label label) => ((PersonViewModel)label.BindingContext).Name;
// Invalid: Method calls
static (PersonViewModel vm) => vm.GetAddress();
static (PersonViewModel vm) => vm.Address?.ToString();
// Invalid: Complex expressions
static (PersonViewModel vm) => vm.Address?.Street + " " + vm.Address?.City;
static (PersonViewModel vm) => $"Name: {vm.Name}";
Warning
A CS0272 compiler error will occur if the set accessor for a property or indexer is inaccessible. If this occurs, increase the accessibility of the accessor.
In addition, .NET MAUI 9 adds a BindingBase.Create method that sets the binding directly on the object with a Func
, and returns the binding object instance:
// in .NET 8
myEntry.SetBinding(Entry.TextProperty, new MultiBinding
{
Bindings = new Collection<BindingBase>
{
new Binding(nameof(Entry.FontFamily), source: RelativeBindingSource.Self),
new Binding(nameof(Entry.FontSize), source: RelativeBindingSource.Self),
new Binding(nameof(Entry.FontAttributes), source: RelativeBindingSource.Self),
},
Converter = new StringConcatenationConverter()
});
// in .NET 9
myEntry.SetBinding(Entry.TextProperty, new MultiBinding
{
Bindings = new Collection<BindingBase>
{
Binding.Create(static (Entry entry) => entry.FontFamily, source: RelativeBindingSource.Self),
Binding.Create(static (Entry entry) => entry.FontSize, source: RelativeBindingSource.Self),
Binding.Create(static (Entry entry) => entry.FontAttributes, source: RelativeBindingSource.Self),
},
Converter = new StringConcatenationConverter()
});
Important
Compiled bindings are required instead of string-based bindings in NativeAOT apps, and in apps with full trimming enabled.
For more information about compiled bindings in code, see Compiled bindings in code.
In .NET MAUI 8, compiled bindings are disabled for any XAML binding expressions that define the Source
property, and are unsupported on multi-bindings. These restrictions have been removed in .NET MAUI 9. For information about compiling XAML binding expressions that define the Source
property, see Compile bindings that define the Source
property.
By default, .NET MAUI 9 produces build warnings for bindings that don't use compiled bindings. For more information about XAML compiled bindings warnings, see XAML compiled bindings warnings.
In a Shell app, you no longer need to register your pages with the dependency injection container unless you want to influence the lifetime of the page relative to the container with the AddSingleton
, AddTransient
, or AddScoped
methods. For more information about these methods, see Dependency lifetime.
When implementing a custom control using handlers, every platform handler implementation is required to implement the DisconnectHandler() method, to perform any native view cleanup such as unsubscribing from events. However, prior to .NET MAUI 9, the DisconnectHandler() implementation is intentionally not invoked by .NET MAUI. Instead, you'd have to invoke it yourself when choosing to cleanup a control, such as when navigating backwards in an app.
In .NET MAUI 9, handlers automatically disconnect from their controls when possible, such as when navigating backwards in an app. In some scenarios you might not want this behavior. Therefore, .NET MAUI 9 adds a HandlerProperties.DisconnectPolicy
attached property for controlling when handlers are disconnected from their controls. This property requires a HandlerDisconnectPolicy argument, with the enumeration defining the following values:
Automatic
, which indicates that handlers will be disconnected automatically. This is the default value of the HandlerProperties.DisconnectPolicy
attached property.Manual
, which indicates that handlers will have to be disconnected manually by invoking the DisconnectHandler() implementation.The following example shows setting the HandlerProperties.DisconnectPolicy
attached property:
<controls:Video x:Name="video"
HandlerProperties.DisconnectPolicy="Manual"
Source="video.mp4"
AutoPlay="False" />
The equivalent C# code is:
Video video = new Video
{
Source = "video.mp4",
AutoPlay = false
};
HandlerProperties.SetDisconnectPolicy(video, HandlerDisconnectPolicy.Manual);
In addition, there's a DisconnectHandlers extension method that disconnects handlers from a given IView:
video.DisconnectHandlers();
When disconnecting, the DisconnectHandlers method will propagate down the control tree until it completes or arrives at a control that has set a manual policy.
.NET MAUI 9 adds the ability to bring a specific window to the front on Mac Catalyst and Windows with the Application.Current.ActivateWindow
method:
Application.Current?.ActivateWindow(windowToActivate);
In .NET MAUI 9 you can opt into Native AOT deployment on iOS and Mac Catalyst. Native AOT deployment produces a .NET MAUI app that's been ahead-of-time (AOT) compiled to native code. This produces the following benefits:
For more information, see Native AOT deployment on iOS and Mac Catalyst.
.NET MAUI 9 includes full APIs for native embedding scenarios, which previously had to be manually added to your project:
var mauiApp = MauiProgram.CreateMauiApp();
#if ANDROID
var mauiContext = new MauiContext(mauiApp.Services, window);
#else
var mauiContext = new MauiContext(mauiApp.Services);
#endif
var mauiView = new MyMauiContent();
var nativeView = mauiView.ToPlatform(mauiContext);
Alternatively, you can use the ToPlatformEmbedded
method, passing in the Window
for the platform on which the app is running:
var mauiApp = MauiProgram.CreateMauiApp();
var mauiView = new MyMauiContent();
var nativeView = mauiView.ToPlatformEmbedded(mauiApp, window);
In both examples, nativeView
is a platform-specific version of mauiView
.
To bootstrap a native embedded app in .NET MAUI 9, call the UseMauiEmbeddedApp
extension method on your MauiAppBuilder
object:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiEmbeddedApp<App>();
return builder.Build();
}
}
For more information, see Native embedding.
The .NET MAUI App project template includes the ability to create a fully functional todo app, using controls from the Syncfusion Toolkit for .NET MAUI to visualize data and persist it to a local database based on SQLite. To create this todo app, create a new project in Visual Studio using the .NET MAUI App project template, and then check the Include sample content checkbox in the Additional information window:
The todo app can also be created from the .NET CLI with the --sample-content
or -sc
option:
dotnet new maui --sample-content -n MyProject
.NET MAUI 9 also adds a .NET MAUI Blazor Hybrid and Web App project template to Visual Studio that creates a solution with a .NET MAUI Blazor Hybrid app with a Blazor Web app, which share common code in a Razor class library project.
The template can also be used from the .NET CLI:
dotnet new maui-blazor-web -n MyProject
In .NET MAUI 9, a stand-alone XAML ResourceDictionary (which isn't backed by a code-behind file) defaults to having its XAML compiled. To opt out of this behavior, specify <?xaml-comp compile="false" ?>
after the XML header.
Full trimming is now supported by setting the $(TrimMode)
MSBuild property to full
. For more information, see Trim a .NET MAUI app.
The following .NET MAUI features are incompatible with full trimming and will be removed by the trimmer:
DynamicallyAccessedMembers
attribute or the DynamicDependency
attribute. However, this is very error prone and isn't recommended.SearchHandler.DisplayMemberName
property. Instead, you should provide an ItemTemplate to define the appearance of SearchHandler results. For more information, see Define search results item appearance.System.Text.Json
serialization features.OnPlatform
XAML markup extension. Instead, you should use the OnPlatform<T> class. For more information, see Customize UI appearance based on the platform.OnIdiom
XAML markup extension. Instead, you should use the OnIdiom<T> class. For more information, see Customize UI appearance based on the device idiom..NET MAUI has trimmer directives, known as feature switches, that make it possible to preserve the code for features that aren't trim safe. These trimmer directives can be used when the $(TrimMode)
build property is set to full
, as well as for Native AOT:
MSBuild property | Description |
---|---|
MauiEnableVisualAssemblyScanning |
When set to true , .NET MAUI will scan assemblies for types implementing IVisual and for [assembly:Visual(...)] attributes, and will register these types. By default, this build property is set to false when full trimming is enabled. |
MauiShellSearchResultsRendererDisplayMemberNameSupported |
When set to false , the value of SearchHandler.DisplayMemberName will be ignored. Instead, you should provide an ItemTemplate to define the appearance of SearchHandler results. By default, this build property is set to false when full trimming or Native AOT is enabled. |
MauiQueryPropertyAttributeSupport |
When set to false , [QueryProperty(...)] attributes won't be used to set property values when navigating. Instead, you should implement the IQueryAttributable interface to accept query parameters. By default, this build property is set to false when full trimming or Native AOT is enabled. |
MauiImplicitCastOperatorsUsageViaReflectionSupport |
When set to false , .NET MAUI won't look for implicit conversion operators when converting values from one type to another. This can affect bindings between properties with different types, and setting a property value of a bindable object with a value of a different type. Instead, you should define a TypeConverter for your type and attach it to the type using the TypeConverterAttribute attribute. By default, this build property is set to false when full trimming or Native AOT is enabled. |
_MauiBindingInterceptorsSupport |
When set to false , .NET MAUI won't intercept any calls to the SetBinding methods and won't try to compile them. By default, this build property is set to true . |
MauiEnableXamlCBindingWithSourceCompilation |
When set to true , .NET MAUI will compile all bindings, including those where the Source property is used. If you enable this feature ensure that all bindings have the correct x:DataType so that they compile, or clear the data type with x:Data={x:Null}} if the binding shouldn't be compiled. By default, this build property is set to true when full trimming or Native AOT is enabled. |
MauiHybridWebViewSupported |
When set to false , the HybridWebView control won't be available. By default, this build property is set to false when full trimming or Native AOT is enabled. |
These MSBuild properties also have equivalent AppContext switches:
MauiEnableVisualAssemblyScanning
MSBuild property has an equivalent AppContext switch named Microsoft.Maui.RuntimeFeature.IsIVisualAssemblyScanningEnabled
.MauiShellSearchResultsRendererDisplayMemberNameSupported
MSBuild property has an equivalent AppContext switch named Microsoft.Maui.RuntimeFeature.IsShellSearchResultsRendererDisplayMemberNameSupported
.MauiQueryPropertyAttributeSupport
MSBuild property has an equivalent AppContext switch named Microsoft.Maui.RuntimeFeature.IsQueryPropertyAttributeSupported
.MauiImplicitCastOperatorsUsageViaReflectionSupport
MSBuild property has an equivalent AppContext switch named Microsoft.Maui.RuntimeFeature.IsImplicitCastOperatorsUsageViaReflectionSupported
._MauiBindingInterceptorsSupport
MSBuild property has an equivalent AppContext switch named Microsoft.Maui.RuntimeFeature.AreBindingInterceptorsSupported
.MauiEnableXamlCBindingWithSourceCompilation
MSBuild property has an equivalent AppContext switch named Microsoft.Maui.RuntimeFeature.MauiEnableXamlCBindingWithSourceCompilationEnabled
.MauiHybridWebViewSupported
MSBuild property has an equivalent AppContext switch named Microsoft.Maui.RuntimeFeature.IsHybridWebViewSupported
.The easiest way to consume a feature switch is by putting the corresponding MSBuild property into your app's project file (*.csproj), which causes the related code to be trimmed from the .NET MAUI assemblies.
When debugging and deploying a new .NET MAUI project to Windows, the default behavior in .NET MAUI 9 is to deploy an unpackaged app. For more information, see Deploy and debug your .NET MAUI app on Windows.
In .NET MAUI 9, the XAML compiler error codes have changed their prefix from XFC
to XC
. Ensure that you update the $(WarningsAsErrors)
, $(WarningsNotAsErrors)
, and $(NoWarn)
build properties in your app's project files, if used, to reference the new prefix.
All classes that implement IMarkupExtension, IMarkupExtension<T>, IValueProvider, and IExtendedTypeConverter need to be annotated with either the RequireServiceAttribute or AcceptEmptyServiceProviderAttribute. This is required due to a XAML compiler optimization introduced in .NET MAUI 9 that enables the generation of more efficient code, which helps reduce the app size and improve runtime performance.
For information about annotating markup extensions with these attributes, see Service providers.
.NET MAUI 9 includes Xcode sync (xcsync
), which is a tool that enables you to use Xcode for managing Apple specific files with .NET projects, including asset catalogs, plist files, storyboards, and xib files. The tool has two main commands to generate a temporary Xcode project from a .NET project, and to synchronize changes from the Xcode files back to your .NET project.
Important
xcsync is currently in preview.
You use dotnet build
with the xcsync-generate
or xcsync-sync
commands, to generate or sync these files, and pass in a project file and additional arguments:
dotnet build /t:xcsync-generate
/p:xcSyncProjectFile=<PROJECT>
/p:xcSyncXcodeFolder=<TARGET_XCODE_DIRECTORY>
/p:xcSyncTargetFrameworkMoniker=<FRAMEWORK>
/p:xcSyncVerbosity=<LEVEL>
For more information, see Xcode sync.
.NET MAUI 9 deprecates some APIs, which will be completely removed in a future release.
The Frame control is marked as obsolete in .NET MAUI 9, and will be completely removed in a future release. The Border control should be used in its place.
When replacing a Frame with a Border, the Frame.BorderColor property value should become the Border.Stroke property value, and the Frame.CornerRadius property value should become part of the Border.StrokeShape property value. In addition, it may be necessary to duplicate the Margin
value as the Padding
value.
The following example shows equivalent Frame and Border elements in XAML:
<Frame BorderColor="DarkGray"
CornerRadius="5"
Margin="20"
HeightRequest="360"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Border Stroke="DarkGray"
StrokeShape="RoundRectangle 5"
Margin="20"
Padding="20"
HeightRequest="360"
HorizontalOptions="Center"
VerticalOptions="Center" />
For more information see Border.
Instead of defining the first page of your app using the MainPage property on an Application object, you should set the Page property on a Window to the first page of your app. This is what happens internally in .NET MAUI when you set the MainPage property, so there's no behavior change introduced by the MainPage property being marked as obsolete.
The following example shows setting the Page property on a Window, via the CreateWindow
override:
public partial class App : Application
{
public App()
{
InitializeComponent();
}
protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}
Code that accesses the Application.Current.MainPage
property should now access the Application.Current.Windows[0].Page
property for apps with a single window. For apps with multiple windows, use the Application.Current.Windows
collection to identify the correct window and then access the Page
property. In addition, each element features a Window
property, that's accessible when the element is part of the current window, from which the Page
property can be accessed (Window.Page
). Platform code can retrieve the app's IWindow object with the Microsoft.Maui.Platform.GetWindow
extension method.
While the MainPage property is retained in .NET MAUI 9 it will be completely removed in a future release.
The compatibility layout classes in the Microsoft.Maui.Controls.Compatibility namespace have been obsoleted.
The following VisualElement measure methods have been obsoleted:
These are legacy measure methods that don't function correctly with .NET MAUI layout expectations.
As a replacement, the VisualElement.Measure(Double, Double) method has been introduced. This method returns the minimum size that an element needs in order to be displayed on a device. Margins are excluded from the measurement, but are returned with the size. This is the preferred method to call when measuring a view.
In addition, the SizeRequest struct is obsoleted. Instead, Size should be used.
To upgrade your .NET MAUI projects from .NET 8 to .NET 9, first install .NET 9 and the .NET MAUI workload with Visual Studio 17.12+, or with Visual Studio Code and the .NET MAUI extension and .NET and the .NET MAUI workloads, or with the standalone installer and the dotnet workload install maui
command.
To update your .NET MAUI app from .NET 8 to .NET 9 open the app's project file (.csproj) and change the Target Framework Monikers (TFMs) from 8 to 9. If you're using a TFM such as net8.0-ios15.2
be sure to match the platform version or remove it entirely. The following example shows the TFMs for a .NET 8 project:
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst;net8.0-tizen</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
The following example shows the TFMs for a .NET 9 project:
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst;net9.0-tizen</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
If your app's project file references a .NET 8 version of the Microsoft.Maui.Controls
NuGet package, either directly or through the $(MauiVersion)
build property, update this to a .NET 9 version. Then, remove the package reference for the Microsoft.Maui.Controls.Compatibility
NuGet package, provided that your app doesn't use any types from this package. In addition, update the package reference for the Microsoft.Extensions.Logging.Debug
NuGet package to the latest .NET 9 release.
If your app targets iOS or Mac Catalyst, update the $(SupportedOSPlatformVersion)
build properties for these platforms to 15.0:
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
When debugging and deploying a new .NET MAUI project to Windows, the default behavior in .NET 9 is to deploy an unpackaged app. To adopt this behavior, see Convert a packaged .NET MAUI Windows app to unpackaged.
Prior to building your upgraded app for the first time, delete the bin
and obj
folders. Any build errors and warnings will guide you towards next steps.
XAML compiler error codes have changed their prefix from XFC
to XC
, so update the $(WarningsAsErrors)
, $(WarningsNotAsErrors)
, and $(NoWarn)
build properties in your app's project file, if used, to reference the new prefix.
Build warnings will be produced for bindings that don't use compiled bindings, and these will need to be addressed. For more information, see XAML compiled bindings warnings.
XAML markup extensions will need to be annotated with either the RequireServiceAttribute or AcceptEmptyServiceProviderAttribute. This is required due to a XAML compiler optimization that enables the generation of more efficient code, which helps reduce the app size and improve runtime performance. For more information, see Service providers.
.NET MAUI 9 deprecates some APIs, which will be completely removed in a future release. Therefore, address any build warnings about deprecated APIs. For more information, see Deprecated APIs.
You can opt into compiling bindings that set the Source
property, to take advantage of better runtime performance. For more information, see Compile bindings that define the Source
property.
You can opt into compiling binding expressions that are declared in code, to take advantage of better runtime performance. For more information, see Compiled bindings in code.
You can adopt into using full trimming, to reduce the overall size of your app, by setting the $(TrimMode)
MSBuild property to full
. For more information, see Trim a .NET MAUI app.
You can opt into Native AOT deployment on iOS and Mac Catalyst. Native AOT deployment produces a .NET MAUI app that's been ahead-of-time (AOT) compiled to native code. For more information, see Native AOT deployment on iOS and Mac Catalyst.
.NET for Android in .NET 9, which adds support for API 35, includes work to reduce build times, and to improve the trimability of apps to reduce size and improve performance. For more information about .NET for Android in .NET 9, see the following release notes:
.NET for Android in .NET 9 introduces the ability to place assets into a separate package, known as an asset pack. This enables you to upload games and apps that would normally be larger than the basic package size allowed by Google Play. By putting these assets into a separate package you gain the ability to upload a package which is up to 2Gb in size, rather than the basic package size of 200Mb.
Important
Asset packs can only contain assets. In the case of .NET for Android this means items that have the AndroidAsset
build action.
.NET MAUI apps define assets via the MauiAsset
build action. An asset pack can be specified via the AssetPack
attribute:
<MauiAsset
Include="Resources\Raw\**"
LogicalName="%(RecursiveDir)%(Filename)%(Extension)"
AssetPack="myassetpack" />
Note
The additional metadata will be ignored by other platforms.
If you have specific items you want to place in an asset pack you can use the Update
attribute to define the AssetPack
metadata:
<MauiAsset Update="Resources\Raw\MyLargeAsset.txt" AssetPack="myassetpack" />
Asset packs can have different delivery options, which control when your assets will install on the device:
InstallTime
metadata.FastFollow
metadata.OnDemand
metadata.In .NET MAUI apps, the delivery type can be specified with the DeliveryType
attribute on a MauiAsset
:
<MauiAsset Update="Resources\Raw\myvideo.mp4" AssetPack="myassetpack" DeliveryType="FastFollow" />
For more information about Android asset packs, see Android asset packs.
.NET for Android in .NET 9 adds .NET bindings for Android 15 (API 35). To build for these APIs, update the target framework of your project to net9.0-android
:
<TargetFramework>net9.0-android</TargetFramework>
Note
You can also specify net9.0-android35
as a target framework, but the number 35 will probably change in future .NET releases to match newer Android OS releases.
.NET for Android in .NET 9 no longer builds the following runtime identifiers (RIDs) by default:
android-arm
android-x86
This should improve build times and reduce the size of Android .apk
files. Note that Google Play supports splitting up app bundles per architecture.
If you need to build for these architectures, you can add them to your project file (.csproj):
<RuntimeIdentifiers>android-arm;android-arm64;android-x86;android-x64</RuntimeIdentifiers>
Or in a multi-targeted project:
<RuntimeIdentifiers Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">android-arm;android-arm64;android-x86;android-x64</RuntimeIdentifiers>
Improvements to Android marshal methods in .NET 9 has made the feature work more reliably in applications but is not yet the default. Enabling this feature has resulted in a ~10% improvement in performance in a test app.
Android marshal methods can be enabled in your project file (.csproj) via the $(AndroidEnableMarshalMethods)
property:
<PropertyGroup>
<AndroidEnableMarshalMethods>true</AndroidEnableMarshalMethods>
</PropertyGroup>
For specific details about the feature, see the feature documentation or implementation on GitHub.
In .NET 9, the Android API assemblies (Mono.Android.dll, Java.Interop.dll) are now fully trim-compatible. To opt into full trimming, set the $(TrimMode)
property in your project file (.csproj):
<PropertyGroup>
<TrimMode>Full</TrimMode>
</PropertyGroup>
This also enables trimming analyzers, so that warnings are introduced for any problematic C# code.
For more information, see Trimming granularity.
.NET 9 on iOS, tvOS, Mac Catalyst, and macOS uses Xcode 16.0 for the following platform versions:
For more information about .NET 9 on iOS, tvOS, Mac Catalyst, and macOS, see the following release notes:
.NET for iOS 9 introduces the ability to multi-target versions of .NET for iOS bindings. For example, a library project may need to build for two distinct iOS versions:
<TargetFrameworks>net9.0-ios17.0;net9.0-ios17.2</TargetFrameworks>
This will produce two libraries, one using iOS 17.0 bindings, and one using iOS 17.2 bindings.
Important
An app project should always target the latest iOS SDK.
In .NET 9, the iOS and Mac Catalyst assemblies (Microsoft.iOS.dll, Microsoft.MacCatalyst.dll etc.) are now fully trim-compatible. To opt into full trimming, set the $(TrimMode)
property in your project file (.csproj):
<PropertyGroup>
<TrimMode>Full</TrimMode>
</PropertyGroup>
This also enables trimming analyzers, so that warnings are introduced for any problematic C# code.
For more information, see Trimming granularity.
In .NET for iOS 9, native Ahead of Time (AOT) compilation for iOS and Mac Catalyst takes advantage of full trimming to reduce your app's package size and startup performance. NativeAOT builds upon full trimming, by also opting into a new runtime.
Important
Your app and it's dependencies must be fully trimmable in order to utilize this feature.
NativeAOT requires applications to be built with zero trimmer warnings, in order to prove the application will work correctly at runtime.
.NET MAUI feedback
.NET MAUI is an open source project. Select a link to provide feedback:
Training
Module
Create a UI that uses data binding in .NET MAUI. - Training
Create a UI with data binding. Your UI automatically updates based on the latest data, while the data updates in response to changes in the UI.