- Silverlight XAML translates to WinUI XAML with predictable namespace and control substitutions
- Most C# code-behind and ViewModel code ports directly; the biggest change is replacing callback-based async with
async/await - The Community Toolkit DataGrid replaces the Silverlight DataGrid with minimal markup changes
- AI agents with access to the Uno Platform MCP can automate XAML translation and verify the result against the running app
- WCF and RIA Services are replaced by REST or gRPC endpoints behind a clean service interface
- You can run the migrated app in WebAssembly and on Windows from a single codebase
Who this is for: .NET developers executing a Silverlight-to-Uno Platform migration, particularly those maintaining LOB applications with data entry forms, grids, and page-based navigation.
Silverlight reached end of support in October 2021, but thousands of line-of-business applications built on it remain in production. These apps run only in Internet Explorer, depend on a browser plugin that modern browsers no longer support, and sit on a technology stack with no path forward. The code itself, however, is often sound: years of battle-tested C# and XAML representing real business logic.
This tutorial walks you through migrating a representative Silverlight LOB application to Uno Platform 6.5 and .NET 10. You will translate XAML, port ViewModels, stub the service layer, and use AI agents with the Uno Platform MCP to accelerate and verify every step. By the end, your app's main data-entry screen and list view will run in the browser via WebAssembly.
The Sample App: ExpenseTracker
Before touching any migration tooling, you need a clear picture of what you are migrating. This tutorial uses a representative Silverlight LOB pattern: an expense-reporting application called ExpenseTracker. It contains the following:
- Data entry form — a page where users submit new expense reports with fields for date, category, amount, description, and receipt attachment
- DataGrid list view — a page displaying all submitted expenses in a sortable, filterable Silverlight DataGrid
- Page navigation — a Silverlight Frame with URI-based navigation between the dashboard, entry form, and list view
- Simple dashboard — a landing page showing summary totals and recent activity
- Service layer — WCF RIA Services calls for CRUD operations against a SQL Server backend
This is the pattern that appears in the vast majority of Silverlight LOB apps: forms, grids, navigation, and service calls. If you can migrate these four elements, you can migrate almost any Silverlight business application.
The original Silverlight project structure:
ExpenseTracker.Silverlight/
Views/
DashboardPage.xaml
ExpenseEntryPage.xaml
ExpenseListPage.xaml
ViewModels/
DashboardViewModel.cs
ExpenseEntryViewModel.cs
ExpenseListViewModel.cs
Models/
Expense.cs
ExpenseCategory.cs
Services/
ExpenseRiaContext.cs (WCF RIA Services)
MainPage.xaml (Frame + navigation links)
App.xaml
Project Setup
Start by creating a new Uno Platform solution. You need the Uno Platform templates installed:
dotnet new install Uno.Templates
Then create the solution with the recommended preset, which includes Uno Extensions for dependency injection, configuration, navigation, and HTTP:
dotnet new unoapp -n ExpenseTracker --preset recommended
This generates a single-project solution targeting WebAssembly, Windows, iOS, Android, macOS, and Linux. For this tutorial, you will focus on WebAssembly and Windows.
Open the generated .csproj and confirm the following UnoFeatures are present:
<UnoFeatures>
Material;
Toolkit;
Extensions;
Hosting;
Http;
Navigation;
Mvvm;
Logging;
</UnoFeatures>
For the DataGrid, add the Windows Community Toolkit package:
<PackageReference Include="CommunityToolkit.WinUI.Controls.DataGrid" Version="8.*" />
Run a quick build to verify everything resolves:
dotnet build ExpenseTracker/ExpenseTracker.csproj
Silverlight XAML Translation
XAML migration from Silverlight to WinUI follows a set of predictable, mechanical transformations. Most layout controls (Grid, StackPanel, Border) and content controls (TextBlock, TextBox, Button) are identical between the two platforms. The changes fall into four categories: namespace updates, base class changes, control substitutions, and navigation rewiring.
Namespace Updates
Every Silverlight XAML file begins with namespace declarations that reference System.Windows. In WinUI, these become Microsoft.UI.Xaml. The core change is in how custom namespaces are declared:
| Silverlight | WinUI / Uno Platform |
|---|---|
xmlns:navigation="clr-namespace:..." | (removed; Page is a built-in type) |
xmlns:sdk="clr-namespace:..." | (removed; use CommunityToolkit namespace for DataGrid) |
xmlns:local="clr-namespace:MyApp.Views;assembly=MyApp" | xmlns:local="using:MyApp.Views" |
The pattern is straightforward: change clr-namespace to using and remove the ;assembly=... suffix.
Base Class: UserControl to Page
Silverlight views commonly use navigation:Page or UserControl as the root element. In WinUI, all navigable views use Page:
<navigation:Page
x:Class="ExpenseTracker.Views.ExpenseEntryPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:navigation="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Navigation"
Style="{StaticResource PageStyle}">
<Page
x:Class="ExpenseTracker.Views.ExpenseEntryPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctk="using:CommunityToolkit.WinUI.Controls"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
Control Substitutions
Several Silverlight Toolkit controls do not exist in WinUI but have direct equivalents:
| Silverlight Control | Uno Platform / WinUI Equivalent | Notes |
|---|---|---|
sdk:DataGrid | ctk:DataGrid (CommunityToolkit) | Nearly identical API; update namespace prefix |
sdk:DatePicker | CalendarDatePicker | Built into WinUI; richer calendar UI |
toolkit:BusyIndicator | ProgressRing | Built into WinUI; simpler API |
toolkit:Accordion | Expander | Built into WinUI; single expand/collapse |
HyperlinkButton | Button + click handler | URI navigation replaced by type-based navigation |
Before/After: The Expense Entry Form
Here is the core of the Silverlight expense entry form translated to WinUI. This is the page where users enter new expenses.
<navigation:Page x:Class="ExpenseTracker.Views.ExpenseEntryPage">
<Grid x:Name="LayoutRoot" Margin="16">
<TextBlock Text="New Expense"
Style="{StaticResource HeaderTextStyle}"/>
<toolkit:BusyIndicator IsBusy="{Binding IsBusy}">
<StackPanel>
<TextBlock Text="Date"/>
<sdk:DatePicker SelectedDate="{Binding ExpenseDate, Mode=TwoWay}"/>
<TextBlock Text="Category"/>
<ComboBox ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"/>
<TextBlock Text="Amount"/>
<TextBox Text="{Binding Amount, Mode=TwoWay}"/>
<Button Content="Submit" Command="{Binding SubmitCommand}"/>
</StackPanel>
</toolkit:BusyIndicator>
</Grid>
</navigation:Page>
<Page x:Class="ExpenseTracker.Views.ExpenseEntryPage"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid x:Name="LayoutRoot" Margin="16">
<TextBlock Text="New Expense"
Style="{StaticResource TitleTextBlockStyle}"/>
<!-- ProgressRing replaces BusyIndicator -->
<ProgressRing IsActive="{Binding IsBusy}"
HorizontalAlignment="Center"/>
<StackPanel>
<TextBlock Text="Date"/>
<!-- CalendarDatePicker replaces DatePicker -->
<CalendarDatePicker Date="{Binding ExpenseDate, Mode=TwoWay}"/>
<TextBlock Text="Category"/>
<ComboBox ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"/>
<TextBlock Text="Amount"/>
<TextBox Text="{Binding Amount, Mode=TwoWay}"/>
<Button Content="Submit" Command="{Binding SubmitCommand}"
Style="{StaticResource AccentButtonStyle}"/>
</StackPanel>
</Grid>
</Page>
Key changes in this translation:
navigation:PagebecomesPage(no namespace prefix needed)toolkit:BusyIndicatorbecomesProgressRingwithIsActiveinstead ofIsBusy; the wrapper pattern is removedsdk:DatePickerbecomesCalendarDatePickerwithDateinstead ofSelectedDateHeaderTextStylebecomesTitleTextBlockStyle(a built-in WinUI style)- Grid, StackPanel, TextBlock, TextBox, ComboBox, and Button are virtually unchanged
Code-Behind and ViewModel Migration
The good news for Silverlight developers: most C# code migrates directly. The INotifyPropertyChanged interface, data binding, and command patterns are the same in WinUI as they are in Silverlight. The primary change is in asynchronous patterns.
Async Pattern: Callbacks to async/await
Silverlight predates the async/await keywords in C#. Asynchronous operations used event-based callbacks with Completed event handlers. Modern .NET uses Task-based async.
public class ExpenseEntryViewModel : INotifyPropertyChanged
{
private readonly ExpenseRiaContext _context;
public ExpenseEntryViewModel()
{
_context = new ExpenseRiaContext();
SubmitCommand = new RelayCommand(SubmitExpense);
LoadCategories();
}
private void LoadCategories()
{
IsBusy = true;
var operation = _context.Load(_context.GetCategoriesQuery());
operation.Completed += (s, e) =>
{
if (operation.HasError)
{
ErrorMessage = operation.Error.Message;
operation.MarkErrorAsHandled();
}
else
{
Categories = new ObservableCollection<ExpenseCategory>(
operation.Entities);
}
IsBusy = false;
};
}
}
public class ExpenseEntryViewModel : INotifyPropertyChanged
{
private readonly IExpenseService _expenseService;
public ExpenseEntryViewModel(IExpenseService expenseService)
{
_expenseService = expenseService;
SubmitCommand = new AsyncRelayCommand(SubmitExpenseAsync);
_ = LoadCategoriesAsync();
}
private async Task LoadCategoriesAsync()
{
try
{
IsBusy = true;
var categories = await _expenseService.GetCategoriesAsync();
Categories = new ObservableCollection<ExpenseCategory>(categories);
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
}
finally
{
IsBusy = false;
}
}
}
Key changes:
- Constructor injection replaces direct context creation. The RIA context is replaced by an
IExpenseServiceinterface. - Completed callbacks become
async Taskmethods withtry/catch/finally. - RelayCommand becomes
AsyncRelayCommand(from CommunityToolkit.Mvvm). - INotifyPropertyChanged is identical. No changes required.
What Ports Directly
- All
INotifyPropertyChangedproperty implementations ObservableCollection<T>usage- Data binding expressions in XAML (both
{Binding}and{x:Bind}) - Model classes (POCOs)
- Validation logic and business rules in ViewModels
Service Layer Modernization
Silverlight applications typically depend on WCF or WCF RIA Services for data access. Neither technology has a path forward in modern .NET. The replacement options are REST APIs (via HttpClient or a typed client library like Refit) or gRPC.
For this tutorial, you will define a clean service interface and provide a mock implementation. The full service layer migration is covered in depth in the next article in this series.
The Service Interface
public interface IExpenseService
{
Task<IEnumerable<ExpenseCategory>> GetCategoriesAsync();
Task<IEnumerable<Expense>> GetExpensesAsync(
DateTime? from = null, DateTime? to = null);
Task<Expense> GetExpenseByIdAsync(int id);
Task CreateExpenseAsync(Expense expense);
Task UpdateExpenseAsync(Expense expense);
Task DeleteExpenseAsync(int id);
Task<ExpenseSummary> GetDashboardSummaryAsync();
}
This interface mirrors the operations your Silverlight RIA context provided, but expressed as clean async methods with no framework dependency.
Register the service in your host builder configuration:
services.AddSingleton<IExpenseService, MockExpenseService>();
When you are ready to connect to a real backend, you swap the registration to a RestExpenseService or GrpcExpenseService implementation. The ViewModels and XAML remain untouched.
Navigation: Silverlight Frame to WinUI Frame
Silverlight uses URI-based navigation through a Frame control with a UriMapper. WinUI uses type-based navigation. This is one of the areas where a direct copy-paste is not possible; you need to rewrite the navigation structure.
<navigation:Frame x:Name="ContentFrame" Source="/Home">
<navigation:Frame.UriMapper>
<uriMapper:UriMapper>
<uriMapper:UriMapping Uri=""
MappedUri="/Views/DashboardPage.xaml"/>
<uriMapper:UriMapping Uri="/{pageName}"
MappedUri="/Views/{pageName}.xaml"/>
</uriMapper:UriMapper>
</navigation:Frame.UriMapper>
</navigation:Frame>
<NavigationView x:Name="NavView"
IsBackButtonVisible="Auto"
ItemInvoked="NavView_ItemInvoked">
<NavigationView.MenuItems>
<NavigationViewItem Content="Dashboard" Tag="DashboardPage">
<NavigationViewItem.Icon>
<SymbolIcon Symbol="Home"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="New Expense" Tag="ExpenseEntryPage"/>
<NavigationViewItem Content="Expense List" Tag="ExpenseListPage"/>
</NavigationView.MenuItems>
<Frame x:Name="ContentFrame"/>
</NavigationView>
The code-behind for navigation changes from URI strings to type references:
private void NavView_ItemInvoked(NavigationView sender,
NavigationViewItemInvokedEventArgs args)
{
var tag = (args.InvokedItemContainer as NavigationViewItem)?.Tag?.ToString();
Type pageType = tag switch
{
"DashboardPage" => typeof(DashboardPage),
"ExpenseEntryPage" => typeof(ExpenseEntryPage),
"ExpenseListPage" => typeof(ExpenseListPage),
_ => typeof(DashboardPage)
};
ContentFrame.Navigate(pageType);
}
This pattern gives you a modern sidebar navigation that works identically on WebAssembly and Windows. The NavigationView adapts automatically to different screen sizes, collapsing to a hamburger menu on narrow viewports.
Agent-Assisted Translation
With the manual patterns established, you can accelerate the remaining migration work by feeding Silverlight pages to an AI agent that has access to the Uno Platform MCP.
How It Works
The Uno Platform provides two MCP (Model Context Protocol) servers. The Uno MCP gives agents access to up-to-date documentation, control mappings, and API references. The App MCP lets agents interact with a running Uno Platform application: taking screenshots, inspecting the visual tree, clicking controls, and reading data context values.
When you provide a Silverlight XAML file to the AI agent, the agent can:
- Parse the Silverlight XAML and identify every control, namespace, and binding
- Query the Uno Platform MCP documentation to find the WinUI equivalent for each control
- Generate the translated WinUI XAML with correct namespaces, control names, and property mappings
- Apply Uno Platform best practices (theming, responsive layout, accessibility)
Example Agent Prompt
"Here is a Silverlight XAML page from our ExpenseTracker app. Translate it to Uno Platform / WinUI XAML. Use the Uno Platform documentation to find the correct control equivalents. Replace the Silverlight DataGrid with the CommunityToolkit DataGrid. Replace BusyIndicator with ProgressRing. Use CalendarDatePicker instead of the Silverlight DatePicker. Keep all data bindings intact."
The agent uses uno_platform_docs_search to look up documentation for each control, confirms the correct namespace and property names, and produces the translated XAML. This is especially valuable for pages with many controls, where manual lookup for each substitution is tedious.
Translating the Expense List Page
The expense list page is a good candidate for agent-assisted translation because it contains the DataGrid, which requires careful namespace and column definition updates.
<sdk:DataGrid x:Name="ExpenseGrid"
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding Expenses}">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Header="Date"
Binding="{Binding Date, StringFormat=\{0:d\}}"/>
<sdk:DataGridTextColumn Header="Category"
Binding="{Binding CategoryName}"/>
<sdk:DataGridTextColumn Header="Amount"
Binding="{Binding Amount, StringFormat=\{0:C\}}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
<ctk:DataGrid x:Name="ExpenseGrid"
AutoGenerateColumns="False"
IsReadOnly="True"
ItemsSource="{Binding Expenses}">
<ctk:DataGrid.Columns>
<ctk:DataGridTextColumn Header="Date"
Binding="{Binding Date, Converter={StaticResource DateFormatConverter}}"/>
<ctk:DataGridTextColumn Header="Category"
Binding="{Binding CategoryName}"/>
<ctk:DataGridTextColumn Header="Amount"
Binding="{Binding Amount, Converter={StaticResource CurrencyConverter}}"/>
</ctk:DataGrid.Columns>
</ctk:DataGrid>
The structure is nearly identical. The namespace prefix changes from sdk: to ctk:, and StringFormat in bindings is replaced by value converters, since StringFormat support differs in WinUI bindings.
Verification with App MCP
Once you have translated the XAML and ported the ViewModels, you need to verify that the app renders correctly and the data bindings are functioning. This is where the Uno Platform App MCP proves its value.
Running the App on WebAssembly
dotnet run --project ExpenseTracker/ExpenseTracker.csproj -f net10.0-browserwasm
The app launches in your default browser. The App MCP connects to the running instance automatically.
Visual Tree Inspection
Ask the AI agent to verify the visual tree:
"Take a visual tree snapshot of the running app and confirm that the ExpenseEntryPage contains a CalendarDatePicker, a ComboBox, two TextBox controls, and a submit Button."
The agent calls uno_app_visualtree_snapshot, which returns a structured representation of every element in the visual tree. The agent can confirm that the controls are present, bound correctly, and connected to their commands.
Screenshot Verification
"Take a screenshot of the running app and describe what you see. Compare it to the expected layout: a form with date picker, category dropdown, amount field, description field, and a submit button."
The agent calls uno_app_get_screenshot and receives an image of the running application. It can describe the layout, identify visual issues, and suggest fixes.
Data Context Verification
"Get the DataContext of the main content area and confirm that the ExpenseEntryViewModel is attached with the expected properties."
The agent calls uno_app_get_element_datacontext on the root element and verifies that the ViewModel is properly bound, the categories are loaded, and the default values are set.
This agent-driven verification cycle replaces what would otherwise be manual clicking, visual inspection, and debugging. It is particularly powerful during migration because you can verify dozens of pages rapidly.
Side-by-Side Comparison
The final validation step is to compare the original Silverlight app against the live Uno Platform WebAssembly app. What to check:
- Layout fidelity — Do the forms, grids, and navigation structure match the original? Exact pixel matching is not the goal; structural equivalence is.
- Data flow — Enter an expense in the new app. Confirm it appears in the list view. Verify that navigation between pages preserves state.
- Control behavior — Open the CalendarDatePicker and confirm date selection. Sort the DataGrid columns. Expand/collapse any Expander controls.
- Responsive behavior — Resize the browser window. The Uno Platform app should adapt gracefully, which is an improvement over the fixed-layout Silverlight original.
The migrated app now runs in any modern browser via WebAssembly, with no plugin required. It also runs natively on Windows from the same codebase. That is the core value of this migration: from a locked-in, end-of-life platform to a cross-platform, standards-based application with a path forward on .NET 10 and beyond.
Migration Checklist
Use this checklist to track your progress on each Silverlight page:
clr-namespace to using, remove assembly references)navigation:Page or UserControl to PageStringFormat with converters where needed)What You Ship
At the end of this tutorial, you have:
- An Uno Platform 6.5 solution targeting WebAssembly and Windows
- The ExpenseTracker's main data-entry form migrated from Silverlight XAML to WinUI XAML
- The expense list view with a CommunityToolkit DataGrid replacing the Silverlight DataGrid
- ViewModels ported from callback-based async to modern async/await
- A clean service interface with a mock implementation, ready for real backend integration
- NavigationView-based navigation replacing Silverlight's URI-based Frame navigation
- Agent-verified visual tree and screenshot confirmation that the app renders correctly
The next article in this series covers the service layer in depth: replacing WCF RIA Services with REST and gRPC endpoints, integrating with Uno Extensions HTTP, and connecting the mock service to a live backend.
FAQ
Can I reuse my Silverlight styles and themes directly?
Not directly. Silverlight styles use System.Windows types, while WinUI uses Microsoft.UI.Xaml types. The structure of Style and ControlTemplate definitions is very similar, so most styles can be migrated by updating type references. Consider adopting the built-in Fluent or Material theme from Uno Platform instead.
What happens to my WCF RIA Services domain context?
WCF RIA Services are not supported in modern .NET. You need to replace them with REST APIs, gRPC services, or another modern service technology. The recommended approach is to define a clean service interface and implement it against your new backend. If you need to maintain the existing WCF backend temporarily, consider CoreWCF as a bridge.
Does the CommunityToolkit DataGrid support all Silverlight DataGrid features?
The CommunityToolkit DataGrid supports column sorting, row selection, cell editing, and custom column templates. Some advanced features like row grouping may require additional work. Check the Windows Community Toolkit documentation for the full feature list.
How do I handle Silverlight's IsolatedStorage?
In Uno Platform, use ApplicationData.Current.LocalFolder for file-based storage or ApplicationData.Current.LocalSettings for key-value pairs. On WebAssembly, these map to browser storage APIs.
Can the AI agent translate my entire Silverlight solution automatically?
The agent works best page by page. Feed it one XAML file at a time along with the corresponding ViewModel. It will produce accurate translations when it can reference the Uno Platform documentation via MCP. Complex pages with deeply nested custom controls may require manual intervention.
Will my app look exactly like the Silverlight original?
The layout structure will match, but the visual appearance will use modern WinUI styling (Fluent Design or Material). This is generally an improvement. If you need precise visual matching, you can create custom styles, but most teams use migration as an opportunity to modernize the visual design.
What about Silverlight animations and Storyboards?
WinUI supports Storyboard animations with the same syntax. Most Silverlight animations can be copied over with namespace updates. For smoother, more modern animations, consider using the Composition API or Lottie animations, both supported by Uno Platform.
- Uno Platform Silverlight Migration Guide →
- Silverlight Navigation Migration →
- Silverlight XAML and Styles Migration →
- Uno Platform MCP Tools Documentation →
- App MCP — Prompting in Your App →
- dotnet new Templates for Uno Platform →
- Windows Community Toolkit DataGrid →
- CalendarDatePicker Implementation →
- ProgressRing Implementation →
- Expander Implementation →
- WPF to Web Migration — Deprecated Technology (WCF) →
Subscribe to Our Blog
Subscribe via RSS
Back to Top