PeakSWC.RemoteBlazorWebView.RemoteComponents 10.0.15.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package PeakSWC.RemoteBlazorWebView.RemoteComponents --version 10.0.15.1
                    
NuGet\Install-Package PeakSWC.RemoteBlazorWebView.RemoteComponents -Version 10.0.15.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" Version="10.0.15.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" Version="10.0.15.1" />
                    
Directory.Packages.props
<PackageReference Include="PeakSWC.RemoteBlazorWebView.RemoteComponents" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PeakSWC.RemoteBlazorWebView.RemoteComponents --version 10.0.15.1
                    
#r "nuget: PeakSWC.RemoteBlazorWebView.RemoteComponents, 10.0.15.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PeakSWC.RemoteBlazorWebView.RemoteComponents@10.0.15.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PeakSWC.RemoteBlazorWebView.RemoteComponents&version=10.0.15.1
                    
Install as a Cake Addin
#tool nuget:?package=PeakSWC.RemoteBlazorWebView.RemoteComponents&version=10.0.15.1
                    
Install as a Cake Tool

Remote components

Run a Blazor component in its own process and show it inside your app, in a sandboxed iframe. Use it when a piece of UI should be isolated from the app: it can run with fewer rights than the app, crash without taking the app down, and can't touch the app's page.

 app process                                        component process (RemoteComponentHost)
 ┌──────────────────────────────┐   stdin: "set"    ┌──────────────────────────────┐
 │ <MyFrame Open=... Value=...> │ ────────────────▶ │ MyReceiver (contract values) │
 │   iframe sandbox, opaque     │ ◀──────────────── │ root component (your UI)     │
 │   origin, height only        │  stdout: "call"   │ RemoteBlazorWebView client   │
 └──────────────┬───────────────┘                   └──────────────┬───────────────┘
                └──── iframe src = <relay>/<id> ◀── relay ◀────────┘
  • Data goes over the process's stdin/stdout, newline-delimited JSON (RemoteComponentMessage): the app sends set with the contract values that changed, the component sends call for a callback. Nothing crosses through the browser, so when the app is viewed remotely, the remote browser can't forge either side.
  • The iframe is sandboxed without allow-same-origin (RemoteComponentFrame.DefaultSandbox), so it has an opaque origin. The only postMessage the app's page accepts from it is its height (rbwv-remote-height), matched by the sending window.

Mirrors

When the app's page is mirrored (a read-only /mirror/<id> view), the mirror renders the same page, frame included. If the mirror loaded the component's page as well, the relay would take that as a reload and the component's process would restart. So the frame is rendered without src: data-rbwv-src holds the component's page, and data-rbwv-mirror-src the relay's read-only view of it (RemoteComponentSession.MirrorSource, by default <relay>/mirror/<id>). The app's page loads the first (remotecomponents.js); a mirror page loads the second (the RemoteWebView script), or nothing when the session has no mirror source. The component's process allows mirrors by default (RemoteComponentHostOptions.EnableMirrors). Mirrors can't click or type in it, like the rest of the mirror.

A contract, and what the generator writes from it

Declare what crosses between the two processes as an interface, in a library both sides reference:

[RemoteComponentContract]
public interface IClock
{
    string TimeZone { get; }              // a value: app -> component
    Alarm? NextAlarm { get; }             // any JSON-serializable type
    Task Snooze();                         // a callback: component -> app
    Task Picked(DateTimeOffset when);      // at most one argument
}

The source generator in this package writes:

  • ClockFrame for the app, a component with [Parameter] TimeZone, NextAlarm, EventCallback Snooze and EventCallback<DateTimeOffset> Picked:

    <ClockFrame Open="ct => launcher.StartAsync(ct)" TimeZone="@zone" NextAlarm="@alarm" Snooze="OnSnooze" Title="Clock" />
    
  • ClockReceiver for the component's process: implements IClock with the values the app sent (Changed fires when they change); calling Snooze() sends the callback. Register it in the host and inject IClock (or ClockReceiver) in the UI:

    return RemoteComponentHost.Run(new RemoteComponentHostOptions
    {
        ServerUri = relay, GrpcBaseUri = grpc, Id = id, ExitWhenInputCloses = true, RootComponent = typeof(ClockRoot),
        ConfigureServices = (services, connection) => services.AddRemoteComponentReceiver<ClockReceiver>(connection),
    });
    

Values and arguments must be JSON-serializable (RemoteComponentJson.Options: web defaults, enums as strings). Values are sent when they change (compared as JSON), all of them the first time. Rules the generator enforces: RBWRC001 for a member that isn't a get-only property or a Task method with at most one argument, RBWRC002 for a generic or nested interface, RBWRC003 for a member name already defined on RemoteComponentFrameBase or RemoteComponentReceiverBase. Names can be set with [RemoteComponentContract(FrameName = "...", ReceiverName = "...")].

Razor compiles before source generators run, so a .razor file can't use a frame generated in its own project: keep the contract in a separate library.

The pieces

