C# WebView2 change user-agent is not working? - c#

here is my code:
public MainWindow() {
this.InitializeComponent();
this.Title = "mainWindow";
Task.Run(async () => {
await this.webView.EnsureCoreWebView2Async();
this.webView.CoreWebView2.Settings.UserAgent = "windows/webview2";
});
}
but in devtool, the user-agent remains unchanged:
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54

Your code doesn't work because you try to access the WebView from another thread than the main ui thread.
Instead of running the code in a separate thread, execute it in a CoreWebView2Initialized event handler:
public sealed partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
// Set eventhandler
webView.CoreWebView2Initialized += WebView_CoreWebView2Initialized;
}
private void WebView_CoreWebView2Initialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
{
// Set custom user agent string in Initialized event
sender.CoreWebView2.Settings.UserAgent = "Test/Stackoverflow";
}
private async void myButton_Click(object sender, RoutedEventArgs e)
{
myButton.Content = "Clicked";
// Browse to website
await webView.EnsureCoreWebView2Async();
webView.Source = new Uri("https://www.browserlookup.com/");
}
}
This works for me in most cases. There is still a known bug in the WebView2 that means that the custom user agent name is not used when a specific website is visited for the first time. Funnily enough, the only website I could reproduce the bug with is "whatismybrowser.com"...

Related

CefBrowser offset render when first loaded

