Claude
Skill
dotnet-uno-platform
Build cross-platform .NET applications with Uno Platform targeting WebAssembly, iOS, Android, macOS, Linux, and Windows from a single XAML/C# codebase.
Virus-scanned
Reviewed automatically before listing.
Download
postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-uno-platform-bfa4ebd.zip · 5 KB
Install
skills CLI
npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-uno-platform
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
Git
git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Uno Platform
Trigger On
- building cross-platform apps from a single C# and XAML codebase
- targeting WebAssembly, iOS, Android, macOS, Linux, and Windows simultaneously
- migrating WPF or UWP applications to cross-platform
- implementing pixel-perfect UI across all platforms
- using WinUI/UWP APIs on non-Windows platforms
Documentation
- Uno Platform Overview
- Getting Started
- Using Uno Platform with WinUI
- Platform-Specific Code
- MVUX Pattern
References
See detailed examples in the references/ folder:
patterns.md— MVUX, XAML, navigation, and performance patterns
Platform Support
| Platform | Rendering | Notes |
|---|---|---|
| Windows | WinUI 3 | Native Windows App SDK |
| WebAssembly | Skia/Canvas | Runs in browser |
| iOS | Skia/Metal | Native iOS app |
| Android | Skia/OpenGL | Native Android app |
| macOS | Skia/Metal | Mac Catalyst or AppKit |
| Linux | Skia/X11 | GTK or Framebuffer |
Workflow
- Choose the right template — Uno Platform offers various templates for different scenarios
- Understand rendering modes — Skia vs native rendering affects performance and fidelity
- Apply MVVM or MVUX patterns — keep views dumb, logic in ViewModels
- Handle platform differences — use conditional XAML or partial classes
- Test on all target platforms — behavior varies across platforms
Project Structure
MyApp/
├── MyApp/ # Shared code
│ ├── App.xaml # Application entry
│ ├── MainPage.xaml # Main page
│ ├── Presentation/ # ViewModels (MVUX/MVVM)
│ ├── Business/ # Business logic
│ └── Services/ # Platform services
├── MyApp.Wasm/ # WebAssembly head
├── MyApp.Mobile/ # iOS and Android head
├── MyApp.Skia.Gtk/ # Linux head
├── MyApp.Skia.WPF/ # Windows Skia head
└── MyApp.Windows/ # Native WinUI head
MVUX Pattern (Uno Extensions)
Model Definition
public partial record MainModel
{
public IListFeed<TodoItem> Items => ListFeed.Async(LoadItems);
private async ValueTask<IImmutableList<TodoItem>> LoadItems(CancellationToken ct)
{
var items = await _todoService.GetAllAsync(ct);
return items.ToImmutableList();
}
}
View Binding with FeedView
<Page xmlns:uen="using:Uno.Extensions.Navigation.UI">
<utu:FeedView Source="{Binding Items}">
<utu:FeedView.ValueTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:TodoItem">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</utu:FeedView.ValueTemplate>
<utu:FeedView.ProgressTemplate>
<DataTemplate>
<ProgressRing IsActive="True" />
</DataTemplate>
</utu:FeedView.ProgressTemplate>
</utu:FeedView>
</Page>
Classic MVVM with MVVM Toolkit
ViewModel
public partial class MainViewModel(ITodoService todoService) : ObservableObject
{
[ObservableProperty]
private ObservableCollection<TodoItem> _items = [];
[ObservableProperty]
private bool _isLoading;
[RelayCommand]
private async Task LoadItemsAsync()
{
IsLoading = true;
try
{
var items = await todoService.GetAllAsync();
Items = new ObservableCollection<TodoItem>(items);
}
finally
{
IsLoading = false;
}
}
}
Platform-Specific Code
Conditional XAML
<TextBlock Text="Welcome"
xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:android="http://uno.ui/android"
xmlns:ios="http://uno.ui/ios"
xmlns:wasm="http://uno.ui/wasm">
<win:TextBlock.Foreground>
<SolidColorBrush Color="Blue" />
</win:TextBlock.Foreground>
<android:TextBlock.Foreground>
<SolidColorBrush Color="Green" />
</android:TextBlock.Foreground>
</TextBlock>
Partial Classes
// Services/DeviceService.cs (shared)
public partial class DeviceService
{
public partial string GetDeviceInfo();
}
// Services/DeviceService.wasm.cs
public partial class DeviceService
{
public partial string GetDeviceInfo() => "WebAssembly";
}
// Services/DeviceService.Android.cs
public partial class DeviceService
{
public partial string GetDeviceInfo() =>
$"Android {Android.OS.Build.VERSION.Release}";
}
Hot Reload and Development
// Enable Hot Reload in App.xaml.cs
public App()
{
this.InitializeComponent();
#if DEBUG
// Enable Hot Reload
this.EnableHotReload();
#endif
}
Anti-Patterns to Avoid
| Anti-Pattern | Why It's Bad | Better Approach |
|---|---|---|
| Platform code in shared | Breaks compilation | Use partial classes or #if |
| Ignoring Skia differences | Visual bugs | Test on all renderers |
| WPF assumptions | Not all APIs exist | Check Uno API coverage |
| Heavy XAML | Slow on WASM | Virtualize, simplify |
| Synchronous loading | UI freezes | Always use async |
Performance Best Practices
Use virtualized lists:
<ListView ItemsSource="{Binding Items}" VirtualizingPanel.VirtualizationMode="Recycling" />Lazy load resources:
// Load images on demand var image = await ImageSource.LoadFromUriAsync(uri);Minimize XAML complexity:
- Avoid deep nesting
- Use compiled bindings (
x:Bindwhere supported) - Consider Skia-specific optimizations
WebAssembly specific:
- Minimize interop calls
- Use
InvokeAsyncfor JS interop - Consider AOT compilation for performance
Uno Extensions
// Use Uno.Extensions for enhanced patterns
var builder = this.CreateBuilder(args)
.Configure(host => host
.UseConfiguration()
.UseLocalization()
.UseNavigation()
.UseMvux()
.ConfigureServices(services =>
{
services.AddSingleton<ITodoService, TodoService>();
}));
Deliver
- single codebase running on web, mobile, and desktop
- consistent UI/UX across all platforms
- platform-specific optimizations where needed
- MVVM or MVUX patterns for testability
Validate
- app builds and runs on all target platforms
- platform-specific features work correctly
- performance is acceptable on WebAssembly
- Hot Reload works during development
- no WPF/UWP-only APIs are used without fallbacks
Files (dotnet-skills)
-
references
-
patterns.md 10.5 KB
# Uno Platform Patterns Reference ## MVUX Patterns ### Basic Model with Feed ```csharp public partial record ProductsModel(IProductService Products) { public IListFeed<Product> Items => ListFeed.Async(Products.GetAllAsync); } ``` ### Model with State ```csharp public partial record ProductDetailModel(IProductService Products) { public IState<int> ProductId => State<int>.Empty(this); public IFeed<Product> Product => ProductId .SelectAsync(async (id, ct) => await Products.GetAsync(id, ct)); } ``` ### Model with Commands ```csharp public partial record OrderModel(IOrderService Orders) { public IState<string> CustomerName => State<string>.Value(this, () => string.Empty); public IState<string> ProductId => State<string>.Value(this, () => string.Empty); public IState<int> Quantity => State<int>.Value(this, () => 1); public async ValueTask SubmitOrder(CancellationToken ct) { var order = new Order { CustomerName = await CustomerName, ProductId = await ProductId, Quantity = await Quantity }; await Orders.CreateAsync(order, ct); } } ``` ### Pagination Pattern ```csharp public partial record PaginatedModel(IProductService Products) { public IState<int> CurrentPage => State<int>.Value(this, () => 0); public IState<int> PageSize => State<int>.Value(this, () => 20); public IListFeed<Product> Items => CurrentPage.CombineWith(PageSize) .SelectAsync(async (state, ct) => { var (page, size) = state; return await Products.GetPageAsync(page, size, ct); }) .AsListFeed(); public async ValueTask NextPage() { await CurrentPage.Update(p => p + 1); } public async ValueTask PreviousPage() { await CurrentPage.Update(p => Math.Max(0, p - 1)); } } ``` ## XAML Patterns ### Responsive Layout ```xml <Grid> <VisualStateManager.VisualStateGroups> <VisualStateGroup> <VisualState x:Name="Narrow"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="0" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="MainContent.Orientation" Value="Vertical" /> <Setter Target="SidePanel.Visibility" Value="Collapsed" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Wide"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="800" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="MainContent.Orientation" Value="Horizontal" /> <Setter Target="SidePanel.Visibility" Value="Visible" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <StackPanel x:Name="MainContent"> <Grid x:Name="SidePanel" Width="300" /> <Grid x:Name="ContentPanel" /> </StackPanel> </Grid> ``` ### Platform-Specific Styling ```xml <Style TargetType="Button" x:Key="PlatformButton"> <Setter Property="Padding" Value="16,8" /> <!-- Windows-specific --> <win:Setter Property="CornerRadius" Value="4" /> <!-- Android-specific --> <android:Setter Property="Background" Value="{StaticResource MaterialPrimary}" /> <!-- iOS-specific --> <ios:Setter Property="Background" Value="{StaticResource iOSBlue}" /> </Style> ``` ### FeedView with All States ```xml <utu:FeedView Source="{Binding Products}"> <!-- Loading state --> <utu:FeedView.ProgressTemplate> <DataTemplate> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <ProgressRing IsActive="True" Width="40" Height="40" /> <TextBlock Text="Loading..." Margin="0,8,0,0" /> </StackPanel> </DataTemplate> </utu:FeedView.ProgressTemplate> <!-- Error state --> <utu:FeedView.ErrorTemplate> <DataTemplate> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <SymbolIcon Symbol="Warning" /> <TextBlock Text="{Binding Message}" Margin="0,8,0,0" /> <Button Content="Retry" Command="{Binding RetryCommand}" /> </StackPanel> </DataTemplate> </utu:FeedView.ErrorTemplate> <!-- Empty state --> <utu:FeedView.NoneTemplate> <DataTemplate> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <SymbolIcon Symbol="List" /> <TextBlock Text="No items found" Margin="0,8,0,0" /> </StackPanel> </DataTemplate> </utu:FeedView.NoneTemplate> <!-- Success state --> <utu:FeedView.ValueTemplate> <DataTemplate> <ListView ItemsSource="{Binding}"> <ListView.ItemTemplate> <DataTemplate x:DataType="local:Product"> <Grid Padding="12"> <TextBlock Text="{x:Bind Name}" /> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> </DataTemplate> </utu:FeedView.ValueTemplate> </utu:FeedView> ``` ## Platform Service Patterns ### Abstracted Platform Service ```csharp // Shared interface public interface IPlatformService { string GetPlatformName(); Task<bool> RequestPermissionAsync(string permission); Task ShareAsync(string text, string? title = null); } // Shared implementation with partial methods public partial class PlatformService : IPlatformService { public partial string GetPlatformName(); public partial Task<bool> RequestPermissionAsync(string permission); public partial Task ShareAsync(string text, string? title = null); } ``` ```csharp // Platforms/Android/PlatformService.cs public partial class PlatformService { public partial string GetPlatformName() => "Android"; public async partial Task<bool> RequestPermissionAsync(string permission) { var status = await Permissions.RequestAsync<Permissions.StorageRead>(); return status == PermissionStatus.Granted; } public async partial Task ShareAsync(string text, string? title) { var intent = new Intent(Intent.ActionSend); intent.SetType("text/plain"); intent.PutExtra(Intent.ExtraText, text); Platform.CurrentActivity.StartActivity(Intent.CreateChooser(intent, title)); } } ``` ```csharp // Platforms/iOS/PlatformService.cs public partial class PlatformService { public partial string GetPlatformName() => "iOS"; public async partial Task<bool> RequestPermissionAsync(string permission) { // iOS permission handling } public async partial Task ShareAsync(string text, string? title) { var controller = new UIActivityViewController( new NSObject[] { new NSString(text) }, null); await UIApplication.SharedApplication.KeyWindow .RootViewController.PresentViewControllerAsync(controller, true); } } ``` ## Navigation Patterns ### Region-Based Navigation ```csharp // App.xaml.cs public App() { this.InitializeComponent(); var builder = this.CreateBuilder(args) .Configure(host => host .UseNavigation(RegisterRoutes)); } void RegisterRoutes(IViewRegistry views, IRouteRegistry routes) { views.Register( new ViewMap<MainPage, MainModel>(), new ViewMap<ProductPage, ProductModel>(), new ViewMap<SettingsPage, SettingsModel>() ); routes.Register( new RouteMap("", View: views.FindByViewModel<MainModel>()), new RouteMap("Product", View: views.FindByViewModel<ProductModel>()), new RouteMap("Settings", View: views.FindByViewModel<SettingsModel>()) ); } ``` ### Navigation with Parameters ```csharp public partial record ProductListModel(INavigator Navigator) { public async ValueTask NavigateToProduct(Product product) { await Navigator.NavigateViewModelAsync<ProductModel>( this, data: new { ProductId = product.Id }); } } public partial record ProductModel(INavigator Navigator) { // Receives ProductId from navigation data public IState<int> ProductId => State<int>.Value(this, () => 0); } ``` ## Performance Patterns ### Virtualized List ```xml <ListView ItemsSource="{Binding Items}" VirtualizingStackPanel.VirtualizationMode="Recycling" VirtualizingStackPanel.IsVirtualizing="True"> <ListView.ItemsPanel> <ItemsPanelTemplate> <ItemsStackPanel Orientation="Vertical" /> </ItemsPanelTemplate> </ListView.ItemsPanel> </ListView> ``` ### Deferred Loading ```xml <Grid> <!-- Load expensive content only when visible --> <Grid x:Name="ExpensiveContent" x:Load="{x:Bind ViewModel.ShowDetails, Mode=OneWay}"> <local:ExpensiveUserControl /> </Grid> </Grid> ``` ### Image Optimization ```csharp // Use appropriate image sizes per platform public static ImageSource GetOptimizedImage(string basePath) { #if __ANDROID__ var density = Android.Content.Res.Resources.System.DisplayMetrics.Density; var suffix = density switch { < 1.5f => "mdpi", < 2.0f => "hdpi", < 3.0f => "xhdpi", _ => "xxhdpi" }; return ImageSource.FromFile($"{basePath}_{suffix}.png"); #elif __IOS__ return ImageSource.FromFile($"{basePath}@2x.png"); #else return ImageSource.FromFile($"{basePath}.png"); #endif } ``` ## Theme Patterns ### Light/Dark Theme Support ```csharp public class ThemeService : IThemeService { public void SetTheme(AppTheme theme) { var resources = Application.Current.Resources; var mergedDictionaries = resources.MergedDictionaries; mergedDictionaries.Clear(); mergedDictionaries.Add(theme switch { AppTheme.Light => new LightTheme(), AppTheme.Dark => new DarkTheme(), _ => new SystemTheme() }); } } ``` ```xml <!-- LightTheme.xaml --> <ResourceDictionary> <Color x:Key="BackgroundColor">#FFFFFF</Color> <Color x:Key="TextColor">#000000</Color> <Color x:Key="AccentColor">#0078D4</Color> </ResourceDictionary> <!-- DarkTheme.xaml --> <ResourceDictionary> <Color x:Key="BackgroundColor">#1E1E1E</Color> <Color x:Key="TextColor">#FFFFFF</Color> <Color x:Key="AccentColor">#0078D4</Color> </ResourceDictionary> ```
-
-
SKILL.md 7.4 KB
--- name: dotnet-uno-platform version: "1.0.0" category: "Cross-Platform UI" description: "Build cross-platform .NET applications with Uno Platform targeting WebAssembly, iOS, Android, macOS, Linux, and Windows from a single XAML/C# codebase." compatibility: "Requires Uno Platform SDK and workloads for target platforms." --- # Uno Platform ## Trigger On - building cross-platform apps from a single C# and XAML codebase - targeting WebAssembly, iOS, Android, macOS, Linux, and Windows simultaneously - migrating WPF or UWP applications to cross-platform - implementing pixel-perfect UI across all platforms - using WinUI/UWP APIs on non-Windows platforms ## Documentation - [Uno Platform Overview](https://platform.uno/docs/articles/intro.html) - [Getting Started](https://platform.uno/docs/articles/getting-started.html) - [Using Uno Platform with WinUI](https://platform.uno/docs/articles/winui-doc-links.html) - [Platform-Specific Code](https://platform.uno/docs/articles/platform-specific-xaml.html) - [MVUX Pattern](https://platform.uno/docs/articles/external/uno.extensions/doc/Overview/Mvux/Overview.html) ## References See detailed examples in the `references/` folder: - [`patterns.md`](references/patterns.md) — MVUX, XAML, navigation, and performance patterns ## Platform Support | Platform | Rendering | Notes | |----------|-----------|-------| | Windows | WinUI 3 | Native Windows App SDK | | WebAssembly | Skia/Canvas | Runs in browser | | iOS | Skia/Metal | Native iOS app | | Android | Skia/OpenGL | Native Android app | | macOS | Skia/Metal | Mac Catalyst or AppKit | | Linux | Skia/X11 | GTK or Framebuffer | ## Workflow 1. **Choose the right template** — Uno Platform offers various templates for different scenarios 2. **Understand rendering modes** — Skia vs native rendering affects performance and fidelity 3. **Apply MVVM or MVUX patterns** — keep views dumb, logic in ViewModels 4. **Handle platform differences** — use conditional XAML or partial classes 5. **Test on all target platforms** — behavior varies across platforms ## Project Structure ``` MyApp/ ├── MyApp/ # Shared code │ ├── App.xaml # Application entry │ ├── MainPage.xaml # Main page │ ├── Presentation/ # ViewModels (MVUX/MVVM) │ ├── Business/ # Business logic │ └── Services/ # Platform services ├── MyApp.Wasm/ # WebAssembly head ├── MyApp.Mobile/ # iOS and Android head ├── MyApp.Skia.Gtk/ # Linux head ├── MyApp.Skia.WPF/ # Windows Skia head └── MyApp.Windows/ # Native WinUI head ``` ## MVUX Pattern (Uno Extensions) ### Model Definition ```csharp public partial record MainModel { public IListFeed<TodoItem> Items => ListFeed.Async(LoadItems); private async ValueTask<IImmutableList<TodoItem>> LoadItems(CancellationToken ct) { var items = await _todoService.GetAllAsync(ct); return items.ToImmutableList(); } } ``` ### View Binding with FeedView ```xml <Page xmlns:uen="using:Uno.Extensions.Navigation.UI"> <utu:FeedView Source="{Binding Items}"> <utu:FeedView.ValueTemplate> <DataTemplate> <ListView ItemsSource="{Binding}"> <ListView.ItemTemplate> <DataTemplate x:DataType="local:TodoItem"> <TextBlock Text="{Binding Title}" /> </DataTemplate> </ListView.ItemTemplate> </ListView> </DataTemplate> </utu:FeedView.ValueTemplate> <utu:FeedView.ProgressTemplate> <DataTemplate> <ProgressRing IsActive="True" /> </DataTemplate> </utu:FeedView.ProgressTemplate> </utu:FeedView> </Page> ``` ## Classic MVVM with MVVM Toolkit ### ViewModel ```csharp public partial class MainViewModel(ITodoService todoService) : ObservableObject { [ObservableProperty] private ObservableCollection<TodoItem> _items = []; [ObservableProperty] private bool _isLoading; [RelayCommand] private async Task LoadItemsAsync() { IsLoading = true; try { var items = await todoService.GetAllAsync(); Items = new ObservableCollection<TodoItem>(items); } finally { IsLoading = false; } } } ``` ## Platform-Specific Code ### Conditional XAML ```xml <TextBlock Text="Welcome" xmlns:win="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:android="http://uno.ui/android" xmlns:ios="http://uno.ui/ios" xmlns:wasm="http://uno.ui/wasm"> <win:TextBlock.Foreground> <SolidColorBrush Color="Blue" /> </win:TextBlock.Foreground> <android:TextBlock.Foreground> <SolidColorBrush Color="Green" /> </android:TextBlock.Foreground> </TextBlock> ``` ### Partial Classes ```csharp // Services/DeviceService.cs (shared) public partial class DeviceService { public partial string GetDeviceInfo(); } // Services/DeviceService.wasm.cs public partial class DeviceService { public partial string GetDeviceInfo() => "WebAssembly"; } // Services/DeviceService.Android.cs public partial class DeviceService { public partial string GetDeviceInfo() => $"Android {Android.OS.Build.VERSION.Release}"; } ``` ## Hot Reload and Development ```csharp // Enable Hot Reload in App.xaml.cs public App() { this.InitializeComponent(); #if DEBUG // Enable Hot Reload this.EnableHotReload(); #endif } ``` ## Anti-Patterns to Avoid | Anti-Pattern | Why It's Bad | Better Approach | |--------------|--------------|-----------------| | Platform code in shared | Breaks compilation | Use partial classes or #if | | Ignoring Skia differences | Visual bugs | Test on all renderers | | WPF assumptions | Not all APIs exist | Check Uno API coverage | | Heavy XAML | Slow on WASM | Virtualize, simplify | | Synchronous loading | UI freezes | Always use async | ## Performance Best Practices 1. **Use virtualized lists:** ```xml <ListView ItemsSource="{Binding Items}" VirtualizingPanel.VirtualizationMode="Recycling" /> ``` 2. **Lazy load resources:** ```csharp // Load images on demand var image = await ImageSource.LoadFromUriAsync(uri); ``` 3. **Minimize XAML complexity:** - Avoid deep nesting - Use compiled bindings (`x:Bind` where supported) - Consider Skia-specific optimizations 4. **WebAssembly specific:** - Minimize interop calls - Use `InvokeAsync` for JS interop - Consider AOT compilation for performance ## Uno Extensions ```csharp // Use Uno.Extensions for enhanced patterns var builder = this.CreateBuilder(args) .Configure(host => host .UseConfiguration() .UseLocalization() .UseNavigation() .UseMvux() .ConfigureServices(services => { services.AddSingleton<ITodoService, TodoService>(); })); ``` ## Deliver - single codebase running on web, mobile, and desktop - consistent UI/UX across all platforms - platform-specific optimizations where needed - MVVM or MVUX patterns for testability ## Validate - app builds and runs on all target platforms - platform-specific features work correctly - performance is acceptable on WebAssembly - Hot Reload works during development - no WPF/UWP-only APIs are used without fallbacks
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.