Uno Platform lets you write C# + XAML once and run it everywhere: WebAssembly (in the browser), Windows (WinUI/WPF host), macOS, Linux (Skia/GTK), Android, and iOS.
If youâre a .NET dev, you already have the skills: XAML for UI, C# for logic, MVVM for structure.
In this guide, weâll go from zero to a working Weather App that fetches data from an API, shows current conditions, and is structured for growth.
Youâll learn:
⢠Project structure & where to put shared code
⢠DI + HttpClient with a typed service
⢠Building a responsive XAML UI that works on all targets
⢠WebAssembly caveats (CORS, base addresses) and how to handle them
First, set up your toolchain with uno-check. Open Windows Terminal/PowerShell and install the tool, then run it:
12
dotnet tool install -g uno.check
Run it on first install, whenever you upgrade .NET/Visual Studio, or when switching machines.
This safely installs/repairs workloads and patches paths; itâs idempotent, so rerunning is fine.
Team tip: add a global.json to pin your .NET SDK, then have everyone run uno.check --fix once.
If everything is set, youâll get the friendly: âCongratulations, everything looks great!â
Visual Studio extension
Here is the complete guide on how to do it.

Creating Uno App - Through Wizard
Now everything is ready to create our first application.

I will not show each section through images, but I will explain them.
1. Framework: Pick .NET 9 unless you have a hard constraint - fast, current, and best-supported.
3. Presentation: Choose MVVM for a first app; pick MVUX only if you already prefer reducer/state patterns.
6. Extensions: Enable DI, Configuration, Localization; HTTP (Basic) is enough; Regions keeps navigation testable.
8. Authentication: Use None for demos; add OIDC/MSAL later via the same DI.
9. Application: Set a reverse-DNS App ID (e.g., com.example.TheWeather) and author- used in manifests/bundles.
10. Testing: You can skip now; add unit tests for VMs first, UI tests once you have navigation/async flows.
Check all the details here.
Development
And your application is finally ready. If you run the application now, you will see the finished application with a text field and a button to switch to another page.
It looks like this:

For example, you can use an Android emulator to display the application on an Android phone:

Instead of this application, we will create a simple application for displaying the time for the passed city. It looks like this:

External API:
For testing purposes, I created a demo API that returns data for the passed city. Here's the endpoint I'm using there (I won't show the full API code).
1234567891011121314151617181920212223242526272829303132
app.MapGet("/weather", (string city) =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
)).ToArray();
// deterministic pick for a couple of city names
var selected = city switch
{
string s when s.Equals("Belgrade", StringComparison.OrdinalIgnoreCase) => forecast[0],
string s when s.Equals("Paris", StringComparison.OrdinalIgnoreCase) => forecast[1],
_ => forecast[2]
};
var humidity = Random.Shared.Next(30, 90);
var icon = iconBySummary.TryGetValue(selected.Summary ?? "", out var ic) ? ic : "01d";
var response = new WeatherResponse(
Name: city,
Main: new MainInfo(Temp: selected.TemperatureC, Humidity: humidity),
Weather: new List<WeatherInfo>
{
new WeatherInfo(
Description: (selected.Summary ?? "unknown").ToLowerInvariant(),
Icon: icon)
}
);
});
Build the XAML UI (MainPage.xaml)
Path: TheWeather/Presentation/MainPage.xaml
MainPage.xaml defines the UI for the appâs main screen in WinUI/Uno Platform. It lays out the title, a text box to enter a city, a button wired to the ViewModelâs LoadCommand, a progress bar for IsBusy, and fields bound to show the weather (icon, temperature, details).
In short, itâs the view that binds to the MainModel and displays whatever the ViewModel provides.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
<Page
x:Class="TheWeather.Presentation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="TheWeather"
FontSize="28"
FontWeight="SemiBold"
Margin="16,24,16,12"
HorizontalAlignment="Center" />
<StackPanel Grid.Row="1"
Spacing="12"
Margin="16"
HorizontalAlignment="Center"
Width="460">
<TextBox PlaceholderText="City"
Text="{Binding City, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Get Weather"
Command="{Binding LoadCommand}" />
<ProgressBar IsIndeterminate="True"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock Text="{Binding StatusMessage}" Opacity="0.75" Foreground="White" />
<Border Padding="16" CornerRadius="12" BorderBrush="#33000000" BorderThickness="1">
<StackPanel Spacing="8" HorizontalAlignment="Center">
<TextBlock Text="{Binding Temperature}" FontSize="36" FontWeight="SemiBold" Foreground="White" />
<TextBlock Text="Feel: Warm" Foreground="White" />
</StackPanel>
</Border>
</StackPanel>
</Grid>
</Page>
Let's make it easier - Hot Desing
Hot Design lets you shape your UI while the app is running. You tweak XAML - spacing, grid definitions, styles, resources - and the change shows up instantly in your live app.
No rebuild, no redeploy, no âone more compile.â
For layout work, that alone feels like cheating (in a good way).
Youâre designing against reality: your real navigation frame, your real fonts, your real data.
That means you catch the stuff that usually slips through - long city names, empty/error states, odd locales - as you design. Flip between phone, tablet, and desktop presets to see how the same page breathes at different widths, and toggle light/dark/system to verify contrast and elevation in seconds.
Edits are bidirectional. Drag or nudge things in the visual surface, and your .xaml file updates; type in XAML and the UI refreshes live.
Because the app stays alive, you can exercise real MVVM/MVUX state. Click your actual âGet Weatherâ button, land in a loading state, then keep adjusting the card while the spinner and text are on-screen. Custom and third-party controls render exactly as they will at runtime, so template quirks show up early, not on QA day.
Fair expectations: structural changes (adding projects, new DI registrations, renaming types) still need a build.
But for 80â90% of day-to-day UI work, Hot Design turns the loop into change â see â iterate, which boosts speed and raises the quality bar at the same time.

