Added template #414

This commit is contained in:
Florian Rappl
2026-09-04 20:44:55 +02:00
parent a04ca0b9bc
commit deca8166ec
25 changed files with 550 additions and 0 deletions

View File

@@ -12,6 +12,7 @@
- Fixed cross-compilation behavior on same platform (#1098) @epsnm
- Fixed resolution of unrelated target (#1099) @epsnm
- Fixed false alarm for `ELECTRON001` on a root `package-lock.json` (#946)
- Added `ElectronNET.Core.Templates` package with a `dotnet new electron-blazor` template (#414)
- Added `WebContents.OnZoomChanged` event (#956)
- Added `WebContents` page loading APIs `LoadFileAsync`, `IsLoadingAsync`, `IsLoadingMainFrameAsync`, `IsWaitingForResponseAsync`, `Reload`, `ReloadIgnoringCache` and `Stop` (#956)
- Added `WebContents.InsertCSSAsync` and `WebContents.RemoveInsertedCSSAsync` for dynamic CSS (#956)

View File

@@ -10,6 +10,11 @@ See [System Requirements](../GettingStarted/System-Requirements.md).
## 🚀 Quick Start
> [!Tip]
> To skip the manual setup below, scaffold a ready-to-run app with the
> [project templates](Templates.md): `dotnet new install ElectronNET.Core.Templates` followed by
> `dotnet new electron-blazor -n MyDesktopApp`.
### 1. Create ASP.NET Core Project
#### Visual Studio

View File

@@ -0,0 +1,88 @@
# Project Templates
Electron.NET ships a `dotnet new` template package so you can scaffold a ready-to-run desktop app instead of wiring up an ASP.NET project by hand.
## 🛠 System Requirements
See [System Requirements](../GettingStarted/System-Requirements.md).
## 📦 Install the Templates
```bash
dotnet new install ElectronNET.Core.Templates
```
To update to the latest version, run the same command again. To remove them:
```bash
dotnet new uninstall ElectronNET.Core.Templates
```
## 🚀 Available Templates
| Template | Short name | Description |
|----------|------------|-------------|
| Electron.NET Blazor App | `electron-blazor` | A Blazor Server app hosted in an Electron shell |
## 🧱 Create a Blazor App
```bash
dotnet new electron-blazor -n MyDesktopApp
cd MyDesktopApp
```
Then start it in the Electron shell:
```bash
dotnet build
electronize start
```
### Options
| Option | Values | Default | Description |
|--------|--------|---------|-------------|
| `-f`, `--framework` | `net8.0`, `net10.0` | `net8.0` | The target framework of the generated project |
| `-e`, `--electron-version` | any Electron version | `38.2.2` | The Electron version the app is built against |
| `-p`, `--port` | port number | `8001` | The port the ASP.NET server uses during development |
| `--no-restore` | — | — | Skip the automatic `dotnet restore` |
Example:
```bash
dotnet new electron-blazor -n MyDesktopApp -f net10.0 -e 30.4.0 -p 8123
```
## 📁 What You Get
```
MyDesktopApp/
├── Components/
│ ├── Layout/ MainLayout.razor, NavMenu.razor
│ ├── Pages/ Home.razor, Counter.razor
│ ├── App.razor
│ ├── Routes.razor
│ └── _Imports.razor
├── Properties/
│ ├── PublishProfiles/ win-x64, linux-x64 and osx-arm64 folder profiles
│ ├── electron-builder.json
│ └── launchSettings.json
├── wwwroot/app.css
├── appsettings.json
├── Program.cs
└── MyDesktopApp.csproj
```
The project already references `ElectronNET.Core` and `ElectronNET.Core.AspNet`, calls `builder.UseElectron(...)` in `Program.cs`, and passes all [Migration Checks](../Core/Migration-Checks.md) out of the box.
> [!Note]
> `ElectronNET.API` also defines a type named `App`, which collides with the Blazor root
> component. That is why the template calls `app.MapRazorComponents<MyDesktopApp.Components.App>()`
> with a fully qualified type name.
## 🚀 Next Steps
- **[Configuration](../Using/Configuration.md)** - Adjust app metadata and Electron settings
- **[Debugging](../Using/Debugging.md)** - Debug the .NET and Electron sides
- **[Package Building](../Using/Package-Building.md)** - Create distributable packages

View File

@@ -15,6 +15,7 @@
# Getting Started
- [System Requirements](GettingStarted/System-Requirements.md)
- [Project Templates](GettingStarted/Templates.md)
- [With ASP.Net](GettingStarted/ASP.Net.md)
- [With a Console App](GettingStarted/Console-App.md)

View File

@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageOutputPath>..\..\artifacts</PackageOutputPath>
<PackageId>$(PackageNamePrefix).Templates</PackageId>
<Title>$(PackageId)</Title>
<Description>$(DescriptionFirstPart) This package contains the 'dotnet new' project templates.</Description>
<PackageTags>electron aspnetcore blazor dotnet-new templates</PackageTags>
<PackageType>Template</PackageType>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
<!-- Template content contains dot-prefixed folders (.template.config) that must be packed -->
<NoDefaultExcludes>true</NoDefaultExcludes>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
<IncludeSymbols>false</IncludeSymbols>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<Nullable>disable</Nullable>
<!-- Must match the Electron.NET package version referenced by the templates -->
<ElectronNetTemplatePackageVersion>0.6.0</ElectronNetTemplatePackageVersion>
</PropertyGroup>
<ItemGroup>
<Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" />
<Compile Remove="**\*" />
</ItemGroup>
<ItemGroup>
<None Include="..\ElectronNET\PackageIcon.png" Pack="true" PackagePath="\" />
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<Target Name="ValidateTemplatePackageVersion" BeforeTargets="Build">
<Error Condition="!$(Version.StartsWith('$(ElectronNetTemplatePackageVersion)'))"
Text="The templates reference Electron.NET $(ElectronNetTemplatePackageVersion) but the current version is $(Version). Update the PackageReference versions in templates\**\*.csproj and the ElectronNetTemplatePackageVersion property." />
</Target>
</Project>

View File

@@ -0,0 +1,21 @@
{
"$schema": "http://json.schemastore.org/dotnetcli.host",
"symbolInfo": {
"Framework": {
"longName": "framework",
"shortName": "f"
},
"ElectronVersion": {
"longName": "electron-version",
"shortName": "e"
},
"Port": {
"longName": "port",
"shortName": "p"
},
"skipRestore": {
"longName": "no-restore",
"shortName": ""
}
}
}

View File

@@ -0,0 +1,75 @@
{
"$schema": "http://json.schemastore.org/template",
"author": "Electron.NET",
"classifications": [ "Blazor", "Desktop", "Electron", "Web" ],
"identity": "ElectronNET.Core.Templates.Blazor.CSharp",
"groupIdentity": "ElectronNET.Core.Templates.Blazor",
"name": "Electron.NET Blazor App",
"description": "A Blazor Server app hosted in an Electron shell, wired up with Electron.NET.",
"shortName": "electron-blazor",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "ElectronBlazorApp",
"preferNameDirectory": true,
"defaultName": "ElectronBlazorApp",
"symbols": {
"Framework": {
"type": "parameter",
"description": "The target framework for the project.",
"datatype": "choice",
"choices": [
{
"choice": "net10.0",
"description": "Target net10.0"
},
{
"choice": "net8.0",
"description": "Target net8.0"
}
],
"replaces": "net8.0",
"defaultValue": "net8.0"
},
"ElectronVersion": {
"type": "parameter",
"description": "The version of Electron to build the app against.",
"datatype": "text",
"replaces": "ELECTRON_VERSION",
"defaultValue": "38.2.2"
},
"Port": {
"type": "parameter",
"description": "Port number to use for the ASP.NET server during development.",
"datatype": "integer",
"replaces": "8001",
"defaultValue": "8001"
},
"skipRestore": {
"type": "parameter",
"datatype": "bool",
"description": "If specified, skips the automatic restore of the project on create.",
"defaultValue": "false"
}
},
"primaryOutputs": [
{
"path": "ElectronBlazorApp.csproj"
}
],
"postActions": [
{
"id": "restore",
"condition": "(!skipRestore)",
"description": "Restore NuGet packages required by this project.",
"manualInstructions": [
{
"text": "Run 'dotnet restore'"
}
],
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
"continueOnError": true
}
]
}

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="app.css" />
<title>ElectronBlazorApp</title>
<HeadOutlet />
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View File

@@ -0,0 +1,9 @@
@inherits LayoutComponentBase
<div class="page">
<NavMenu />
<main>
@Body
</main>
</div>

View File

@@ -0,0 +1,4 @@
<nav class="nav-menu">
<NavLink href="" Match="NavLinkMatch.All">Home</NavLink>
<NavLink href="counter">Counter</NavLink>
</nav>

View File

@@ -0,0 +1,19 @@
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@@ -0,0 +1,34 @@
@page "/"
@rendermode InteractiveServer
@using ElectronNET.API
@using ElectronNET.API.Entities
<PageTitle>Home</PageTitle>
<h1>Hello from Electron.NET!</h1>
<p>
This Blazor app is served by ASP.NET Core and displayed in an Electron window.
Everything in the <code>ElectronNET.API</code> namespace is available from your components.
</p>
<button class="btn" @onclick="ShowNotification">Show a native notification</button>
<p role="status">@status</p>
@code {
private string? status;
private void ShowNotification()
{
if (HybridSupport.IsElectronActive)
{
Electron.Notification.Show(new NotificationOptions("ElectronBlazorApp", "Hello from Blazor!"));
status = "Notification sent.";
}
else
{
status = "Not running inside Electron.";
}
}
}

View File

@@ -0,0 +1,6 @@
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>

View File

@@ -0,0 +1,9 @@
@using System.Net.Http
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.JSInterop
@using ElectronBlazorApp
@using ElectronBlazorApp.Components
@using ElectronBlazorApp.Components.Layout

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Electron hosts the app out of process -->
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
<PropertyGroup Label="ElectronNetCommon">
<ElectronPackageId>electron-blazor-app</ElectronPackageId>
<Title>ElectronBlazorApp</Title>
<Description>A Blazor app running in Electron.</Description>
<Version>1.0.0</Version>
<Company>My Company</Company>
<Copyright>Copyright © $(Company)</Copyright>
<ElectronVersion>ELECTRON_VERSION</ElectronVersion>
<ElectronSingleInstance>true</ElectronSingleInstance>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ElectronNET.Core" Version="0.6.0" />
<PackageReference Include="ElectronNET.Core.AspNet" Version="0.6.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,33 @@
using ElectronNET.API;
using ElectronNET.API.Entities;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// Starts the Electron shell and invokes ElectronAppReady once it is up.
builder.UseElectron(args, ElectronAppReady);
var app = builder.Build();
app.UseStaticFiles();
app.UseAntiforgery();
// Fully qualified because ElectronNET.API also defines an 'App' type.
app.MapRazorComponents<ElectronBlazorApp.Components.App>()
.AddInteractiveServerRenderMode();
app.Run();
static async Task ElectronAppReady()
{
var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
{
Width = 1152,
Height = 940,
Show = false,
});
window.OnReadyToShow += () => window.Show();
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<DeleteExistingFiles>true</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>false</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<DeleteExistingFiles>true</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>false</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<DeleteExistingFiles>true</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>false</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,22 @@
{
"compression": "maximum",
"linux": {
"target": [ "AppImage" ],
"executableArgs": [ "--no-sandbox" ]
},
"win": {
"target": [
{
"target": "nsis",
"arch": "x64"
}
]
},
"mac": {
"target": [ "dmg" ]
},
"nsis": {
"oneClick": true,
"perMachine": false
}
}

View File

@@ -0,0 +1,11 @@
{
"profiles": {
"ElectronBlazorApp": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:8001/"
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,49 @@
:root {
color-scheme: light dark;
}
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
line-height: 1.5;
}
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.nav-menu {
display: flex;
gap: 1rem;
padding: 0.75rem 1.5rem;
background-color: #1b1b1f;
}
.nav-menu a {
color: #d7d7d7;
text-decoration: none;
}
.nav-menu a.active {
color: #fff;
font-weight: 600;
}
main {
padding: 1.5rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
background-color: #1b6ec2;
color: #fff;
cursor: pointer;
}
.btn:hover {
background-color: #1861ac;
}

View File

@@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "!Config", "!Config", "{02EA
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.AspNet", "ElectronNET.AspNet\ElectronNET.AspNet.csproj", "{DD10D21A-D131-1D9C-33F9-406046E0C5B0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.Templates", "ElectronNET.Templates\ElectronNET.Templates.csproj", "{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronNET.ConsoleApp", "ElectronNET.ConsoleApp\ElectronNET.ConsoleApp.csproj", "{EE38A326-5DE8-AF09-9EB9-DF0878938783}"
ProjectSection(ProjectDependencies) = postProject
{1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6} = {1C5FD66E-A1C6-C436-DF7C-3ECE4FEDDFE6}
@@ -99,6 +101,10 @@ Global
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD10D21A-D131-1D9C-33F9-406046E0C5B0}.Release|Any CPU.Build.0 = Release|Any CPU
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11}.Release|Any CPU.Build.0 = Release|Any CPU
{EE38A326-5DE8-AF09-9EB9-DF0878938783}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE38A326-5DE8-AF09-9EB9-DF0878938783}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE38A326-5DE8-AF09-9EB9-DF0878938783}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -131,6 +137,7 @@ Global
{829FC339-4785-4229-ABA5-53ADB544DA00} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
{8860606D-6847-F22A-5AED-DF4E0984DD24} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
{DD10D21A-D131-1D9C-33F9-406046E0C5B0} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
{3F2A1D64-9C71-4E1B-9A2D-5C7B0E4F8A11} = {1BB6F634-2831-4496-83A6-BC6761DCEC8D}
{EE38A326-5DE8-AF09-9EB9-DF0878938783} = {EDCBFC49-2AEE-4BAF-9368-4409298C52FC}
{015CB06B-6CAE-209F-E050-21C3ACA5FE9F} = {985D39A7-5216-4945-8167-2FD0CB387BD8}
{06CAADC7-DE5B-47B4-AB2A-E9501459A2D1} = {D36CDFFD-3438-42E4-A7FF-88BA19AC4964}