Press "Enter" to skip to content

.net 5 WebView2 un browser moderno nelle app Desktop

Una delle novità per cui è valsa la pena restare svegli fino a tardi lo scorso novembre per seguire la .Net Conf 2020   è stata la presentazione della WebView2 uno user control che era in sviluppo da tempo e finalmente è divenuto parte della nuova release di .Net. Lo vedremo in una applicazione WPF .net 5 ma è disponibile e funziona anche su WindowsForms e funziona anche sul Full Framework. Era un po’ che volevo esplorarne le possibilità per vedere come usarlo nelle mie applicazioni pertanto condivido qui ciò che ho fatto per dare una mano a chi volesse provarlo.

 

Cosa c’è di nuovo

Perchè agitarsi tanto per un componente? Qualcuno potrebbe chiedersi, beh, avete provato a visualizzare un testo HTML in una applicazione Desktop?

  • NO – siete dei fortunati, non avendo dovuto destreggiarvi con un controllo basato su Internet explorer.
  • SI – Probabilmente comprenderete bene la mia gioia al riguardo avendo dovuto destreggiarvi con un controllo basato su Internet Explorer.

La nuova WebView2 è Open Source (come ormai tutte le piattaforme di sviluppo desktop) ed è basata su Chromium, l’engine che sta dietro a Chrome e a Edge pertanto risponde agli standard più attuali e continuerà a farlo in futuro.

Sappiamo bene che il futuro è il web, che le applicazioni in browser stanno diventando la nuova frontiera, che noi dinosauri del Desktop dobbiamo evolverci. Tutto bello, però avete provato a debuggare una applicazione Javascript? Al momento i miei colleghi che lavorano sul web usano ancora gli Alert (è come debuggare con il printf negli anni ’90). Certamente ci sono nuove cose in arrivo, prima fra tutte Blazor, che vi invito ad iniziare a guardare anche se come me preferite ancora il caro Desktop. Ma intanto, pensiamo al presente e alle nostre applicazioni Desktop che hanno sempre più bisogno di interagire con il Web in modo estensivo.

Ora bando alle chiacchiere e creiamo una applicazione dove fare il primo test.

Pertanto Apriamo Visual Studio 2019 se non lo aveste già scaricato, vi ricordo che la versione Community, che è più che sufficiente per chi si avvicina alla programmazione C# è gratuita e forse perché sono molto viziata, la trovo molto meglio di Visual Studio Code che molti usano. E so che fa figo usare la linea di comando, l’ho fatto per 15 anni abbondanti e preferisco avere tutto sulla mia IDE.

Qui potete Scaricare Visual Studio 2019 Community 

La prima cosa da fare è generare una nuova Applicazione.

Per creare una nuova applicazione WPF scritta per .Net 5 selezioniamo il Template base della stessa, sul vostro Visual Studio potrebbe essere necessario selezionare dei filtri, io, come vedete ce l’ho in cima alle applicazioni più recentemente generate. Ricordate che .Net ora è la versione .Net Core da 3.1 in su mentre .NET Framework è il full framework da .net 3.0 a 4.8.

 

Ho evidenziato il fatto di aver tolto il flag usualmente spuntato che pone la soluzione e il progetto nella stessa cartella. Non fatelo mai, perché se poi volete suddividere il progetto in più moduli, (ad esempio DLL per la gestione del database e User Interface), avere la solution sulla cartella del progetto vuol dire dover lavorare a mano per fare quello che togliere il flag fa automaticamente. Se avete notato, ho anche cambiato nome alla Solution, è solo ai fini dimostrativi, funziona anche lasciando lo stesso nome a Progetto e Soluzione.

La mia solution finale è quella che vedete qui sopra. Non vi spiegherò nuovamente come creare una applicazione WPF, vi elenco solo le cose che ho fatto e poi andremo a elaborare quanto riguarda l’inserimento della WebView2 nella nostra applicazione.

  1. Ho generato la cartella Windows, perché in una app nel mondo reale, tutte le classi sulla cartella principale fanno confusione, pertanto sposto la MainWindow su una sottocartella.
  2. Modifico il Namespace del file xaml e del file .xaml.cs in modo che rispecchi la nuova posizione all’interno del progetto.
  3. Modifico la classe App.xaml togliendo la riga che chiama la MainWindow direttamente dal codice XAML perché anche in questo caso in una applicazione nel mondo reale, prima di aprire la MainWindow ci saranno delle cose da predisporre, delle inizializzazioni, dei controlli, o semplicemente delle configurazioni da fare, e inserire da subito le chiamate base nell’evento OnStartup ci aiuterà a costruire una applicazione solida.
  4. Aggiungo anche un event handler per le eccezioni non gestite in modo da ricevere un messaggio se qualcosa non va.
  5. Utilizzo Nuget per aggiungere due riferimenti, Newtonsoft.json, che ormai non manca mai in alcuna applicazione, e Microsoft.Web.WebView2, scelgo la versione più recente, che mentre scrivo l’articolo è la 1.0.705.50 ma quello che faremo non dovrebbe necessitare modifiche al cambio di versione.
  6. Procedo a inserire il controllo nella MainWindow con una interfaccia minimale, ovvero una TextBox ove poter inserire un indirizzo web o una stringa di ricerca, un Button per avviare la navigazione, una seconda TextBox per visualizzare l’indirizzo corrente del browser ed il controllo WebView2. 

