.NET Aspire med .NET MAUI: Guide till tjänsteupptäckt, telemetri och resiliens

Komplett guide till .NET Aspire-integration med .NET MAUI 10. Lär dig konfigurera tjänsteupptäckt, Dev Tunnels, OpenTelemetry-telemetri och resiliensmönster med praktiska kodexempel.

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:

  1. En säker tunnel skapas som exponerar dina lokala tjänster
  2. MAUI-appen konfigureras med rätt URL:er via tjänsteupptäckt
  3. Ingen manuell nätverkskonfiguration behövs
  4. 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:

  1. 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.
  2. 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.
  3. 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 HttpClient direkt. 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.json och 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.

Om Författaren Editorial Team

Our team of expert writers and editors.