Skip to content

Conversation

@jfversluis
Copy link
Member

Description

This PR adds Android platform support to Aspire's MAUI hosting capabilities, enabling developers to run and debug MAUI applications on Android devices and emulators directly from Aspire. The implementation follows the same architectural patterns as the existing Windows and Mac Catalyst implementations and includes OpenTelemetry connectivity through dev tunnels.

Follow up from #12284 and #11942 and #12342

Contributes to #4684

Features

  • Android Device Registration - Add Android physical devices using AddAndroidDevice() or AddAndroidDevice(name, deviceId) for targeting specific devices
  • Android Emulator Registration - Add Android emulators using AddAndroidEmulator() or AddAndroidEmulator(name, emulatorId) for targeting specific emulators
  • Target Framework Detection: Validates that MAUI projects include an Android target framework (e.g., net10.0-android) and fails fast with clear error messages if missing
  • Multiple Devices/Emulators: Support for adding multiple Android device/emulator resources to the same MAUI project with unique names
  • Environment Variable Forwarding: Android resources automatically forward all environment variables (custom, service discovery, OTLP) via MSBuild targets file since Android cannot receive environment variables through process environment
  • OTLP Dev Tunnel Support: New WithOtlpDevTunnel() extension method automatically creates dev tunnels for OpenTelemetry connectivity from mobile platforms that cannot reach localhost
  • Explicit Start: Android devices/emulators require explicit user action to start (not auto-started), allowing developers to control when the app launches

New Files

  • MauiAndroidExtensions.cs - Extension methods for adding Android devices and emulators via AddAndroidDevice() and AddAndroidEmulator()
  • MauiAndroidDeviceResource.cs - Resource class representing a physical Android device instance
  • MauiAndroidEmulatorResource.cs - Resource class representing an Android emulator instance
  • MauiOtlpExtensions.cs - Extension methods for configuring OTLP connectivity via WithOtlpDevTunnel()
  • Otlp/OtlpLoopbackResource.cs - Synthetic resource for service discovery of OTLP endpoint
  • Otlp/OtlpEndpointResolver.cs - Resolves OTLP endpoint from configuration
  • Annotations/OtlpDevTunnelConfigurationAnnotation.cs - Stores shared dev tunnel infrastructure
  • Utilities/MauiAndroidEnvironmentAnnotation.cs - Subscriber that generates MSBuild targets files for Android environment variables
  • Utilities/MauiEnvironmentHelper.cs - Helper for creating Android environment targets files

Modified Files

  • IMauiPlatformResource.cs - Changed from internal to public (required for generic constraint in WithOtlpDevTunnel<T>)
  • MauiPlatformHelper.cs - Added ConfigureOtlpExporter() method that manually replaces DCP template placeholders for Android (follows Docker Compose/Azure App Service pattern)
  • Aspire.Hosting.Maui.csproj - Added Aspire.Hosting.DevTunnels project reference
  • api/Aspire.Hosting.Maui.cs - Updated API surface with new Android and OTLP extension methods
  • Playground example - Updated AspireWithMaui to demonstrate Android device/emulator usage with OTLP dev tunnel

Public API

namespace Aspire.Hosting;

public static class MauiAndroidExtensions
{
    /// <summary>
    /// Adds a physical Android device resource to run the MAUI application.
    /// </summary>
    public static IResourceBuilder<MauiAndroidDeviceResource> AddAndroidDevice(
        this IResourceBuilder<MauiProjectResource> builder);

    /// <summary>
    /// Adds a physical Android device resource with a specific name and optional device ID.
    /// </summary>
    public static IResourceBuilder<MauiAndroidDeviceResource> AddAndroidDevice(
        this IResourceBuilder<MauiProjectResource> builder,
        string name,
        string? deviceId = null);

    /// <summary>
    /// Adds an Android emulator resource to run the MAUI application.
    /// </summary>
    public static IResourceBuilder<MauiAndroidEmulatorResource> AddAndroidEmulator(
        this IResourceBuilder<MauiProjectResource> builder);

    /// <summary>
    /// Adds an Android emulator resource with a specific name and optional emulator ID.
    /// </summary>
    public static IResourceBuilder<MauiAndroidEmulatorResource> AddAndroidEmulator(
        this IResourceBuilder<MauiProjectResource> builder,
        string name,
        string? emulatorId = null);
}

public static class MauiOtlpExtensions
{
    /// <summary>
    /// Configures the MAUI platform resource to send OpenTelemetry data through an automatically created dev tunnel.
    /// </summary>
    public static IResourceBuilder<T> WithOtlpDevTunnel<T>(this IResourceBuilder<T> builder)
        where T : IMauiPlatformResource, IResourceWithEnvironment;
}

namespace Aspire.Hosting.Maui;

public interface IMauiPlatformResource : IResourceWithParent<MauiProjectResource>
{
}

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");

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

builder.Build().Run();

Architecture

Android Environment Variable Forwarding

Android applications launched via dotnet run cannot receive environment variables through the standard process environment. This PR implements automatic environment variable forwarding through MSBuild:

  • MauiAndroidEnvironmentSubscriber: Event subscriber that processes BeforeResourceStartedEvent for Android resources
  • Targets File Generation: Creates a temporary MSBuild .targets file containing <AndroidEnvironmentVariables> items
  • MSBuild Integration: Adds -p:CustomAfterMicrosoftCommonTargets={targetsPath} to pass the file to MSBuild
  • Idempotent Generation: Uses closure variable pattern to ensure file generated only once even if callback invoked multiple times
  • Variable Normalization: Android requires UPPERCASE variable names, semicolons encoded as %3B
  • Automatic Cleanup: Targets files older than 24 hours are cleaned up on next build

