Electron.Net window not opening in blazor server app (.Net 6.0) - blazor-server-side

I am trying to create a new Blazor server application working with Electron.Net on .Net 6.0.
Unlike .Net 5.0 and previous, app configuration and startup code is now done in Program.cs class, as explained here.
What is unclear to me at this stage is how to convert the required Electron.Net lines in .Net 6.0.
From the documentation, we should add the following lines
// -- in Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseElectron(args); // <= THIS LINE
webBuilder.UseStartup<Startup>();
});
// -- in Startup.cs
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
// Open the Electron-Window here
Task.Run(async () => await Electron.WindowManager.CreateWindowAsync());
}
Now in .Net 6.0 we have only the Program.cs class remaining, so these two lines should be converted to :
// -- Program.cs .Net 6.0
public static void Main(string[] args) {
var builder = WebApplication.CreateBuilder(args);
// --- Add electron...
builder.WebHost.UseElectron(args);
....
// -- run the app
app.Run();
// --- if running Electron - should window be created here - this way?
Task.Run(async () => await Electron.WindowManager.CreateWindowAsync());
}
I then run the application as electronize start /PublishSingleFile false
Output shows app is running but the Electron window is not showing up. Here the console output:
ElectronHostHook handling started...
Invoke electron.cmd - in dir: C:\myProject\obj\Host\node_modules\.bin
electron.cmd "..\..\main.js"
Electron Socket IO Port: 8000
Electron Socket started on port 8000 at 127.0.0.1
ASP.NET Core Port: 8001
stdout: info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:5001
stdout: info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
stdout: info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\myProject\obj\Host\bin\
And navigating in my brower to https://localhost:5001 shows the running app. As it seems, electron is not running properly despite being started.

Think I figured it out. Need all these for it to work.
builder.Services.AddElectron();
builder.WebHost.UseElectron(args);
if (HybridSupport.IsElectronActive)
{
var window = await Electron.WindowManager.CreateWindowAsync();
window.OnClosed += () =>
{
Electron.App.Quit();
};
}

Just making clear the main method may NOT be async, which leads to this code :
// --- Add electron...
builder.Services.AddElectron();
builder.WebHost.UseElectron(args);
if (HybridSupport.IsElectronActive) {
// Open the Electron-Window here
Task.Run(async () => {
var window = await Electron.WindowManager.CreateWindowAsync();
window.OnClosed += () => {
Electron.App.Quit();
};
});
}

Related

VUE2 + Electron + Flask -> Python not spawning on build