La selezione del controllo WebView2 da Nuget.

E la selezione per il Newtonsoft.Json.

Passiamo ora al codice dei punti importanti ai fini di questo articolo.

protected override void OnStartup(StartupEventArgs e)
{
	AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

	this.ShutdownMode = System.Windows.ShutdownMode.OnMainWindowClose;
	base.OnStartup(e);

	OpenMainWindow();

}

L’event handler dell’evento Startup in App.xaml.cs, in cui aggiungiamo la gestione delle eventuali Exception non gestite nella nostra applicazione e modifichiamo il metodo di Shutdown chiamando poi il metodo che prepara ed apre la MainWindow.

private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{

	Exception ex = e.ExceptionObject as Exception;
	StringBuilder sb = new StringBuilder();
	sb.AppendLine(ex.Message);
	sb.AppendLine(new string('-', 80));
	sb.AppendLine(ex.Data.ToString());
	sb.AppendLine(new string('-', 80));
	sb.AppendLine(ex.StackTrace);
	MessageBox.Show(sb.ToString(), "Si è verificato un errore inatteso");

}

L’event handler per le Exception non gestite, che proverà a darci i dati di quanto accade. Io lo inserisco sempre perché soprattutto quando si inizia una nuova applicazione la generazione del setup potrebbe avere dei problemi, magari perché abbiamo dimenticato un file e l’applicazione potrebbe chiudersi senza alcun messaggio, questo event handler intercetta proprio quello che normalmente non riusciamo a gestire direttamente dandoci le informazioni necessarie a intervenire.

private void OpenMainWindow()
{
	MainWindow mwin = new MainWindow();
	mwin.WindowStartupLocation = WindowStartupLocation.CenterScreen;
	this.MainWindow = mwin;
	MainWindow.Show();
}

L’ultima parte è il metodo che crea ed istanzia la MainWindow.

Per poter far funzionare il componente all’interno dell’applicazione dobbiamo anche installare sul nostro computer un componente. Si chiama WebView2 Runtime ed è una applicazione a supporto della WebView2. se volete saperne di più  Trovate la documentazione qui e prima di proseguire, andate a fare il Download qui. Una volta installato proseguiremo con la predisposizione del controllo sulla Window del programma. Tenetene conto se intendete usarlo nei vostri software perché dovrete prevederne lo scaricamento e installazione al momento del setup.

Ora passiamo al codice sulla MainWindow.

<Window
	...
	xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
	...
	Loaded="MainWindow_Loaded"
	...>

Qui sopra vedete la porzione del Tag Window della nostra MainWindow che riguarda la WebView. Per prima cosa il namespace ove è definito il controllo per poterlo utilizzare all’interno dello XAML, e poi l’evento Loaded della Window in cui andremo ad inizializzare il componente.

<wv2:WebView2
	x:Name="Wbro"
	Grid.Row="2"
	Margin="4"
	CreationProperties="{DynamicResource EvergreenWebView2CreationProperties}" />

Qui sopra il codice con cui inserisco il componente nella Window, come potete notare, la property CreationProperties contiene una risorsa dinamica, questa risorsa deve essere dichiarata per poter essere utilizzata, altrimenti all’avvio dell’applicazione si verificherà una eccezione.

<Application
	x:Class="TestWebView2.App"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:local="clr-namespace:TestWebView2"
	xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf">
	
	<Application.Resources>
		<!--
			These CreationProperties use the Evergreen Edge executable that's installed on the machine.
		-->
		<wv2:CoreWebView2CreationProperties x:Key="EvergreenWebView2CreationProperties" />

	</Application.Resources>
</Application>

La risorsa EvergreenWebView2CreationProperties va dichiarata all’interno dell’App.xaml, dove inseriremo per prima cosa il namespace corretto e poi la dichiarazione che permette al sistema di utilizzare il runtime che abbiamo installato sul computer.

