Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
9c6a635
Add MAUI playground
jfversluis Sep 16, 2025
c7a82cc
Basic implementation
jfversluis Sep 18, 2025
adc8aee
Add webapi reference in playground
jfversluis Sep 18, 2025
b0c84a0
Correct reference name
jfversluis Sep 18, 2025
172170c
Really fix webapi reference
jfversluis Sep 19, 2025
6235edb
Windows & Mac Catalyst work
jfversluis Sep 22, 2025
8c5eed8
Add warning if no platform is enabled
jfversluis Sep 22, 2025
20bf85e
Add basic tests
jfversluis Sep 23, 2025
1fb5516
Add more tests
jfversluis Sep 23, 2025
a1a4a73
Code style
jfversluis Sep 23, 2025
da9e1c3
Add auto detection when no WithPlatform() method
jfversluis Sep 24, 2025
96f32fb
Add auto detect propagate SD test
jfversluis Sep 24, 2025
878e53e
Refactoring
jfversluis Sep 24, 2025
27a1ce0
Normalize AppHost project path
jfversluis Sep 24, 2025
52517a3
Add warnings for unsupported platforms/hosts
jfversluis Sep 24, 2025
3e46372
Unsupported platform test
jfversluis Sep 24, 2025
014feab
Pass in env vars to iOS
jfversluis Sep 24, 2025
9f24259
Add OTLP tunnel
jfversluis Sep 26, 2025
d34727f
Refactoring
jfversluis Sep 26, 2025
ce6f94c
Do not use obsolete stuff
jfversluis Oct 1, 2025
d56097f
WithiOS
jfversluis Oct 1, 2025
e40b7f1
Remove duplicate MauiServiceDefaults code
jfversluis Oct 7, 2025
839acb8
Set correct MAUI app resource name
jfversluis Oct 7, 2025
f18a0fe
Forward Android environment variables
jfversluis Oct 8, 2025
5bcbea8
Provide value for OTEL_RESOURCE_ATTRIBUTES
jfversluis Oct 8, 2025
59d5a26
Better Unsupported state
jfversluis Oct 8, 2025
01c5fc8
Add icons
jfversluis Oct 8, 2025
5f3335c
Last couple of touchups
jfversluis Oct 9, 2025
9112303
Missing TFM warning
jfversluis Oct 9, 2025
55871e6
Detect duplicate platform
jfversluis Oct 9, 2025
761895c
Add MAUI workload restore script and README
jfversluis Oct 9, 2025
1949ef0
Add myself as CODEOWNER
jfversluis Oct 10, 2025
143ea0c
Update MAUI playground
jfversluis Oct 10, 2025
536e754
Apply suggestions from code review
jfversluis Oct 10, 2025
0bfaa46
Update playground/AspireWithMaui/AspireWithMaui.MauiServiceDefaults/E…
jfversluis Oct 10, 2025
2f814ca
Add API baseline file for Aspire.Hosting.Maui
jfversluis Oct 10, 2025
62fcacf
Fix build warning in Extensions.cs
jfversluis Oct 10, 2025
aed047f
Exclude MAUI playground from build (for now)
jfversluis Oct 10, 2025
b99aa32
Update MauiProjectBuilder.cs
jfversluis Oct 10, 2025
af423ef
Update MauiProjectBuilder.cs
jfversluis Oct 10, 2025
48ff7e0
Fix security risk in playground app
jfversluis Oct 10, 2025
1119eb7
Fix test for running on Linux and macOS
jfversluis Oct 10, 2025
70ae3b7
Fix automatic Android detection on macOS
jfversluis Oct 10, 2025
30eb28b
Remove Tizen platform files from MAUI playground
jfversluis Oct 10, 2025
79cc0ed
Limit to only not building the MauiClient project
jfversluis Oct 10, 2025
1fb0783
Update AspireWithMaui.ServiceDefaults.csproj
jfversluis Oct 10, 2025
b1d25f1
Refactor to IResourceBuilder AddMauiProject
jfversluis Oct 20, 2025
5f92784
Merge error in Build.props
jfversluis Oct 20, 2025
c0aa2e0
Fix test
jfversluis Oct 20, 2025
31393ac
Comment out MAUI SDK telemetry
jfversluis Oct 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
/src/Aspire.Hosting.Azure.AppContainers @captainsafia @eerhardt
/src/Aspire.Hosting.Azure.AppService @captainsafia @eerhardt
/src/Aspire.Hosting.Docker @captainsafia
/src/Aspire.Hosting.Maui @jfversluis

# tests