OTLP Dev Tunnel Implementation

Mobile platforms cannot reach localhost, so this PR introduces WithOtlpDevTunnel() for automatic OpenTelemetry connectivity:

  • Service Discovery Pattern: Creates internal OtlpLoopbackResource implementing IResourceWithEndpoints
  • Shared Infrastructure: Dev tunnel created once per MAUI project, shared across all platforms via OtlpDevTunnelConfigurationAnnotation
  • Endpoint Resolution: OtlpEndpointResolver reads configuration in priority order (unified endpoint → HTTP → gRPC → defaults)
  • Automatic Configuration: Uses WithReference() for service discovery, reads tunnel URL, sets OTEL_EXPORTER_OTLP_ENDPOINT, cleans up temporary variables
  • DCP Template Replacement: Manually replaces DCP template placeholders for Android (follows Docker Compose/Azure App Service pattern)

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?

@github-actions
Copy link
Contributor

github-actions bot commented Oct 25, 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 -- 12381

Or

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

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 adds comprehensive Android platform support to Aspire's MAUI hosting capabilities, enabling developers to run and debug MAUI applications on Android devices and emulators directly from Aspire. The implementation includes automatic environment variable forwarding via MSBuild targets files (required for Android) and introduces OpenTelemetry connectivity through dev tunnels to solve the localhost accessibility problem on mobile platforms.

Key Changes:

  • Added Android device and emulator support with new resource types and extension methods
  • Implemented environment variable forwarding for Android via MSBuild targets files
  • Introduced WithOtlpDevTunnel() for automatic OpenTelemetry connectivity through dev tunnels
  • Consolidated test files to reduce duplication using theory-based testing

Reviewed Changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
MauiAndroidExtensions.cs New extension methods for adding Android devices and emulators to MAUI projects
MauiAndroidDeviceResource.cs Resource class representing physical Android device instances
MauiAndroidEmulatorResource.cs Resource class representing Android emulator instances
MauiOtlpExtensions.cs New WithOtlpDevTunnel() extension method for OpenTelemetry connectivity
MauiAndroidEnvironmentAnnotation.cs Event subscriber that generates MSBuild targets files for Android environment variables
MauiEnvironmentHelper.cs Helper utilities for creating Android environment targets files
OtlpLoopbackResource.cs Synthetic resource for service discovery of OTLP endpoints
OtlpEndpointResolver.cs Resolves OTLP endpoint configuration from standard environment variables
OtlpDevTunnelConfigurationAnnotation.cs Stores shared dev tunnel infrastructure across platforms
IMauiPlatformResource.cs Changed from internal to public and added IResourceWithParent inheritance
MauiPlatformHelper.cs Added ConfigureOtlpExporter() for DCP template placeholder replacement
MauiPlatformExtensionsTests.cs New consolidated test file using theory-based testing for all platforms
MauiWindowsExtensionsTests.cs Deleted - tests moved to consolidated file
MauiMacCatalystExtensionsTests.cs Deleted - tests moved to consolidated file
MauiProjectResourceExtensions.cs Added call to register MAUI-specific hosting services
MauiHostingExtensions.cs New internal extension for registering lifecycle hooks
README.md Added comprehensive documentation for Android support and dev tunnels
Playground files Updated example to demonstrate Android emulator with OTLP dev tunnel

jfversluis and others added 5 commits October 26, 2025 01:06
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.

// Add Android emulator with default emulator (uses running or default emulator)
mauiapp.AddAndroidEmulator()
.WithOtlpDevTunnel() // Needed to get the OpenTelemetry data to "localhost"
Copy link
Member

@JamesNK JamesNK Oct 26, 2025

Choose a reason for hiding this comment

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

Why not have this on by default? OTEL that just works by default is one of Aspire's most popular features.

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess I built it like this with the option in mind to provide a custom endpoint later or even use something entirely different like ngrok?

Copy link
Member Author

Choose a reason for hiding this comment

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

Experimented with this a little but didn't get it quite right yet. My idea would then be: automatic dev tunnel when nothing is configured and when we detect a custom OTLP endpoint env var, then no dev tunnel and we just use that value.

People can then set it through SetEnvironment() or just as an env var on the host machine directly or if they choose to build some ngrok integration, they need to make sure to set it from there. Does that make sense?

If we want to explore down that path I think it might be good to do that in a follow-up PR.

@JamesNK
Copy link
Member

JamesNK commented Oct 26, 2025

Can you show a screenshot of the dashboard resource pages when a MAUI project is used? I want to see how resources are displayed in the resource grid and graph. i.e. are parent/child resources nested correctly, do they have good icons, do relationships make sense, etc.

@jfversluis
Copy link
Member Author

jfversluis commented Oct 26, 2025

@JamesNK I think that all looks good. Not thrilled about the otlp tunnel name, so any suggestions there are welcome, but then again maybe it also doesn't matter that much

image image

Replaces hardcoded default OTLP port and URL values with named constants and static readonly fields for improved maintainability and clarity.
@jfversluis
Copy link
Member Author

Updated screenshot for the OTLP tunnel name. Now take the .NET MAUI app resource name, a random ID (prefixed with t to satisfy the requirement that it needs to start with a character) and -otlp (the stub name). For example: mauiapp-t53c1897b-otlp. That way its clear to what resource(s) it belongs and what the use is (otlp). Even if you do WithOtlpTunnel() on multiple AddAndroidEmulator() calls, it will still reuse the one.

image

@jfversluis jfversluis added this to the 13.0 milestone Oct 27, 2025
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