Home C# / .NET Introduction to .NET MAUI: Build Cross-Platform Apps with C#
Intermediate 4 min · July 13, 2026

Introduction to .NET MAUI: Build Cross-Platform Apps with C#

Learn .NET MAUI basics: create cross-platform mobile and desktop apps using C# and .NET.

N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Written from production experience, not tutorials.

Follow
Production
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of C# and .NET
  • Familiarity with object-oriented programming
  • Visual Studio 2022 or VS Code with MAUI extension
  • .NET 6 SDK or later
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • .NET MAUI is a cross-platform framework for building native mobile and desktop apps with C# and XAML.
  • It replaces Xamarin.Forms and supports Android, iOS, macOS, Windows, and Tizen.
  • Use MVVM pattern for clean separation of concerns.
  • Data binding connects UI to data models automatically.
  • Hot Reload allows real-time UI changes during development.
✦ Definition~90s read
What is Introduction to .NET MAUI?

.NET MAUI is a cross-platform framework that lets you build native mobile and desktop apps using C# and XAML from a single codebase.

Think of .NET MAUI as a universal remote that can control your TV, sound system, and streaming device with one set of buttons.
Plain-English First

Think of .NET MAUI as a universal remote that can control your TV, sound system, and streaming device with one set of buttons. Instead of writing separate code for Android, iOS, and Windows, you write one C# codebase and .NET MAUI translates it to each platform's native language.

Imagine you need to build a mobile app for your startup that tracks inventory across warehouses. Your team uses Android phones, but the warehouse managers prefer iPads. Later, the CEO wants a desktop dashboard for Windows. Traditionally, you'd need three separate codebases—multiplying development time and bugs. .NET MAUI solves this by letting you write one C# codebase that targets all platforms.

.NET MAUI (Multi-platform App UI) is the evolution of Xamarin.Forms, now part of .NET 6+. It provides a single project structure, a shared UI layer using XAML, and access to native platform APIs. You can build apps for Android, iOS, macOS, Windows, and even Tizen (Samsung smart TVs) from a single codebase.

In this tutorial, you'll learn the fundamentals: setting up a .NET MAUI project, understanding the app structure, using XAML for UI, implementing the MVVM pattern, and handling data binding. We'll also cover real-world debugging scenarios and common pitfalls. By the end, you'll be able to create a simple but functional cross-platform app.

Setting Up Your .NET MAUI Project

To start, ensure you have .NET 6 SDK or later installed. Open a terminal and run:

``bash dotnet new maui -n MyFirstMauiApp cd MyFirstMauiApp dotnet build ``

This creates a new .NET MAUI project with a default structure. The project contains: - MauiProgram.cs: Entry point where you configure services and the app. - App.xaml / App.xaml.cs: Application-level resources and lifecycle. - AppShell.xaml / AppShell.xaml.cs: Defines navigation structure. - MainPage.xaml / MainPage.xaml.cs: The default page. - Platforms/ folder: Platform-specific code for Android, iOS, macOS, Windows, etc.

```csharp using Microsoft.Extensions.Logging;

namespace MyFirstMauiApp;

public static class MauiProgram { public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); });

#if DEBUG builder.Logging.AddDebug(); #endif

return builder.Build(); } } ```

This is similar to ASP.NET Core's startup. You can register services, configure fonts, and add logging. The UseMauiApp<App>() method sets the application class.

Key Takeaway: The project template provides a solid foundation. Customize MauiProgram.cs to add dependency injection and logging.

MauiProgram.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using Microsoft.Extensions.Logging;

namespace MyFirstMauiApp;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}
🔥Prerequisites
📊 Production Insight
In production, ensure you register all services in MauiProgram.cs to avoid runtime resolution failures.
🎯 Key Takeaway
The MauiProgram.cs is the entry point for configuring your app, similar to ASP.NET Core's Program.cs.

Understanding XAML and Code-Behind

XAML (eXtensible Application Markup Language) is used to define the UI declaratively. Each XAML file has a corresponding code-behind file (e.g., MainPage.xaml.cs) that contains event handlers and logic.

