Tutorial Silverlight Migration
Key Takeaways
  • 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.

Sample App

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:

Project
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
Setup

Project Setup

Start by creating a new Uno Platform solution. You need the Uno Platform templates installed:

Terminal
dotnet new install Uno.Templates

Then create the solution with the recommended preset, which includes Uno Extensions for dependency injection, configuration, navigation, and HTTP:

Terminal
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:

XML
<UnoFeatures>
  Material;
  Toolkit;
  Extensions;
  Hosting;
  Http;
  Navigation;
  Mvvm;
  Logging;
</UnoFeatures>

For the DataGrid, add the Windows Community Toolkit package:

XML
<PackageReference Include="CommunityToolkit.WinUI.Controls.DataGrid" Version="8.*" />

Run a quick build to verify everything resolves:

Terminal
dotnet build ExpenseTracker/ExpenseTracker.csproj
XAML

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:

SilverlightWinUI / 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:

Silverlight
<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}">
Uno Platform
<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 ControlUno Platform / WinUI EquivalentNotes
sdk:DataGridctk:DataGrid (CommunityToolkit)Nearly identical API; update namespace prefix
sdk:DatePickerCalendarDatePickerBuilt into WinUI; richer calendar UI
toolkit:BusyIndicatorProgressRingBuilt into WinUI; simpler API
toolkit:AccordionExpanderBuilt into WinUI; single expand/collapse
HyperlinkButtonButton + click handlerURI 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.

Silverlight
<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>
Uno Platform
<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:

  1. navigation:Page becomes Page (no namespace prefix needed)
  2. toolkit:BusyIndicator becomes ProgressRing with IsActive instead of IsBusy; the wrapper pattern is removed
  3. sdk:DatePicker becomes CalendarDatePicker with Date instead of SelectedDate
  4. HeaderTextStyle becomes TitleTextBlockStyle (a built-in WinUI style)
  5. Grid, StackPanel, TextBlock, TextBox, ComboBox, and Button are virtually unchanged
ViewModels

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.

Silverlight
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;
        };
    }
}
Uno Platform
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:

  1. Constructor injection replaces direct context creation. The RIA context is replaced by an IExpenseService interface.
  2. Completed callbacks become async Task methods with try/catch/finally.
  3. RelayCommand becomes AsyncRelayCommand (from CommunityToolkit.Mvvm).
  4. INotifyPropertyChanged is identical. No changes required.

What Ports Directly

  • All INotifyPropertyChanged property implementations
  • ObservableCollection<T> usage
  • Data binding expressions in XAML (both {Binding} and {x:Bind})
  • Model classes (POCOs)
  • Validation logic and business rules in ViewModels
Services

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

C#
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:

C#
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

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.

Silverlight
<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>
Uno Platform
<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:

C#
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.

AI-Assisted

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:

  1. Parse the Silverlight XAML and identify every control, namespace, and binding
  2. Query the Uno Platform MCP documentation to find the WinUI equivalent for each control
  3. Generate the translated WinUI XAML with correct namespaces, control names, and property mappings
  4. 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.

Silverlight
<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>
Uno Platform
<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

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

Terminal
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.

Validation

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.

Checklist

Migration Checklist

Use this checklist to track your progress on each Silverlight page:

Create the Page in the Uno Platform project
Update XML namespaces (clr-namespace to using, remove assembly references)
Change root element from navigation:Page or UserControl to Page
Substitute controls (DataGrid, DatePicker, BusyIndicator, Accordion)
Update data bindings (replace StringFormat with converters where needed)
Port ViewModel (replace callbacks with async/await)
Replace WCF/RIA calls with service interface calls
Wire up navigation (URI-based to type-based)
Build and resolve compilation errors
Run on WebAssembly and verify with App MCP
Run on Windows and verify
Compare against original Silverlight app
What You Ship

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
Next

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

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.