Skip to content

Conversation

@jfversluis
Copy link
Member

Description

This PR adds support for running .NET MAUI applications on Windows devices through the Aspire app host.

After discussion with the always wonderful Aspire team about #11942, making some changes in how the MAUI integration will work. This is the first of a series of PRs, this one is adding the Windows app integration first, plus some plumbing, the rest we can add after this for shorter review cycles.

Features

  • Windows Device Resources: Add Windows platform targets to MAUI projects that run via dotnet run with the appropriate target framework
  • Platform Validation: Automatically detects if the host OS supports Windows execution and marks resources as "Unsupported" when running on non-Windows hosts
  • Target Framework Detection: Validates that MAUI projects include a Windows target framework (e.g., net10.0-windows10.0.19041.0) and fails fast with clear error messages if missing
  • Multiple Devices: Support for adding multiple Windows device resources to the same MAUI project with unique names
  • Explicit Start: Windows devices require explicit user action to start (not auto-started), allowing developers to control when the app launches

Public API

namespace Aspire.Hosting;

public static class MauiProjectExtensions
{
    /// <summary>
    /// Adds a .NET MAUI project to the application model.
    /// </summary>
    public static IResourceBuilder<MauiProjectResource> AddMauiProject(
        this IDistributedApplicationBuilder builder,
        string name,
        string projectPath);
}

public static class MauiWindowsExtensions
{
    /// <summary>
    /// Adds a Windows device resource to run the MAUI application on the Windows platform.
    /// </summary>
    public static IResourceBuilder<MauiWindowsPlatformResource> AddWindowsDevice(
        this IResourceBuilder<MauiProjectResource> builder,
        string? name = null);
}

Usage Example

var builder = DistributedApplication.CreateBuilder(args);

var weatherApi = builder.AddProject<Projects.WeatherApi>("api");

var maui = builder.AddMauiProject("mauiapp", "../MyMauiApp/MyMauiApp.csproj");
var windowsDevice = maui.AddWindowsDevice()
    .WithReference(weatherApi);

builder.Build().Run();

Architecture

The implementation is structured for extensibility with upcoming iOS, Android, and macOS platform support:

  • Annotations/: Shared UnsupportedPlatformAnnotation for cross-platform unsupported state handling
  • Lifecycle/: Shared UnsupportedPlatformEventSubscriber for setting resource states after orchestration
  • Utilities/: Reusable ProjectFileReader for detecting target frameworks across all platforms

Testing

  • 11 comprehensive unit tests covering resource creation, naming, TFM detection, and error scenarios

Contributes to #4684

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces initial support for running .NET MAUI applications on Windows devices through the Aspire app host. The implementation adds a new Aspire.Hosting.Maui integration package that enables developers to manage MAUI apps alongside their other Aspire-orchestrated services, with Windows being the first supported platform. The PR establishes foundational architecture (utilities, annotations, lifecycle management) that will support additional platforms (iOS, Android, macOS) in future PRs.

Key Changes:

  • New Aspire.Hosting.Maui package with Windows device integration
  • Platform detection and validation infrastructure
  • Comprehensive test coverage (11 unit tests)
  • Sample playground demonstrating MAUI + Aspire integration

Reviewed Changes

