From 066efa80e20f97d319def02fe787169a91637d39 Mon Sep 17 00:00:00 2001 From: Aeon Date: Sat, 20 Jun 2026 01:24:30 +0200 Subject: [PATCH 1/5] Add service for resolving on-app-ready callback --- .../Services/AppReadyCallbackResolver.cs | 37 +++++++++++++++++++ .../Services/IAppReadyCallbackResolver.cs | 11 ++++++ 2 files changed, 48 insertions(+) create mode 100644 src/ElectronNET.AspNet/Runtime/Services/AppReadyCallbackResolver.cs create mode 100644 src/ElectronNET.AspNet/Runtime/Services/IAppReadyCallbackResolver.cs diff --git a/src/ElectronNET.AspNet/Runtime/Services/AppReadyCallbackResolver.cs b/src/ElectronNET.AspNet/Runtime/Services/AppReadyCallbackResolver.cs new file mode 100644 index 00000000..b1e95447 --- /dev/null +++ b/src/ElectronNET.AspNet/Runtime/Services/AppReadyCallbackResolver.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; + +namespace ElectronNET.AspNet.Runtime +{ + internal class AppReadyCallbackResolver : IAppReadyCallbackResolver + { + private readonly Func _callback; + + public AppReadyCallbackResolver() + { } + + public AppReadyCallbackResolver(Func callback) + { + _callback = callback; + } + + public AppReadyCallbackResolver(string[] args, Func callback) + { + _callback = () => callback.Invoke(args); + } + + public AppReadyCallbackResolver(IServiceProvider serviceProvider, Func callback) + { + _callback = () => callback.Invoke(serviceProvider); + } + + public AppReadyCallbackResolver(IServiceProvider serviceProvider, string[] args, Func callback) + { + _callback = () => callback.Invoke(serviceProvider, args); + } + + public bool HasCallback => _callback != null; + + public Task Invoke() => _callback?.Invoke() ?? Task.CompletedTask; + } +} diff --git a/src/ElectronNET.AspNet/Runtime/Services/IAppReadyCallbackResolver.cs b/src/ElectronNET.AspNet/Runtime/Services/IAppReadyCallbackResolver.cs new file mode 100644 index 00000000..7fc84942 --- /dev/null +++ b/src/ElectronNET.AspNet/Runtime/Services/IAppReadyCallbackResolver.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; + +namespace ElectronNET.AspNet.Runtime +{ + internal interface IAppReadyCallbackResolver + { + public bool HasCallback { get; } + + public Task Invoke(); + } +} From 8e900655b9ffb7cb40a0fb527d06350dfbae7c69 Mon Sep 17 00:00:00 2001 From: Aeon Date: Sat, 20 Jun 2026 01:26:00 +0200 Subject: [PATCH 2/5] Replace static property with resolver service --- src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs | 10 +++++++++- .../Runtime/Controllers/RuntimeControllerAspNetBase.cs | 10 ++++++---- .../Controllers/RuntimeControllerAspNetDotnetFirst.cs | 2 +- .../RuntimeControllerAspNetElectronFirst.cs | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs index 553ffda1..e40f232b 100644 --- a/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs +++ b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs @@ -61,8 +61,16 @@ public static class WebHostBuilderExtensions /// public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func onAppReadyCallback) { - ElectronNetRuntime.OnAppReadyCallback = onAppReadyCallback; + builder.ConfigureServices(services => + { + services.AddSingleton(_ => new AppReadyCallbackResolver(onAppReadyCallback)); + }); + return UseElectronCore(builder, args); + } + + private static IWebHostBuilder UseElectronCore(IWebHostBuilder builder, string[] args) + { // no matter how this is set - let's unset to prevent Electron not starting as expected // e.g., VS Code sets this env variable, but this will cause `require("electron")` to not // work as expected, see issue #952 diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs index b7d2f338..f214e8a3 100644 --- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs +++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs @@ -17,13 +17,15 @@ internal abstract class RuntimeControllerAspNetBase : RuntimeControllerBase { private readonly IServer server; private readonly AspNetLifetimeAdapter aspNetLifetimeAdapter; + private readonly IAppReadyCallbackResolver callbackResolver; private readonly IElectronAuthenticationService authenticationService; private SocketBridgeService socketBridge; - protected RuntimeControllerAspNetBase(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) + protected RuntimeControllerAspNetBase(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null) { this.server = server; this.aspNetLifetimeAdapter = aspNetLifetimeAdapter; + this.callbackResolver = callbackResolver; this.authenticationService = authenticationService; this.aspNetLifetimeAdapter.Ready += this.AspNetLifetimeAdapter_Ready; this.aspNetLifetimeAdapter.Stopping += this.AspNetLifetimeAdapter_Stopping; @@ -130,15 +132,15 @@ private void AspNetLifetimeAdapter_Stopping(object sender, EventArgs e) private async Task RunReadyCallback() { - if (ElectronNetRuntime.OnAppReadyCallback == null) + if (!callbackResolver.HasCallback) { - Console.WriteLine("Warning: Non OnReadyCallback provided in UseElectron() setup."); + Console.WriteLine("Warning: No OnReadyCallback provided in UseElectron() setup."); return; } try { - await ElectronNetRuntime.OnAppReadyCallback().ConfigureAwait(false); + await callbackResolver.Invoke().ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs index 7a94732a..615e7d34 100644 --- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs +++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs @@ -14,7 +14,7 @@ internal class RuntimeControllerAspNetDotnetFirst : RuntimeControllerAspNetBase { private ElectronProcessBase electronProcess; - public RuntimeControllerAspNetDotnetFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, authenticationService) + public RuntimeControllerAspNetDotnetFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, callbackResolver, authenticationService) { } diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs index 757507d0..a377fcca 100644 --- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs +++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs @@ -11,7 +11,7 @@ internal class RuntimeControllerAspNetElectronFirst : RuntimeControllerAspNetBas { private ElectronProcessBase electronProcess; - public RuntimeControllerAspNetElectronFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, authenticationService) + public RuntimeControllerAspNetElectronFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IAppReadyCallbackResolver callbackResolver, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, callbackResolver, authenticationService) { } From 27a8c69a9a09b049f50fc79033ebcdaf9fdee809 Mon Sep 17 00:00:00 2001 From: Aeon Date: Sat, 20 Jun 2026 01:26:25 +0200 Subject: [PATCH 3/5] Add missing extension method overloads --- .../API/WebApplicationBuilderExtensions.cs | 93 ++++++++++++ .../API/WebHostBuilderExtensions.cs | 138 ++++++++++++++++++ 2 files changed, 231 insertions(+) diff --git a/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs b/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs index 0283bea1..7a68fd64 100644 --- a/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs +++ b/src/ElectronNET.AspNet/API/WebApplicationBuilderExtensions.cs @@ -44,5 +44,98 @@ public static WebApplicationBuilder UseElectron(this WebApplicationBuilder build return builder; } + + /// + /// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback. + /// + /// The to extend. + /// The command-line arguments passed to the process, forwarded to Electron. + /// + /// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization. + /// + /// + /// The same instance to enable fluent configuration. + /// + /// + /// + /// var builder = WebApplication.CreateBuilder(args) + /// .UseElectron(args, async (processArgs) => + /// { + /// // Create the main browser window or perform other startup tasks. + /// }); + /// + /// var app = builder.Build(); + /// app.MapRazorPages(); + /// app.Run(); + /// + /// + public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func onAppReadyCallback) + { + builder.WebHost.UseElectron(args, onAppReadyCallback); + + return builder; + } + + /// + /// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback. + /// + /// The to extend. + /// The command-line arguments passed to the process, forwarded to Electron. + /// + /// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization. + /// + /// + /// The same instance to enable fluent configuration. + /// + /// + /// + /// var builder = WebApplication.CreateBuilder(args) + /// .UseElectron(args, async (serviceProvider) => + /// { + /// // Create the main browser window or perform other startup tasks. + /// }); + /// + /// var app = builder.Build(); + /// app.MapRazorPages(); + /// app.Run(); + /// + /// + public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func onAppReadyCallback) + { + builder.WebHost.UseElectron(args, onAppReadyCallback); + + return builder; + } + + /// + /// Adds Electron.NET support to the current ASP.NET Core application and registers an application-ready callback. + /// + /// The to extend. + /// The command-line arguments passed to the process, forwarded to Electron. + /// + /// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization. + /// + /// + /// The same instance to enable fluent configuration. + /// + /// + /// + /// var builder = WebApplication.CreateBuilder(args) + /// .UseElectron(args, async (serviceProvider, processArgs) => + /// { + /// // Create the main browser window or perform other startup tasks. + /// }); + /// + /// var app = builder.Build(); + /// app.MapRazorPages(); + /// app.Run(); + /// + /// + public static WebApplicationBuilder UseElectron(this WebApplicationBuilder builder, string[] args, Func onAppReadyCallback) + { + builder.WebHost.UseElectron(args, onAppReadyCallback); + + return builder; + } } } \ No newline at end of file diff --git a/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs index e40f232b..2812474f 100644 --- a/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs +++ b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs @@ -69,6 +69,144 @@ public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] return UseElectronCore(builder, args); } + /// + /// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback. + /// + /// The to extend. + /// The command-line arguments passed to the process. + /// + /// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization. + /// + /// + /// The same instance to enable fluent configuration. + /// + /// + /// + /// using Microsoft.AspNetCore.Hosting; + /// using Microsoft.Extensions.Hosting; + /// using ElectronNET.API; + /// + /// public class Program + /// { + /// public static void Main(string[] args) + /// { + /// Host.CreateDefaultBuilder(args) + /// .ConfigureWebHostDefaults(webBuilder => + /// { + /// webBuilder.UseStartup<Startup>(); + /// webBuilder.UseElectron(args, async (processArgs) => + /// { + /// // Create the main browser window or perform other startup tasks. + /// }); + /// }) + /// .Build() + /// .Run(); + /// } + /// } + /// + /// + public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func onAppReadyCallback) + { + builder.ConfigureServices(services => + { + services.AddSingleton(_ => new AppReadyCallbackResolver(args, onAppReadyCallback)); + }); + + return UseElectronCore(builder, args); + } + + /// + /// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback. + /// + /// The to extend. + /// The command-line arguments passed to the process. + /// + /// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization. + /// + /// + /// The same instance to enable fluent configuration. + /// + /// + /// + /// using Microsoft.AspNetCore.Hosting; + /// using Microsoft.Extensions.Hosting; + /// using ElectronNET.API; + /// + /// public class Program + /// { + /// public static void Main(string[] args) + /// { + /// Host.CreateDefaultBuilder(args) + /// .ConfigureWebHostDefaults(webBuilder => + /// { + /// webBuilder.UseStartup<Startup>(); + /// webBuilder.UseElectron(args, async (serviceProvider) => + /// { + /// // Create the main browser window or perform other startup tasks. + /// }); + /// }) + /// .Build() + /// .Run(); + /// } + /// } + /// + /// + public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func onAppReadyCallback) + { + builder.ConfigureServices(services => + { + services.AddSingleton(provider => new AppReadyCallbackResolver(provider, onAppReadyCallback)); + }); + + return UseElectronCore(builder, args); + } + + /// + /// Adds Electron.NET support to the current ASP.NET Core web host and registers an application-ready callback. + /// + /// The to extend. + /// The command-line arguments passed to the process. + /// + /// An asynchronous callback invoked when the Electron app is ready. Use this to create windows or perform initialization. + /// + /// + /// The same instance to enable fluent configuration. + /// + /// + /// + /// using Microsoft.AspNetCore.Hosting; + /// using Microsoft.Extensions.Hosting; + /// using ElectronNET.API; + /// + /// public class Program + /// { + /// public static void Main(string[] args) + /// { + /// Host.CreateDefaultBuilder(args) + /// .ConfigureWebHostDefaults(webBuilder => + /// { + /// webBuilder.UseStartup<Startup>(); + /// webBuilder.UseElectron(args, async (serviceProvider, processArgs) => + /// { + /// // Create the main browser window or perform other startup tasks. + /// }); + /// }) + /// .Build() + /// .Run(); + /// } + /// } + /// + /// + public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func onAppReadyCallback) + { + builder.ConfigureServices(services => + { + services.AddSingleton(provider => new AppReadyCallbackResolver(provider, args, onAppReadyCallback)); + }); + + return UseElectronCore(builder, args); + } + private static IWebHostBuilder UseElectronCore(IWebHostBuilder builder, string[] args) { // no matter how this is set - let's unset to prevent Electron not starting as expected From 056661028b4770df8f03ed7f9894655de49bc04d Mon Sep 17 00:00:00 2001 From: Aeon Date: Sat, 20 Jun 2026 01:26:55 +0200 Subject: [PATCH 4/5] Remove unused property --- src/ElectronNET.API/ElectronNetRuntime.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ElectronNET.API/ElectronNetRuntime.cs b/src/ElectronNET.API/ElectronNetRuntime.cs index 3a285c4b..97fa2ed1 100644 --- a/src/ElectronNET.API/ElectronNetRuntime.cs +++ b/src/ElectronNET.API/ElectronNetRuntime.cs @@ -50,8 +50,6 @@ static ElectronNetRuntime() internal static int? ElectronProcessId { get; set; } - internal static Func OnAppReadyCallback { get; set; } - internal static ISocketConnection GetSocket() { return RuntimeControllerCore?.Socket; From 3a10e7e98d167f6855f61c5006a185a7bf3e9e37 Mon Sep 17 00:00:00 2001 From: Aeon Date: Sat, 20 Jun 2026 01:38:58 +0200 Subject: [PATCH 5/5] Add missing null-check --- .../Runtime/Services/AppReadyCallbackResolver.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ElectronNET.AspNet/Runtime/Services/AppReadyCallbackResolver.cs b/src/ElectronNET.AspNet/Runtime/Services/AppReadyCallbackResolver.cs index b1e95447..67b16494 100644 --- a/src/ElectronNET.AspNet/Runtime/Services/AppReadyCallbackResolver.cs +++ b/src/ElectronNET.AspNet/Runtime/Services/AppReadyCallbackResolver.cs @@ -17,17 +17,26 @@ public AppReadyCallbackResolver(Func callback) public AppReadyCallbackResolver(string[] args, Func callback) { - _callback = () => callback.Invoke(args); + if (callback != null) + { + _callback = () => callback.Invoke(args); + } } public AppReadyCallbackResolver(IServiceProvider serviceProvider, Func callback) { - _callback = () => callback.Invoke(serviceProvider); + if (callback != null) + { + _callback = () => callback.Invoke(serviceProvider); + } } public AppReadyCallbackResolver(IServiceProvider serviceProvider, string[] args, Func callback) { - _callback = () => callback.Invoke(serviceProvider, args); + if (callback != null) + { + _callback = () => callback.Invoke(serviceProvider, args); + } } public bool HasCallback => _callback != null;