Sharing object between .NET host and WebView2
WebView2 is a new web browser control for Windows desktop applications. It is based on the Chromium open-source project and is powered by the Microsoft Edge browser. It is available in the Microsoft.Web.WebView2
package. Using this control, you can have a web browser in your application and interact with it. You can use it to display web pages, execute JavaScript code, inject custom CSS, handle navigation events, call .NET methods from JavaScript, and so on. In this post, I describe how to share objects between .NET host and a WebView2 instance, so it can be used from .NET and from JavaScript.
You can expose an object to the WebView2 instance by using the AddHostObjectToScript
method. This method takes a name and an object. The object must be public and ComVisible
. The name is used to access the object from JavaScript. For example, if you use sample
as the name, you can access the object from JavaScript using chrome.webview.hostObjects.sample
. As the object must be ComVisible, there are some constraints on the type of the object. For example, you cannot use a Task
as a return type. You need to use Task<T>
instead. If you want to expose an indexer (e.g. object this[int index] => ...
), you need to add the [IndexerName]
attribute.
Let's create the WPF application and add a reference to the Microsoft.Web.WebView2
package:
dotnet new wpf
dotnet add package Microsoft.Web.WebView2
Then, you can add a WebView2
control to the main window:
<Window x:Class="DemoWebView2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DemoWebView2"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<wv2:WebView2 Name="webView" />
</Grid>
</Window>
Then, you can create a class that will be shared between .NET and JavaScript. This class must be public and ComVisible
:
// Type must be public and ComVisible
[ComVisible(true)]
public class Sample
{
public string Name { get; set; } = "meziantou";
public int ClickCount { get; private set; }
// Use [IndexerName] if you want to use an indexer from JavaScript
[System.Runtime.CompilerServices.IndexerName("Items")]
public int this[int index] => index;
// Public methods are accessible from JS
public void OnClick() => ClickCount++;
// Task is not supported as return type. You need to use Task<T>. Otherwise, you get errors such as:
// 'System.Threading.Tasks.VoidTaskResult' cannot be marshalled to a Variant.
public async Task<int> DelayAsync(int milliseconds)
{
await Task.Delay(milliseconds);
return 0;
}
}
Then, you must register the object in the WebView2 control:
public partial class MainWindow : Window
{
// Shared object between .NET and JavaScript
private readonly Sample _sample = new();
public MainWindow() => InitializeComponent();
protected override async void OnSourceInitialized(EventArgs e)
{
await webView.EnsureCoreWebView2Async();
webView.CoreWebView2.AddHostObjectToScript("sample", _sample);
webView.Source = new Uri("index.html");
}
}
Finally, you can use the object from JavaScript:
<button id="btn" type="button">Click me</button>
<output id="result"></output>
<script>
document.getElementById("btn").addEventListener("click", async e => {
// Access the host object asynchronously
const demo = chrome.webview.hostObjects.demo;
await demo.DelayAsync(1000); // Call async methods
const name = await demo.Name; // Get property value
// Access the host object synchronously
// This is not recommended as it may block the UI thread of the webview.
const demoSync = chrome.webview.hostObjects.sync.demo;
demoSync.OnClick(); // Call sync method
const clickCount = demoSync.ClickCount; // Get property value
const index = demoSync.Items[0]; // Get indexer value
demoSync.Name = "new name"; // Set property value
document.getElementById("result").textContent = `${name} ${clickCount}`;
});
</script>
#Additional resources
Do you have a question or a suggestion about this post? Contact me!