diff --git a/src/ElectronNET.AspNet/API/ServiceCollectionExtensions.cs b/src/ElectronNET.AspNet/API/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..b12f636
--- /dev/null
+++ b/src/ElectronNET.AspNet/API/ServiceCollectionExtensions.cs
@@ -0,0 +1,33 @@
+namespace ElectronNET.API
+{
+ using Microsoft.Extensions.DependencyInjection;
+
+ ///
+ ///
+ ///
+ public static class ServiceCollectionExtensions
+ {
+ ///
+ /// Adds the Members to the Service Collection
+ ///
+ public static IServiceCollection AddElectron(this IServiceCollection services)
+ => services
+ // adding in this manner to ensure late binding.
+ .AddSingleton(_ => IpcMain.Instance)
+ .AddSingleton(_ => App.Instance)
+ .AddSingleton(_ => AutoUpdater.Instance)
+ .AddSingleton(_ => WindowManager.Instance)
+ .AddSingleton(_ => Menu.Instance)
+ .AddSingleton(_ => Dialog.Instance)
+ .AddSingleton(_ => Notification.Instance)
+ .AddSingleton(_ => Tray.Instance)
+ .AddSingleton(_ => GlobalShortcut.Instance)
+ .AddSingleton(_ => Shell.Instance)
+ .AddSingleton(_ => Screen.Instance)
+ .AddSingleton(_ => Clipboard.Instance)
+ .AddSingleton(_ => HostHook.Instance)
+ .AddSingleton(_ => PowerMonitor.Instance)
+ .AddSingleton(_ => NativeTheme.Instance)
+ .AddSingleton(_ => Dock.Instance);
+ }
+}
diff --git a/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs
new file mode 100644
index 0000000..c607836
--- /dev/null
+++ b/src/ElectronNET.AspNet/API/WebHostBuilderExtensions.cs
@@ -0,0 +1,103 @@
+namespace ElectronNET.API
+{
+ using System;
+ using System.IO;
+ using System.Threading.Tasks;
+ using ElectronNET.AspNet;
+ using ElectronNET.AspNet.Runtime;
+ using ElectronNET.Runtime;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Helpers;
+ using Microsoft.AspNetCore.Hosting;
+ using Microsoft.Extensions.DependencyInjection;
+
+ ///
+ /// Provides extension methods for to enable Electron.NET
+ /// integration in ASP.NET Core applications (including Razor Pages) using the WebHost-based hosting model.
+ ///
+ ///
+ /// Call this extension during web host configuration (for example, inside ConfigureWebHostDefaults in Program.cs)
+ /// to wire up Electron with any command-line arguments and an optional application-ready callback.
+ ///
+ public static class WebHostBuilderExtensions
+ {
+ ///
+ /// 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 () =>
+ /// {
+ /// // Create the main browser window or perform other startup tasks.
+ /// });
+ /// })
+ /// .Build()
+ /// .Run();
+ /// }
+ /// }
+ ///
+ ///
+ public static IWebHostBuilder UseElectron(this IWebHostBuilder builder, string[] args, Func onAppReadyCallback)
+ {
+ ElectronNetRuntime.OnAppReadyCallback = onAppReadyCallback;
+
+ var webPort = PortHelper.GetFreePort(ElectronNetRuntime.AspNetWebPort ?? ElectronNetRuntime.DefaultWebPort);
+ ElectronNetRuntime.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.
+ // now we have implemented the live reload if app is run using /watch then we need to use the default project path.
+ if (Directory.Exists($"{AppDomain.CurrentDomain.BaseDirectory}\\wwwroot"))
+ {
+ builder = builder.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
+ .UseUrls("http://localhost:" + webPort);
+ }
+ else
+ {
+ builder = builder.UseUrls("http://localhost:" + webPort);
+ }
+
+ builder = builder.ConfigureServices(services =>
+ {
+ services.AddTransient();
+ services.AddSingleton();
+
+ switch (ElectronNetRuntime.StartupMethod)
+ {
+ case StartupMethod.PackagedElectronFirst:
+ case StartupMethod.UnpackedElectronFirst:
+ services.AddSingleton();
+ break;
+ case StartupMethod.PackagedDotnetFirst:
+ case StartupMethod.UnpackedDotnetFirst:
+ services.AddSingleton();
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ });
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ElectronNET.AspNet/ElectronNET.AspNet.csproj b/src/ElectronNET.AspNet/ElectronNET.AspNet.csproj
new file mode 100644
index 0000000..d3a95fe
--- /dev/null
+++ b/src/ElectronNET.AspNet/ElectronNET.AspNet.csproj
@@ -0,0 +1,44 @@
+
+
+
+
+
+ net6.0;net8.0
+ ..\..\artifacts
+ $(PackageNamePrefix).AspNet
+ $(PackageId)
+ $(DescriptionFirstPart) This package contains the ASP.Net Core integration.
+ true
+ True
+ snupkg
+ disable
+ ElectronNET
+
+
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+ 1701;1702;4014;CS4014;CA1416;CS1591
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ElectronNET.AspNet/PackageIcon.png b/src/ElectronNET.AspNet/PackageIcon.png
new file mode 100644
index 0000000..10d55eb
Binary files /dev/null and b/src/ElectronNET.AspNet/PackageIcon.png differ
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs
new file mode 100644
index 0000000..65487df
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs
@@ -0,0 +1,132 @@
+namespace ElectronNET.AspNet.Runtime
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.API;
+ using ElectronNET.Common;
+ using ElectronNET.Runtime.Controllers;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Services.SocketBridge;
+
+ internal abstract class RuntimeControllerAspNetBase : RuntimeControllerBase
+ {
+ private readonly AspNetLifetimeAdapter aspNetLifetimeAdapter;
+ private SocketBridgeService socketBridge;
+
+ protected RuntimeControllerAspNetBase(AspNetLifetimeAdapter aspNetLifetimeAdapter)
+ {
+ this.aspNetLifetimeAdapter = aspNetLifetimeAdapter;
+ this.aspNetLifetimeAdapter.Ready += this.AspNetLifetimeAdapter_Ready;
+ this.aspNetLifetimeAdapter.Stopping += this.AspNetLifetimeAdapter_Stopping;
+ this.aspNetLifetimeAdapter.Stopped += this.AspNetLifetimeAdapter_Stopped;
+
+ ElectronNetRuntime.RuntimeControllerCore = this;
+ }
+
+ internal override SocketBridgeService SocketBridge => this.socketBridge;
+
+ internal override SocketIoFacade Socket
+ {
+ get
+ {
+ if (this.State == LifetimeState.Ready)
+ {
+ return this.socketBridge.Socket;
+ }
+
+ throw new Exception("Cannot access socket bridge. Runtime is not in 'Ready' state");
+ }
+ }
+
+ protected void CreateSocketBridge(int port)
+ {
+ this.socketBridge = new SocketBridgeService(port);
+ this.socketBridge.Ready += this.SocketBridge_Ready;
+ this.socketBridge.Stopped += this.SocketBridge_Stopped;
+ this.socketBridge.Start();
+ }
+
+ protected void HandleReady()
+ {
+ if (this.SocketBridge.IsReady() &&
+ this.ElectronProcess.IsReady() &&
+ this.aspNetLifetimeAdapter.IsReady())
+ {
+ this.TransitionState(LifetimeState.Ready);
+ Task.Run(this.RunReadyCallback);
+ }
+ }
+
+ protected void HandleStopped()
+ {
+ this.TransitionState(LifetimeState.Stopping);
+
+ if (this.SocketBridge.IsNotStopped())
+ {
+ this.SocketBridge.Stop();
+ }
+
+ if (this.ElectronProcess.IsNotStopped())
+ {
+ this.ElectronProcess.Stop();
+ }
+
+ if (this.aspNetLifetimeAdapter.IsNotStopped())
+ {
+ this.aspNetLifetimeAdapter.Stop();
+ }
+
+ if ((this.SocketBridge.IsNullOrStopped()) &&
+ (this.ElectronProcess.IsNullOrStopped()) &&
+ (this.aspNetLifetimeAdapter.IsNullOrStopped()))
+ {
+ this.TransitionState(LifetimeState.Stopped);
+ }
+ }
+
+ protected abstract override Task StopCore();
+
+ private void SocketBridge_Ready(object sender, EventArgs e)
+ {
+ this.HandleReady();
+ }
+
+ private void AspNetLifetimeAdapter_Ready(object sender, EventArgs e)
+ {
+ this.HandleReady();
+ }
+
+ private void SocketBridge_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+
+ private void AspNetLifetimeAdapter_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+
+ private void AspNetLifetimeAdapter_Stopping(object sender, EventArgs e)
+ {
+ }
+
+ private async Task RunReadyCallback()
+ {
+ if (ElectronNetRuntime.OnAppReadyCallback == null)
+ {
+ Console.WriteLine("Warning: Non OnReadyCallback provided in UseElectron() setup.");
+ return;
+ }
+
+ try
+ {
+ await ElectronNetRuntime.OnAppReadyCallback().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Exception while executing OnAppReadyCallback. Stopping...\n" + ex);
+ this.Stop();
+ }
+ }
+ }
+}
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs
new file mode 100644
index 0000000..4c76291
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs
@@ -0,0 +1,58 @@
+namespace ElectronNET.AspNet.Runtime
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.Common;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Helpers;
+ using ElectronNET.Runtime.Services.ElectronProcess;
+
+ internal class RuntimeControllerAspNetDotnetFirst : RuntimeControllerAspNetBase
+ {
+ private ElectronProcessBase electronProcess;
+ private int? port;
+
+ public RuntimeControllerAspNetDotnetFirst(AspNetLifetimeAdapter aspNetLifetimeAdapter) : base(aspNetLifetimeAdapter)
+ {
+ }
+
+ internal override ElectronProcessBase ElectronProcess => this.electronProcess;
+
+ protected override Task StartCore()
+ {
+ var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged();
+ var electronBinaryName = ElectronNetRuntime.ElectronExecutable;
+ var args = Environment.CommandLine;
+ this.port = ElectronNetRuntime.ElectronSocketPort;
+
+ if (!this.port.HasValue)
+ {
+ this.port = PortHelper.GetFreePort(ElectronNetRuntime.DefaultSocketPort);
+ ElectronNetRuntime.ElectronSocketPort = this.port;
+ }
+
+ this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, this.port.Value);
+ this.electronProcess.Ready += this.ElectronProcess_Ready;
+ this.electronProcess.Stopped += this.ElectronProcess_Stopped;
+
+ return this.electronProcess.Start();
+ }
+
+ protected override Task StopCore()
+ {
+ this.electronProcess.Stop();
+ return Task.CompletedTask;
+ }
+
+ private void ElectronProcess_Ready(object sender, EventArgs e)
+ {
+ this.TransitionState(LifetimeState.Started);
+ this.CreateSocketBridge(this.port!.Value);
+ }
+
+ private void ElectronProcess_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+ }
+}
diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs
new file mode 100644
index 0000000..c9eb069
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs
@@ -0,0 +1,56 @@
+namespace ElectronNET.AspNet.Runtime
+{
+ using System;
+ using System.Threading.Tasks;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Services.ElectronProcess;
+
+ internal class RuntimeControllerAspNetElectronFirst : RuntimeControllerAspNetBase
+ {
+ private ElectronProcessBase electronProcess;
+ private int? port;
+
+ public RuntimeControllerAspNetElectronFirst(AspNetLifetimeAdapter aspNetLifetimeAdapter) : base(aspNetLifetimeAdapter)
+ {
+ }
+
+ internal override ElectronProcessBase ElectronProcess => this.electronProcess;
+
+ protected override Task StartCore()
+ {
+ this.port = ElectronNetRuntime.ElectronSocketPort;
+
+ if (!this.port.HasValue)
+ {
+ throw new Exception("No port has been specified by Electron!");
+ }
+
+ if (!ElectronNetRuntime.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.Stopped += this.ElectronProcess_Stopped;
+
+ this.electronProcess.Start();
+
+ Task.Run(() => this.TransitionState(LifetimeState.Started));
+
+ return Task.CompletedTask;
+ }
+
+ protected override Task StopCore()
+ {
+ this.electronProcess.Stop();
+ return Task.CompletedTask;
+ }
+
+ private void ElectronProcess_Stopped(object sender, EventArgs e)
+ {
+ this.HandleStopped();
+ }
+ }
+}
diff --git a/src/ElectronNET.AspNet/Runtime/Helpers/ServerReadyStartupFilter.cs b/src/ElectronNET.AspNet/Runtime/Helpers/ServerReadyStartupFilter.cs
new file mode 100644
index 0000000..115a96f
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Helpers/ServerReadyStartupFilter.cs
@@ -0,0 +1,30 @@
+namespace ElectronNET.AspNet
+{
+ using System;
+ using ElectronNET.AspNet.Runtime;
+ using ElectronNET.Runtime;
+ using Microsoft.AspNetCore.Builder;
+ using Microsoft.AspNetCore.Hosting;
+ using Microsoft.Extensions.DependencyInjection;
+
+ internal sealed class ServerReadyStartupFilter : IStartupFilter
+ {
+ ///
+ /// Extends the provided and returns an of the same type.
+ ///
+ /// The Configure method to extend.
+ /// A modified .
+ public Action Configure(Action next)
+ {
+ return app =>
+ {
+ _ = app.ApplicationServices.GetService();
+ var runtimeController = app.ApplicationServices.GetService();
+
+ runtimeController.Start();
+
+ next(app);
+ };
+ }
+ }
+}
diff --git a/src/ElectronNET.AspNet/Runtime/Services/AspNetLifetimeAdapter.cs b/src/ElectronNET.AspNet/Runtime/Services/AspNetLifetimeAdapter.cs
new file mode 100644
index 0000000..9c108a0
--- /dev/null
+++ b/src/ElectronNET.AspNet/Runtime/Services/AspNetLifetimeAdapter.cs
@@ -0,0 +1,27 @@
+namespace ElectronNET.AspNet.Runtime
+{
+ using System.Threading.Tasks;
+ using ElectronNET.Runtime.Data;
+ using ElectronNET.Runtime.Services;
+ using Microsoft.Extensions.Hosting;
+
+ internal class AspNetLifetimeAdapter : LifetimeServiceBase
+ {
+ private readonly IHostApplicationLifetime lifetimeSercice;
+
+ public AspNetLifetimeAdapter(IHostApplicationLifetime lifetimeSercice)
+ {
+ this.lifetimeSercice = lifetimeSercice;
+
+ this.lifetimeSercice.ApplicationStarted.Register(() => this.TransitionState(LifetimeState.Ready));
+ this.lifetimeSercice.ApplicationStopping.Register(() => this.TransitionState(LifetimeState.Stopping));
+ this.lifetimeSercice.ApplicationStopped.Register(() => this.TransitionState(LifetimeState.Stopped));
+ }
+
+ protected override Task StopCore()
+ {
+ this.lifetimeSercice.StopApplication();
+ return Task.CompletedTask;
+ }
+ }
+}