Introduktion till .NET Aspire och .NET MAUI
Mobilappar blir allt mer komplexa — och om du jobbar med .NET MAUI vet du hur krångligt det kan vara att hantera kopplingar till backend-tjänster. Nätverkskonfiguration för emulatorer, hårdkodade URL:er, och avsaknad av ordentlig observerbarhet. Det har helt enkelt inte funnits ett smidigt sätt att hantera det hela.
Tills nu.
Med .NET 10 har Microsoft äntligen tagit .NET Aspire — deras ramverk för distribuerade applikationer — och gjort det tillgängligt för MAUI-utvecklare. Det här är, ärligt talat, en ganska stor grej. Aspire ger dig orkestering, tjänsteupptäckt, telemetri via OpenTelemetry och inbyggda resiliensmönster. Funktioner som tidigare var reserverade för ASP.NET Core-världen.
I den här guiden går vi igenom hela processen. Från projektuppsättning och tjänsteupptäckt till Dev Tunnels, telemetri och produktionsdriftsättning. Vi kör med praktiska kodexempel genom hela artikeln, så häng med.
Förutsättningar och systemkrav
Innan du sätter igång behöver du ha följande på plats:
- .NET 10 SDK eller senare
- Aspire 13 eller senare (med CLI-verktyg och projektmallar)
- Visual Studio 2026 eller VS Code med C# Dev Kit
- En .NET MAUI-app som riktar sig mot .NET 10
- En eller flera webbtjänster (t.ex. ett ASP.NET Core Web API)
- Docker Desktop (valfritt, men rekommenderas starkt för dashboard-funktionalitet)
Installera Aspire-verktygen genom att köra följande i terminalen:
dotnet workload install aspire
dotnet new install Aspire.ProjectTemplates
Kör sedan detta för att verifiera att allt sitter som det ska:
dotnet aspire --version
Projektstruktur och arkitektur
En MAUI-app med Aspire-integration består av flera projekt som samverkar. Det kan se lite överväldigande ut vid första anblick, men strukturen är logisk när man väl förstår den.
Översikt av projektstrukturen
Den rekommenderade lösningsstrukturen ser ut så här:
- AppHost-projektet — Orkestreringsnavet som kopplar ihop alla tjänster och enheter
- MAUI Service Defaults-projektet — Delad konfiguration för tjänsteupptäckt, telemetri och resiliens
- MAUI-appprojektet — Din mobilapplikation
- Webbtjänstprojekt — Backend-API:er som appen pratar med
- Delade modeller — Gemensamma datamodeller mellan klient och server
MinApp.sln
├── MinApp.AppHost/ # Aspire App Host (orkestrering)
│ └── Program.cs
├── MinApp.MauiServiceDefaults/ # Service Defaults för MAUI
│ └── Extensions.cs
├── MinApp.MauiApp/ # .NET MAUI-applikationen
│ ├── MauiProgram.cs
│ ├── Services/
│ └── Views/
├── MinApp.WebApi/ # ASP.NET Core Web API
│ └── Program.cs
└── MinApp.SharedModels/ # Delade modeller
└── WeatherForecast.cs
Steg-för-steg: Konfigurera Aspire-integrationen
Steg 1: Skapa MAUI Service Defaults-projektet
Service Defaults-projektet är i princip hjärtat i hela integrationen. Det innehåller de delade konfigurationerna som din MAUI-app behöver för tjänsteupptäckt, telemetri och resiliens.
dotnet new maui-aspire-servicedefaults -n MinApp.MauiServiceDefaults
dotnet sln add MinApp.MauiServiceDefaults/MinApp.MauiServiceDefaults.csproj
Lägg sedan till en referens från ditt MAUI-projekt:
dotnet add MinApp.MauiApp/MinApp.MauiApp.csproj reference MinApp.MauiServiceDefaults/MinApp.MauiServiceDefaults.csproj
Projektet genererar automatiskt en Extensions.cs-fil med metoden AddServiceDefaults(). Den konfigurerar OpenTelemetry, lägger till tjänsteupptäckt och ställer in HttpClient för att funka med service discovery. Ganska smidigt.
Steg 2: Skapa App Host-projektet
App Host fungerar som din orkestreringscentral — den definierar hur dina tjänster och enhetstyper hänger ihop:
dotnet new aspire-apphost -n MinApp.AppHost
dotnet sln add MinApp.AppHost/MinApp.AppHost.csproj
Lägg till projektreferenser:
dotnet add MinApp.AppHost/MinApp.AppHost.csproj reference MinApp.MauiApp/MinApp.MauiApp.csproj
dotnet add MinApp.AppHost/MinApp.AppHost.csproj reference MinApp.WebApi/MinApp.WebApi.csproj
Och installera NuGet-paketet för MAUI-hosting:
dotnet add MinApp.AppHost/MinApp.AppHost.csproj package Aspire.Hosting.Maui
Steg 3: Konfigurera App Host Program.cs
Nu blir det intressant. Här definierar vi alla tjänster, enhetstyper och deras beroenden:
var builder = DistributedApplication.CreateBuilder(args);
// Registrera webbtjänsten
var weatherApi = builder.AddProject<Projects.MinApp_WebApi>("webapi");
// Skapa en offentlig Dev Tunnel för iOS och Android
var publicDevTunnel = builder.AddDevTunnel("devtunnel-public")
.WithAnonymousAccess()
.WithReference(weatherApi.GetEndpoint("https"));
// Registrera MAUI-appen
var mauiapp = builder.AddMauiProject("mauiapp",
@"../MinApp.MauiApp/MinApp.MauiApp.csproj");
// Windows-enhet (använder localhost direkt)
mauiapp.AddWindowsDevice()
.WithReference(weatherApi);
// Mac Catalyst-enhet
mauiapp.AddMacCatalystDevice()
.WithReference(weatherApi);
// iOS-simulator med Dev Tunnel
mauiapp.AddiOSSimulator()
.WithOtlpDevTunnel()
.WithReference(weatherApi, publicDevTunnel);
// Android-emulator med Dev Tunnel
mauiapp.AddAndroidEmulator()
.WithOtlpDevTunnel()
.WithReference(weatherApi, publicDevTunnel);
builder.Build().Run();
Lägg märke till att olika plattformar hanteras olika. Windows och Mac Catalyst kan prata direkt via localhost, medan iOS-simulatorn och Android-emulatorn behöver Dev Tunnels. Metoden WithOtlpDevTunnel() skapar dessutom en separat tunnel specifikt för OpenTelemetry-data, så att telemetri kan samlas in även från dessa enheter.
Tjänsteupptäckt (Service Discovery) i praktiken
Det här är kanske den smidigaste delen av hela integrationen. Istället för att hårdkoda URL:er till dina backend-tjänster använder du tjänstenamn som löses upp automatiskt beroende på körmiljö. Inga fler magiska strängar som sprids runt i kodbasen.
Konfigurera MauiProgram.cs
I din MAUI-apps MauiProgram.cs aktiverar du tjänsteupptäckt genom att anropa AddServiceDefaults():
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");
});
// Lägg till Aspire service defaults
builder.AddServiceDefaults();
// Konfigurera HttpClient med tjänsteupptäckt
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
// Tjänstenamnet matchar registreringen i App Host
client.BaseAddress = new Uri("https+http://webapi");
});
// Registrera sidor och tjänster för DI
builder.Services.AddTransient<MainPage>();
return builder.Build();
}
}
Notera URI-schemat https+http:// — det talar om för tjänsteupptäckten att föredra HTTPS men falla tillbaka på HTTP vid behov. Och tjänstenamnet webapi måste exakt matcha det du angav i App Host-konfigurationen. Det här är en vanlig felkälla, så dubbelkolla alltid det.
Skapa en typad HTTP-klient
Att använda typade HTTP-klienter är definitivt best practice här. Här är ett komplett exempel:
public class WeatherApiClient
{
private readonly HttpClient _httpClient;
public WeatherApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<WeatherForecast[]?> GetWeatherForecastAsync(
CancellationToken cancellationToken = default)
{
return await _httpClient.GetFromJsonAsync<WeatherForecast[]>(
"/weatherforecast",
cancellationToken);
}
public async Task<WeatherForecast?> GetForecastByIdAsync(
int id, CancellationToken cancellationToken = default)
{
return await _httpClient.GetFromJsonAsync<WeatherForecast>(
$"/weatherforecast/{id}",
cancellationToken);
}
public async Task<bool> CreateForecastAsync(
WeatherForecast forecast,
CancellationToken cancellationToken = default)
{
var response = await _httpClient.PostAsJsonAsync(
"/weatherforecast", forecast, cancellationToken);
return response.IsSuccessStatusCode;
}
}
Använda klienten i en MAUI-sida
Tack vare dependency injection kan du injicera den typade klienten direkt i dina sidor. Inga konstigheter:
public partial class MainPage : ContentPage
{
private readonly WeatherApiClient _weatherClient;
public MainPage(WeatherApiClient weatherClient)
{
_weatherClient = weatherClient;
InitializeComponent();
}
private async void OnGetWeatherClicked(object sender, EventArgs e)
{
try
{
LoadingIndicator.IsRunning = true;
var forecasts = await _weatherClient.GetWeatherForecastAsync();
if (forecasts is not null)
{
WeatherList.ItemsSource = forecasts;
StatusLabel.Text = $"Hämtade {forecasts.Length} prognoser";
}
}
catch (HttpRequestException ex)
{
StatusLabel.Text = $"Nätverksfel: {ex.Message}";
await DisplayAlert("Fel", "Kunde inte ansluta till tjänsten.", "OK");
}
finally
{
LoadingIndicator.IsRunning = false;
}
}
}
Dev Tunnels: Bryggan mellan mobilenhet och lokala tjänster
Om du har utvecklat mobilappar ett tag vet du hur frustrerande nätverkskonfiguration kan vara. Android-emulatorn med sin 10.0.2.2-adress, iOS-simulatorn med ATS-undantag och manuella IP-adresser... Det har aldrig varit direkt kul. Aspire löser det här problemet elegant med Dev Tunnels.
Hur Dev Tunnels fungerar
Dev Tunnels skapar säkra, temporära tunnlar mellan din utvecklingsmaskin och de mobila enheterna. När du konfigurerar en Dev Tunnel i App Host händer följande automatiskt:
- En säker tunnel skapas som exponerar dina lokala tjänster
- MAUI-appen konfigureras med rätt URL:er via tjänsteupptäckt
- Ingen manuell nätverkskonfiguration behövs
- HTTPS-certifikat hanteras automatiskt
Tidigare fick man hantera allt detta manuellt — specialadresser på Android, ATS-konfiguration på iOS, bräckliga lösningar som gick sönder beroende på nätverksmiljö. Dev Tunnels eliminerar allt det genom att skapa en stabil, krypterad kanal mellan enhet och utvecklingsserver.
Det finns två typer av Dev Tunnels att känna till:
- Tjänste-Dev Tunnel — Hanterar trafik mellan appen och backend-tjänster
- OTLP Dev Tunnel — En dedikerad tunnel för OpenTelemetry-telemetridata, som aktiveras via
WithOtlpDevTunnel()
Plattformsspecifikt beteende
Olika plattformar beter sig (inte helt oväntat) olika:
- Windows och Mac Catalyst kör på samma maskin som servern — ingen Dev Tunnel behövs, localhost funkar rakt av
- iOS Simulator sitter i en sandlåda med begränsad nätverksåtkomst och kräver Dev Tunnels
- Android Emulator har sin egen nätverksstack och behöver Dev Tunnels istället för det gamla
10.0.2.2-tricket - Fysiska enheter kräver också Dev Tunnels för att nå utvecklingsmaskinens tjänster
Telemetri och observerbarhet med OpenTelemetry
Det här är ärligt talat en av de mest imponerande delarna av Aspire-integrationen. Du får full insyn i hur din mobilapp interagerar med backend-tjänsterna — spårningar, mätvärden och loggar samlas in automatiskt, utan att du behöver konfigurera särskilt mycket.
Automatisk telemetrikonfiguration
När du anropar AddServiceDefaults() i MauiProgram.cs får du automatiskt:
- Distribuerad spårning — Varje HTTP-anrop spåras med korrelations-ID:n som följer en förfrågan genom hela systemet
- Mätvärden — HTTP-klientmätvärden som svarstider, felfrekvenser och genomströmning
- Loggar — Strukturerade loggar som skickas till Aspire Dashboard via OTLP
Aspire Dashboard i praktiken
Dashboarden ger dig en visuell översikt av hela systemet. Du kan se:
- Resurser — Status för alla tjänster och enheter
- Konsoliderade loggar — Loggar från alla tjänster samlade på ett ställe
- Distribuerade spårningar — Visuella tidslinjer som visar hur förfrågningar flödar genom systemet
- Prestandamätvärden — Realtidsövervakning av svarstider och felfrekvenser
Öppna dashboarden via URL:en som visas i terminalen när App Host körs (vanligtvis https://localhost:18888). MAUI-enheter syns som separata resurser men måste startas manuellt.
Anpassad telemetri
Utöver den automatiska telemetrin kan du såklart lägga till egna mätvärden och spårningar. Här skapar vi en anpassad ActivitySource för att spåra specifika operationer:
using System.Diagnostics;
using System.Diagnostics.Metrics;
public class AppTelemetry
{
public static readonly string ServiceName = "MinApp.MauiApp";
public static readonly ActivitySource ActivitySource =
new(ServiceName);
public static readonly Meter Meter = new(ServiceName);
// Skapa anpassade mätvärden
private static readonly Counter<int> _weatherRequestsCounter =
Meter.CreateCounter<int>("weather.requests.count",
description: "Antal väderprognosförfrågningar");
private static readonly Histogram<double> _requestDuration =
Meter.CreateHistogram<double>("weather.request.duration",
unit: "ms",
description: "Varaktighet för väderprognosförfrågningar");
public static void RecordWeatherRequest()
{
_weatherRequestsCounter.Add(1);
}
public static Activity? StartWeatherActivity(string operation)
{
return ActivitySource.StartActivity(operation);
}
}
// Användning i en tjänst
public class InstrumentedWeatherApiClient
{
private readonly HttpClient _httpClient;
public InstrumentedWeatherApiClient(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<WeatherForecast[]?> GetWeatherForecastAsync(
CancellationToken cancellationToken = default)
{
using var activity = AppTelemetry.StartWeatherActivity(
"GetWeatherForecast");
var stopwatch = Stopwatch.StartNew();
try
{
var result = await _httpClient.GetFromJsonAsync<WeatherForecast[]>(
"/weatherforecast", cancellationToken);
AppTelemetry.RecordWeatherRequest();
activity?.SetTag("forecast.count", result?.Length ?? 0);
activity?.SetStatus(ActivityStatusCode.Ok);
return result;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
throw;
}
finally
{
stopwatch.Stop();
}
}
}
Resiliensmönster för mobilappar
Mobilappar lever i en helt annan verklighet jämfört med serverapplikationer. Användare hoppar mellan Wi-Fi och mobilnät, åker genom tunnlar och befinner sig ibland i områden med usel täckning. Det är bara att acceptera. Aspire Service Defaults har som tur är inbyggt stöd för resiliensmönster via Microsoft.Extensions.Resilience (som bygger på Polly).
Konfigurera resiliens med HttpClient
Du lägger till resiliensmönster direkt i din HttpClient-konfiguration:
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
client.BaseAddress = new Uri("https+http://webapi");
})
.AddStandardResilienceHandler(options =>
{
// Konfigurera retry-strategi
options.Retry.MaxRetryAttempts = 3;
options.Retry.BackoffType = DelayBackoffType.Exponential;
options.Retry.UseJitter = true;
options.Retry.Delay = TimeSpan.FromSeconds(1);
// Konfigurera circuit breaker
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
options.CircuitBreaker.FailureRatio = 0.3;
options.CircuitBreaker.MinimumThroughput = 5;
options.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(15);
// Konfigurera timeout
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(10);
options.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(60);
});
Förklaring av resiliensmönstren
De tre mönstren jobbar tillsammans för att skydda din app:
- Retry (Omförsök) — Vid tillfälliga fel görs automatiskt nya försök med exponentiell fördröjning. Jitter-parametern lägger till slumpmässig variation så att inte alla klienter hamrar servern samtidigt.
- Circuit Breaker (Kretsbrytare) — Om en tjänst visar ihållande fel (mer än 30% felfrekvens under 30 sekunder), bryts kretsen och inga anrop görs under 15 sekunder. Det ger tjänsten andrum att återhämta sig.
- Timeout — Varje enskilt försök har en tidsgräns på 10 sekunder. Den totala tidsgränsen inklusive omförsök är 60 sekunder.
Anpassad resiliens för specifika scenarier
Ibland behöver du skräddarsy resiliensmönstren. Här är ett exempel med en mer anpassad pipeline:
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
client.BaseAddress = new Uri("https+http://webapi");
})
.AddResilienceHandler("weather-pipeline", pipelineBuilder =>
{
// Lägg till retry med anpassade villkor
pipelineBuilder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
BackoffType = DelayBackoffType.Exponential,
Delay = TimeSpan.FromMilliseconds(500),
ShouldHandle = args => ValueTask.FromResult(
args.Outcome.Result?.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable
|| args.Outcome.Exception is HttpRequestException)
});
// Lägg till timeout per försök
pipelineBuilder.AddTimeout(TimeSpan.FromSeconds(8));
});
Köra och felsöka applikationen
Okej, med alla pusselbitar på plats — hur kör man egentligen allt?
Via kommandoraden
Det enklaste sättet är med Aspire CLI:
# Med Aspire CLI
aspire run
# Eller med dotnet direkt
dotnet run --project MinApp.AppHost/MinApp.AppHost.csproj
När App Host startar dyker en URL till Aspire Dashboard upp i terminalen. Öppna den för att se statusen för alla dina tjänster.
Via Visual Studio 2026
I Visual Studio ställer du in App Host som startprojekt och trycker F5. Dashboarden öppnas automatiskt i webbläsaren. MAUI-enheter syns i resursöversikten men behöver startas manuellt — antingen från dashboarden eller genom att köra MAUI-appen separat.
Felsökning med Aspire Dashboard
Dashboarden är ett fantastiskt verktyg för felsökning. Några praktiska scenarier:
- Spåra långsamma förfrågningar — I spårningsvyn ser du exakt var tid spenderas. Om ett API-anrop tar lång tid kan du se om fördröjningen ligger i nätverket, tjänsten eller en databasoperation.
- Identifiera felande tjänster — Resursöversikten visar statusen direkt. Är en tjänst nere ser du det omedelbart och kan gräva ner i loggarna.
- Korrelera händelser — Du kan följa en enstaka användarinteraktion genom hela systemet. Från knapptryckning i MAUI-appen, genom API-anropet, till databasen och tillbaka. Det är ganska kraftfullt.
Backend-konfiguration med ASP.NET Core
Din backend behöver också konfigureras för att spela bra med Aspire. Här är ett exempel på ett ASP.NET Core Web API som är redo:
var builder = WebApplication.CreateBuilder(args);
// Lägg till Aspire service defaults
builder.AddServiceDefaults();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Mappning av Aspire-standardendpoints
app.MapDefaultEndpoints();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
// Definiera API-endpoints
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 40),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
var summaries = new[]
{
"Frysande", "Kallt", "Svalt", "Milt", "Varmt",
"Balmy", "Hett", "Kvavt", "Stekande"
};
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC * 1.8);
}
Notera att backend-tjänsten också anropar AddServiceDefaults() och MapDefaultEndpoints(). Det säkerställer att servern skickar telemetridata till dashboarden och deltar i den distribuerade spårningen.
Hantera flera tjänster och databaser
I verkligheten har du sällan bara en backend-tjänst. Du kanske behöver integrera med databaser, caching-lager och meddelandeköer. Aspire gör det enkelt att orkestrera alla dessa komponenter:
var builder = DistributedApplication.CreateBuilder(args);
// Lägg till infrastruktur
var postgres = builder.AddPostgres("postgres")
.AddDatabase("weatherdb");
var redis = builder.AddRedis("cache");
// Lägg till backend-tjänster med beroenden
var weatherApi = builder.AddProject<Projects.MinApp_WebApi>("webapi")
.WithReference(postgres)
.WithReference(redis);
var authApi = builder.AddProject<Projects.MinApp_AuthApi>("authapi")
.WithReference(postgres);
// Dev Tunnel med referens till alla tjänster
var publicDevTunnel = builder.AddDevTunnel("devtunnel-public")
.WithAnonymousAccess()
.WithReference(weatherApi.GetEndpoint("https"))
.WithReference(authApi.GetEndpoint("https"));
// MAUI-app med referens till alla tjänster
var mauiapp = builder.AddMauiProject("mauiapp",
@"../MinApp.MauiApp/MinApp.MauiApp.csproj");
mauiapp.AddWindowsDevice()
.WithReference(weatherApi)
.WithReference(authApi);
mauiapp.AddiOSSimulator()
.WithOtlpDevTunnel()
.WithReference(weatherApi, publicDevTunnel)
.WithReference(authApi, publicDevTunnel);
mauiapp.AddAndroidEmulator()
.WithOtlpDevTunnel()
.WithReference(weatherApi, publicDevTunnel)
.WithReference(authApi, publicDevTunnel);
builder.Build().Run();
I MAUI-appen registrerar du sedan flera typade klienter:
// I MauiProgram.cs
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
client.BaseAddress = new Uri("https+http://webapi");
})
.AddStandardResilienceHandler();
builder.Services.AddHttpClient<AuthApiClient>(client =>
{
client.BaseAddress = new Uri("https+http://authapi");
})
.AddStandardResilienceHandler();
Bästa praxis och rekommendationer
Efter att ha jobbat med Aspire och MAUI i olika projekt har jag samlat ihop en del lärdomar. Här är det viktigaste.
Arkitektur och projektstruktur
- Använd typade klienter — Injicera aldrig
HttpClientdirekt. Typade klienter ger bättre testbarhet, tydligare API-gränssnitt och centraliserad felhantering. - Separera delade modeller — Lägg datamodeller som delas mellan klient och server i ett eget projekt. Det undviker duplicering och håller typerna synkade.
- Konfigurera resiliens per klient — Olika tjänster har olika behov. En autentiseringstjänst bör kanske ha kortare timeout än en datatjänst.
Nätverkshantering
- Hantera offline-scenarier — Resiliensmönster hjälper, men din app måste fortfarande klara sig utan nät. Överväg att kombinera Aspire med en offline-first-arkitektur.
- Cacha strategiskt — Använd Redis eller lokal caching för att minska nätverksanrop. Aspire gör det trivialt att integrera Redis.
- Övervaka nätverksanvändning — Använd telemetridata från dashboarden för att identifiera och eliminera onödiga API-anrop.
Telemetri och felsökning
- Glöm inte OTLP Dev Tunnel — Utan
WithOtlpDevTunnel()för iOS och Android får du ingen telemetri från dessa plattformar. Det här är lätt att missa. - Använd strukturerade loggar — Använd
ILogger<T>med namngivna parametrar istället för stränginterpolation. Det gör det mycket enklare att söka i dashboarden. - Lägg till anpassade mätvärden — De automatiska HTTP-mätvärdena är bra, men skapa även egna för affärslogik som är viktig att följa.
Produktion vs. utveckling
- Aspire är primärt ett utvecklingsverktyg — Orkestreringen används under utveckling. I produktion bör du använda standard .NET-konfiguration för tjänsteupptäckt och telemetri.
- Miljöspecifik konfiguration — Använd
appsettings.jsonoch miljövariabler för att konfigurera endpoints och telemetri i produktion. - Testa på alla plattformar — Dev Tunnels beter sig olika på olika plattformar. Testa regelbundet, inte bara på den plattform du råkar utveckla mot just nu.
Migrera befintliga MAUI-appar till Aspire
Har du redan en MAUI-app med hårdkodade URL:er och manuell konfiguration? Ingen panik — du kan migrera stegvis. Processen delas upp i fyra faser.
Fas 1: Förbered din kodbas
Börja med att se till att din app redan använder typade HTTP-klienter och dependency injection. Om du har direkt HttpClient-användning i sidor eller ViewModels, refaktorisera dessa först. Det är en förutsättning för Aspire, men det är också generellt god praxis som förbättrar både testbarhet och underhållbarhet.
Fas 2: Lägg till Service Defaults
Skapa Service Defaults-projektet och referera till det från ditt MAUI-projekt. Anropa AddServiceDefaults() i MauiProgram.cs. I det här steget kan du behålla dina befintliga URL:er — Service Defaults lägger till telemetri och resiliens utan att kräva tjänsteupptäckt.
Fas 3: Skapa App Host
Lägg till App Host och konfigurera tjänster och enhetstyper. Uppdatera klienternas basadresser till tjänsteupptäckts-URI:er (som https+http://webapi). Testa noggrant på alla plattformar — Dev Tunnels kan vara lite knepiga första gången.
Fas 4: Optimera och utöka
När grundintegrationen funkar kan du börja utnyttja de mer avancerade funktionerna. Anpassa resiliensmönster per klient, lägg till anpassad telemetri och integrera infrastrukturkomponenter som databaser och meddelandeköer i App Host-konfigurationen.
Vanliga fallgropar och lösningar
Här är de vanligaste problemen som dyker upp, och hur du löser dem. (Jag har själv stött på samtliga.)
Tjänstenamn matchar inte
Det absolut vanligaste felet. Tjänstenamnet i HttpClient-konfigurationen måste exakt matcha det i App Host. Kontrollera att "webapi" i new Uri("https+http://webapi") stämmer med builder.AddProject<T>("webapi"). En enkel typo kan kosta dig en hel felsökningssession.
Saknad WithOtlpDevTunnel
Ser du ingen telemetridata från iOS eller Android i dashboarden? Kontrollera att du har lagt till .WithOtlpDevTunnel() för respektive enhetskonfiguration. Utan det kan telemetridata helt enkelt inte nå fram.
AddServiceDefaults saknas
Om tjänsteupptäckt inte funkar, verifiera att du anropar builder.AddServiceDefaults() i MauiProgram.cs. Utan det anropet aktiveras varken tjänsteupptäckt, telemetri eller resiliensmönster.
Dev Tunnel-anslutningsproblem
Om din enhet inte når backend-tjänsten via Dev Tunnel, kolla följande:
- Se till att Dev Tunnel har
.WithAnonymousAccess()konfigurerat - Verifiera att rätt endpoint refereras i konfigurationen
- Kontrollera att enheten har internetåtkomst (Dev Tunnels kräver internet)
- Prova att starta om App Host om tunneln verkar instabil
Enhetstestning av Aspire-integrerade tjänster
En bra testningsstrategi är avgörande. Men tack vare att vi använder typade HTTP-klienter och DI är det faktiskt ganska rättframt att skriva tester.
Testa typade klienter med MockHttp
Med RichardSzalay.MockHttp kan du mocka HTTP-svar i dina tester:
using RichardSzalay.MockHttp;
using System.Net;
using System.Text.Json;
public class WeatherApiClientTests
{
[Fact]
public async Task GetWeatherForecastAsync_ReturnsForecasts()
{
// Arrange
var expectedForecasts = new[]
{
new WeatherForecast(
DateOnly.FromDateTime(DateTime.Now),
22,
"Varmt")
};
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("/weatherforecast")
.Respond("application/json",
JsonSerializer.Serialize(expectedForecasts));
var httpClient = mockHttp.ToHttpClient();
httpClient.BaseAddress = new Uri("https://localhost");
var client = new WeatherApiClient(httpClient);
// Act
var result = await client.GetWeatherForecastAsync();
// Assert
Assert.NotNull(result);
Assert.Single(result);
Assert.Equal(22, result[0].TemperatureC);
Assert.Equal("Varmt", result[0].Summary);
}
[Fact]
public async Task GetWeatherForecastAsync_HandlesServerError()
{
// Arrange
var mockHttp = new MockHttpMessageHandler();
mockHttp.When("/weatherforecast")
.Respond(HttpStatusCode.InternalServerError);
var httpClient = mockHttp.ToHttpClient();
httpClient.BaseAddress = new Uri("https://localhost");
var client = new WeatherApiClient(httpClient);
// Act & Assert
await Assert.ThrowsAsync<HttpRequestException>(
() => client.GetWeatherForecastAsync());
}
}
Integrationstester med Aspire.Testing
Aspire har även stöd för integrationstester som verifierar hela systemet. Med Aspire.Hosting.Testing kan du starta hela App Host i en testmiljö:
using Aspire.Hosting.Testing;
public class IntegrationTests
{
[Fact]
public async Task WebApi_ReturnsWeatherForecasts()
{
// Arrange - Starta hela App Host
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.MinApp_AppHost>();
await using var app = await appHost.BuildAsync();
await app.StartAsync();
// Act - Skapa en HTTP-klient för webapi-tjänsten
var httpClient = app.CreateHttpClient("webapi");
var response = await httpClient.GetAsync("/weatherforecast");
// Assert
response.EnsureSuccessStatusCode();
var forecasts = await response.Content
.ReadFromJsonAsync<WeatherForecast[]>();
Assert.NotNull(forecasts);
Assert.NotEmpty(forecasts);
}
}
Dessa integrationstester startar hela miljön i en isolerad testkontext. Det ger hög konfidensgrad att allt fungerar ihop. Notera dock att de tar längre tid att köra, så separera dem i egna testprojekt — enhetstester vid varje commit, integrationstester vid pull requests eller nattliga byggen.
Miljökonfiguration för produktion
Aspire-orkestreringen är ett utvecklingsverktyg, men din app behöver ju funka i produktion också — utan App Host. Så här hanterar du övergången.
Miljöspecifik basadresskonfiguration
Du kan konfigurera olika basadresser beroende på miljö:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
// Kontrollera om Aspire är tillgängligt
var useAspire = builder.Configuration
.GetValue<bool>("UseAspire", false);
if (useAspire)
{
builder.AddServiceDefaults();
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
client.BaseAddress = new Uri("https+http://webapi");
})
.AddStandardResilienceHandler();
}
else
{
// Produktionskonfiguration
var apiBaseUrl = builder.Configuration
.GetValue<string>("ApiSettings:BaseUrl")
?? "https://api.minapp.se";
builder.Services.AddHttpClient<WeatherApiClient>(client =>
{
client.BaseAddress = new Uri(apiBaseUrl);
})
.AddStandardResilienceHandler();
}
builder.Services.AddTransient<MainPage>();
return builder.Build();
}
}
Konfigurationsfiler per miljö
Skapa separata konfigurationsfiler för utveckling och produktion:
// appsettings.json (standard)
{
"UseAspire": false,
"ApiSettings": {
"BaseUrl": "https://api.minapp.se"
},
"Telemetry": {
"Enabled": true,
"OtlpEndpoint": "https://otel.minapp.se"
}
}
// appsettings.Development.json
{
"UseAspire": true,
"Telemetry": {
"Enabled": true
}
}
Med den här uppsättningen använder din app Aspire-tjänsteupptäckt under utveckling men faller tillbaka på konfigurerade URL:er i produktion. Resiliensmönstren förblir aktiva i båda fallen, vilket ger konsekvent felhantering oavsett miljö.
Produktions-telemetri utan Aspire
I produktion kan du fortfarande använda OpenTelemetry, bara utan Aspire Dashboard. Konfigurera OTLP-exportern att skicka data till din observerbarhetsplattform (Grafana, Datadog, Azure Monitor, eller vad du nu kör med):
// I MauiProgram.cs för produktion
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing
.AddSource(AppTelemetry.ServiceName)
.AddHttpClientInstrumentation()
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri(
builder.Configuration["Telemetry:OtlpEndpoint"]
?? "https://otel.minapp.se");
});
})
.WithMetrics(metrics =>
{
metrics
.AddMeter(AppTelemetry.ServiceName)
.AddHttpClientInstrumentation()
.AddOtlpExporter();
});
Sammanfattning
Integrationen mellan .NET MAUI och .NET Aspire är ett rejält kliv framåt för mobilutveckling med .NET. Att samla tjänsteupptäckt, telemetri, resiliensmönster och orkestrering i ett ramverk förenklar vardagen avsevärt för oss som bygger mobilappar med backend-beroenden.
De viktigaste vinsterna:
- Eliminerad nätverkskomplexitet — Dev Tunnels tar bort behovet av manuella nätverkskonfigurationer för emulatorer och simulatorer
- Automatisk telemetri — Full observerbarhet utan manuell instrumentering tack vare OpenTelemetry
- Inbyggd resiliens — Retry, circuit breaker och timeout direkt ur lådan
- Enhetlig orkestering — Starta och hantera alla tjänster och enheter från en enda punkt
Mitt råd? Börja med att integrera Aspire i ett nytt eller mindre projekt för att lära dig arbetsflödet. Sedan utökar du gradvis till dina befintliga appar. Den stegvisa migreringsstrategin som beskrivs ovan gör det möjligt att införa Aspire utan att behöva skriva om allt på en gång.
Med .NET MAUI 10 och Aspire 13 har du verktygen för att bygga robusta, observerbara mobilappar. Automatisk tjänsteupptäckt, inbyggd telemetri och resiliensmönster baserade på Polly — det är precis vad mobilutveckling behöver. Ge det ett försök.