Copilot reviewed 64 out of 72 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Aspire.Hosting.Maui/MauiProjectResourceExtensions.cs Adds AddMauiProject extension method to register MAUI projects in the app model
src/Aspire.Hosting.Maui/MauiWindowsExtensions.cs Implements AddWindowsDevice extension with TFM validation and platform detection
src/Aspire.Hosting.Maui/MauiProjectResource.cs Defines parent resource that contains platform-specific device collections
src/Aspire.Hosting.Maui/MauiWindowsPlatformResource.cs Represents a Windows platform instance derived from ExecutableResource
src/Aspire.Hosting.Maui/Utilities/ProjectFileReader.cs Utility for parsing project files to detect target frameworks
src/Aspire.Hosting.Maui/Annotations/UnsupportedPlatformAnnotation.cs Annotation marking resources as unsupported on current host platform
src/Aspire.Hosting.Maui/Lifecycle/UnsupportedPlatformEventSubscriber.cs Sets "Unsupported" state for resources with unsupported platform annotation
tests/Aspire.Hosting.Maui.Tests/MauiWindowsExtensionsTests.cs 11 comprehensive tests covering resource creation, naming, TFM detection, and error scenarios
playground/AspireWithMaui/* Complete sample application demonstrating MAUI + Aspire integration with Weather API
eng/Build.props Excludes MAUI client from standard build to avoid MAUI workload requirement
.github/CODEOWNERS Adds @jfversluis as code owner for MAUI integration

@github-actions
Copy link
Contributor

github-actions bot commented Oct 22, 2025

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12284

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12284"

@jfversluis jfversluis added this to the 13.0 milestone Oct 22, 2025
Moves the logic for setting command line arguments with the detected Windows TFM to immediately after TFM detection, rather than during resource start. Removes redundant annotation and streamlines resource configuration.
/// AppHost startup times while still allowing incremental builds during development.
/// </para>
/// </remarks>
public class MauiProjectResource(string name, string projectPath) : Resource(name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you build a custom resource, you need implement all of the life cycle and behavior, is that what you want?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think of this as a root resource, the add AddWindows(...) call will be the real project resource.

eng/build.ps1 Outdated
Invoke-Expression "& `"$PSScriptRoot/common/build.ps1`" $arguments"
$buildExitCode = $LASTEXITCODE

# Install MAUI workload after restore if -restore was passed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is gonna force install the maui workload on build by default?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Step one. Quantify the impact.

I suspect it might not even work on the build agents.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, see #12284 (comment) talked about it with Mitch who says: lets try it and see what it does for build times.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not just about build times, it's about disrupting everyone else working on aspire for one project 😄. The node and python samples go out of their way to skip if nothing is installed.

@jfversluis jfversluis force-pushed the jfversluis/maui-windows branch from c74dd85 to 9bc6609 Compare October 23, 2025 12:18
@jfversluis jfversluis force-pushed the jfversluis/maui-windows branch from 9bc6609 to 5fc6849 Compare October 23, 2025 12:38
@mitchdenny mitchdenny merged commit 7cd4419 into main Oct 24, 2025
305 checks passed
@mitchdenny mitchdenny deleted the jfversluis/maui-windows branch October 24, 2025 01:29
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet9" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json" />
<add key="dotnet10" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10/nuget/v3/index.json" />
<add key="dotnet10-workloads" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet10-workloads/nuget/v3/index.json" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure about this change. With SDK workloads, you want to be careful when you add dependencies to feeds that have unreleased versions of workloads or workload sets, as that can potentially put your machine in a torn state if a version is picked up and it depends on something else that is not part of the feed.

IMO we should probably consider reverting this particular feed, and instead see if there is a way to have the SDK team push only released versions into dotnet-public to prevent the above.

cc: @marcpopMSFT in case anything I said above is wrong.

Write-Host "Installing MAUI workload into local .dotnet..."

$repoRoot = Split-Path $PSScriptRoot -Parent
$dotnetRoot = Join-Path $repoRoot ".dotnet"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not always be true, as there are cases where the machine-wide version matches the version in global.json then the build won't create a local .dotnet folder. Instead, this script should be calling the dotnet.cmd or dotnet.sh scripts in the root of the repo which will resolve to the right place always.

cc: @jfversluis


# Install MAUI workload after restore if -restore-maui was passed
# Only on Windows and macOS (MAUI doesn't support Linux)
$restoreMauiPassed = $properties -contains "-restore-maui"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jfversluis is the plan to also add a github workflow or something that is protecting this part of the script and ensuring it doesn't get broken? We should have some sort of protection by having a job that restores the workload and tries to build the maui playground project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants