Skip to content

Conversation

@jfversluis
Copy link
Member

Description

This PR adds iOS platform support to Aspire's MAUI hosting capabilities, enabling developers to run and debug MAUI applications on iOS simulators and physical devices directly from Aspire. The implementation follows the same architectural patterns as the existing Windows, Mac Catalyst, and Android implementations and includes validation to help developers avoid common device/simulator ID mistakes.

Follow up from #12284 and #11942 and #12342 and #12381

Contributes to #4684

Features

  • iOS Simulator Registration - Add iOS simulators using AddiOSSimulator() or AddiOSSimulator(name, simulatorId) for targeting specific simulators by UDID
  • iOS Device Registration - Add iOS physical devices using AddiOSDevice() or AddiOSDevice(name, deviceId) for targeting specific devices by UDID (requires device provisioning)
  • Target Framework Detection - Validates that MAUI projects include an iOS target framework (e.g., net10.0-ios) and fails fast with clear error messages if missing
  • Multiple Devices/Simulators - Support for adding multiple iOS device/simulator resources to the same MAUI project with unique names
  • Environment Variable Forwarding - iOS resources automatically forward all environment variables (custom, service discovery, OTLP) via MSBuild targets file using MlaunchEnvironmentVariables ItemGroup pattern
  • OTLP Dev Tunnel Support - Works with existing WithOtlpDevTunnel() extension method for OpenTelemetry connectivity from iOS platforms that cannot reach localhost
  • Device ID Validation - Automatic validation at startup to detect when device IDs and simulator IDs are accidentally swapped, with clear error messages displayed in the dashboard
  • Explicit Start - iOS devices/simulators require explicit user action to start (not auto-started), allowing developers to control when the app launches
  • macOS Requirement - iOS resources are marked as "Unsupported" with clear messaging when running on non-macOS hosts

New Files

  • MauiiOSExtensions.cs - Extension methods for adding iOS devices and simulators via AddiOSDevice() and AddiOSSimulator()
  • MauiiOSDeviceResource.cs - Resource class representing a physical iOS device instance
  • MauiiOSSimulatorResource.cs - Resource class representing an iOS simulator instance
  • Utilities/MauiiOSEnvironmentAnnotation.cs - Annotation that triggers MSBuild targets file generation for iOS environment variables
  • Lifecycle/MauiiOSEnvironmentSubscriber.cs - Event subscriber that generates MSBuild targets files for iOS environment variables

Modified Files

  • MauiEnvironmentHelper.cs - Added iOS-specific methods:
    • CreateiOSEnvironmentTargetsFileAsync() - Generates targets file with MlaunchEnvironmentVariables ItemGroup
    • GenerateiOSTargetsFileContent() - Creates XML content for iOS environment forwarding
  • MauiHostingExtensions.cs - Registered MauiiOSEnvironmentSubscriber for iOS lifecycle management
  • MauiOtlpExtensions.cs - Updated to support iOS resources with WithOtlpDevTunnel()
  • api/Aspire.Hosting.Maui.cs - Updated API surface with new iOS extension methods
  • Playground example - Updated AspireWithMaui to demonstrate iOS simulator/device usage with OTLP dev tunnel
  • README files - Added comprehensive iOS documentation with UDID discovery steps and validation notes
  • Tests - Added iOS to unified platform tests and iOS-specific validation scenarios

Public API

namespace Aspire.Hosting;

public static class MauiiOSExtensions
{
    /// <summary>
    /// Adds an iOS device resource to run the MAUI application on a physical iOS device.
    /// </summary>
    public static IResourceBuilder<MauiiOSDeviceResource> AddiOSDevice(
        this IResourceBuilder<MauiProjectResource> builder);

    /// <summary>
    /// Adds an iOS device resource with a specific name.
    /// </summary>
    public static IResourceBuilder<MauiiOSDeviceResource> AddiOSDevice(
        this IResourceBuilder<MauiProjectResource> builder,
        string name);

    /// <summary>
    /// Adds an iOS device resource with a specific name and optional device UDID.
    /// </summary>
    public static IResourceBuilder<MauiiOSDeviceResource> AddiOSDevice(
        this IResourceBuilder<MauiProjectResource> builder,
        string name,
        string? deviceId = null);

    /// <summary>
    /// Adds an iOS simulator resource to run the MAUI application.
    /// </summary>
    public static IResourceBuilder<MauiiOSSimulatorResource> AddiOSSimulator(
        this IResourceBuilder<MauiProjectResource> builder);

    /// <summary>
    /// Adds an iOS simulator resource with a specific name.
    /// </summary>
    public static IResourceBuilder<MauiiOSSimulatorResource> AddiOSSimulator(
        this IResourceBuilder<MauiProjectResource> builder,
        string name);

    /// <summary>
    /// Adds an iOS simulator resource with a specific name and optional simulator UDID.
    /// </summary>
    public static IResourceBuilder<MauiiOSSimulatorResource> AddiOSSimulator(
        this IResourceBuilder<MauiProjectResource> builder,
        string name,
        string? simulatorId = null);
}

Usage Example

var builder = DistributedApplication.CreateBuilder(args);

var weatherApi = builder.AddProject("webapi", @"../AspireWithMaui.WeatherApi/AspireWithMaui.WeatherApi.csproj");

// Create dev tunnel for API access from mobile devices
var publicDevTunnel = builder.AddDevTunnel("devtunnel-public")
    .WithAnonymousAccess()
    .WithReference(weatherApi.GetEndpoint("https"));

var mauiapp = builder.AddMauiProject("mauiapp", @"../AspireWithMaui.MauiClient/AspireWithMaui.MauiClient.csproj");

// iOS simulator needs dev tunnels for both API and OTLP
mauiapp.AddiOSSimulator()
    .WithOtlpDevTunnel()  // Automatically creates dev tunnel for telemetry
    .WithReference(weatherApi, publicDevTunnel);  // Dev tunnel for API calls

// iOS device with specific UDID
mauiapp.AddiOSDevice("my-iphone", "00008030-001234567890123A")
    .WithOtlpDevTunnel()
    .WithReference(weatherApi, publicDevTunnel);

builder.Build().Run();

Architecture

iOS Environment Variable Forwarding

iOS applications launched via dotnet run with mlaunch can receive environment variables through MSBuild's MlaunchEnvironmentVariables property. This PR implements automatic environment variable forwarding:

  • MauiiOSEnvironmentSubscriber: Event subscriber that processes BeforeResourceStartedEvent for iOS resources
  • Targets File Generation: Creates a temporary MSBuild .targets file containing <MlaunchEnvironmentVariables Include="KEY=VALUE" /> items
  • MSBuild Integration: Adds -p:CustomAfterMicrosoftCommonTargets={targetsPath} to pass the file to MSBuild
  • Idempotent Generation: Uses annotation marker pattern to ensure file generated only once
  • Variable Encoding: Semicolons encoded as %3B to prevent MSBuild item separator issues
  • Automatic Cleanup: Targets files older than 24 hours are cleaned up on next build

Device ID Validation

To help developers avoid common mistakes when specifying iOS device/simulator UDIDs, this PR includes validation:

  • Pattern Detection: Uses Guid.TryParse() to detect GUID-format IDs (iOS Simulator UDIDs are standard GUIDs)
  • Startup Validation: Validation occurs in OnBeforeResourceStarted() hook, displaying errors in the dashboard
  • Clear Error Messages: Shows detected ID format, expected format with examples, and suggests correct method to use
  • Device Validation: AddiOSDevice() throws exception if GUID-format ID is detected (likely a simulator ID)
  • Simulator Validation: AddiOSSimulator() throws exception if non-GUID format ID is detected (likely a device ID)
  • User-Friendly: Errors appear prominently in the dashboard when users try to start the resource

Example validation error:

Device ID 'E25BBE37-69BA-4720-B6FD-D54C97791E79' for iOS device resource 'my-iphone' appears to be an iOS Simulator UDID (GUID format). 
iOS physical devices typically use a different UDID format (e.g., 00008030-001234567890123A). 
If you intended to target an iOS Simulator, use AddiOSSimulator("my-iphone", "E25BBE37-69BA-4720-B6FD-D54C97791E79") instead.

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?

@jfversluis jfversluis added this to the 13.0 milestone Oct 27, 2025
@jfversluis jfversluis requested a review from mitchdenny October 27, 2025 13:47
@github-actions
Copy link
Contributor

github-actions bot commented Oct 27, 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 -- 12402

Or

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

@jfversluis
Copy link
Member Author

Builds on top of #12381, will rebase when that one gets merged to clean this one up, that's why its a draft for now.

jfversluis and others added 17 commits November 5, 2025 10:46
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Simplifies MauiAndroidEnvironmentProcessedAnnotation by removing the unused TargetsFilePath property and constructor. Updates usage to reflect the new parameterless constructor and clarifies documentation to indicate the annotation is a marker for callback registration.
Replaces hardcoded default OTLP port and URL values with named constants and static readonly fields for improved maintainability and clarity.
Copy link
Member

@mitchdenny mitchdenny left a comment

Choose a reason for hiding this comment

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

Provided there are no functional changes and this is just refactoring the OTLP bits into shared code for reuse this is fine. That said I cannot validate the end to end for macOS.

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.

3 participants