Attached is a screenshot as well as the code. There isn't much of it.
When I first open the window, everything is rendered with an offset.
If I close the window and reopen it, it renders correctly the second time.
public partial class frmTwinklyLogin : Form
{
public ChromiumWebBrowser webBrowser;
public frmTwinklyLogin()
{
InitializeComponent();
InitializeBrowser();
}
private void frmTwinklyLogin_Load(object sender, EventArgs e)
{
}
private void InitializeBrowser()
{
webBrowser = new ChromiumWebBrowser("https://api.twinkly.com/v2/auth/login?response_type=code&client_id=omen-hp-client&redirect_uri=http://localhost:8487/login");
webBrowser.AddressChanged += Browser_AddressChanged;
this.Controls.Add(webBrowser);
webBrowser.Dock = DockStyle.Fill;
webBrowser.Refresh();
}
private void Browser_AddressChanged(object sender, AddressChangedEventArgs e)
{
var x = e.Address;
if (x.Contains("code="))
{
var code = x.Split('=')[1];
TwinklyWebApi.SetCode(code);
}
}
}
and the initialization of the library is in application load.
static void Main()
{
CefSettings settings = new CefSettings();
Cef.Initialize(settings);
I can only assume I've got something in the wrong event. Either that or my monitor dpi is screwing with it. Note: It's merely rendered off-center. The click areas for the controls are still where they are supposed to be, which makes the page unusable.

e.NewWindow = (CoreWebView2)sender still results in a separate instance

I want to navigate to the URL rather than it opening a separate instance.
No matter what I do it still opens another instance of WebView2.
private void CoreWebView2_NewWindowRequested(object sender,
CoreWebView2NewWindowRequestedEventArgs e)
{
//e.NewWindow = webView21.CoreWebView2;
e.NewWindow = (CoreWebView2)sender;
//e.Handled = true;
}
here's the original post, what do I need to do for it to handle the new window request?
To receive a notification when a new Popup Windows is requested, subscribe to the the NewWindowRequested of CoreWebView2.
The event is raised when there's a request to generate a Popup. Clicking a link that just cause the Browser to navigate to a different URI doesn't raise the event (no popup).
A new Popup Window can be requested if the User clicks the Open link in new window (sic) option of the standard ContextMenu provided by the Browser.
Or if the Web Page generates one without user intervention.
Unfortunately, the e.IsUserInitiated property is always true, so you may have a hard time determining (without injecting JavaScripts) whether the popup should be blocked (in case you want to, that is).
When a new Window is requested, you can block it indiscriminately setting e.Handled = true
If you want to open the new Window URI to the same Window, you can specify either:
e.Handled = true;
e.NewWindow = (CoreWebView2)sender;
// or
e.Handled = true;
((CoreWebView2)sender).Navigate(e.Uri);
Sample WebView2 main handler Form:
using Microsoft.Web.WebView2.Core;
public partial class MainForm : Form
{
public MainForm() => InitializeComponent();
protected override async void OnLoad(EventArgs e)
{
base.OnLoad(e);
webView2.CoreWebView2InitializationCompleted += OnCoreWebView2InitializationCompleted;
var env = await CoreWebView2Environment.CreateAsync(null, null, null);
await webView2.EnsureCoreWebView2Async(env);
webView2.Source = new Uri("https://www.somesite.com");
}
private void OnCoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
webView2.CoreWebView2.NewWindowRequested += OnNewWindowRequested;
}
private void OnNewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
{
// Open the Uri requested in the current Window
e.Handled = true;
((CoreWebView2)sender).Navigate(e.Uri);
}
// Or, if you want to handle Popup Windows using your own Form template
// => Note that it's the same event handler as above, pick one, not both!
private void OnNewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
{
// Open the Uri requested in a new instance of the PopupWindow Form
var deferral = e.GetDeferral();
e.Handled = true;
var popup = new PopupWindow(deferral, e);
popup.Show();
}
}
If you instead want to create a new Form that will show the popup, you need a Form template (it can be just a Form that contains a WebView2 Control) that receive the CoreWebView2Deferral returned by e.GetDeferral().
In the initialization procedure of this Form check whether the CoreWebView2Deferral object is null. If it's not, complete the deferred event by calling its Complete() method.
Then subscribe to the NewWindowRequested event to perform the same action when a new Popup Window is requested (unless you want to block it).
Of course you can show these Forms inside a Tabbed Control, to generate a standard tabbed view, common in all WebBrowsers.
Sample PopupWindow Form:
using Microsoft.Web.WebView2.Core;
using System.Windows.Forms;
public partial class PopupWindow : Form
{
public PopupWindow() => InitializeComponent();
public PopupWindow(CoreWebView2Deferral deferral, CoreWebView2NewWindowRequestedEventArgs args)
: this() {
Core2Deferral = deferral;
NewWindowArgs = args;
}
protected virtual CoreWebView2Deferral Core2Deferral { get; private set; }
protected virtual CoreWebView2NewWindowRequestedEventArgs NewWindowArgs { get; private set; }
protected async override void OnLoad(EventArgs e)
{
base.OnLoad(e);
webView2.CoreWebView2InitializationCompleted += OnCoreWebView2InitializationCompleted;
var env = await CoreWebView2Environment.CreateAsync(null, null, null);
await webView2.EnsureCoreWebView2Async(env);
}
private void OnCoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
{
webView2.CoreWebView2.Settings.AreDefaultContextMenusEnabled = true;
if (Core2Deferral != null) {
NewWindowArgs.NewWindow = webView2.CoreWebView2;
Core2Deferral.Complete();
}
webView2.CoreWebView2.NewWindowRequested += OnNewWindowRequested;
}
private void OnNewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
{
e.Handled = true;
var popup = new PopupWindow(e.GetDeferral(), e);
popup.Show();
}
}
How about making it works like real web browsers. Simply, opens in an new Tab. Just with a few lines of codes ;)
At first, create a new class which inherits from WebView2 and include a TabControl field inside it:
internal class WebViewInTab:WebView2
{
TabControl tabCtrl;
public WebViewInTab(TabControl tabCtrl) :base()
{
Dock = DockStyle.Fill; // necessary for showing
this.tabCtrl = tabCtrl; // for adding new TabPage controls
CoreWebView2InitializationCompleted += WebViewInTab_CoreWebView2InitializationCompleted;
}
Then, you'll use this custom webview2 to create new webview2 objects every time CoreWebView2.NewWindowRequested event is raised. So to handle the event:
private void WebViewInTab_CoreWebView2InitializationCompleted(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
{
CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested; // This is the man
CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged; // Just cosmetic code
}
After that, you'll handle the new window by yourself, i.e. , Just add a new TabPage control with our custom webview2 to the TabControl TabPages collection. Of course, not forgeting the Uri from handler arg.
private void CoreWebView2_NewWindowRequested(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NewWindowRequestedEventArgs e)
{
e.Handled = true; // let the default new window
TabPage tpage = new TabPage(); // boy
tpage.Controls.Add(new WebViewInTab(tabCtrl) { Source = new Uri(e.Uri)}); // toy
tabCtrl.TabPages.Add(tpage); // daddy
tabCtrl.SelectedTab = tpage; // user expectation
}
//Just cosmetic code
private void CoreWebView2_DocumentTitleChanged(object? sender, object e)
{
int last = tabCtrl.TabPages.Count - 1;
tabCtrl.TabPages[last].Text = CoreWebView2.DocumentTitle
}
}
Finally, :) start the recursion-ready operation in the main app form constructor.
public Form1()
{
InitializeComponent();
string uriAdd = "https://www.w3schools.com/";
tabControl1.TabPages[0].Controls.Add(new WebViewInTab(tabControl1) { Source = new Uri(uriAdd) });
}

