diff --git a/Changelog.md b/Changelog.md index 7730869..a45094a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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) diff --git a/docs/GettingStarted/ASP.Net.md b/docs/GettingStarted/ASP.Net.md index 953a641..8bc8024 100644 --- a/docs/GettingStarted/ASP.Net.md +++ b/docs/GettingStarted/ASP.Net.md @@ -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 diff --git a/docs/GettingStarted/Templates.md b/docs/GettingStarted/Templates.md new file mode 100644 index 0000000..7b2a3b7 --- /dev/null +++ b/docs/GettingStarted/Templates.md @@ -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()` +> 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 diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 6b88356..28af355 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -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) diff --git a/src/ElectronNET.Templates/ElectronNET.Templates.csproj b/src/ElectronNET.Templates/ElectronNET.Templates.csproj new file mode 100644 index 0000000..b3659c2 --- /dev/null +++ b/src/ElectronNET.Templates/ElectronNET.Templates.csproj @@ -0,0 +1,42 @@ + + + + + + netstandard2.0 + ..\..\artifacts + $(PackageNamePrefix).Templates + $(PackageId) + $(DescriptionFirstPart) This package contains the 'dotnet new' project templates. + electron aspnetcore blazor dotnet-new templates + Template + true + false + content + + true + false + false + true + $(NoWarn);NU5128 + disable + + 0.6.0 + + + + + + + + + + + + + + + + + diff --git a/src/ElectronNET.Templates/templates/blazor/.template.config/dotnetcli.host.json b/src/ElectronNET.Templates/templates/blazor/.template.config/dotnetcli.host.json new file mode 100644 index 0000000..c23c37a --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/.template.config/dotnetcli.host.json @@ -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": "" + } + } +} diff --git a/src/ElectronNET.Templates/templates/blazor/.template.config/template.json b/src/ElectronNET.Templates/templates/blazor/.template.config/template.json new file mode 100644 index 0000000..8b5639f --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/.template.config/template.json @@ -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 + } + ] +} diff --git a/src/ElectronNET.Templates/templates/blazor/Components/App.razor b/src/ElectronNET.Templates/templates/blazor/Components/App.razor new file mode 100644 index 0000000..99acfbb --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Components/App.razor @@ -0,0 +1,18 @@ + + + + + + + + + ElectronBlazorApp + + + + + + + + + diff --git a/src/ElectronNET.Templates/templates/blazor/Components/Layout/MainLayout.razor b/src/ElectronNET.Templates/templates/blazor/Components/Layout/MainLayout.razor new file mode 100644 index 0000000..058086a --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Components/Layout/MainLayout.razor @@ -0,0 +1,9 @@ +@inherits LayoutComponentBase + +
+ + +
+ @Body +
+
diff --git a/src/ElectronNET.Templates/templates/blazor/Components/Layout/NavMenu.razor b/src/ElectronNET.Templates/templates/blazor/Components/Layout/NavMenu.razor new file mode 100644 index 0000000..d2d7591 --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Components/Layout/NavMenu.razor @@ -0,0 +1,4 @@ + diff --git a/src/ElectronNET.Templates/templates/blazor/Components/Pages/Counter.razor b/src/ElectronNET.Templates/templates/blazor/Components/Pages/Counter.razor new file mode 100644 index 0000000..bba7e16 --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Components/Pages/Counter.razor @@ -0,0 +1,19 @@ +@page "/counter" +@rendermode InteractiveServer + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/src/ElectronNET.Templates/templates/blazor/Components/Pages/Home.razor b/src/ElectronNET.Templates/templates/blazor/Components/Pages/Home.razor new file mode 100644 index 0000000..76b64ba --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Components/Pages/Home.razor @@ -0,0 +1,34 @@ +@page "/" +@rendermode InteractiveServer +@using ElectronNET.API +@using ElectronNET.API.Entities + +Home + +

Hello from Electron.NET!

+ +

+ This Blazor app is served by ASP.NET Core and displayed in an Electron window. + Everything in the ElectronNET.API namespace is available from your components. +

+ + + +

@status

+ +@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."; + } + } +} diff --git a/src/ElectronNET.Templates/templates/blazor/Components/Routes.razor b/src/ElectronNET.Templates/templates/blazor/Components/Routes.razor new file mode 100644 index 0000000..faa2a8c --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/ElectronNET.Templates/templates/blazor/Components/_Imports.razor b/src/ElectronNET.Templates/templates/blazor/Components/_Imports.razor new file mode 100644 index 0000000..8912ce8 --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Components/_Imports.razor @@ -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 diff --git a/src/ElectronNET.Templates/templates/blazor/ElectronBlazorApp.csproj b/src/ElectronNET.Templates/templates/blazor/ElectronBlazorApp.csproj new file mode 100644 index 0000000..1493efb --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/ElectronBlazorApp.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + OutOfProcess + false + + + + electron-blazor-app + ElectronBlazorApp + A Blazor app running in Electron. + 1.0.0 + My Company + Copyright © $(Company) + ELECTRON_VERSION + true + + + + + + + + diff --git a/src/ElectronNET.Templates/templates/blazor/Program.cs b/src/ElectronNET.Templates/templates/blazor/Program.cs new file mode 100644 index 0000000..3a94ddc --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Program.cs @@ -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() + .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(); +} diff --git a/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/linux-x64.pubxml b/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/linux-x64.pubxml new file mode 100644 index 0000000..4aa6f5d --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/linux-x64.pubxml @@ -0,0 +1,17 @@ + + + + + true + false + false + Release + FileSystem + publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\ + FileSystem + <_TargetId>Folder + net8.0 + linux-x64 + true + + diff --git a/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/osx-arm64.pubxml b/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/osx-arm64.pubxml new file mode 100644 index 0000000..d6d1332 --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/osx-arm64.pubxml @@ -0,0 +1,17 @@ + + + + + true + false + false + Release + FileSystem + publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\ + FileSystem + <_TargetId>Folder + net8.0 + osx-arm64 + true + + diff --git a/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/win-x64.pubxml b/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/win-x64.pubxml new file mode 100644 index 0000000..e2fb6d0 --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Properties/PublishProfiles/win-x64.pubxml @@ -0,0 +1,17 @@ + + + + + true + false + false + Release + FileSystem + publish\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\ + FileSystem + <_TargetId>Folder + net8.0 + win-x64 + true + + diff --git a/src/ElectronNET.Templates/templates/blazor/Properties/electron-builder.json b/src/ElectronNET.Templates/templates/blazor/Properties/electron-builder.json new file mode 100644 index 0000000..168dbf1 --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Properties/electron-builder.json @@ -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 + } +} diff --git a/src/ElectronNET.Templates/templates/blazor/Properties/launchSettings.json b/src/ElectronNET.Templates/templates/blazor/Properties/launchSettings.json new file mode 100644 index 0000000..42be436 --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "ElectronBlazorApp": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:8001/" + } + } +} diff --git a/src/ElectronNET.Templates/templates/blazor/appsettings.Development.json b/src/ElectronNET.Templates/templates/blazor/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/ElectronNET.Templates/templates/blazor/appsettings.json b/src/ElectronNET.Templates/templates/blazor/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/ElectronNET.Templates/templates/blazor/wwwroot/app.css b/src/ElectronNET.Templates/templates/blazor/wwwroot/app.css new file mode 100644 index 0000000..5eff1cb --- /dev/null +++ b/src/ElectronNET.Templates/templates/blazor/wwwroot/app.css @@ -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; +} diff --git a/src/ElectronNET.sln b/src/ElectronNET.sln index 43fbaaf..c19b2cb 100644 --- a/src/ElectronNET.sln +++ b/src/ElectronNET.sln @@ -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}