The Story Behind WindowSill’s Extensibility
If you’re a .NET dev building desktop apps with the Windows App SDK, you’ve probably hit that point where “just ship a feature” turns into “I really need this to be extensible.” You want plugins, not hardcoded menus. You want other people to bolt on UI and behavior without you cutting a new release every time.
WindowSill leans all the way into that idea. It’s an AI-powered command bar for Windows where each “sill” is an extension: a NuGet package with metadata, APIs to implement, and its own UI. Think VS Code or Office add-ins, but for a command bar that can grow in directions the original author never planned.
That architecture choice is the interesting part: extensions are installed into local app data, loaded dynamically, and allowed to bring their own XAML. On paper, that’s exactly the kind of flexibility you want. In practice, it runs straight into WinUI 3’s assumptions about compiled XAML and PRI files living in the app install folder. As soon as an extension tries to surface UI, the resource system taps out.
This post walks through how Etienne worked around that limitation by shifting to code-first UI with Uno Platform’s C# Markup, then later folded in a community workaround (DynamicXaml) to bring “normal” XAML back into the mix. By the end, you’ll have a concrete pattern for building dynamically loaded UI in .NET – when to reach for C# Markup, when XAML still makes sense, and how to design your own extensible app so it doesn’t collapse the moment you step off the happy path.
The Technical Hurdle: When XAML Doesn’t Load
WindowSill has an extensibility mechanism. Each “sill” or “command bar” you see in the UI is powered by an extension, which is nothing more than NuGet package with extra metadata and implementation of some API entry-points. They work similarly to Visual Studio extensions, Visual Studio Code extensions or Office adds-in. Extensions are installed in the app’s local data and loaded dynamically at runtime.