Hosting an ASP Web Api inside a WPF app wont stop gracefully

I have an Asp Core 3.1 app hosting a rest API. It can run standalone, but I have a WPF app that I want to be able to also host the rest API while it is running.
I have gotten this working, but my problem is that when I close my last WPF window, I tell ASP's IHost to shutdown and it leaves the process open. I have recreated the problem with brand new projects with just a couple modifications:
In the WPF project, I have removed the StartupURI and use the Startup and Exit events:
private void Application_Startup(object sender, StartupEventArgs e)
{
RestApiProgram.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
MainWindow window = new MainWindow();
window.Show();
}
private void Application_Exit(object sender, ExitEventArgs e)
{
RestApiProgram.StopAsync(CancellationToken.None).GetAwaiter().GetResult();
}
In the Asp project, I have modified the Program class so I can call start and stop on the IHost:
private static IHost _host;
//Main is called when running the ASP project by itself
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
//StartAsync is called by the WPF app
public static Task StartAsync(CancellationToken token)
{
_host = CreateHostBuilder(Array.Empty<string>()).Build();
return _host.StartAsync(token);
}
public static async Task StopAsync(CancellationToken token)
{
using (_host)
{
await _host.StopAsync(token);
}
}
When I close the main window, my WPF app calls StopAsync, I see the message "Microsoft.Hosting.Lifetime: Information: Application is shutting down..." in the output, but the process does not shut down. When I pause, it is stuck waiting for the StopAsync to complete. Any ideas why?
You should await the Task returned by StopAsync.
To prevent the WPF app from shutting down before the task has completed, you could handle the OnClosing event of the window:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Closing += WhenClosing;
}
...
private async void WhenClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
await RestApiProgram.StopAsync(CancellationToken.None);
this.Closing -= WhenClosing;
Close();
}
}
The same goes for StartAsync:
private async void Application_Startup(object sender, StartupEventArgs e)
{
await RestApiProgram.StartAsync(CancellationToken.None);
MainWindow window = new MainWindow();
window.Show();
}
Calling .GetAwaiter().GetResult() is almost always a bad idea as it might cause a deadlock.

Software freeze after Puppeteer launch

I have created a small application which should get some data from internet trought Puppeteer Sharp, the problem's that after I instantiate the browser the software freeze without no error.
public partial class MainWindow : Window
{
public Handler Handler { get; } = new Handler();
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Handler.Init(Handler).Wait();
}
}
as you can see I have Handler which contains all the properties of the software:
public class Handler
{
private static Url URL = new Url("https://www.diretta.it/");
public static Browser Browser { get; set; }
public async Task<bool> Init(Handler prop)
{
DotNetEnv.Env.Load("./config.env");
// The problem's here
Browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
ExecutablePath = Environment.GetEnvironmentVariable("CHROME_PATH"),
});
return true;
}
}
where CHROME_PATH is this: CHROME_PATH="C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe"
what I did wrong? I have the latest version of Chrome and PuppeteerSharp too.
Change your Window_Loaded event method to async and do an await on the Init method, Event Handler methods are an exception. using async void instead of async Task is ok in this scenario. - Reference (Should I avoid 'async void' event handlers?):
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
await Handler.Init(Handler);
}

GUI and server application

I have a question regarding WPF and server application. I was given a task to write a simple TCP server with a GUI. I'm new to C# (and GUIs in general), hence I have a question.
I have 2 classes:
App.xaml.cs
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
MainWindow window = new MainWindow();
if (e.Args.Length != 1)
{
MessageBox.Show("Wrong number of arguments!", "An error has occured", MessageBoxButton.OK, MessageBoxImage.Error);
Environment.Exit(1);
}
window.Show();
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ShowConnectionsButton_Click(object sender, RoutedEventArgs e)
{
LogsTextBox.Text += "text\n";
}
}
that are both initially generated by Visual Studio. I assume that MainWindow.xaml is for handling GUI-related stuff and App.xaml is for application's logic. So, my (simple) question is, how should I start the server part? Should it be
server = new Server();
server.start();
window.Show();
or maybe
window.Show();
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
server = new Server();
server.start();
}).Start();
or maybe use BackgroundWorker?
Where you put the server logic will depend on how you want your UI to behave.
Usually, you will want the window to load separately with loading/retrieving data.
You could put your server call in the Loaded event. For example:
public void OnLoad(object sender, RoutedEventArgs e)
{
server = new Server();
server.start();
...
}
This will be called when the window has loaded, and can be started. How you update data bindings will depend on how your server object is built.

Categories

Resources