/tests/Aspire.EndToEnd.Tests @radical @eerhardt
/tests/Aspire.Hosting.Maui.Tests @jfversluis
/tests/Aspire.Hosting.Testing.Tests @reubenbond
/tests/Aspire.Hosting.Tests @mitchdenny
/tests/Aspire.Templates.Tests @radical @eerhardt
Expand All @@ -20,3 +22,4 @@
# playground apps
/playground/deployers @captainsafia
/playground/publishers @captainsafia
/playground/AspireWithMaui @jfversluis
4 changes: 3 additions & 1 deletion eng/Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
<ProjectToBuild Include="$(RepoRoot)eng\dcppack\**\*.csproj" />
<ProjectToBuild Include="$(RepoRoot)eng\dashboardpack\**\*.csproj" />

<ProjectToBuild Include="$(RepoRoot)playground\**\*.csproj" />
<!-- Exclude only the MAUI client project as it requires the .NET MAUI workload.
Other projects in the AspireWithMaui playground can still be built. -->
<ProjectToBuild Include="$(RepoRoot)playground\**\*.csproj" Exclude="$(RepoRoot)playground\AspireWithMaui\AspireWithMaui.MauiClient\*.csproj" />
</ItemGroup>

<!-- `$(SkipTestProjects)` allows skipping test projects from being
Expand Down
17 changes: 17 additions & 0 deletions playground/AspireWithMaui/AspireWithMaui.AppHost/AppHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var builder = DistributedApplication.CreateBuilder(args);

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

var publicDevTunnel = builder.AddDevTunnel("devtunnel-public")
.WithAnonymousAccess() // All ports on this tunnel default to allowing anonymous access
.WithReference(weatherApi.GetEndpoint("https"));

builder.AddMauiProject("mauiclient", @"../AspireWithMaui.MauiClient/AspireWithMaui.MauiClient.csproj")
.WithWindows()
.WithAndroid()
.WithiOS()
.WithMacCatalyst()
.WithOtlpDevTunnel() // Setup a tunnel for the Open Telemetry communication, only needed for Android & iOS, but also works for desktop
.WithReference(weatherApi, publicDevTunnel);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>c086a0d6-b7a6-1337-a6a3-b62aa4616d88</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<AspireProjectOrPackageReference Include="Aspire.Hosting" />
<AspireProjectOrPackageReference Include="Aspire.Hosting.AppHost" />
<AspireProjectOrPackageReference Include="Aspire.Hosting.Maui" />
<AspireProjectOrPackageReference Include="Aspire.Hosting.DevTunnels" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17279;http://localhost:15173",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21046",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22093"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15173",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19172",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20091"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
14 changes: 14 additions & 0 deletions playground/AspireWithMaui/AspireWithMaui.MauiClient/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:AspireWithMaui.MauiClient"
x:Class="AspireWithMaui.MauiClient.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
14 changes: 14 additions & 0 deletions playground/AspireWithMaui/AspireWithMaui.MauiClient/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace AspireWithMaui.MauiClient;

public partial class App : Application
{
public App()
{
InitializeComponent();
}

protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}
24 changes: 24 additions & 0 deletions playground/AspireWithMaui/AspireWithMaui.MauiClient/AppShell.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="AspireWithMaui.MauiClient.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:AspireWithMaui.MauiClient"
Title="AspireWithMaui.MauiClient">

<TabBar>
<Tab Title="Weather" Icon="weather.png">
<ShellContent
Title="Weather"
ContentTemplate="{DataTemplate local:MainPage}"
Route="weather" />
</Tab>
<Tab Title="Environment" Icon="aspire_outline.png">
<ShellContent
Title="Environment"
ContentTemplate="{DataTemplate local:EnvironmentPage}"
Route="environment" />
</Tab>
</TabBar>

</Shell>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace AspireWithMaui.MauiClient;

public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net10.0-android;net10.0-ios;net10.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>

<!-- Note for MacCatalyst:
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->

<OutputType>Exe</OutputType>
<RootNamespace>AspireWithMaui.MauiClient</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<!-- Disable CPM for this MAUI project -->
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>

<!-- Disable strong name checking for playground sample -->
<NoWarn>$(NoWarn);CS8002</NoWarn>

<!-- Ignore unnecessary using directive warnings -->
<NoWarn>$(NoWarn);IDE0005</NoWarn>

<!-- Display name -->
<ApplicationTitle>AspireWithMaui.MauiClient</ApplicationTitle>

<!-- App Identifier -->
<ApplicationId>com.companyname.aspirewithmaui.mauiclient</ApplicationId>