I've just finished my first vue+electron+flask project and I am having quite a hard time trying to package it. Everything is workig "perfectly" when using "npm run electron:serve" but when running "npm run electron:build" I do not get any error, but Flask is not launched at all. I do not really know how to fix the problem, my guess is that when building the dist folder the path to app.py is not correct, but I tried to fix it without luck.
Here is the background.js code:
'use strict'
import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
async function createWindow() {
// spawn flask app (https://medium.com/red-buffer/integrating-python-flask-backend-with-electron-nodejs-frontend-8ac621d13f72)
var python = require('child_process').spawn('py', ['../server/app.py']);
python.stdout.on('data', function (data) {
console.log("data: ", data.toString('utf8'));
});
python.stderr.on('data', (data) => {
console.log(`stderr: ${data}`); // when error
});
// Create the browser window.
const win = new BrowserWindow({
width: 1500,
height: 1200,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION
}
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html')
}
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
The relevant part of the code calling app.py is the following:
async function createWindow() {
// spawn flask app (https://medium.com/red-buffer/integrating-python-flask-backend-with-electron-nodejs-frontend-8ac621d13f72)
var python = require('child_process').spawn('py', ['../server/app.py']);
python.stdout.on('data', function (data) {
console.log("data: ", data.toString('utf8'));
});
python.stderr.on('data', (data) => {
console.log(`stderr: ${data}`); // when error
});
// Create the browser window.
const win = new BrowserWindow({
width: 1500,
height: 1200,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION
}
})
I tried to put 3 dots insted of 2 in the app.py path ['.../server/app.py] just in case when creating the dist folder I need this extra dot to find the app.py file, but this is not working either.
My folder structure is the follwing:
Vue-Electron
client
dist_electron
node_modules
public
src
assets
components
router
views
App.vue
background.js
main.js
other config files
server
data
env
app.py
requirements.txt
other python scripts imported to app.py
sqlite_portofolio.db
As this program will only be used by me in my personal pc, I did not want to bother using pyInstaller (I thought it would be easier to not package the python side, but if I am wrong please let me know). I would like to have a electron .exe file that I can just doble click to open the electron build and then spawn the Flask server.
Also, my feeling is that I am not killing the Flask server correctly when closing the app. I think Flask is still running when closing electron. What should I do to ensure Flask server is properly closed.
There is not a lot of information of those topics that I can follow. Any help will be aprreaciated.
I´m having the same problem. I followed the link to this article (https://medium.com/red-buffer/integrating-python-flask-backend-with-electron-nodejs-frontend-8ac621d13f72), and it has the answer about killing the python flask server. And if you follow everything the article says, it's supposed to run the backend when opening the electron.exe, but this is not happening here on my end.
EDIT: I found the error, you need to change the path on your spawn. I sugest you to run the electron.exe on the cmd so you can see the error on it, so you will see the path that spawn is trying to run.
it´s probably:
var python = require('child_process').spawn('py', ['../resources/app/server/app.py']);
you will need to acess the app.py through [resources/app] as spawn start at the base dir of the electron build.
PS: I used electron-packeger that´s why mine need to add resources/app, and I used pyinstaller on my backend
Hope it will help you.

Logging LogLevel-Information not showing in Windows event log [duplicate]

This question already has answers here:
How to write logs to EventLog by ILogger<T> in Asp.net Core?
(3 answers)
Closed 12 months ago.
I ran into some problems with logging **Information** Logs to Windows event log.
Starting from a blank ASP.NET Core-Web-API .Net 5
I edited the following to Program.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddEventLog(eventLogSettings =>
{
eventLogSettings.SourceName = "MyTestLog";
});
});
webBuilder.UseStartup<Startup>();
});
and wrote some sample logs into a Controler
public IEnumerable<WeatherForecast> Get()
{
_logger.LogCritical("Critical Log");
_logger.LogError("Error Log");
_logger.LogWarning("Warning Log");
_logger.LogInformation("Info Log");
_logger.LogDebug("Debug Log");
_logger.LogTrace("Trace Log");
...
}
If I Run it, it shows logs for Critical - Error - Warning - Information in Console. Matching what is configured in AppSettings.
appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
In Windows event log it only shows Critical, Error and Warning -Logs, not caring for my appsettings whatsoever.
I'm sure this is an easy configuration problem, but i don't find any documentation for this.
How do I get Logs with Loglevel-Information in Windows event log ?
According to this article, you could find the ASP.NET Core provide the event logging feature. Unlike the other providers, the EventLog provider does not inherit the default non-provider settings.
If EventLog log settings aren't specified, they default to LogLevel.Warning.
To log events lower than LogLevel.Warning, explicitly set the log level. The following example sets the Event Log default log level to LogLevel.Information:
"Logging": {
"EventLog": {
"LogLevel": {
"Default": "Information"
}
}
}

Setup Asp.Net Core app to launch Vue SPA with webpack and HMR

