The Win32 WebViewControl (EdgeHTML) does not support AddWebAllowedObject (sometimes searched as AddAllowedWebObject) or ObjectForScripting, so you cannot inject a native object into the page. This sample shows a JavaScript bridge that uses window.external.notify and the ScriptNotify event, with promise-based calls, for WinForms and WPF.
If you are building a new app, use WebView2 instead. WebView2 supports AddHostObjectToScript. See Migrating to WebView2.
This sample targets a legacy control. Use it to maintain an existing app, not to start a new one.
- The control is deprecated. The Windows Deprecated features page (checked 2026-10-11) lists Legacy Web View, announced September 2025: "These components, built on the EdgeHTML engine, are no longer in active development and are being phased out." The same entry says they are "expected to eventually stop receiving nonsecurity and security updates and will be removed from future versions of Windows."
- The original package is deprecated. NuGet marks
Microsoft.Toolkit.Win32.UI.Controls5.0.0 as deprecated (Legacy, CriticalBugs) with this message: "This package is now obsolete and contains no DLLs. Please use Microsoft.Toolkit.Wpf.UI.Controls.WebView or Microsoft.Toolkit.Forms.UI.Controls.WebView packages instead." - The replacement packages are deprecated too. Microsoft.Toolkit.Wpf.UI.Controls.WebView and Microsoft.Toolkit.Forms.UI.Controls.WebView 6.1.2 are the last versions. NuGet marks both as Legacy and names
Microsoft.Web.WebView2as the alternate package: "The Toolkit Edge-HTML based WebView control has been replaced with the new Microsoft Edge (Chromium) based WebView2." TheirWebViewclasses carry[Obsolete], so the compiler reports warning CS0618 when you use them.NavigateToLocal, used below, also reports CS0618 and points toNavigateToLocalStreamUri. - The projects in this repository still reference
Microsoft.Toolkit.Win32.UI.Controls4.0.1. The JavaScriptBridge source also compiles unchanged againstMicrosoft.Toolkit.Wpf.UI.Controls.WebView6.1.2 andMicrosoft.Toolkit.Forms.UI.Controls.WebView6.1.2.
The bridge has two halves: Bridge.js runs in the page, and JavaScriptBridge.cs runs in your app.
JavaScriptBridge.CreateAndStartturns on JavaScript andScriptNotifyfor the WebView. It also records the URIs that may talk to the bridge.- When the page raises
DOMContentLoadedfrom an allowed URI, the bridge injectsBridge.jsif the page does not already have it.Bridge.jscreateswindow.JavaScriptBridgeand fires aJavaScriptBridgeReadyevent. - Your page calls
JavaScriptBridge.callNative(handlerId, data). The call adds a{ handler, handlerdata, callbackId }message to a queue. Then it callswindow.external.notify("jsbridge://queue_message"). - The
ScriptNotifyhandler drops any event whose URI is not inAllowedScriptNotifyUris, or whose value is not a bridge message. Otherwise, it callsJavaScriptBridge.fetchQueue()throughInvokeScriptAsyncand gets every queued message. - For each message, the bridge runs the C# delegate you registered with
AddScriptingHandler. If the delegate throws, or no handler has that name, the reply carries an error object instead. Itsmessageis the exception text. - The bridge sends each reply back through
InvokeScriptAsync, which runsJavaScriptBridge.handleNativeMessage(...). That call resolves the promise (or runs the success callback) with the result. An error rejects the promise (or runs the error callback).
A minimal round trip looks like this. In C#:
_javaScriptBridge = JavaScriptBridge.CreateAndStart(webView1, new Uri("ms-local-stream://microsoft.win32webviewhost_cw5n1h2txyewy_4c6f63616c436f6e74656e74/Content.html"));
_javaScriptBridge.AddScriptingHandler("HelloWorld", @params => $"Hello, {@params["name"]}!");In the page, using the ConnectWebViewBridge helper defined in Add the page content:
ConnectWebViewBridge(async function (bridge) {
const response = await bridge.callNative("HelloWorld", { name: "World" });
console.log(response); // "Hello, World!"
});- Windows 10, version 1803 or later. The
WebViewControlclass was introduced in that version. - Visual Studio with the .NET desktop development workload.
- .NET Framework 4.6.2 or later. The WebView packages require it.
Follow these steps to add the bridge to a new WinForms or WPF app. The examples show both frameworks, so use the one that matches your app.
- Clone this repository or download the source code. You need the JavaScriptBridge folder.
- Create a new WinForms or WPF project that targets .NET Framework 4.6.2 or later.
- Install the WebView package for your UI framework, version 6.1.2:
- WPF:
Microsoft.Toolkit.Wpf.UI.Controls.WebView - WinForms:
Microsoft.Toolkit.Forms.UI.Controls.WebView
- WPF:
- Copy the JavaScriptBridge folder into your solution folder.
- In Solution Explorer, right-click your solution, then select Add > Existing Project. Select
JavaScriptBridge.csproj. - In the JavaScriptBridge project, replace the
Microsoft.Toolkit.Win32.UI.Controlspackage with the same WebView package your app uses. UpdateNewtonsoft.Jsonto 13.0.1 or later. - In your app project, add a project reference to JavaScriptBridge.
The JavaScriptBridge project copies Bridge.js to the build output. The bridge reads the file by the relative path Bridge.js, so the working directory of your app must be the output folder. This is the default when you start the app from Visual Studio or from its own folder.
Add a WebView control to your window or form. The WPF control is Microsoft.Toolkit.Wpf.UI.Controls.WebView. The WinForms control is Microsoft.Toolkit.Forms.UI.Controls.WebView.
These examples name the control WebView. Add it to MainWindow.xaml:
<Window
x:Class="AddWebAllowedObject_GettingStarted.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls.WebView"
Title="MainWindow">
<Grid>
<controls:WebView x:Name="WebView" Loaded="WebView_OnLoaded" />
</Grid>
</Window>Then replace the contents of MainWindow.xaml.cs:
using System.Windows;
namespace AddWebAllowedObject_GettingStarted
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
WebView.DOMContentLoaded += (o, e) => Title = WebView.DocumentTitle;
}
private void WebView_OnLoaded(object sender, RoutedEventArgs e)
{
WebView.NavigateToLocal("/Content.html");
}
}
}These examples use the default designer name, webView1. Replace the contents of Form1.cs:
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
webView1.DOMContentLoaded += (o, e) => Text = webView1.DocumentTitle;
webView1.NavigateToLocal("/Content.html");
}
}
}The code in the previous step calls NavigateToLocal("/Content.html"). Create that page now.
- Add a new HTML page named
Content.htmlto your app project. - In the Properties window for
Content.html, set Copy to Output Directory to Copy if newer. - Replace the contents of
Content.htmlwith the following markup.
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>JavaScript Bridge: Getting Started</title>
</head>
<body>
<button id="hello-world">Hello, World!</button>
<div id="output"></div>
<script>
var ConnectWebViewBridge = function (callback) {
if (window.JavaScriptBridge) {
callback(JavaScriptBridge);
} else {
// Bridge is not yet loaded, wait for event
document.addEventListener(
"JavaScriptBridgeReady",
function () {
callback(JavaScriptBridge);
},
false);
}
};
ConnectWebViewBridge(function (bridge) {
console.log("JavaScript bridge is ready!");
});
</script>
</body>
</html>The script block defines ConnectWebViewBridge. This helper runs your code as soon as the bridge is ready. It checks for window.JavaScriptBridge first, then waits for the JavaScriptBridgeReady event. The script sits at the end of the body tag so it does not block other scripts, and the DOM is ready before the script runs.
Run the app. You should see an empty window titled JavaScript Bridge: Getting Started.
The bridge only accepts messages from URIs that you allow. To allow your page, you need its exact URI. You can read it with a debugger.
The debugger for this control is Microsoft Edge DevTools Preview from the Microsoft Store. The Windows deprecation entry above also lists the legacy Microsoft Edge (EdgeHTML) DevTools, so this tool may not stay available.
- Install Microsoft Edge DevTools Preview from the Microsoft Store.
- Start your app.
- Start Microsoft Edge DevTools Preview.
- Under Local, find the Debug Target titled JavaScript Bridge: Getting Started. Its URI is
ms-local-stream://microsoft.win32webviewhost_cw5n1h2txyewy_4c6f63616c436f6e74656e74/Content.html. - Select the target. A new window opens with the debugger attached to your page.
- Select the Console tab.
- Type
document.URLand press Enter. The console shows"ms-local-stream://Microsoft.Win32WebViewHost_cw5n1h2txyewy_4c6f63616c436f6e74656e74/Content.html". The host casing differs from the samples, which use lowercase. On current .NET,System.Uricomparison ignores host case, so both forms match. If the bridge does not load, pass the exactdocument.URLvalue toCreateAndStart. - Copy the value, then close the debugger and your app.
To start the bridge, you need two things: the IWebView instance you added earlier, and the page URI from the previous step. The bridge listens for IWebView.ScriptNotify events from that URI only.
Replace the contents of MainWindow.xaml.cs:
using System;
using System.Windows;
using Microsoft.Toolkit.Win32.UI.Controls.WebViewExtensions;
namespace AddWebAllowedObject_GettingStarted
{
public partial class MainWindow : Window
{
private JavaScriptBridge _javaScriptBridge;
public MainWindow()
{
InitializeComponent();
_javaScriptBridge = JavaScriptBridge.CreateAndStart(
WebView,
new Uri("ms-local-stream://microsoft.win32webviewhost_cw5n1h2txyewy_4c6f63616c436f6e74656e74/Content.html"));
WebView.DOMContentLoaded += (o, e) => Title = WebView.DocumentTitle;
}
private void WebView_OnLoaded(object sender, RoutedEventArgs e)
{
WebView.NavigateToLocal("/Content.html");
}
}
}Replace the contents of Form1.cs:
using Microsoft.Toolkit.Win32.UI.Controls.WebViewExtensions;
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private JavaScriptBridge _javaScriptBridge;
public Form1()
{
InitializeComponent();
_javaScriptBridge = JavaScriptBridge.CreateAndStart(
webView1,
new Uri("ms-local-stream://microsoft.win32webviewhost_cw5n1h2txyewy_4c6f63616c436f6e74656e74/Content.html"));
webView1.DOMContentLoaded += (o, e) => Text = webView1.DocumentTitle;
webView1.NavigateToLocal("/Content.html");
}
}
}To check that the bridge loaded:
- Start your app.
- Start Microsoft Edge DevTools Preview and attach to the JavaScript Bridge: Getting Started target, as in Find the page URI.
- Select the Console tab.
The console shows JavaScript bridge is ready!. If it does not, see Troubleshooting.
A scripting handler connects a name to a .NET delegate. The delegate takes named parameters and can return a value. Register a handler with JavaScriptBridge.AddScriptingHandler.
Replace the contents of MainWindow.xaml.cs:
using System;
using System.Windows;
using Microsoft.Toolkit.Win32.UI.Controls.WebViewExtensions;
namespace AddWebAllowedObject_GettingStarted
{
public partial class MainWindow : Window
{
private JavaScriptBridge _javaScriptBridge;
public MainWindow()
{
InitializeComponent();
_javaScriptBridge = JavaScriptBridge.CreateAndStart(
WebView,
new Uri("ms-local-stream://microsoft.win32webviewhost_cw5n1h2txyewy_4c6f63616c436f6e74656e74/Content.html"));
_javaScriptBridge.AddScriptingHandler(
"HelloWorld",
@params => "Hello, World!");
WebView.DOMContentLoaded += (o, e) => Title = WebView.DocumentTitle;
}
private void WebView_OnLoaded(object sender, RoutedEventArgs e)
{
WebView.NavigateToLocal("/Content.html");
}
}
}Replace the contents of Form1.cs:
using Microsoft.Toolkit.Win32.UI.Controls.WebViewExtensions;
using System;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
private JavaScriptBridge _javaScriptBridge;
public Form1()
{
InitializeComponent();
_javaScriptBridge = JavaScriptBridge.CreateAndStart(
webView1,
new Uri("ms-local-stream://microsoft.win32webviewhost_cw5n1h2txyewy_4c6f63616c436f6e74656e74/Content.html"));
_javaScriptBridge.AddScriptingHandler(
"HelloWorld",
@params => "Hello, World!");
webView1.DOMContentLoaded += (o, e) => Text = webView1.DocumentTitle;
webView1.NavigateToLocal("/Content.html");
}
}
}This code registers a handler named HelloWorld. The lambda takes the named parameters as @params, a Dictionary<string, object>.
To call HelloWorld, the page calls callNative on the bridge. In this example, the Hello, World! button calls it.
callNative takes a handler ID, optional data for the handler as named arguments, an optional success callback, and an optional error callback. You can call it three ways: with callbacks, with a promise, or with async and await. Pick the style that matches your code. In each example below, replace the ConnectWebViewBridge(function (bridge) { ... }); call at the end of the script block in Content.html. Keep the helper definition above it.
This example passes the handler ID and a success callback.
ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
bridge.callNative(handlerId, function(response) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
});
};
});Without callbacks, callNative returns a Promise. This example uses .then to handle the result.
ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
bridge.callNative(handlerId)
.then(function (response) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;});
};
});This example awaits the Promise that callNative returns.
ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = async function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const response = await bridge.callNative(handlerId);
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
};
});Run your app and select Hello, World!. The text Hello, World! appears below the button.
The HelloWorld handler takes no parameters and returns a string. Next, change it to take a name parameter and include it in the response.
In your .NET code, change the handler from this:
_javaScriptBridge.AddScriptingHandler(
"HelloWorld",
@params => "Hello, World!");To this:
_javaScriptBridge.AddScriptingHandler(
"HelloWorld",
@params => $"Hello, {@params["name"]}!");In the page, pass the data as the second argument to callNative, after the handler ID. The data is a JavaScript object of key and value pairs:
const handlerData = { name: "World" };This object has one property, name, with the string value World. The bridge turns it into the Dictionary<string, object> that the handler receives as @params.
ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = { name: "World" };
bridge.callNative(handlerId, handlerData, function(response) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
});
};
});ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = { name: "World" };
bridge.callNative(handlerId, handlerData)
.then(function (response) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
});
};
});ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = async function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = { name: "World" };
const response = await bridge.callNative(handlerId, handlerData);
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
};
});Run your app and select Hello, World!. The text Hello, World! appears below the button. If you change World to another name, the text changes to match.
When a .NET handler throws an exception, the bridge returns an object that describes the exception. With a callback, the bridge passes it to your error callback. With a promise, the bridge rejects the promise. With await, your code can catch it.
In your .NET code, change the handler from this:
_javaScriptBridge.AddScriptingHandler(
"HelloWorld",
@params => $"Hello, {@params["name"]}!");To this:
_javaScriptBridge.AddScriptingHandler("HelloWorld", @params =>
{
if (@params == null)
{
throw new ArgumentNullException(nameof(@params));
}
if (@params.Count != 1)
{
throw new ArgumentOutOfRangeException(nameof(@params), "Expected one parameter.");
}
if (!@params.ContainsKey("name"))
{
throw new ArgumentOutOfRangeException(nameof(@params), "Expected parameter 'name'.");
}
return $"Hello, {@params["name"]}!";
});Check every parameter that comes from JavaScript, as this handler does.
Then add error handling to the page. Use the example that matches your style.
ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = { name: "World" };
bridge.callNative(
handlerId,
handlerData,
function(response) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
console.info(response);
}, function(err) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(err, null, 2)}</code></pre>`;
console.error(err.message);
});
};
});ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = { name: "World" };
bridge.callNative(handlerId, handlerData)
.then(function(response) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
console.info(response);})
.catch(function(err) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(err, null, 2)}</code></pre>`;
console.error(err.message);});
};
});ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = async function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = { name: "World" };
try {
const response = await bridge.callNative(handlerId, handlerData);
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
console.info(response);
} catch (err) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(err, null, 2)}</code></pre>`;
console.error(err.message);
}
};
});Run your app. The output is Hello, World!, as before. To see the error handler run, change the data in the page from this:
const handlerData = { name: "World" };To this:
const handlerData = { name2: "World" };The handler expects a key named name. When you select the button, the page shows this error:
{
"message": "Expected parameter 'name'.\\r\\nParameter name: params",
"data": {},
"source": "JavaScript Bridge",
"hResult": -2146233088
}The bridge can also return .NET objects, as long as they can be serialized to JSON. Update the handler in your .NET code:
_javaScriptBridge.AddScriptingHandler("HelloWorld", @params =>
{
if (@params == null)
{
throw new ArgumentNullException(nameof(@params));
}
if (@params.Count == 0 || @params.Count > 3)
{
throw new ArgumentOutOfRangeException(nameof(@params), "Expected three or less parameters");
}
var retval = new AddressBookEntry();
if (@params.ContainsKey("firstName"))
{
retval.FirstName = @params["firstName"]?.ToString();
}
if (@params.ContainsKey("lastName"))
{
retval.LastName = @params["lastName"]?.ToString();
}
if (@params.ContainsKey("company"))
{
retval.Company = @params["company"]?.ToString();
}
if (string.IsNullOrEmpty(retval.FullName))
{
throw new InvalidOperationException("Parameters did not produce a valid name.");
}
return retval;
});The handler checks the number of parameters, as before. Then it builds an AddressBookEntry from the keys in the dictionary. Add the AddressBookEntry class to your project so the code compiles:
public class AddressBookEntry
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Company { get; set; }
public string FullName
{
get
{
var fnEmpty = string.IsNullOrEmpty(FirstName);
var lnEmpty = string.IsNullOrEmpty(LastName);
var cEmpty = string.IsNullOrEmpty(Company);
if (fnEmpty && lnEmpty && !cEmpty)
{
return Company;
}
if (!fnEmpty || !lnEmpty)
{
var sb = new StringBuilder(256);
if (!fnEmpty)
{
sb.Append(FirstName);
sb.Append(" ");
}
if (!lnEmpty)
{
sb.Append(LastName);
sb.Append(" ");
}
if (!cEmpty)
{
sb.Append("(");
sb.Append(Company);
sb.Append(")");
}
return sb.ToString().Trim();
}
return string.Empty;
}
}
public override string ToString()
{
return FullName;
}
}If you run the app without changing the page, the bridge returns this error:
{
"message": "Parameters did not produce a valid name.",
"data": {},
"source": "JavaScript Bridge",
"hResult": -2146233088
}The page sent one parameter, but the handler cannot use its key to build an AddressBookEntry. So the handler throws an InvalidOperationException instead of returning an empty entry.
To fix this, send the keys the handler expects. As before, the data is a JavaScript object of key and value pairs:
const handlerData = {
firstName: "Hello",
lastName: "World"
};ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = {
firstName: "Hello",
lastName: "World"
};
bridge.callNative(
handlerId,
handlerData,
function(response) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
console.info(response);
}, function(err) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(err, null, 2)}</code></pre>`;
console.error(err.message);
});
};
});ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = {
firstName: "Hello",
lastName: "World"
};
bridge.callNative(handlerId, handlerData)
.then(function(response) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
console.info(response);
})
.catch(function (err) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(err, null, 2)}</code></pre>`;
console.error(err.message);
});
};
});ConnectWebViewBridge(function(bridge) {
console.log("JavaScript bridge is ready!");
const helloWorldButton = document.getElementById("hello-world");
helloWorldButton.onclick = async function(e) {
e.preventDefault();
const handlerId = "HelloWorld";
const handlerData = {
firstName: "Hello",
lastName: "World"
};
try {
const response = await bridge.callNative(handlerId, handlerData);
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(response, null, 2)}</code></pre>`;
console.info(response);
} catch (err) {
const log = document.getElementById("output");
log.innerHTML = `<pre><code>${JSON.stringify(err, null, 2)}</code></pre>`;
console.error(err.message);
}
};
});Run your app and select Hello, World!. The page shows the serialized AddressBookEntry:
{
"firstName": "Hello",
"lastName": "World",
"fullName": "Hello World"
}The bridge writes trace messages with the category JavaScript Bridge. When you debug in Visual Studio, they appear in the Output window.
- The console never shows
JavaScript bridge is ready!. Check that the URI you passed toCreateAndStartmatchesdocument.URL. The bridge injectsBridge.jsonly for allowed URIs. Also check thatBridge.jsis in the working directory of your app. The bridge loads the file without awaiting the result, so a missing file does not show an error in your app. To spot this case, look for theCould not detect JavaScriptBridge. Injecting scripttrace followed by aFileNotFoundExceptionfrom the debugger in the Output window. - The Output window shows
ScriptNotify received from unknown origin. The page URI is not in the allowed list. Pass the exact URI toCreateAndStart. - The error message is
Handler '<name>' not supported.No handler with that name is registered. Check the name you passed toAddScriptingHandler. Names are not case sensitive.
WebView2 has two built-in ways to call native code from JavaScript, so you do not need this bridge.
| This bridge (WebViewControl) | WebView2 |
|---|---|
window.external.notify(message) |
window.chrome.webview.postMessage(message) |
ScriptNotify event |
CoreWebView2.WebMessageReceived event |
InvokeScriptAsync to send a reply |
CoreWebView2.PostWebMessageAsString or PostWebMessageAsJson |
AddScriptingHandler plus callNative |
CoreWebView2.AddHostObjectToScript plus chrome.webview.hostObjects |
Add the Microsoft.Web.WebView2 package and a WebView2 control named webView. Then, in an async method of your window or form:
await webView.EnsureCoreWebView2Async();
webView.CoreWebView2.WebMessageReceived += (sender, e) =>
webView.CoreWebView2.PostWebMessageAsString($"Hello, {e.TryGetWebMessageAsString()}!");In the page:
window.chrome.webview.addEventListener("message", e => console.log(e.data));
window.chrome.webview.postMessage("World");First, define a COM visible class:
using System.Runtime.InteropServices;
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class Greeter
{
public string Hello(string name) => $"Hello, {name}!";
}Then add an instance to the page:
// After EnsureCoreWebView2Async completes:
webView.CoreWebView2.AddHostObjectToScript("greeter", new Greeter());In the page, call the method from an async function:
async function greet() {
const text = await chrome.webview.hostObjects.greeter.Hello("World");
console.log(text); // "Hello, World!"
}- Interop of native-side and web-side code
- Call native-side code from web-side code
- CoreWebView2.AddHostObjectToScript method
AddWebAllowedObject belongs to the UWP Windows.UI.Xaml.Controls.WebView class. The Win32 WebViewControl class does not have it, and neither do the WinForms and WPF wrappers built on that class.
Call window.external.notify from the page and handle the ScriptNotify event in your app. This bridge does that for you. It adds a message queue, named handlers, and promise, callback, and async/await support.
No. The control and its packages are deprecated. Use WebView2.
Use CoreWebView2.AddHostObjectToScript to expose a native object. Or use window.chrome.webview.postMessage with the WebMessageReceived event to pass messages. See Migrating to WebView2.