
Maui Aspire
- 28 installs
- 163 repo stars
- Updated July 6, 2026
- davidortinau/maui-skills
Connects .NET MAUI apps to .NET Aspire-hosted backend services, covering AppHost configuration and service discovery for mobile clients.
About
Guides .NET MAUI apps consuming .NET Aspire-hosted backend services, covering AppHost configuration and service discovery for mobile clients. A developer uses it when wiring a MAUI app to an Aspire-orchestrated backend.
- AppHost configuration for Aspire-hosted services
- Service discovery for mobile clients
Maui Aspire by the numbers
- 28 all-time installs (skills.sh)
- Ranked #684 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/davidortinau/maui-skills --skill maui-aspireAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 28 |
|---|---|
| repo stars | ★ 163 |
| Last updated | July 6, 2026 |
| Repository | davidortinau/maui-skills ↗ |
What it does
Connects .NET MAUI apps to .NET Aspire-hosted backend services, covering AppHost configuration and service discovery for mobile clients.
Files
.NET MAUI with .NET Aspire
Key Differences from Other Aspire Clients
MAUI apps are NOT orchestrated by the AppHost — they run on devices/simulators and connect over the network. This changes everything:
- ❌ Cannot use Aspire service discovery URIs (
https+http://apiservice) - ❌ Cannot be added with
.WithReference()in the AppHost - ❌ Cannot use
AddServiceDefaults()(not an ASP.NET Core host) - ✅ Must use real network-reachable endpoints
- ✅ Must use MSAL.NET (public client) — not OIDC (confidential client)
Common Gotchas
1. ❌ Don't add MAUI to the AppHost
// ❌ MAUI doesn't implement IServiceMetadata — this will fail
builder.AddProject<Projects.MyMauiApp>("mauiapp");
// ✅ Only add backend services to the AppHost
var apiService = builder.AddProject<Projects.MyApp_ApiService>("apiservice");2. ❌ Don't use Aspire service discovery URIs in MAUI
// ❌ This only resolves inside the Aspire AppHost orchestration
client.BaseAddress = new Uri("https+http://apiservice");
// ✅ Use real endpoints
client.BaseAddress = new Uri("https://localhost:7001");3. ⚠️ Android emulator can't reach localhost
The Android emulator has its own network stack. localhost points to the emulator itself, not the host machine.
// ❌ Fails silently on Android emulator
client.BaseAddress = new Uri("https://localhost:7001");
// ✅ Use 10.0.2.2 for host loopback on Android emulator
#if ANDROID && DEBUG
client.BaseAddress = new Uri("https://10.0.2.2:7001");
#else
client.BaseAddress = new Uri("https://localhost:7001");
#endif4. ⚠️ Android emulator doesn't trust .NET dev certificates
The emulator won't trust your HTTPS dev cert. During local development:
// ⚠️ Development only — never ship this
#if ANDROID && DEBUG
builder.Services.AddHttpClient<IWeatherApiClient, WeatherApiClient>(client =>
{
client.BaseAddress = new Uri("https://10.0.2.2:7001");
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
});
#endif5. ⚠️ Auth is public client, not confidential
MAUI uses PublicClientApplication (MSAL.NET). The Entra Aspire auth skill (entra-id-aspire-authentication) is for the API/Blazor Server side only. For the MAUI side, use the maui-authentication skill.
6. ⚠️ Aspire requires .NET 10+
Ensure your MAUI project also targets net10.0-* TFMs to match the Aspire backend.
Blazor Hybrid Caveat
In MAUI Blazor Hybrid apps calling Aspire services, authentication happens at the MAUI layer (MSAL.NET), not in the Blazor WebView. Don't use AddMicrosoftIdentityWebApp or server-side OIDC patterns in the MAUI app.
Aspire Service Defaults Don't Apply
MAUI projects should not use AddServiceDefaults() because:
- MAUI apps are not ASP.NET Core hosts
- OpenTelemetry for mobile has different requirements
- Health check endpoints don't apply to client apps
For telemetry correlation, configure OpenTelemetry separately using System.Diagnostics APIs.
Development Workflow Decision
| Scenario | What to do |
|---|---|
| Debugging API issues | Use the Aspire dashboard at https://localhost:17178 |
| Debugging MAUI + API | Run Aspire AppHost in one terminal, MAUI in another |
| VS: simultaneous debug | Open two VS instances — one per project |
| VS Code | Two terminals, or Aspire extension + MAUI extension |
Platform Networking Quick Reference
| Platform | localhost works? | HTTPS dev cert trusted? | Special config needed? |
|---|---|---|---|
| iOS Simulator | ✅ Yes | ✅ Yes (macOS keychain) | ATS exception for HTTP |
| Android Emulator | ❌ Use 10.0.2.2 | ❌ No | Network security config |
| Mac Catalyst | ✅ Yes | ✅ Yes | None |
| Windows | ✅ Yes | ✅ Yes | None |
Checklist
- [ ] MAUI project is NOT added to AppHost with
AddProject - [ ] API base URL uses real endpoints, not
https+http://URIs - [ ] Android: uses
10.0.2.2instead oflocalhost - [ ] Android: SSL bypass or HTTP config for local dev only
- [ ] Auth uses MSAL.NET
PublicClientApplication, not confidential client - [ ] MAUI and Aspire both target
net10.0-* - [ ]
AddServiceDefaults()is NOT called in the MAUI project
.NET MAUI with .NET Aspire — API Reference
Architecture
┌─────────────────────┐ HTTPS + Bearer Token ┌─────────────────────┐
│ MAUI App │ ──────────────────────────► │ Aspire API Service │
│ (device/emulator) │ │ (JWT validation) │
│ │ Sign-in via system browser │ │
│ MSAL.NET ─────────┼──► Entra ID ──► Access Token │ Microsoft.Identity │
│ │ │ .Web │
└─────────────────────┘ └─────────────────────┘
▲
│ Orchestrated by
┌──────┴──────────────┐
│ Aspire AppHost │
│ (dashboard, config)│
└─────────────────────┘AppHost Configuration
The MAUI project is NOT directly orchestrated. The AppHost only manages backend services.
// AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
var apiService = builder.AddProject<Projects.MyApp_ApiService>("apiservice");
// The MAUI project is NOT added here — it runs independently on device.
// The API endpoint is available via the Aspire dashboard.
builder.Build().Run();Service Discovery for MAUI
MAUI cannot use https+http://apiservice — it needs real HTTP(S) URLs.
Option 1: Configuration-based (recommended for production)
Store the API base URL in appsettings.json or a config class:
public static class ApiConfig
{
public static string BaseUrl =>
#if DEBUG
DeviceInfo.Platform == DevicePlatform.Android
? "https://10.0.2.2:7001" // Android emulator → host loopback
: "https://localhost:7001" // iOS sim, Mac Catalyst, Windows
#else
"https://myapi.azurecontainerapps.io"
#endif
;
}Option 2: Aspire dashboard endpoint discovery
1. Run aspire run to start the AppHost 2. Open the Aspire dashboard (default: https://localhost:17178) 3. Find your API service's endpoint URL 4. Use that URL as the base address in MAUI
Option 3: Environment-injected (CI/CD)
Pass the API URL as a build property or environment variable:
<!-- In your MAUI .csproj -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<ApiBaseUrl>https://localhost:7001</ApiBaseUrl>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<ApiBaseUrl>https://myapi.azurecontainerapps.io</ApiBaseUrl>
</PropertyGroup>HttpClient Setup
Register a typed HttpClient pointing to the Aspire API service:
// MauiProgram.cs
builder.Services.AddHttpClient<IWeatherApiClient, WeatherApiClient>(client =>
{
client.BaseAddress = new Uri(ApiConfig.BaseUrl);
});public interface IWeatherApiClient
{
Task<WeatherForecast[]?> GetForecastAsync(CancellationToken ct = default);
}
public class WeatherApiClient : IWeatherApiClient
{
private readonly HttpClient _http;
public WeatherApiClient(HttpClient http) => _http = http;
public async Task<WeatherForecast[]?> GetForecastAsync(CancellationToken ct = default)
{
return await _http.GetFromJsonAsync<WeatherForecast[]>(
"/weatherforecast", ct);
}
}Authentication (Entra ID)
When the Aspire API is protected with JWT Bearer authentication, the MAUI app needs to acquire access tokens via MSAL.NET.
Quick summary
1. Provision Entra app registrations — Use the Entra team's provisioning skill:
mkdir -p .github/skills && cd .github/skills
curl -LO https://aka.ms/msidweb/aspire/entra-id-provisioning-skill2. Protect the API — Use the Entra team's authentication skill on the Aspire API project:
curl -LO https://aka.ms/msidweb/aspire/entra-id-code-skillAsk your AI assistant: "Add Entra ID authentication to my Aspire app"
3. Wire up MSAL.NET in MAUI — Follow the maui-authentication skill's MSAL.NET section for PublicClientApplication setup, platform configs, and IAuthService.
4. Attach tokens to API calls — Use a DelegatingHandler:
public class AuthTokenHandler : DelegatingHandler
{
private readonly IAuthService _authService;
private readonly string[] _scopes;
public AuthTokenHandler(IAuthService authService, string[] scopes)
{
_authService = authService;
_scopes = scopes;
InnerHandler = new HttpClientHandler();
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken ct)
{
var token = await _authService.GetAccessTokenAsync(_scopes, ct);
if (token != null)
{
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, ct);
}
}Register with the HttpClient:
builder.Services.AddTransient(sp =>
new AuthTokenHandler(
sp.GetRequiredService<IAuthService>(),
new[] { "api://<api-client-id>/access_as_user" }));
builder.Services.AddHttpClient<IWeatherApiClient, WeatherApiClient>(client =>
{
client.BaseAddress = new Uri(ApiConfig.BaseUrl);
})
.AddHttpMessageHandler<AuthTokenHandler>();Development Workflow
Running Aspire + MAUI simultaneously
1. Start the Aspire AppHost (backend services):
cd MyApp.AppHost
aspire run
# Or: dotnet runThe Aspire dashboard opens at https://localhost:17178
2. Note the API endpoint from the dashboard (e.g., https://localhost:7001)
3. Run the MAUI app targeting your platform:
# Mac Catalyst
dotnet build -f net10.0-maccatalyst -t:Run
# Android emulator
dotnet build -f net10.0-android -t:Install
adb shell am start -n com.companyname.myapp/crc64XXX.MainActivity
# iOS simulator
dotnet build -f net10.0-ios -t:Run -p:_DeviceName=:v2:udid=<UDID>4. The MAUI app connects to the Aspire-hosted API using the configured base URL
Debugging both simultaneously
- Visual Studio: Open two instances — one for the Aspire AppHost, one for MAUI
- VS Code: Use two terminal sessions, or use the Aspire extension + MAUI extension
- CLI: Run
aspire runin one terminal,dotnet build -t:Runin another
Platform-Specific Networking
Android Emulator
The Android emulator cannot reach localhost directly. Use 10.0.2.2 to access the host machine's loopback interface:
#if ANDROID && DEBUG
client.BaseAddress = new Uri("https://10.0.2.2:7001");
#endifIf using HTTP (not HTTPS) during development, add a network security config:
<!-- Platforms/Android/Resources/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>Reference in AndroidManifest.xml:
<application android:networkSecurityConfig="@xml/network_security_config" />Android HTTPS with dev certificates
The Android emulator doesn't trust the .NET dev certificate. Options: 1. Use HTTP during local dev (with network security config above) 2. Install the dev certificate on the emulator 3. Add HttpClientHandler that bypasses SSL validation in DEBUG only:
#if ANDROID && DEBUG
builder.Services.AddHttpClient<IWeatherApiClient, WeatherApiClient>(client =>
{
client.BaseAddress = new Uri("https://10.0.2.2:7001");
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
});
#endifiOS Simulator
iOS simulators share the host's network stack — localhost works directly. For HTTPS with dev certificates, the simulator trusts the macOS keychain.
If you need HTTP (not HTTPS) during development, add an ATS exception in Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>Mac Catalyst
Uses the host network directly. localhost and dev certificates work as-is.
Blazor Hybrid + Aspire
For MAUI Blazor Hybrid apps calling Aspire services, authentication happens at the MAUI layer (MSAL.NET), not in the Blazor WebView. The pattern is:
1. MAUI handles sign-in via IAuthService (MSAL.NET) 2. A custom AuthenticationStateProvider exposes auth state to Blazor 3. HttpClient with DelegatingHandler attaches bearer tokens automatically 4. Blazor components use @inject IWeatherApiClient normally
See the maui-authentication skill's "Blazor Hybrid" section for the MsalAuthenticationStateProvider implementation.
Deployment
When deploying the Aspire backend to Azure (Container Apps, App Service, etc.), update the MAUI app's API base URL to point to the deployed endpoint:
// Release config
public static string BaseUrl => "https://myapi.azurecontainerapps.io";The Entra ID app registration's redirect URIs are platform-specific and don't change between local dev and production.
Related Skills & Resources
- `maui-authentication` — MSAL.NET setup, platform configs, auth service, bearer tokens
- `maui-rest-api` — HttpClient DI, JSON serialization, error handling
- Entra ID skills (from Microsoft): https://github.com/AzureAD/microsoft-identity-web/tree/master/.github/skills
entra-id-aspire-authentication— API JWT protection + Blazor Server authentra-id-aspire-provisioning— Automated app registration via Graph PowerShell- Azure-Samples/ms-identity-dotnetcore-maui — MSAL.NET MAUI sample: https://github.com/Azure-Samples/ms-identity-dotnetcore-maui
- Aspire docs: https://learn.microsoft.com/dotnet/aspire