Per la parte grafica dell’applicazione abbiamo completato il necessario, ora spostiamoci sul codice della Window e vediamo quali sono le parti del codice da implementare per far funzionare la nostra mini interfaccia base per testare la WebView2.

Spostiamoci in MainWindow.xaml.cs e vediamo che metodi ed event handler ho implementato per gestire la WebView2.

private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
	InitializeAsync();
	Wbro.CoreWebView2InitializationCompleted += Wbro_CoreWebView2InitializationCompleted;

}

Nell’event handler dell’evento Loaded della Window, andiamo a implementare quanto necessario ad inizializzare la WebView2. Il metodo InitializeAsync, che vedremo subito si occupa di inizializzare il componente. Una volta che il componente è inizializzato aggiungiamo un event handler che lo predispone all’uso. Il motivo per cui usare un evento per terminare l’inizializzazione è insito nel fatto che la fase di inizializzazione è asincrona. 

async private void InitializeAsync()
{

	var env = await CoreWebView2Environment.CreateAsync(null, UserDataFolder);
	await Wbro.EnsureCoreWebView2Async(env); 
}

Come potete vedere, l’inizializzazione del componente è in asincrono, ed è necessario attendere la fine dell’inizializzazione, per terminare di rendere usabile il componente. Nel codice qui sopra c’è un altra cosa molto importante, per scoprire la quale ho dovuto navigare per un ora o giù di li ed ho trovato tal cosa nei meandri del forum di supporto al componente. Il secondo parametro che passo al metodo di CreateAsync, ovvero UserDataFolder, ha un nome che dovrebbe essere autoesplicativo, ovvero se guardiamo il codice della property:

public string UserDataFolder
{
	get
	{
		string dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
		dir = Path.Combine(dir, "TestWebView2\\Browser");
		if (!Directory.Exists(dir))
		{
			Directory.CreateDirectory(dir);
		}
		return dir;
	}
}

Lo inizializzo con una sottocartella generata sulla cartella Documenti dell’utente corrente. Potete inserire in questa property la cartella che preferite, ma assicuratevi sia una cartella dove l’utente ha diritto di scrittura. Si tratta infatti del luogo ove il Browser della vostra applicazione andrà a generare tutti i suoi file di supporto e file temporanei. Se omettete questo path, tali cartelle e sottocartelle verranno generate sulla cartella di installazione della vostra applicazione, che, se come me siete degli sviluppatori normali, verrà installata da un file di installazione generato con un tool apposito e si troverà sotto Programmi o Programmi (x86) dove ovviamente l’utente non ha diritti di scrittura e quindi la WebView non avrà modo di creare i file di appoggio ed esploderà (di qui la necessità di intercettare le eccezioni non gestite) come pensate abbia scoperto tutto questo? Detto tutto ciò proseguiamo con il codice di inizializzazione.

private void Wbro_CoreWebView2InitializationCompleted(object sender, EventArgs e)
{
	Wbro.NavigationCompleted += WBro_NavigationCompleted;
}

La funzione di “callback” al termine dell’inizializzazione viene utilizzata per agganciare l’event handler che gestisce l’evento di Navigation completed, che utilizziamo per recuperare l’indirizzo web della landing page corrente del componente.

private void NavigateToBrowser()
{
	try
	{
		if (BrowserAddress != null)
		{
			if (!BrowserAddress.StartsWith("http"))
			{
				string addr = string.Format("https://www.bing.com/search?q={0}", BrowserAddress);
				Wbro.Source = new System.Uri(addr, UriKind.Absolute);
			}
			else
			{
				Wbro.Source = new Uri(BrowserAddress, UriKind.Absolute);
			}
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
	}
}

Aggiungo il codice per la funzione di navigazione che ho implementato e agganciato a due diversi event handler, il primo è legato al Click del Button ed il secondo al KeyUp della TextBox intercettando la pressione del tasto Invio per attivare la navigazione, visto che è simile al comportamento standard di una search.

Nella funzione di Navigazione ho semplicemente inserito una verifica per cui se il contenuto della textbox inizia per “http” effettuo una navigazione, in caso contrario chiamo il motore di ricerca usando quanto scritto come parole chiave.

Ed ora, prima di chiudere l’articolo, un mini test.

Nel primo caso ho inserito un indirizzo web che inizia per HTTP nel secondo solo una parola. Adesso che abbiamo impostato il nostro controllo, proveremo a verificare che tipo di interattività avremo in una applicazione WPF.