```xml <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyFirstMauiApp.MainPage">

<ScrollView> <VerticalStackLayout Padding="30,0" Spacing="25"> <Label Text="Hello, World!" Style="{StaticResource Headline}" SemanticProperties.HeadingLevel="Level1" /> <Button x:Name="CounterBtn" Text="Click me" SemanticProperties.Hint="Counts the number of times you click" Clicked="OnCounterClicked" /> </VerticalStackLayout> </ScrollView>

</ContentPage> ```

```csharp namespace MyFirstMauiApp;

public partial class MainPage : ContentPage { int count = 0;

public MainPage() { InitializeComponent(); }

private void OnCounterClicked(object sender, EventArgs e) { count++; CounterBtn.Text = $"Clicked {count} times"; } } ```

The x:Class attribute links the XAML to the code-behind class. The InitializeComponent() method is generated and loads the XAML. Event handlers like Clicked are wired up in XAML.

Key Takeaway: XAML separates UI design from logic. Use code-behind for simple event handling; for complex apps, use MVVM.

MainPage.xamlCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MyFirstMauiApp.MainPage">

    <ScrollView>
        <VerticalStackLayout
            Padding="30,0"
            Spacing="25">
            <Label
                Text="Hello, World!"
                Style="{StaticResource Headline}"
                SemanticProperties.HeadingLevel="Level1" />
            <Button
                x:Name="CounterBtn"
                Text="Click me"
                SemanticProperties.Hint="Counts the number of times you click"
                Clicked="OnCounterClicked" />
        </VerticalStackLayout>
    </ScrollView>

</ContentPage>
💡XAML Hot Reload
📊 Production Insight
Avoid putting complex logic in code-behind; it makes unit testing difficult. Use MVVM instead.
🎯 Key Takeaway
XAML defines UI, code-behind handles logic. Use x:Name to reference controls in code.

The MVVM Pattern in .NET MAUI

MVVM (Model-View-ViewModel) is the recommended pattern for .NET MAUI apps. It separates concerns: - Model: Data and business logic. - View: XAML UI. - ViewModel: Mediator that exposes data and commands to the View.

To implement MVVM, you need: 1. A ViewModel class that implements INotifyPropertyChanged. 2. Data binding in XAML to bind UI elements to ViewModel properties. 3. Commands to handle user actions.

```csharp using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows.Input;

namespace MyFirstMauiApp.ViewModels;

public class MainViewModel : INotifyPropertyChanged { private int _count; public int Count { get => _count; set { if (_count != value) { _count = value; OnPropertyChanged(); OnPropertyChanged(nameof(CountDisplay)); } } }

public string CountDisplay => $"Clicked {Count} times";

public ICommand IncrementCommand { get; }

public MainViewModel() { IncrementCommand = new Command(Increment); }

private void Increment() { Count++; }

public event PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } ```

```xml <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:vm="clr-namespace:MyFirstMauiApp.ViewModels" x:Class="MyFirstMauiApp.MainPage">

<ContentPage.BindingContext> <vm:MainViewModel /> </ContentPage.BindingContext>

<VerticalStackLayout Padding="30" Spacing="25"> <Label Text="{Binding CountDisplay}" /> <Button Text="Click me" Command="{Binding IncrementCommand}" /> </VerticalStackLayout>

</ContentPage> ```

Notice
  • BindingContext is set to an instance of MainViewModel.
  • Label binds to CountDisplay property.
  • Button binds to IncrementCommand.

Key Takeaway: MVVM promotes testability and maintainability. Use data binding to connect View and ViewModel.

MainViewModel.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace MyFirstMauiApp.ViewModels;

public class MainViewModel : INotifyPropertyChanged
{
    private int _count;
    public int Count
    {
        get => _count;
        set
        {
            if (_count != value)
            {
                _count = value;
                OnPropertyChanged();
                OnPropertyChanged(nameof(CountDisplay));
            }
        }
    }

    public string CountDisplay => $"Clicked {Count} times";

    public ICommand IncrementCommand { get; }

    public MainViewModel()
    {
        IncrementCommand = new Command(Increment);
    }