I'm trying to set up a new application using ASP.NET Core 3.0 and Vue. I've been struggling to set it up properly so that the backend and frontend servers are correctly integrated, with proper routing and working HMR. I do have it working now, but I can't tell if this is the correct way do this, and there are some pieces that I don't feel are working as well as they can.
My current setup:
Webpack is configured to output files into wwwroot. I also have HMR and
WriteFilePlugin enabled when running webpack-dev-server:
mode: 'development',
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, '../wwwroot'),
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
// write files to fs instead of memory
// so that the server can access and serve index.html
new WriteFilePlugin(),
],
devServer: {
hot: true,
watchOptions: {
poll: true,
},
port: 9001,
},
In Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
.....
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "wwwroot";
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
if (env.IsDevelopment())
{
if (System.Diagnostics.Debugger.IsAttached)
{
// run the dev server
endpoints.MapToVueCliProxy("{*path}", new SpaOptions { SourcePath = "ClientApp" },
npmScript: "serve", regex: "Compiled successfully");
}
}
// serve index.html file when no api endpoints are matched
endpoints.MapFallbackToFile("index.html");
}
My problems with this setup:
Typically, HMR builds files in memory, but I have it set up here to write the new files to the file system so that the backend server can access and serve the index.html. This works, but now I have tons of bloat being written to my file system. Is there a way to use the in-memory HMR in conjunction with the ASP.NET server?
The proxying isn't always reliable; I often get errors in the console that it can't connect to the server. Additionally, index.html sometimes gets cached and I have to do a hard reload. Can this be made more reliable somehow?

How do you await long running process from ASPNET CORE SignalR Hub

I have an application flow where:
Clientside Javascript triggers signalR Hub
Asynchronous Call is made
for long running operation
When operation is complete Clientside JavaScript is notified by signalR
I had assumed this would be as simple as:
Server Side:
public async void SendMessage()
{
await Task.Delay(1000);
Clients.All.SendAsync("ReceiveMessage");
}
Client Side:
var hub = new signalR.HubConnectionBuilder().withUrl('/disposeBugHub').build();
this.hub.on("ReceiveMessage", function () {
alert('Message Received');
});
this.hub.start()
.then(function () {
hub.invoke("SendMessage");
})
.catch(function (err) {
return console.error(err.toString());
});
However the Clients.All.SendAsync("ReceiveMessage"); call always throws a System.ObjectDisposedException: 'Cannot access a disposed object.' Exception.
This appears to be expected behavoir and not a bug, so my question is how do i programmatically acheive the desired workflow? I assume there must be a well known pattern to acheive this but I cant find it online.
First of all , remove void method for long running process. use Task return type method .
Try this.
public async Task SendMessage()
{
await Task.Delay(10000);
Clients.All.SendAsync("ReceiveMessage");
}

NET CORE - How to create Home Page in API?

Everytime my api is started, it's executed with LOCALHOST:PORT/api/values/. How to LOCALHOST:PORT/ with a static home page?
In your project, locate your launchSettings.json file. In visual studio you will need to expand Properties to find it from solution explorer or use Ctrl + T. This file contains an array of profiles. Each profile has a launchUrl field where you can mention your path as empty.
As of adding a content in the home page, you can always make a middleware as follows:
app.Use(async (context, _next) => {
if (string.IsNullOrEmpty(context.Request.Path.ToString())
|| context.Request.Path.ToString() == "/")
{
context.Response.StatusCode = 200;
await context.Response.WriteAsync("Web API is now running.");
}
else
await _next();
});
You can always have an action, But I would recommend using a middleware like the above.
possible duplicate of How to set start page in dotnet core web api?
I assume you mean having a default page when the user navigates to http://localhost instead of calling http://localhost/api/controller.
In .net core 2 it's fairy easy to do. You can use static files if you only want to show a simple static page by adding
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...other code here...
app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "index.html" } });
app.UseDefaultFiles();
app.UseStaticFiles();
}
and making sure there is an index.html in the wwwroot folder.
or you could use routing in mvc
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}");
});
see answer by aslan at https://stackoverflow.com/a/40651363/3786363
oh and unless your server is mapped to port 80 you will probably need to call localhost:port not just localhost.
Are you looking for something like this?
$.ajax({
url: "/api/values/METHOD/?PARAM=0",
type: "GET",
dataType: "json",
cache: false,
statusCode: {
200: function (data) {
//Do stuff
}
}
});
Anything running within the solution context will start at the root.