Side Type Package
App RemoteComponentFrameBase: the iframe, sends values, raises callbacks. Open starts the process and returns a RemoteComponentSession (the frame's address and the RemoteComponentConnection to the process) this one
Component process RemoteComponentHost.Run(options): the never-shown WPF window with a RemoteBlazorWebView client of the relay; exits when the frame closes or reloads, or the app exits PeakSWC.RemoteBlazorWebView.Wpf
Component process RemoteComponentReceiverBase: holds the values the app sent, raises Changed, sends callbacks; services.AddRemoteComponentReceiver<T>(connection) this one

How the component's process notices that the app has gone: with ExitWhenInputCloses = true it exits when its stdin ends, which Windows does as soon as the app exits, crashes or is killed (the app must keep its end of the pipe open while the component is shown, as RemoteComponentSession does). Without a pipe on stdin, ParentProcessId polls the process list every 2 seconds instead.

How the app starts the process (token, folder, relay) is up to it: Open is a function you pass to the frame. The relay is usually an embedded one (PeakSWC.RemoteWebViewService.Lib) with FrameAncestors set to the app's origin and AllowOpaqueOrigin = true.

The component's host page

Include the height script (RemoteComponentFrame.HeightScript) so the app can size the frame:

<script>
(function () {
    var last = 0;
    function post() {
        var h = Math.ceil(document.documentElement.scrollHeight);
        if (h !== last) { last = h; window.parent.postMessage({ type: 'rbwv-remote-height', height: h }, '*'); }
    }
    if (window.ResizeObserver) new ResizeObserver(post).observe(document.documentElement);
    window.addEventListener('load', post);
})();
</script>
<script src="_framework/blazor.webview.js"></script>
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on PeakSWC.RemoteBlazorWebView.RemoteComponents:

Package Downloads
PeakSWC.RemoteBlazorWebView.Wpf

This package will transport the user interface from an app built using BlazorWebView to a server running in the cloud. This allows an app running behind a firewall to be accessed through a browser by accessing the cloud server.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
11.0.0 0 11/11/2026
10.0.15.1 0 11/11/2026
10.0.15 39 11/11/2026
10.0.14 103 11/8/2026

Based on maui 10.0.15.1

Changes since 10.0.14
Merge branch 'claude/session-bar-opt-in': session bar is opt-in, templates, restart keeps the owner role (10.0.15.1)
A page comes back from a restart with the query it was opened with
Session bar is opt-in: no placeholder, no bar; a template makes it the app's own
Merge branch 'claude/session-bar-slot-declared': session bar waits for a declared placeholder (10.0.15.1)
Generator README and ShareControl doc comment mention the host-page tag for the session bar
Version 10.0.15.1 (was 10.0.16): the first three parts follow the .NET release
Test: visibility waits take their own options type
Session bar waits for a declared placeholder instead of floating at startup (10.0.16)
Release workflow publishes to NuGet when dry_run is off (trusted publishing, nuget-org approval)
10-client test waits 2 minutes before attaching browsers again (idle sessions must survive)
Server stats tests: the app's own blazor.modules.json is served now (2 bytes fewer per client)
ParticipantsChanged needs relay 10.0.15
Merge PR #85 (module, JS, relay, lifecycle, generator and system test fixes)
README and release notes: sign-in providers, owner key, ParticipantsChanged
Authorization is injected, not compiled in: None, Fake (tests) and AzureB2C providers
Tests: owner URL carries the key; spoofed-owner test waits for the roster
WinForms: OwnerKey is not a designer property
WIP: owner key, names in the roster, ParticipantsChanged, session group check
Remove UTF-8 BOM from embedded blazor.modules.json and clear CS8602 warnings
Use bundled protoc 3.x binary in scripts/protoc.js on Linux CI
Fix bugs across module, JS, relay, lifecycle, generator, and system tests
Browser swarm keeps 300 separate clients, spread over 10 Chromium processes
Fix RunTests.ps1 packaging/tool issues, concurrent browser tests, and MSB3277 warnings
RunTests: -NoBuild runs the tests on what the last run built
Lost-connection test cuts the viewer's connections itself
A lost connection is shown and a /mirror page comes back; unattended hand-over retried; frames follow the latest control change
Web.JS test file as EditWebView writes it
EditWebView keeps this repository's own changes across a run
Upstream source from MAUI 10.0.130 and aspnetcore 10.0.15 (EditWebView run)
Document EditWebView in the release process.
EditWebView: ask for tag dates only on the wanted major version, and print progress
EditWebView prints what a run changed; Web.JS/src and test no longer git-ignored
update packages
EditWebView: fall back to the GitHub CLI sign-in when GITHUB_PAT is not set
Microsoft 10.0 libraries to 10.0.15, SDK 10.0.404; EditWebView re-applies the Web.JS patches
Control test: a browser that has not been told yet is 'not agreed', not an error
Restart test: wait for the restart to start before waiting for the page to return
RunTests: -Filter for a partial browser run; restart test keeps the bar in view
Agent: hand the requester the session's join link
Generator: voice, run-time choices, app-supplied startup values, and a restart that survives a stuck window
Library: a restart request still restarts an app whose UI thread is stuck
Session bar: opt-in Restart button
Shared control: one change of controller at a time
Voice: a mirror page gets the remote end whenever nobody holds it
Relay: every write to the app's stream goes through one guarded method
Shared control: a message from before a hand-off can no longer replace a real one
RunTests: quote the -p: switch so PowerShell passes it whole
RunTests: run every test project, quick suites first
BugBountyTests: the mirror-scroll test ignores the control notices browsers are now sent
ControlTransferTests: the fake browser stream accepts the relay's token overload
Tests for sharing control: relay policy, render acks, URL loading, and end-to-end browser tests

Full list: https://github.com/budcribar/RemoteBlazorWebView/compare/10.0.14...227d88ffc73906e1f5d6999da10e6962b1fe51cb