In this GitHub example, an XAF-powered Blazor app is wrapped inside a native "web view" container (Electron desktop shell) for the target OS and connected to our Middle Tier application server.
By employing strategies outlined in this example, you can deliver XAF-powered solutions that target both Windows and non-Windows operating systems (when WinForms/Windows or browser-based apps do not fully address your business needs). For end users, the hybrid desktop solution documented herein will feel and behave (performance-wise) no different than Microsoft Teams, Visual Studio Code, Outlook (new), and similar hybrid apps. This web-based approach can serve as an alternative to native WinForms/WPF/VCL/Avalonia desktop development ("write once, run everywhere").
Important
This example references ElectronNET.Core 0.6.0 and may not be updated for newer versions. Electron.NET is a third-party library and its setup instructions, APIs, and package names can change between releases. Before you integrate Electron.NET into your application, check the official Electron.NET documentation for current instructions.
- .NET SDK 8.0 or later (the example projects target
net10.0) - Node.js 22.x or later (must be available in
PATH) - XAF Blazor Server application with Middle Tier Security enabled. You can create this application using our Template Kit
Tip
Run the following command to check the installed Node.js version:
node --versionNote
The example solution uses SQL Server LocalDB (Windows only). To run the example on macOS, see Run the Example on macOS.
Add the following packages to the ApplicationName.Blazor.Server project:
dotnet add package ElectronNET.Core
dotnet add package ElectronNET.Core.AspNetEnsure that the project file contains the following references:
<PackageReference Include="ElectronNET.Core" Version="0.6.0" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.6.0" />Note
ElectronNET.Core does not require the legacy electronize CLI tool or the electron.manifest.json file. The library configures the Electron host through MSBuild.
Open the ApplicationName.Blazor.Server/Program.cs file and add the following namespaces:
using ElectronNET.API;
using ElectronNET.API.Entities;Register Electron services conditionally. Add the following code after the WebApplication.CreateBuilder method call, AddRazorComponents registration, and before the AddXaf method call:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
bool useElectron = builder.Configuration.GetValue<bool>("USE_ELECTRON");
if (useElectron) {
builder.Services.AddElectron();
builder.UseElectron(args, async () => {
var options = new BrowserWindowOptions {
Show = false,
IsRunningBlazor = true,
};
if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux()) {
options.AutoHideMenuBar = true;
}
var browserWindow = await Electron.WindowManager.CreateWindowAsync(options);
browserWindow.OnReadyToShow += () => browserWindow.Show();
});
}
builder.Services.AddXaf(builder.Configuration, b => {
// ...
});The code referenced above performs the following:
IsRunningBlazor = trueconfigures the Electron renderer so that the Blazor SignalR circuit works correctly.Show = falsein combination with theOnReadyToShowevent handler prevents an empty window from being displayed while the XAF application loads.- The
USE_ELECTRONcheck keeps default browser hosting unchanged. Do not callUseElectronif the setting is not specified.
The ElectronNET.API namespace contains an App type. After you add the using ElectronNET.API; directive, the App identifier conflicts with the Blazor root component and the build fails with a CS0104 error.
To resolve this conflict, specify the fully qualified name of the root component in the MapRazorComponents method call:
app.MapRazorComponents<ApplicationName.Blazor.Server.Components.App>()
.AddInteractiveServerRenderMode()
.AddAdditionalAssemblies(typeof(BlazorApplication).Assembly);The rest of the XAF pipeline (AddXaf, Security System configuration, UseXaf, and MapXafEndpoints) does not require modification.
In Electron mode, the application listens to HTTP only. If you call UseHttpsRedirection, the middleware cannot determine the HTTPS port and logs a Failed to determine the https port for redirect warning. Call UseHttpsRedirection only if the application does not run in Electron mode:
if (!useElectron) {
app.UseHttpsRedirection();
}Traffic between Electron window and Kestrel does not leave the local machine, so HTTPS is not required. UseHsts does not need modification: HSTS middleware ignores localhost by default.
Open the ApplicationName.Blazor.Server/Properties/launchSettings.json file and add a profile that sets the USE_ELECTRON environment variable and disables browser launch:
"Electron": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"USE_ELECTRON": "true"
}
}-
Start the ApplicationName.MiddleTierWebApi project (Middle Tier Security server).
In the example solution, run the following command in the ElectronExample.MiddleTier folder:
dotnet run --launch-profile ElectronExample.MiddleTier
For hybrid Electron usage scenarios, we recommend that you always use XAF Blazor UI with the Middle Tier Security for secure communication between the frontend and backend, helping to protect sensitive data and enforce stricter access controls.
-
Run the ApplicationName.Blazor.Server project with the Electron launch profile:
- In Visual Studio or JetBrains Rider, select the Electron profile and start the project.
- In the command line, run
dotnet run --launch-profile Electronin the project folder. The first run restores and installs Electron npm packages, so it takes longer than subsequent runs.
-
A desktop window opens and displays the XAF application's logon page. Use the Admin or User credential with an empty password to log in.
To run the application in a browser, use any other launch profile. The application then behaves as a standard XAF Blazor application.
In this setup, the XAF Blazor application and Electron shell run on the same machine. The .NET process hosts the Kestrel server, and Electron displays it in a native window. This configuration allows XAF Controllers to call the Electron.NET API (the static Electron class) and access native OS features that a standard browser cannot reach: message boxes, file dialogs, notifications, the system tray, the clipboard, etc. This setup works as follows:
- A user clicks an XAF Action in the Electron window.
- The Action's
Executeevent handler runs on the server (local .NET process), as in any XAF Blazor application. - The handler calls an Electron.NET API, such as
Electron.Dialog.ShowMessageBoxAsync. - Electron.NET forwards the call to the Electron main process, which displays a native OS dialog.
Note
Native APIs only work when the application runs with the Electron launch profile. In a standard browser, the Electron process does not exist. Use the HybridSupport.IsElectronActive property to deactivate Electron-specific Actions in such instances.
The ElectronExample.Blazor.Server/Controllers/ElectronMessageBoxController.cs file in the example solution implements a Window Controller with a Native Message Box (SimpleAction). The Action displays a native Electron message box:
using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Actions;
using DevExpress.Persistent.Base;
using ElectronNET.API;
using ElectronNET.API.Entities;
namespace ApplicationName.Blazor.Server.Controllers;
public class ElectronMessageBoxController : WindowController {
public ElectronMessageBoxController() {
TargetWindowType = WindowType.Main;
var showMessageBoxAction = new SimpleAction(this, "ShowElectronMessageBox", PredefinedCategory.Tools) {
Caption = "Native Message Box",
ImageName = "Action_AboutInfo"
};
showMessageBoxAction.Execute += ShowMessageBoxAction_Execute;
Active["ElectronActive"] = HybridSupport.IsElectronActive;
}
private async void ShowMessageBoxAction_Execute(object sender, SimpleActionExecuteEventArgs e) {
var options = new MessageBoxOptions("Hello from the native Electron message box!") {
Type = MessageBoxType.info,
Title = "ElectronExample",
Buttons = new[] { "OK" }
};
try {
var window = Electron.WindowManager.BrowserWindows.FirstOrDefault();
if (window != null)
await Electron.Dialog.ShowMessageBoxAsync(window, options);
else
await Electron.Dialog.ShowMessageBoxAsync(options);
}
catch (Exception ex) {
Tracing.Tracer.LogError(ex);
}
}
}How the Controller works:
- Declare an XAF Action: The Controller creates a
SimpleActionand handles itsExecuteevent. XAF renders the Action in the toolbar. You do not need custom Blazor markup. - Activate the Controller in Electron mode only:
Active["ElectronActive"] = HybridSupport.IsElectronActivehides the Action when the application runs in a standard browser, where native APIs are unavailable. - Call the native API in the
Executehandler: Configure the dialog withMessageBoxOptionsand pass it toElectron.Dialog.ShowMessageBoxAsync. Electron.NET forwards the call to the Electron main process, which displays the native OS dialog. - Pass the parent window:
Electron.WindowManager.BrowserWindows.FirstOrDefault()returns the application window. When you pass it toShowMessageBoxAsync, the dialog is modal to this window. - Handle exceptions: The handler is
async voidbecause theExecuteevent is synchronous. Wrap the code intry/catchand log errors withTracing.Tracer.LogError. Otherwise, an unhandled exception can terminate the application.
To configure the dialog, use the Type (info, warning, error, question), Title, Buttons, DefaultId, CancelId, and Detail properties of MessageBoxOptions. ShowMessageBoxAsync returns a MessageBoxResult object. Its Response property contains the index of the clicked button. Use this value to implement a confirmation dialog:
var result = await Electron.Dialog.ShowMessageBoxAsync(window, new MessageBoxOptions("Delete the record?") {
Type = MessageBoxType.question,
Buttons = new[] { "Yes", "No" },
CancelId = 1
});
if (result.Response == 0) {
// The user clicked "Yes".
}To test the Action, run the application with the Electron launch profile and click Native Message Box in the toolbar.
You can use the same pattern to call other Electron.NET APIs from XAF Controllers:
| API | Purpose |
|---|---|
Electron.Dialog.ShowOpenDialogAsync / ShowSaveDialogAsync |
Native file open and save dialogs |
Electron.Notification.Show |
OS notifications |
Electron.Tray |
A system tray icon and menu |
Electron.Clipboard |
Read and write the system clipboard |
Electron.Shell.OpenExternalAsync |
Open a URL in the default browser |
Electron.Menu.SetApplicationMenu |
A native application menu |
The example solution runs on macOS (tested on Apple silicon) with the following changes.
The ElectronExample.MiddleTier/appsettings.json file specifies a SQL Server LocalDB connection string. LocalDB is only available on Windows. To use SQLite instead, follow the steps below:
-
Add the
Microsoft.Data.Sqlitepackage to the ElectronExample.MiddleTier project:dotnet add CS/ElectronExample.MiddleTier package Microsoft.Data.Sqlite
-
Override the connection string with an environment variable when you start the Middle Tier server:
cd CS/ElectronExample.MiddleTier ConnectionStrings__ConnectionString="XpoProvider=SQLite;Data Source=ElectronExample.db" \ dotnet run --launch-profile ElectronExample.MiddleTier
The server creates the database and demo users on the first run.
The Middle Tier server also listens to HTTPS and requires a trusted development certificate. If the certificate is missing, run dotnet dev-certs https --trust.
In a separate terminal, run the following commands:
cd CS/ElectronExample.Blazor.Server
dotnet run --launch-profile ElectronTo log in, use the Admin or User credential with an empty password.
(you will be redirected to DevExpress.com to submit your response)