WindowSill is developed in WinUI3. For resource management, such as strings from RESW files or XAML, a `.pri` file is generated at built time for each WinUI3 project. A PRI file (Package Resource Index) is essentially a binary lookup table mapping resource keys (like “Resources/String1”) to assets. It’s part of the resource loading system used by XAML, ResourceLoader, and ResourceManager.
By design, WinUI3 apps expect all .pri files to be in the application installation folder. However, in the case of WindowSill, since extensions are installed in the app’s local data folder and not the installation folder (due to the importance of isolating extensions from the rest of the app), extension’s .pri files can’t be found by WinUI3. This cause extensions to crash as soon as they try to surface a UI coded using compiled XAML.
A potential workaround have been explored though: XamlReader.Load(string). This method allows to read a XAML file on the fly. But it comes with its own caveat. For example, {ThemeResources} are not supported. {x:Bind} is also not supported. Therefore, we need to use {Binding} and set the DataContext are runtime. Similarly, the code behind (often placed in a `.xaml.cs`) and FindName are not supported.
This means that in order to access a control that is deep in the visual tree of the XAML we loaded, we need to iterate through various children until finding the control we desire. .
This isn’t ideal as it makes the whole developer experience cumbersome.
Another workaround is to create all the UI using C#. Create a new instance of TextBox, Buttons and set their properties directly when our sill / UI should be presenter.
The Workaround: Bringing Uno Platform’s C# Markup into the Picture
This is where Uno Platform’s C# Markup became ideal. It provides a declarative, fluent API for defining WinUI/Uno XAML user interfaces directly in C# code, without relying on compiled XAML or PRI-based resource resolution. While it is just like creating instances of TextBox of Button manually like mentioned above, it comes with a set of features that makes it much more pleasant to write. For example, Binding are much more simplified, making MVVM easier to implement
// Before
DataContext = new CounterViewModel();
var button = new Button
{
Content = "Increment"
};
button.SetBinding(
Button.CommandProperty,
new Binding
{
Path = new PropertyPath(nameof(CounterViewModel.IncrementCommand)),
Mode = BindingMode.OneWay
});
Content = new StackPanel
{
Children = { button }
};
// After
DataContext(
new CounterViewModel(),
(view, viewModel) => view
.Content(
new StackPanel()
.Children(
new Button()
.Content("Increment")
.Command(x => x.Binding(() => viewModel.IncrementCommand).OneTime())
)
)
);
Coupled with an awesome support of auto-completion in Visual Studio which surfaces the C# Markup’s extension methods instantly, this simple “trick” made the development of UI for WindowSill extensions much faster and smoother, bypassing the limitation of XAML and Pri files, along with making C#-based UI creation much easier.
The Breakthrough: When XAML Became Possible Again
Later on during WindowSill development, and thanks to the awesome support of Windows App Community, another workaround has been discovered, allowing to make WinUI3 loading PRI files even when they are not in the application installation folder. DynamicXaml, a C++ library for UWP and WinUI3 made by Ahmed Walid, hacks into WinUI3 API to load PRI files any time. It hooks up the ResourceManager, allowing it to load any PRI file and map resources accordingly.
Thanks to this, WindowSill extension developers can also use regular XAML, even with `{x:Bind}` and code behind. There’s no need of extra work for developers as WindowSill will automatically look for PRI files and load them on startup, making the developer experience seamless. This gives a great flexibility, as developers can now choose between declarative (XAML) or fluent (C# Markup) styles depending on needs and preference. When the UI is simple enough, C# Markup can make a lot of sense. When the UI becomes complex, a traditional XAML approach is probably preferred.
Real Extensions, Real Use Cases
Here is an example with WindowSill’s URL Helper extension, which allows to shorten any URL selected in any app (such as Microsoft Edge, Notepad, Evernote, Notion, etc.).
The snippet below allows you to get the UI you can see inn the popup appearing above “Shorten URL”:
var shortenURLSillPopupViewModel = new ShortenURLSillPopup(currentSelection);
return new SillPopupContent(shortenURLSillPopupViewModel.OnOpening)
.Width(350)
.DataContext(
shortenURLSillPopupViewModel,
(view, viewModel) => view
.Content(
new Grid()
.Padding(8)
.ColumnSpacing(8)
.HorizontalAlignment(HorizontalAlignment.Stretch)
.ColumnDefinitions(
new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition() { Width = GridLength.Auto }
)
.Children(
new TextBox()
.Grid(column: 0)
.VerticalAlignment(VerticalAlignment.Center)
.IsReadOnly(true)
.IsEnabled(x => x.Binding(() => viewModel.GeneratedUrl).Convert(url => !string.IsNullOrEmpty(url)))
.PlaceholderText("/WindowSill.URLHelper/ShortenURL/UrlTextBoxPlaceholderText".GetLocalizedString())
.Text(() => viewModel.GeneratedUrl),
new Button()
.Grid(column: 1)
.Style(x => x.ThemeResource("AccentButtonStyle"))
.VerticalAlignment(VerticalAlignment.Stretch)
.IsEnabled(x => x.Binding(() => viewModel.GeneratedUrl).Convert(url => !string.IsNullOrEmpty(url)))
.MinWidth(64)
.ToolTipService(toolTip: "/WindowSill.URLHelper/ShortenURL/CopyButtonToolTip".GetLocalizedString())
.Command(() => viewModel.CopyCommand)
.Content(
new Grid()
.Children(
new ProgressRing()
.IsIndeterminate(x => x.Binding(() => viewModel.GeneratedUrl).Convert(url => string.IsNullOrEmpty(url)))
.Height(16)
.Width(16),
new TextBlock()
.Visibility(x => x.Binding(() => viewModel.GeneratedUrl).Convert(url => string.IsNullOrEmpty(url) ? Visibility.Collapsed : Visibility.Visible))
.Text("/WindowSill.URLHelper/ShortenURL/Copied".GetLocalizedString())
)
)
)
)
);
This is what you can build once you remove the constraints. WindowSill’s GitHub contains the source code of all built-in extensions, which are a great source of sample for extension developer in addition to the official documentation.
Lessons for Developers: Dynamic UI Beyond XAML
If you’re building extensible .NET apps – plugins, extensions, or “mini-apps inside the app” – WindowSill is a useful reality check. At some point, your UI stops being something you compile once and ship, and starts being something you have to compose at runtime.
In that world, a few patterns from this story are worth keeping in your mental toolbox.
Dynamic UI pushes you toward code-first
Once extensions live outside your main app package, a lot of “normal” XAML assumptions break:
PRI files aren’t where WinUI expects them
Compiled XAML can’t see resources the way you’d like
You don’t control when or how extension assemblies show up
At that point, defining UI in code stops being a stylistic preference and becomes the simplest way to keep things working. You still care about clean structure and maintainability – you just can’t rely on the usual build-time pipeline.
C# Markup makes code-first ergonomic, not painful
Etienne could have hand-wired new Button { … } trees everywhere, but that gets unreadable fast. UnoPlatform’s C# Markup fixes the usual pain points:
You keep strong typing and tooling support
Bindings stay concise enough that MVVM still feels natural
Layout and visual structure are expressed in a way that’s easy to refactor and share
C# Markup doesn’t just “cover” for a missing feature – it turns the limitation into a design strength. Extension UIs become easier to generate, test, and evolve as regular C# code.
Uno Platform is modular – even in a Windows-only world
None of this required “going full cross-platform” or rewriting the app on Uno Platform. WindowSill is still a Windows App SDK / WinUI 3 app. Uno Platforms C# Markup is being used as a targeted tool to solve one specific class of problem: dynamic, extensible UI.
That mindset scales:
You can adopt Uno Platform pieces incrementally
You can pull in C# Markup where XAML is awkward or impossible
You don’t have to change your whole stack to benefit from the ecosystem
Design for options, not one permanent answer
Later in the project, DynamicXaml made it possible to load PRI files and use “normal” XAML again – including {x:Bind} and code-behind – even for extensions. Because the architecture wasn’t locked into one approach, WindowSill could keep both:
C# Markup for simple, highly dynamic or code-friendly UIs
XAML for more complex layouts where designers and XAML tooling shine
The bigger takeaway: flexible architecture pays off when the ecosystem moves. New libraries, hacks, and community discoveries can slot in without forcing a rewrite.
If this resonated with the kind of problems you’re solving, here are a few concrete next steps:
- Try WindowSill – See the extensibility model in a real app
- Explore Uno C# Markup – Start with the Uno docs and try rebuilding one small screen in fluent C#.
- Join the Uno community – Share your own extension / plugin stories or ask questions about C# Markup patterns.
Subscribe to Our Blog
Subscribe via RSS
Back to Top