<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>

<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
<WindowsPackageType>None</WindowsPackageType>

<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
</PropertyGroup>

<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />

<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />

<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="680,628" />
<MauiImage Update="Resources\Images\aspire_outline.svg" Resize="True" BaseSize="256,256" />
<MauiImage Update="Resources\Images\weather.png" Resize="True" BaseSize="512,512" />

<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />

<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="10.0.0-rc.1.25452.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0-rc.2.25465.106" />
<PackageReference Include="System.Net.Http.Json" Version="10.0.0-rc.2.25465.106" />
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25465.106" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\AspireWithMaui.MauiServiceDefaults\AspireWithMaui.MauiServiceDefaults.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="AspireWithMaui.MauiClient.EnvironmentPage"
Title="Environment">

<Grid Padding="24,20"
RowSpacing="18"
RowDefinitions="Auto,*">
<VerticalStackLayout Spacing="12">
<Label Text="Aspire Environment Variables"
Style="{StaticResource Headline}"
SemanticProperties.HeadingLevel="Level1" />

<Label Text="These values are forwarded from the AppHost to the MAUI client so the app can connect to services and discovery endpoints."
Style="{StaticResource Body}"
LineBreakMode="WordWrap" />
</VerticalStackLayout>

<CollectionView Grid.Row="1"
ItemsSource="{Binding AspireEnvironmentVariables}"
BackgroundColor="Transparent"
EmptyView="No Aspire-specific environment variables found.">
<CollectionView.ItemTemplate>
<DataTemplate>
<Frame HasShadow="False"
Padding="14"
Margin="0,0,0,12"
BackgroundColor="{AppThemeBinding Light=#F0F0F0, Dark=#1F1F1F}">
<VerticalStackLayout Spacing="6">
<Label Text="{Binding Key}"
FontAttributes="Bold"
LineBreakMode="CharacterWrap" />
<Label Text="{Binding Value}"
FontSize="12"
LineBreakMode="WordWrap" />
</VerticalStackLayout>
</Frame>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>

</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Collections;
using System.Collections.ObjectModel;

namespace AspireWithMaui.MauiClient;

public partial class EnvironmentPage : ContentPage
{
public ObservableCollection<KeyValuePair<string, string>> AspireEnvironmentVariables { get; } = new();

public EnvironmentPage()
{
InitializeComponent();
BindingContext = this;
}

protected override void OnAppearing()
{
base.OnAppearing();
LoadAspireEnvironmentVariables();
}

private void LoadAspireEnvironmentVariables()
{
AspireEnvironmentVariables.Clear();

var variables = Environment.GetEnvironmentVariables()
.Cast<DictionaryEntry>()
.Select(entry => new KeyValuePair<string, string>(entry.Key?.ToString() ?? string.Empty, DecodeValue(entry.Value?.ToString())))
.Where(item => IsAspireVariable(item.Key))
.OrderBy(item => item.Key, StringComparer.OrdinalIgnoreCase);

foreach (var variable in variables)
{
AspireEnvironmentVariables.Add(variable);
}
}

private static string DecodeValue(string? value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}

try
{
var decoded = Uri.UnescapeDataString(value);

// Validate that the decoded string doesn't contain control characters that could indicate malicious content
// Allow only printable characters, tabs, and newlines
if (decoded.Any(c => char.IsControl(c) && c != '\t' && c != '\n' && c != '\r'))
{
// If suspicious control characters found, return the original encoded value for safety
return value;
}

return decoded;
}
catch (UriFormatException)
{
// If decoding fails, return the original value
return value;
}
}

private static bool IsAspireVariable(string key)
=> key.StartsWith("services__", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("connectionstrings__", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("ASPIRE_", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("AppHost__", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("OTEL_", StringComparison.OrdinalIgnoreCase)
|| key.StartsWith("LOGGING__CONSOLE", StringComparison.OrdinalIgnoreCase)
|| key.Equals("ASPNETCORE_ENVIRONMENT", StringComparison.OrdinalIgnoreCase)
|| key.Equals("ASPNETCORE_URLS", StringComparison.OrdinalIgnoreCase)
|| key.Equals("DOTNET_ENVIRONMENT", StringComparison.OrdinalIgnoreCase)
|| key.Equals("DOTNET_URLS", StringComparison.OrdinalIgnoreCase)
|| key.Equals("DOTNET_LAUNCH_PROFILE", StringComparison.OrdinalIgnoreCase)
|| key.Equals("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION", StringComparison.OrdinalIgnoreCase);
}
Loading
Loading