Add DTOs for Accepting Weather data
Path: TheWeather/Models/WeatherModels.cs
MainPage.xaml defines the UI for the appâs main screen in WinUI/Uno Platform. It lays out the title, a text box to enter a city, a button wired to the ViewModelâs LoadCommand, a progress bar for IsBusy, and fields bound to show the weather (icon, temperature, details).
In short, itâs the view that binds to the MainModel and displays whatever the ViewModel provides.
123456789101112131415161718
public sealed class WeatherResponse
{
[JsonPropertyName("name")] public string? City { get; set; }
[JsonPropertyName("main")] public MainInfo? Main { get; set; }
[JsonPropertyName("weather")] public List<WeatherInfo>? Weather { get; set; }
}
public sealed class MainInfo
{
[JsonPropertyName("temp")] public double Temp { get; set; }
}
public sealed class WeatherInfo
{
[JsonPropertyName("description")] public string? Description { get; set; }
[JsonPropertyName("icon")] public string? Icon { get; set; }
}
Build a Weather Service
Path: TheWeather/Services/API/WeatherService.cs
We use this service to connect to an external API and provide weather data for a specific city.
123456789101112131415
public sealed class WeatherService(HttpClient http) : IWeatherService
{
private static readonly JsonSerializerOptions JsonOpts = new(JsonSerializerDefaults.Web);
public async Task<WeatherResponse?> GetByCityAsync(string city, CancellationToken ct = default)
{
// Expecting a proxy endpoint /weather?city=...
var response = await http.GetAsync($"/weather?city={Uri.EscapeDataString(city)}", ct);
if (!response.IsSuccessStatusCode) return null;
await using var s = await response.Content.ReadAsStreamAsync(ct);
return await JsonSerializer.DeserializeAsync<WeatherResponse>(s, JsonOpts, ct);
}
}
Update ViewModel (MainModel.cs)
Path: TheWeather/Presentation/MainModel.cs
MainModel is the ViewModel for MainPage: it exposes bindable properties (City, IsBusy, StatusMessage, Temperature, Details, IconUrl) using CommunityToolkit.MVVM, and an IAsyncRelayCommand (LoadCommand) that calls IWeatherService to fetch weather by city.
It handles the async flow (sets IsBusy, catches errors), maps the API response into UI-friendly strings (e.g., "23.4°C", "overcast"), and builds the icon URL, raising property change notifications so the page updates automatically.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
public partial class MainModel : ObservableObject
{
private readonly IWeatherService _service;
[ObservableProperty] private string _city = "Paris";
[ObservableProperty] private bool _isBusy;
[ObservableProperty] private string? _statusMessage = "Ready";
[ObservableProperty] private string? _temperature;
[ObservableProperty] private string? _details;
[ObservableProperty] private string? _iconUrl;
public IAsyncRelayCommand LoadCommand { get; }
public MainModel(IWeatherService service)
{
_service = service;
LoadCommand = new AsyncRelayCommand(LoadAsync);
}
private async Task LoadAsync()
{
try
{
IsBusy = true;
StatusMessage = $"Loading weather for {City}...";
var data = await _service.GetByCityAsync(City);
if (data is null)
{
StatusMessage = "No data found.";
Temperature = Details = IconUrl = null;
return;
}
var tempC = data.Main?.Temp ?? 0;
var desc = data.Weather?.FirstOrDefault()?.Description ?? "n/a";
var hum = data.Main?.Humidity ?? 0;
var icon = data.Weather?.FirstOrDefault()?.Icon;
Temperature = $"{tempC:0.#}°C";
Details = $"{desc} ¡ Humidity {hum}%";
IconUrl = string.IsNullOrWhiteSpace(icon) ? null : $"https://openweathermap.org/img/wn/{icon}@2x.png";
StatusMessage = $"Weather for {data.City ?? City}";
}
catch (Exception ex)
{
StatusMessage = $"Error: {ex.Message}";
}
finally
{
IsBusy = false;
}
}
}
And the last piece here would be registering necessary services in App.xaml.cs file, which is like Program.cs file in your API projects.
123456789
.ConfigureServices((context, services) =>
{
var baseUri = new Uri("https://localhost:7121");
services.AddHttpClient<IWeatherService, WeatherService>(c => c.BaseAddress = baseUri);
services.AddTransient<MainModel>();
})
.UseNavigation(ReactiveViewModelMappings.ViewModelMappings, RegisterRoutes)
You write WinUI-style XAML once, bind it to your .NET code, and Uno Platform renders it with native controls where it makes sense or with Skia for pixel-perfect fidelity. That means the same layout, the same bindings, the same control templates, you know - no context-switching to a different UI stack per platform.
Uno Platform shines when you need to reach without rebuilding screens multiple times. Because itâs âWinUI-first,â your XAML knowledge transfers immediately: Grid, StackPanel, DependencyProperties, DataTemplates, and the whole binding story all behave as you expect.
From a product-engineering standpoint, Uno Platformâs value is in consistency and reuse. You keep a shared UI layer plus platform heads for the few places you need native APIs or platform-specific polish. You can drop into #if guards or partial classes when you must, but the bulk of the code - including your view models, navigation, HTTP calls, and styling - remains shared.
Conclusion
In this beginner tutorial, you shipped a working Weather App that runs on Web, Desktop, and Mobile.
From here, try:
⢠MVVM Navigation (add a Favorites page)
⢠Geolocation to auto-detect the city on mobile
⢠Local storage for recent searches
⢠Forecast view (list + chart)
⢠Polished theming and responsive layout
That's all from me for today.