    private void Increment()
    {
        Count++;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
💡CommunityToolkit.Mvvm
📊 Production Insight
In production, use a dependency injection container to inject ViewModels into pages, avoiding tight coupling.
🎯 Key Takeaway
MVVM separates UI from logic. Implement INotifyPropertyChanged for data binding.

Data Binding and Commands in Depth

Data binding is the backbone of MVVM. It synchronizes the UI with the underlying data. There are several binding modes: - OneWay: Target updates when source changes. - TwoWay: Both source and target update each other. - OneTime: Target is set once and never updated. - OneWayToSource: Source updates when target changes.

Commands replace event handlers. The ICommand interface has Execute and CanExecute methods. You can bind a Button's Command property to a command in the ViewModel.

``xml <Entry Text="{Binding UserName, Mode=TwoWay}" /> ``

``csharp private string _userName; public string UserName { get => _userName; set { if (_userName != value) { _userName = value; OnPropertyChanged(); } } } ``

```csharp public ICommand SubmitCommand { get; }

public MainViewModel() { SubmitCommand = new Command<string>(Submit); }

private void Submit(string parameter) { // Use parameter } ```

``xml <Button Text="Submit" Command="{Binding SubmitCommand}" CommandParameter="Hello" /> ``

Key Takeaway: Choose the right binding mode. Use TwoWay for input controls. Commands with parameters allow passing data from View to ViewModel.

BindingExample.xamlCSHARP
1
2
3
4
<StackLayout>
    <Entry Text="{Binding UserName, Mode=TwoWay}" Placeholder="Enter name" />
    <Button Text="Submit" Command="{Binding SubmitCommand}" CommandParameter="{Binding UserName}" />
</StackLayout>
⚠ Binding Errors
📊 Production Insight
Avoid complex logic in property getters; they can be called frequently and degrade performance.
🎯 Key Takeaway
Data binding automates UI updates. Use TwoWay for inputs and commands for actions.

Navigation allows moving between pages. .NET MAUI supports several navigation patterns: - Shell navigation: Uses URI-based routing. - NavigationPage: Stack-based navigation. - Modal navigation: For popups.

Shell is the recommended approach. Define routes in AppShell.xaml:

```xml <Shell x:Class="MyFirstMauiApp.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:pages="clr-namespace:MyFirstMauiApp.Pages">

<TabBar> <ShellContent Title="Home" ContentTemplate="{DataTemplate pages:MainPage}" /> <ShellContent Title="Settings" ContentTemplate="{DataTemplate pages:SettingsPage}" /> </TabBar>

</Shell> ```

``csharp await Shell.Current.GoToAsync("//Settings"); ``

``csharp await Shell.Current.GoToAsync("//Details?id=123"); ``

And receive in the target page by implementing IQueryAttributable:

``csharp public class DetailsPage : ContentPage, IQueryAttributable { public void ApplyQueryAttributes(IDictionary<string, object> query) { if (query.TryGetValue("id", out var id)) { // Use id } } } ``

Key Takeaway: Shell navigation provides a consistent routing system. Use GoToAsync for navigation and IQueryAttributable for receiving parameters.

AppShell.xamlCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="MyFirstMauiApp.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:pages="clr-namespace:MyFirstMauiApp.Pages">

    <TabBar>
        <ShellContent
            Title="Home"
            ContentTemplate="{DataTemplate pages:MainPage}" />
        <ShellContent
            Title="Settings"
            ContentTemplate="{DataTemplate pages:SettingsPage}" />
    </TabBar>

</Shell>
🔥NavigationPage vs Shell
📊 Production Insight
Always handle navigation errors gracefully; use try-catch around GoToAsync.
🎯 Key Takeaway
Shell navigation uses URI routing. Register routes and use GoToAsync.

Accessing Platform-Specific Features

One of MAUI's strengths is accessing native APIs. Use the Microsoft.Maui.Essentials namespace for common features like geolocation, camera, and sensors.

```csharp using Microsoft.Maui.Devices.Sensors;

async Task GetLocationAsync() { try { var location = await Geolocation.GetLastKnownLocationAsync(); if (location == null) { location = await Geolocation.GetLocationAsync(new GeolocationRequest { DesiredAccuracy = GeolocationAccuracy.Medium, Timeout = TimeSpan.FromSeconds(10) }); } // Use location } catch (FeatureNotSupportedException) { // Not supported on device } catch (PermissionException) { // Permissions not granted } } ```

For platform-specific code, use conditional compilation or partial classes:

``csharp #if ANDROID // Android-specific code #elif IOS // iOS-specific code #endif ``

``csharp #if ANDROID using Android.Content; #endif ``

Key Takeaway: MAUI Essentials provides a unified API for common features. Use conditional compilation for platform-specific logic.

LocationService.csCSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using Microsoft.Maui.Devices.Sensors;

public async Task<Location> GetCurrentLocationAsync()
{
    try
    {
        var location = await Geolocation.GetLastKnownLocationAsync();
        if (location == null)
        {
            location = await Geolocation.GetLocationAsync(new GeolocationRequest
            {
                DesiredAccuracy = GeolocationAccuracy.Medium,
                Timeout = TimeSpan.FromSeconds(10)
            });
        }
        return location;
    }
    catch (FeatureNotSupportedException)
    {
        // Handle
    }
    catch (PermissionException)
    {
        // Handle
    }
    return null;
}
⚠ Permissions
📊 Production Insight
Test platform-specific features on actual devices; emulators may not support all sensors.
🎯 Key Takeaway
Use MAUI Essentials for cross-platform APIs. Handle platform-specific code with #if directives.

Dependency Injection in .NET MAUI

Dependency injection (DI) is built into .NET MAUI. Register services in MauiProgram.cs:

``csharp builder.Services.AddSingleton<IDataService, DataService>(); builder.Services.AddTransient<MainViewModel>(); builder.Services.AddTransient<MainPage>(); ``

Then inject into constructors:

``csharp public partial class MainPage : ContentPage { public MainPage(MainViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; } } ``

```csharp public class MainViewModel { private readonly IDataService _dataService;

public MainViewModel(IDataService dataService) { _dataService = dataService; } } ```

Key Takeaway: DI promotes loose coupling. Register services with appropriate lifetimes (Singleton, Transient, Scoped).

MauiProgram.cs with DICSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using Microsoft.Extensions.Logging;

namespace MyFirstMauiApp;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            });

        // Register services
        builder.Services.AddSingleton<IDataService, DataService>();
        builder.Services.AddTransient<MainViewModel>();
        builder.Services.AddTransient<MainPage>();

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }
}
💡ViewModel Locator
📊 Production Insight
Use Singleton for services that hold state, Transient for short-lived objects.
🎯 Key Takeaway
Register services in MauiProgram.cs and inject them via constructors.
● Production incidentPOST-MORTEMseverity: high

The Case of the Missing Data: When BindingContext Was Null

Symptom
Users on iOS reported that the main list page displayed nothing, while Android users saw data correctly.
Assumption
The developer assumed it was a platform-specific rendering bug in iOS.
Root cause
The BindingContext was set in the code-behind constructor, but on iOS the page lifecycle caused the constructor to run before the XAML was fully loaded, resulting in a null BindingContext.
Fix
Moved the BindingContext assignment to the OnAppearing method, ensuring the page is fully initialized.
Key lesson
  • Always set BindingContext after the page is loaded, e.g., in OnAppearing or via a ViewModel locator.
  • Test on all target platforms early in development.
  • Use conditional breakpoints to inspect BindingContext values per platform.
  • Consider using a DI container to inject ViewModels and avoid manual assignment.
  • Log BindingContext state in OnAppearing to catch null references.
Production debug guideSymptom to Action4 entries
Symptom · 01
UI not updating after data change
Fix
Check if ViewModel implements INotifyPropertyChanged and properties raise PropertyChanged.
Symptom · 02
App crashes on iOS but not Android
Fix
Look for platform-specific code in #if directives; verify iOS-specific API usage.
Symptom · 03
XAML binding shows no value
Fix
Enable XAML binding errors in output window; check BindingContext and property paths.
Symptom · 04
Hot Reload not working
Fix
Ensure you're running in Debug mode; restart the app and try again.
★ Quick Debug Cheat SheetCommon .NET MAUI issues and immediate fixes.
Binding not working
Immediate action
Check BindingContext and property names.
Commands
Debug.WriteLine(BindingContext?.GetType())
Check Output window for binding errors
Fix now
Set BindingContext in OnAppearing
App crashes on startup+
Immediate action
Check MainPage.xaml for syntax errors.
Commands
Clean and rebuild solution
Check Application.Current.MainPage assignment
Fix now
Wrap page initialization in try-catch
UI not updating+
Immediate action
Verify INotifyPropertyChanged implementation.
Commands
Check property setter calls OnPropertyChanged
Ensure ViewModel is bound correctly
Fix now
Use CommunityToolkit.Mvvm [ObservableProperty]
Feature.NET MAUIXamarin.Forms
Target PlatformsAndroid, iOS, macOS, Windows, TizenAndroid, iOS, Windows, macOS (limited)
Project StructureSingle projectMultiple projects (shared + platform)
PerformanceImproved (native rendering)Good
Hot ReloadYesLimited
DI Built-inYesNo (requires third-party)
.NET Version.NET 6+Mono/.NET Framework
⚙ Quick Reference
7 commands from this guide
FileCommand / CodePurpose
MauiProgram.csusing Microsoft.Extensions.Logging;Setting Up Your .NET MAUI Project
MainPage.xamlUnderstanding XAML and Code-Behind
MainViewModel.csusing System.ComponentModel;The MVVM Pattern in .NET MAUI
BindingExample.xamlData Binding and Commands in Depth
AppShell.xamlNavigation in .NET MAUI
LocationService.csusing Microsoft.Maui.Devices.Sensors;Accessing Platform-Specific Features
MauiProgram.cs with DIusing Microsoft.Extensions.Logging;Dependency Injection in .NET MAUI

Key takeaways

1
.NET MAUI allows building cross-platform apps with a single C# codebase.
2
Use MVVM pattern with data binding and commands for clean architecture.
3
Shell navigation provides URI-based routing for pages.
4
Leverage MAUI Essentials for cross-platform access to device features.
5
Dependency injection is built-in and promotes testability.

Common mistakes to avoid

5 patterns
×

Setting BindingContext in constructor before InitializeComponent

×

Forgetting to call OnPropertyChanged in property setter

×

Using code-behind for complex logic instead of MVVM

×

Not handling permissions for platform features

×

Ignoring binding errors in Output window

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the MVVM pattern and why is it used in .NET MAUI?
Q02SENIOR
Explain how data binding works in .NET MAUI. What are the different bind...
Q03SENIOR
How do you navigate between pages in .NET MAUI using Shell?
Q04JUNIOR
What is the purpose of INotifyPropertyChanged in .NET MAUI?
Q05SENIOR
How do you access platform-specific APIs like camera or GPS in .NET MAUI...
Q01 of 05JUNIOR

What is the MVVM pattern and why is it used in .NET MAUI?

ANSWER
MVVM stands for Model-View-ViewModel. It separates UI (View) from business logic (ViewModel) and data (Model). It's used to improve testability, maintainability, and data binding support.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
What is the difference between .NET MAUI and Xamarin.Forms?
02
Can I use .NET MAUI for desktop apps?
03
How do I handle platform-specific code in .NET MAUI?
04
Do I need to know XAML to use .NET MAUI?
05
How do I debug .NET MAUI apps?
N
Naren Founder & Principal Engineer

20+ years shipping production .NET services in enterprise systems. Written from production experience, not tutorials.

Follow
Verified
production tested
July 13, 2026
last updated
2,043
articles · all by Naren
🔥

That's ASP.NET. Mark it forged?

4 min read · try the examples if you haven't

Previous
Dapper vs EF Core: Decision Guide
33 / 35 · ASP.NET
Next
ASP.NET Core Identity Complete Guide