diff --git a/docs/GettingStarted/ASP.Net.md b/docs/GettingStarted/ASP.Net.md
index 953a6410..3c7b36ee 100644
--- a/docs/GettingStarted/ASP.Net.md
+++ b/docs/GettingStarted/ASP.Net.md
@@ -88,11 +88,11 @@ public class Program
#### ASP.NET Port
-If you want to launch a specific URL, you can retrieve the actual ASP.NET port from the new `ElectronNetRuntime` static class, for example:
+If you want to launch a specific URL, you can retrieve the actual ASP.NET port from `ElectronHostEnvironment.Current`, for example:
```csharp
await browserWindow.WebContents
- .LoadURLAsync($"http://localhost:{ElectronNetRuntime.AspNetWebPort}/mypage.html");
+ .LoadURLAsync($"http://localhost:{ElectronNET.Runtime.ElectronHostEnvironment.Current.AspNetWebPort}/mypage.html");
```
### 4. Alternative: IWebHostBuilder Setup
diff --git a/docs/GettingStarted/Console-App.md b/docs/GettingStarted/Console-App.md
index 4a006713..48da298f 100644
--- a/docs/GettingStarted/Console-App.md
+++ b/docs/GettingStarted/Console-App.md
@@ -71,6 +71,7 @@ Here's a complete console application example:
using System;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
+using ElectronNET.Runtime;
namespace MyElectronApp
@@ -78,7 +79,7 @@ public class Program
{
public static async Task Main(string[] args)
{
- var runtimeController = ElectronNetRuntime.RuntimeController;
+ var runtimeController = ElectronHostEnvironment.Current.RuntimeController;
try
{
diff --git a/docs/Using/Startup-Methods.md b/docs/Using/Startup-Methods.md
index 1c684839..f6cdee6a 100644
--- a/docs/Using/Startup-Methods.md
+++ b/docs/Using/Startup-Methods.md
@@ -106,7 +106,7 @@ app.Run();
// Program.cs
public static async Task Main(string[] args)
{
- var runtimeController = ElectronNetRuntime.RuntimeController;
+ var runtimeController = ElectronNET.Runtime.ElectronHostEnvironment.Current.RuntimeController;
await runtimeController.Start();
await runtimeController.WaitReadyTask;
@@ -189,7 +189,7 @@ ElectronNET.Core automatically manages process lifecycle:
Access runtime controller for advanced scenarios:
```csharp
-var runtime = ElectronNetRuntime.RuntimeController;
+var runtime = ElectronNET.Runtime.ElectronHostEnvironment.Current.RuntimeController;
// Wait for Electron to be ready
await runtime.WaitReadyTask;
diff --git a/src/ElectronNET.API/API/HybridSupport.cs b/src/ElectronNET.API/API/HybridSupport.cs
index a84641e7..e59de8ec 100644
--- a/src/ElectronNET.API/API/HybridSupport.cs
+++ b/src/ElectronNET.API/API/HybridSupport.cs
@@ -1,7 +1,9 @@
-namespace ElectronNET.API
+namespace ElectronNET.API
{
+ using ElectronNET.Runtime;
+
///
- ///
+ ///
///
public static class HybridSupport
{
@@ -15,8 +17,8 @@ public static bool IsElectronActive
{
get
{
- return ElectronNetRuntime.RuntimeController != null;
+ return ElectronHostEnvironment.Current.RuntimeController != null;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ElectronNET.API/API/WindowManager.cs b/src/ElectronNET.API/API/WindowManager.cs
index 20a6b193..76ba767b 100644
--- a/src/ElectronNET.API/API/WindowManager.cs
+++ b/src/ElectronNET.API/API/WindowManager.cs
@@ -1,5 +1,6 @@
using ElectronNET.API.Entities;
using ElectronNET.API.Serialization;
+using ElectronNET.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -117,9 +118,10 @@ public async Task CreateWindowAsync(BrowserWindowOptions options,
}
});
- if (loadUrl.Equals("http://localhost", StringComparison.OrdinalIgnoreCase) && ElectronNetRuntime.AspNetWebPort.HasValue)
+ var host = ElectronHostEnvironment.Current;
+ if (loadUrl.Equals("http://localhost", StringComparison.OrdinalIgnoreCase) && host.AspNetWebPort.HasValue)
{
- loadUrl = $"{loadUrl}:{ElectronNetRuntime.AspNetWebPort}";
+ loadUrl = $"{loadUrl}:{host.AspNetWebPort}";
}
// Workaround Windows 10 / Electron Bug
diff --git a/src/ElectronNET.API/Bridge/BridgeConnector.cs b/src/ElectronNET.API/Bridge/BridgeConnector.cs
index 3c06b430..7aedb11f 100644
--- a/src/ElectronNET.API/Bridge/BridgeConnector.cs
+++ b/src/ElectronNET.API/Bridge/BridgeConnector.cs
@@ -2,13 +2,15 @@
// ReSharper disable once CheckNamespace
namespace ElectronNET.API
{
+ using ElectronNET.Runtime;
+
internal static class BridgeConnector
{
public static SocketIoFacade Socket
{
get
{
- return ElectronNetRuntime.GetSocket();
+ return ElectronHostEnvironment.Current.GetSocket();
}
}
}
diff --git a/src/ElectronNET.API/ElectronAppLifetimeEvents.cs b/src/ElectronNET.API/ElectronAppLifetimeEvents.cs
new file mode 100644
index 00000000..f6a52209
--- /dev/null
+++ b/src/ElectronNET.API/ElectronAppLifetimeEvents.cs
@@ -0,0 +1,43 @@
+namespace ElectronNET
+{
+ using System;
+ using System.Threading.Tasks;
+
+ ///
+ /// Represents callbacks that are invoked during different phases of the Electron application
+ /// lifetime. These callbacks allow consumers to hook into the startup sequence more
+ /// granularly than the existing single callback. Callbacks return
+ /// enabling asynchronous work to be awaited before the next phase of the Electron runtime
+ /// commences.
+ ///
+ public class ElectronAppLifetimeEvents
+ {
+ ///
+ /// Gets or sets the callback that is invoked once the Electron process and socket bridge
+ /// have been established but before the Electron ready event has been
+ /// acknowledged. Use this hook to register custom protocols or perform other
+ /// initialization that must occur prior to the ready event.
+ ///
+ public Func OnBeforeReady { get; set; } = () => Task.CompletedTask;
+
+ ///
+ /// Gets or sets the callback that is invoked when the Electron ready event is
+ /// fired. Use this hook to create browser windows or perform post-ready initialization.
+ ///
+ public Func OnReady { get; set; } = () => Task.CompletedTask;
+
+ ///
+ /// Gets or sets the callback that is invoked when the Electron process is about to quit.
+ /// This maps to the will-quit event in Electron and can be used to perform
+ /// graceful shutdown logic. The default implementation does nothing.
+ ///
+ public Func OnWillQuit { get; set; } = () => Task.CompletedTask;
+
+ ///
+ /// Gets or sets the callback that is invoked when the Electron process has fully
+ /// terminated. This can be used for cleanup tasks. The default implementation does
+ /// nothing.
+ ///
+ public Func OnQuit { get; set; } = () => Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/ElectronNetOptions.cs b/src/ElectronNET.API/ElectronNetOptions.cs
new file mode 100644
index 00000000..e25d2422
--- /dev/null
+++ b/src/ElectronNET.API/ElectronNetOptions.cs
@@ -0,0 +1,20 @@
+namespace ElectronNET
+{
+ ///
+ /// Provides configuration options for Electron.NET. Consumers can assign
+ /// an instance of to the
+ /// property to hook into the Electron application lifecycle. Additional
+ /// configuration properties can be added to this class in future versions without
+ /// breaking existing consumers.
+ ///
+ public class ElectronNetOptions
+ {
+ ///
+ /// Gets or sets the collection of lifecycle callbacks. The default value is
+ /// an instance of with no-op
+ /// implementations. Assigning a new instance or modifying individual
+ /// callbacks allows consumers to customize the startup sequence.
+ ///
+ public ElectronAppLifetimeEvents Events { get; set; } = new ElectronAppLifetimeEvents();
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.API/ElectronNetRuntime.cs b/src/ElectronNET.API/ElectronNetRuntime.cs
deleted file mode 100644
index 78d976e8..00000000
--- a/src/ElectronNET.API/ElectronNetRuntime.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-namespace ElectronNET
-{
- using ElectronNET.API;
- using ElectronNET.Runtime;
- using ElectronNET.Runtime.Controllers;
- using ElectronNET.Runtime.Data;
- using System;
- using System.Collections.Immutable;
- using System.Threading.Tasks;
-
- public static class ElectronNetRuntime
- {
- internal static StartupManager StartupManager;
-
- internal const int DefaultSocketPort = 8000;
- internal const int DefaultWebPort = 8001;
- internal const string ElectronPortArgumentName = "electronPort";
- internal const string ElectronPidArgumentName = "electronPID";
-
- /// Initializes the class.
- static ElectronNetRuntime()
- {
- StartupManager = new StartupManager();
- StartupManager.Initialize();
- }
-
- public static string ElectronExtraArguments { get; set; }
-
- public static int? ElectronSocketPort { get; internal set; }
-
- public static int? AspNetWebPort { get; internal set; }
-
- public static StartupMethod StartupMethod { get; internal set; }
-
- public static DotnetAppType DotnetAppType { get; internal set; }
-
- public static string ElectronExecutable { get; internal set; }
-
- public static ImmutableList ProcessArguments { get; internal set; }
-
- public static BuildInfo BuildInfo { get; internal set; }
-
- public static IElectronNetRuntimeController RuntimeController => RuntimeControllerCore;
-
- // The below properties are non-public
- internal static RuntimeControllerBase RuntimeControllerCore { get; set; }
-
- internal static int? ElectronProcessId { get; set; }
-
- internal static Func OnAppReadyCallback { get; set; }
-
- internal static SocketIoFacade GetSocket()
- {
- return RuntimeControllerCore?.Socket;
- }
- }
-}
\ No newline at end of file
diff --git a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs
index 70591674..2299d15c 100644
--- a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs
+++ b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs
@@ -38,15 +38,16 @@ internal override SocketIoFacade Socket
protected override Task StartCore()
{
- var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();
- var electronBinaryName = ElectronNetRuntime.ElectronExecutable;
- var args = string.Format("{0} {1}", ElectronNetRuntime.ElectronExtraArguments, Environment.CommandLine).Trim();
- this.port = ElectronNetRuntime.ElectronSocketPort;
+ var host = ElectronHostEnvironment.InternalHost;
+ var isUnPacked = host.StartupMethod.IsUnpackaged();
+ var electronBinaryName = host.ElectronExecutable;
+ var args = string.Format("{0} {1}", host.ElectronExtraArguments, Environment.CommandLine).Trim();
+ this.port = host.ElectronSocketPort;
if (!this.port.HasValue)
{
- this.port = PortHelper.GetFreePort(ElectronNetRuntime.DefaultSocketPort);
- ElectronNetRuntime.ElectronSocketPort = this.port;
+ this.port = PortHelper.GetFreePort(ElectronHostDefaults.DefaultSocketPort);
+ host.ElectronSocketPort = this.port;
}
Console.Error.WriteLine("[StartCore]: isUnPacked: {0}", isUnPacked);
diff --git a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs
index fdb458f0..8f091ac9 100644
--- a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs
+++ b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs
@@ -36,14 +36,15 @@ internal override SocketIoFacade Socket
protected override Task StartCore()
{
- this.port = ElectronNetRuntime.ElectronSocketPort;
+ var host = ElectronHostEnvironment.InternalHost;
+ this.port = host.ElectronSocketPort;
if (!this.port.HasValue)
{
throw new Exception("No port has been specified by Electron!");
}
- if (!ElectronNetRuntime.ElectronProcessId.HasValue)
+ if (!host.ElectronProcessId.HasValue)
{
throw new Exception("No electronPID has been specified by Electron!");
}
@@ -54,7 +55,7 @@ protected override Task StartCore()
this.socketBridge.Stopped += this.SocketBridge_Stopped;
this.socketBridge.Start();
- this.electronProcess = new ElectronProcessPassive(ElectronNetRuntime.ElectronProcessId.Value);
+ this.electronProcess = new ElectronProcessPassive(host.ElectronProcessId.Value);
this.electronProcess.Ready += this.ElectronProcess_Ready;
this.electronProcess.Stopped += this.ElectronProcess_Stopped;
diff --git a/src/ElectronNET.API/Runtime/ElectronHost.cs b/src/ElectronNET.API/Runtime/ElectronHost.cs
new file mode 100644
index 00000000..a45ead5f
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/ElectronHost.cs
@@ -0,0 +1,57 @@
+namespace ElectronNET.Runtime
+{
+ using System.Collections.Immutable;
+ using ElectronNET.Runtime.Controllers;
+ using ElectronNET.Runtime.Data;
+
+ ///
+ /// Default implementation of that keeps track of the
+ /// runtime state shared between the Electron.NET CLI bootstrapper and ASP.NET
+ /// applications.
+ ///
+ public sealed class ElectronHost : IElectronHost
+ {
+ private readonly StartupManager startupManager;
+
+ public ElectronHost()
+ {
+ this.Options = new ElectronNetOptions();
+ this.startupManager = new StartupManager(this);
+ this.startupManager.Initialize();
+ }
+
+ public string ElectronExtraArguments { get; set; }
+
+ public int? ElectronSocketPort { get; set; }
+
+ public int? AspNetWebPort { get; set; }
+
+ public StartupMethod StartupMethod { get; set; }
+
+ public DotnetAppType DotnetAppType { get; set; }
+
+ public string ElectronExecutable { get; set; }
+
+ public ImmutableList ProcessArguments { get; set; }
+
+ public BuildInfo BuildInfo { get; set; }
+
+ public IElectronNetRuntimeController RuntimeController => this.RuntimeControllerCore;
+
+ public int? ElectronProcessId { get; set; }
+
+ public ElectronNetOptions Options { get; private set; }
+
+ internal RuntimeControllerBase RuntimeControllerCore { get; set; }
+
+ public SocketIoFacade GetSocket()
+ {
+ return this.RuntimeControllerCore?.Socket;
+ }
+
+ public void ApplyOptions(ElectronNetOptions options)
+ {
+ this.Options = options ?? new ElectronNetOptions();
+ }
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/ElectronHostDefaults.cs b/src/ElectronNET.API/Runtime/ElectronHostDefaults.cs
new file mode 100644
index 00000000..c0eb865c
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/ElectronHostDefaults.cs
@@ -0,0 +1,13 @@
+namespace ElectronNET.Runtime
+{
+ ///
+ /// Provides shared default values for the Electron.NET runtime host.
+ ///
+ public static class ElectronHostDefaults
+ {
+ public const int DefaultSocketPort = 8000;
+ public const int DefaultWebPort = 8001;
+ public const string ElectronPortArgumentName = "electronPort";
+ public const string ElectronPidArgumentName = "electronPID";
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/ElectronHostEnvironment.cs b/src/ElectronNET.API/Runtime/ElectronHostEnvironment.cs
new file mode 100644
index 00000000..86130c69
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/ElectronHostEnvironment.cs
@@ -0,0 +1,18 @@
+namespace ElectronNET.Runtime
+{
+ using System;
+
+ ///
+ /// Provides access to the singleton instance that is
+ /// used across the application. Consumers can resolve the same instance from
+ /// dependency injection via services.AddSingleton(ElectronHostEnvironment.Current).
+ ///
+ public static class ElectronHostEnvironment
+ {
+ private static readonly Lazy LazyHost = new(() => new ElectronHost());
+
+ public static IElectronHost Current => LazyHost.Value;
+
+ internal static ElectronHost InternalHost => LazyHost.Value;
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/Helpers/LaunchOrderDetector.cs b/src/ElectronNET.API/Runtime/Helpers/LaunchOrderDetector.cs
index 203fb476..eb32ae84 100644
--- a/src/ElectronNET.API/Runtime/Helpers/LaunchOrderDetector.cs
+++ b/src/ElectronNET.API/Runtime/Helpers/LaunchOrderDetector.cs
@@ -38,7 +38,8 @@ public static bool CheckIsLaunchedByDotNet()
private static bool? CheckIsDotNetStartup1()
{
- var hasPortArg = ElectronNetRuntime.ProcessArguments.Any(e => e.Contains(ElectronNetRuntime.ElectronPortArgumentName, StringComparison.OrdinalIgnoreCase));
+ var host = ElectronHostEnvironment.InternalHost;
+ var hasPortArg = host.ProcessArguments.Any(e => e.Contains(ElectronHostDefaults.ElectronPortArgumentName, StringComparison.OrdinalIgnoreCase));
if (hasPortArg)
{
return false;
@@ -50,7 +51,8 @@ public static bool CheckIsLaunchedByDotNet()
private static bool? CheckIsDotNetStartup2()
{
- var hasPidArg = ElectronNetRuntime.ProcessArguments.Any(e => e.Contains(ElectronNetRuntime.ElectronPidArgumentName, StringComparison.OrdinalIgnoreCase));
+ var host = ElectronHostEnvironment.InternalHost;
+ var hasPidArg = host.ProcessArguments.Any(e => e.Contains(ElectronHostDefaults.ElectronPidArgumentName, StringComparison.OrdinalIgnoreCase));
if (hasPidArg)
{
return false;
diff --git a/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs b/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs
index f5c5f549..3e1e22b8 100644
--- a/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs
+++ b/src/ElectronNET.API/Runtime/Helpers/UnpackagedDetector.cs
@@ -44,7 +44,7 @@ public static bool CheckIsUnpackaged()
private static bool? CheckUnpackaged1()
{
- var cfg = ElectronNetRuntime.BuildInfo.BuildConfiguration;
+ var cfg = ElectronHostEnvironment.InternalHost.BuildInfo.BuildConfiguration;
if (cfg != null)
{
if (cfg.Equals("Debug", StringComparison.OrdinalIgnoreCase))
@@ -90,14 +90,15 @@ public static bool CheckIsUnpackaged()
private static bool? CheckUnpackaged4()
{
- var isUnpackaged = ElectronNetRuntime.ProcessArguments.Any(e => e.Contains("unpacked", StringComparison.OrdinalIgnoreCase));
+ var host = ElectronHostEnvironment.InternalHost;
+ var isUnpackaged = host.ProcessArguments.Any(e => e.Contains("unpacked", StringComparison.OrdinalIgnoreCase));
if (isUnpackaged)
{
return true;
}
- var isPackaged = ElectronNetRuntime.ProcessArguments.Any(e => e.Contains("dotnetpacked", StringComparison.OrdinalIgnoreCase));
+ var isPackaged = host.ProcessArguments.Any(e => e.Contains("dotnetpacked", StringComparison.OrdinalIgnoreCase));
if (isPackaged)
{
return false;
diff --git a/src/ElectronNET.API/Runtime/IElectronHost.cs b/src/ElectronNET.API/Runtime/IElectronHost.cs
new file mode 100644
index 00000000..10ab2f00
--- /dev/null
+++ b/src/ElectronNET.API/Runtime/IElectronHost.cs
@@ -0,0 +1,38 @@
+namespace ElectronNET.Runtime
+{
+ using System.Collections.Immutable;
+ using ElectronNET.Runtime.Controllers;
+ using ElectronNET.Runtime.Data;
+
+ ///
+ /// Represents the mutable runtime state for the Electron.NET host. Consumers can
+ /// resolve this interface from dependency injection to inspect the current
+ /// startup mode, configured ports and the active runtime controller.
+ ///
+ public interface IElectronHost
+ {
+ string ElectronExtraArguments { get; set; }
+
+ int? ElectronSocketPort { get; set; }
+
+ int? AspNetWebPort { get; set; }
+
+ StartupMethod StartupMethod { get; set; }
+
+ DotnetAppType DotnetAppType { get; set; }
+
+ string ElectronExecutable { get; set; }
+
+ ImmutableList ProcessArguments { get; set; }
+
+ BuildInfo BuildInfo { get; set; }
+
+ IElectronNetRuntimeController RuntimeController { get; }
+
+ int? ElectronProcessId { get; set; }
+
+ ElectronNetOptions Options { get; }
+
+ SocketIoFacade GetSocket();
+ }
+}
diff --git a/src/ElectronNET.API/Runtime/StartupManager.cs b/src/ElectronNET.API/Runtime/StartupManager.cs
index 125b6dee..9cf85a7c 100644
--- a/src/ElectronNET.API/Runtime/StartupManager.cs
+++ b/src/ElectronNET.API/Runtime/StartupManager.cs
@@ -11,11 +11,18 @@
internal class StartupManager
{
+ private readonly ElectronHost host;
+
+ public StartupManager(ElectronHost host)
+ {
+ this.host = host ?? throw new ArgumentNullException(nameof(host));
+ }
+
public void Initialize()
{
try
{
- ElectronNetRuntime.BuildInfo = this.GatherBuildInfo();
+ this.host.BuildInfo = this.GatherBuildInfo();
}
catch (Exception ex)
{
@@ -25,19 +32,18 @@ public void Initialize()
this.CollectProcessData();
this.SetElectronExecutable();
+ this.host.StartupMethod = this.DetectAppTypeAndStartup();
+ Console.WriteLine((string)("Evaluated StartupMethod: " + this.host.StartupMethod));
- ElectronNetRuntime.StartupMethod = this.DetectAppTypeAndStartup();
- Console.WriteLine((string)("Evaluated StartupMethod: " + ElectronNetRuntime.StartupMethod));
-
- if (ElectronNetRuntime.DotnetAppType != DotnetAppType.AspNetCoreApp)
+ if (this.host.DotnetAppType != DotnetAppType.AspNetCoreApp)
{
- ElectronNetRuntime.RuntimeControllerCore = this.CreateRuntimeController();
+ this.host.RuntimeControllerCore = this.CreateRuntimeController();
}
}
private RuntimeControllerBase CreateRuntimeController()
{
- switch (ElectronNetRuntime.StartupMethod)
+ switch (this.host.StartupMethod)
{
case StartupMethod.PackagedDotnetFirst:
case StartupMethod.UnpackedDotnetFirst:
@@ -79,29 +85,29 @@ private void CollectProcessData()
{
var argsList = Environment.GetCommandLineArgs().ToImmutableList();
- ElectronNetRuntime.ProcessArguments = argsList;
+ this.host.ProcessArguments = argsList;
- var portArg = argsList.FirstOrDefault(e => e.Contains(ElectronNetRuntime.ElectronPortArgumentName, StringComparison.OrdinalIgnoreCase));
+ var portArg = argsList.FirstOrDefault(e => e.Contains(ElectronHostDefaults.ElectronPortArgumentName, StringComparison.OrdinalIgnoreCase));
if (portArg != null)
{
var parts = portArg.Split('=', StringSplitOptions.TrimEntries);
if (parts.Length > 1 && int.TryParse(parts[1], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result))
{
- ElectronNetRuntime.ElectronSocketPort = result;
+ this.host.ElectronSocketPort = result;
Console.WriteLine("Use Electron Port: " + result);
}
}
- var pidArg = argsList.FirstOrDefault(e => e.Contains(ElectronNetRuntime.ElectronPidArgumentName, StringComparison.OrdinalIgnoreCase));
+ var pidArg = argsList.FirstOrDefault(e => e.Contains(ElectronHostDefaults.ElectronPidArgumentName, StringComparison.OrdinalIgnoreCase));
if (pidArg != null)
{
var parts = pidArg.Split('=', StringSplitOptions.TrimEntries);
if (parts.Length > 1 && int.TryParse(parts[1], NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var result))
{
- ElectronNetRuntime.ElectronProcessId = result;
+ this.host.ElectronProcessId = result;
Console.WriteLine("Electron Process ID: " + result);
}
@@ -110,7 +116,7 @@ private void CollectProcessData()
private void SetElectronExecutable()
{
- string executable = ElectronNetRuntime.BuildInfo.ElectronExecutable;
+ string executable = this.host.BuildInfo.ElectronExecutable;
if (string.IsNullOrEmpty(executable))
{
throw new Exception("AssemblyMetadataAttribute 'ElectronExecutable' could not be found!");
@@ -121,7 +127,7 @@ private void SetElectronExecutable()
executable += ".exe";
}
- ElectronNetRuntime.ElectronExecutable = executable;
+ this.host.ElectronExecutable = executable;
}
private BuildInfo GatherBuildInfo()
@@ -162,7 +168,7 @@ private BuildInfo GatherBuildInfo()
if (isAspNet?.Length > 0 && bool.TryParse(isAspNet, out var isAspNetActive) && isAspNetActive)
{
- ElectronNetRuntime.DotnetAppType = DotnetAppType.AspNetCoreApp;
+ this.host.DotnetAppType = DotnetAppType.AspNetCoreApp;
}
if (isSingleInstance?.Length > 0 && bool.TryParse(isSingleInstance, out var isSingleInstanceActive) && isSingleInstanceActive)
@@ -176,7 +182,7 @@ private BuildInfo GatherBuildInfo()
if (httpPort?.Length > 0 && int.TryParse(httpPort, out var port))
{
- ElectronNetRuntime.AspNetWebPort = port;
+ this.host.AspNetWebPort = port;
}
}
diff --git a/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs b/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs
index 0283bea1..59247a5f 100644
--- a/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs
+++ b/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs
@@ -40,7 +40,18 @@ public static class WebApplicationBuilderExtensions
///
public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func onAppReadyCallback)
{
- builder.WebHost.UseElectron(args, onAppReadyCallback);
+ return UseElectron(builder, options =>
+ {
+ options.Events = new()
+ {
+ OnReady = onAppReadyCallback
+ };
+ });
+ }
+
+ public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, Action configure)
+ {
+ builder.WebHost.UseElectron(configure);
return builder;
}
diff --git a/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs
index c6078365..1aa9280b 100644
--- a/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs
+++ b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs
@@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Threading.Tasks;
+ using ElectronNET;
using ElectronNET.AspNet;
using ElectronNET.AspNet.Runtime;
using ElectronNET.Runtime;
@@ -59,10 +60,38 @@ public static class WebHostBuilderExtensions
///
public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func onAppReadyCallback)
{
- ElectronNetRuntime.OnAppReadyCallback = onAppReadyCallback;
+ if (onAppReadyCallback == null)
+ {
+ throw new ArgumentNullException(nameof(onAppReadyCallback));
+ }
+
+ // Backwards compatible overload – wraps the single callback into the new options model.
+ return UseElectron(builder, options =>
+ {
+ options.Events.OnReady = onAppReadyCallback;
+ });
+ }
+
+ ///
+ /// Adds Electron.NET support to the current ASP.NET Core web host with granular lifecycle
+ /// configuration. The provided allows registration of callbacks
+ /// for different phases of the Electron runtime.
+ ///
+ public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, Action configure)
+ {
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
+
+ var options = new ElectronNetOptions();
+ configure(options);
+
+ var host = ElectronHostEnvironment.InternalHost;
+ host.ApplyOptions(options);
- var webPort = PortHelper.GetFreePort(ElectronNetRuntime.AspNetWebPort ?? ElectronNetRuntime.DefaultWebPort);
- ElectronNetRuntime.AspNetWebPort = webPort;
+ var webPort = PortHelper.GetFreePort(host.AspNetWebPort ?? ElectronHostDefaults.DefaultWebPort);
+ host.AspNetWebPort = webPort;
// check for the content folder if its exists in base director otherwise no need to include
// It was used before because we are publishing the project which copies everything to bin folder and contentroot wwwroot was folder there.
@@ -81,8 +110,9 @@ public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[]
{
services.AddTransient();
services.AddSingleton();
+ services.AddSingleton(host);
- switch (ElectronNetRuntime.StartupMethod)
+ switch (host.StartupMethod)
{
case StartupMethod.PackagedElectronFirst:
case StartupMethod.UnpackedElectronFirst:
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs
index 65487df8..bc9ba3bb 100644
--- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs
@@ -2,8 +2,10 @@
{
using System;
using System.Threading.Tasks;
+ using ElectronNET;
using ElectronNET.API;
using ElectronNET.Common;
+ using ElectronNET.Runtime;
using ElectronNET.Runtime.Controllers;
using ElectronNET.Runtime.Data;
using ElectronNET.Runtime.Services.SocketBridge;
@@ -20,7 +22,7 @@ protected RuntimeControllerAspNetBase(AspNetLifetimeAdapter aspNetLifetimeAdapte
this.aspNetLifetimeAdapter.Stopping += this.AspNetLifetimeAdapter_Stopping;
this.aspNetLifetimeAdapter.Stopped += this.AspNetLifetimeAdapter_Stopped;
- ElectronNetRuntime.RuntimeControllerCore = this;
+ ElectronHostEnvironment.InternalHost.RuntimeControllerCore = this;
}
internal override SocketBridgeService SocketBridge => this.socketBridge;
@@ -81,9 +83,22 @@ protected void HandleStopped()
(this.aspNetLifetimeAdapter.IsNullOrStopped()))
{
this.TransitionState(LifetimeState.Stopped);
+
+ // Everything is fully stopped – fire the OnQuit callback.
+ Task.Run(this.RunQuitCallback);
}
}
+ ///
+ /// Invoked when ASP.NET lifetime enters Stopping (ApplicationStopping).
+ /// We only trigger the OnWillQuit callback here; the actual state
+ /// transition to Stopping is handled in .
+ ///
+ protected void HandleStopping()
+ {
+ Task.Run(this.RunWillQuitCallback);
+ }
+
protected abstract override Task StopCore();
private void SocketBridge_Ready(object sender, EventArgs e)
@@ -108,11 +123,68 @@ private void AspNetLifetimeAdapter_Stopped(object sender, EventArgs e)
private void AspNetLifetimeAdapter_Stopping(object sender, EventArgs e)
{
+ this.HandleStopping();
+ }
+
+ private async Task RunWillQuitCallback()
+ {
+ var events = ElectronHostEnvironment.InternalHost.Options?.Events;
+ var handler = events?.OnWillQuit;
+
+ if (handler == null)
+ {
+ return;
+ }
+
+ try
+ {
+ await handler().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Exception while executing OnWillQuit callback.\n" + ex);
+ // We are already stopping; no need to call this.Stop() here.
+ }
+ }
+
+ private async Task RunQuitCallback()
+ {
+ var events = ElectronHostEnvironment.InternalHost.Options?.Events;
+ var handler = events?.OnQuit;
+
+ if (handler == null)
+ {
+ return;
+ }
+
+ try
+ {
+ await handler().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Exception while executing OnQuit callback.\n" + ex);
+ }
}
private async Task RunReadyCallback()
{
- if (ElectronNetRuntime.OnAppReadyCallback == null)
+ var events = ElectronHostEnvironment.InternalHost.Options?.Events;
+ if (events?.OnBeforeReady != null)
+ {
+ try
+ {
+ await events.OnBeforeReady().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Exception while executing OnBeforeReady callback. Stopping...\n" + ex);
+ this.Stop();
+ return;
+ }
+ }
+
+ if (events.OnReady == null)
{
Console.WriteLine("Warning: Non OnReadyCallback provided in UseElectron() setup.");
return;
@@ -120,7 +192,7 @@ private async Task RunReadyCallback()
try
{
- await ElectronNetRuntime.OnAppReadyCallback().ConfigureAwait(false);
+ await events.OnReady().ConfigureAwait(false);
}
catch (Exception ex)
{
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs
index 4c762915..5eaedad0 100644
--- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs
@@ -3,6 +3,7 @@
using System;
using System.Threading.Tasks;
using ElectronNET.Common;
+ using ElectronNET.Runtime;
using ElectronNET.Runtime.Data;
using ElectronNET.Runtime.Helpers;
using ElectronNET.Runtime.Services.ElectronProcess;
@@ -20,15 +21,16 @@ public RuntimeControllerAspNetDotnetFirst(AspNetLifetimeAdapter aspNetLifetimeAd
protected override Task StartCore()
{
- var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();
- var electronBinaryName = ElectronNetRuntime.ElectronExecutable;
+ var host = ElectronNET.Runtime.ElectronHostEnvironment.InternalHost;
+ var isUnPacked = host.StartupMethod.IsUnpackaged();
+ var electronBinaryName = host.ElectronExecutable;
var args = Environment.CommandLine;
- this.port = ElectronNetRuntime.ElectronSocketPort;
+ this.port = host.ElectronSocketPort;
if (!this.port.HasValue)
{
- this.port = PortHelper.GetFreePort(ElectronNetRuntime.DefaultSocketPort);
- ElectronNetRuntime.ElectronSocketPort = this.port;
+ this.port = PortHelper.GetFreePort(ElectronHostDefaults.DefaultSocketPort);
+ host.ElectronSocketPort = this.port;
}
this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, this.port.Value);
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs
index c9eb0697..0c6be056 100644
--- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs
@@ -2,6 +2,7 @@
{
using System;
using System.Threading.Tasks;
+ using ElectronNET.Runtime;
using ElectronNET.Runtime.Data;
using ElectronNET.Runtime.Services.ElectronProcess;
@@ -18,21 +19,22 @@ public RuntimeControllerAspNetElectronFirst(AspNetLifetimeAdapter aspNetLifetime
protected override Task StartCore()
{
- this.port = ElectronNetRuntime.ElectronSocketPort;
+ var host = ElectronHostEnvironment.InternalHost;
+ this.port = host.ElectronSocketPort;
if (!this.port.HasValue)
{
throw new Exception("No port has been specified by Electron!");
}
- if (!ElectronNetRuntime.ElectronProcessId.HasValue)
+ if (!host.ElectronProcessId.HasValue)
{
throw new Exception("No electronPID has been specified by Electron!");
}
this.CreateSocketBridge(this.port!.Value);
- this.electronProcess = new ElectronProcessPassive(ElectronNetRuntime.ElectronProcessId.Value);
+ this.electronProcess = new ElectronProcessPassive(host.ElectronProcessId.Value);
this.electronProcess.Stopped += this.ElectronProcess_Stopped;
this.electronProcess.Start();
diff --git a/src/ElectronNET.ConsoleApp/Program.cs b/src/ElectronNET.ConsoleApp/Program.cs
index 858473cc..22f98105 100644
--- a/src/ElectronNET.ConsoleApp/Program.cs
+++ b/src/ElectronNET.ConsoleApp/Program.cs
@@ -1,4 +1,5 @@
using ElectronNET.API;
+using ElectronNET.Runtime;
namespace ElectronNET.WebApp
{
@@ -10,7 +11,7 @@ public class Program
{
public static async Task Main(string[] args)
{
- var runtimeController = ElectronNetRuntime.RuntimeController;
+ var runtimeController = ElectronHostEnvironment.Current.RuntimeController;
try
{
diff --git a/src/ElectronNET.IntegrationTests/ElectronFixture.cs b/src/ElectronNET.IntegrationTests/ElectronFixture.cs
index cfaf8df2..4457a3be 100644
--- a/src/ElectronNET.IntegrationTests/ElectronFixture.cs
+++ b/src/ElectronNET.IntegrationTests/ElectronFixture.cs
@@ -4,6 +4,7 @@ namespace ElectronNET.IntegrationTests
using System.Reflection;
using ElectronNET.API;
using ElectronNET.API.Entities;
+ using ElectronNET.Runtime;
// Shared fixture that starts Electron runtime once
[SuppressMessage("ReSharper", "MethodHasAsyncOverload")]
@@ -19,8 +20,9 @@ public async Task InitializeAsync()
AppDomain.CurrentDomain.SetData("ElectronTestAssembly", Assembly.GetExecutingAssembly());
Console.WriteLine("[ElectronFixture] Acquire RuntimeController");
- var runtimeController = ElectronNetRuntime.RuntimeController;
- ElectronNetRuntime.ElectronExtraArguments = "--no-sandbox";
+ var host = ElectronHostEnvironment.Current;
+ var runtimeController = host.RuntimeController;
+ host.ElectronExtraArguments = "--no-sandbox";
Console.Error.WriteLine("[ElectronFixture] Starting Electron runtime...");
await runtimeController.Start();
@@ -55,7 +57,7 @@ public async Task InitializeAsync()
public async Task DisposeAsync()
{
- var runtimeController = ElectronNetRuntime.RuntimeController;
+ var runtimeController = ElectronHostEnvironment.Current.RuntimeController;
Console.Error.WriteLine("[ElectronFixture] Stopping Electron runtime...");
await runtimeController.Stop();
await runtimeController.WaitStoppedTask;
diff --git a/src/ElectronNET.WebApp/Controllers/CrashHangController.cs b/src/ElectronNET.WebApp/Controllers/CrashHangController.cs
index e298dede..c75addf3 100644
--- a/src/ElectronNET.WebApp/Controllers/CrashHangController.cs
+++ b/src/ElectronNET.WebApp/Controllers/CrashHangController.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using ElectronNET.API;
using ElectronNET.API.Entities;
+using ElectronNET.Runtime;
namespace ElectronNET.WebApp.Controllers
{
@@ -12,7 +13,8 @@ public IActionResult Index()
{
Electron.IpcMain.On("process-crash", async (args) =>
{
- string viewPath = $"http://localhost:{ElectronNetRuntime.AspNetWebPort}/crashhang/processcrash";
+ var host = ElectronHostEnvironment.Current;
+ string viewPath = $"http://localhost:{host.AspNetWebPort}/crashhang/processcrash";
var browserWindow = await Electron.WindowManager.CreateWindowAsync(viewPath);
browserWindow.WebContents.OnCrashed += async (killed) =>
@@ -38,7 +40,8 @@ public IActionResult Index()
Electron.IpcMain.On("process-hang", async (args) =>
{
- string viewPath = $"http://localhost:{ElectronNetRuntime.AspNetWebPort}/crashhang/processhang";
+ var host = ElectronHostEnvironment.Current;
+ string viewPath = $"http://localhost:{host.AspNetWebPort}/crashhang/processhang";
var browserWindow = await Electron.WindowManager.CreateWindowAsync(viewPath);
browserWindow.OnUnresponsive += async () =>
diff --git a/src/ElectronNET.WebApp/Controllers/WindowsController.cs b/src/ElectronNET.WebApp/Controllers/WindowsController.cs
index eb037af1..5ef06dd1 100644
--- a/src/ElectronNET.WebApp/Controllers/WindowsController.cs
+++ b/src/ElectronNET.WebApp/Controllers/WindowsController.cs
@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Mvc;
using ElectronNET.API;
using ElectronNET.API.Entities;
+using ElectronNET.Runtime;
namespace ElectronNET.WebApp.Controllers
{
@@ -11,7 +12,8 @@ public IActionResult Index()
{
if (HybridSupport.IsElectronActive)
{
- string viewPath = $"http://localhost:{ElectronNetRuntime.AspNetWebPort}/windows/demowindow";
+ var host = ElectronHostEnvironment.Current;
+ string viewPath = $"http://localhost:{host.AspNetWebPort}/windows/demowindow";
Electron.IpcMain.On("new-window", async (args) =>
{
diff --git a/src/ElectronNET.WebApp/Program.cs b/src/ElectronNET.WebApp/Program.cs
index d3a36968..f064a92d 100644
--- a/src/ElectronNET.WebApp/Program.cs
+++ b/src/ElectronNET.WebApp/Program.cs
@@ -5,6 +5,7 @@
namespace ElectronNET.WebApp
{
+ using System;
using System.Threading.Tasks;
using ElectronNET.API.Entities;
@@ -19,24 +20,32 @@ public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureLogging((hostingContext, logging) => { logging.AddConsole(); })
- .UseElectron(args, ElectronBootstrap)
+ .UseElectron(ElectronBootstrap)
.UseStartup();
}
- public static async Task ElectronBootstrap()
+ private static void ElectronBootstrap(ElectronNetOptions options)
{
- //AddDevelopmentTests();
-
- var browserWindow = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
+ options.Events = new()
{
- Width = 1152,
- Height = 940,
- Show = false
- });
+ OnBeforeReady = async () =>
+ {
+ Console.WriteLine("Firing before ready callback!");
+ },
+ OnReady = async () =>
+ {
+ var window = await Electron.WindowManager.CreateWindowAsync(new BrowserWindowOptions
+ {
+ Width = 1152,
+ Height = 940,
+ Show = false
+ });
- await browserWindow.WebContents.Session.ClearCacheAsync();
+ await window.WebContents.Session.ClearCacheAsync();
- browserWindow.OnReadyToShow += () => browserWindow.Show();
+ window.OnReadyToShow += window.Show;
+ }
+ };
}
private static void AddDevelopmentTests()