Different Ways to Host Web Pages in Windows Forms

Jason Ge
6 min readApr 11, 2023

--

There many ways to display web pages inside Window Forms application. In this article, we will discuss 3 different ways and the pros and cons of them.

WebBrowser Control

The WebBrowser control provides a managed wrapper for the WebBrowser ActiveX control. This control is based on old browser InternetExplorer. It has several limitations of using this control:

  1. The WebBrowser class requires full trust permission, otherwise a SecurityException will be thrown when a derived class or any caller in the call stack does not have full trust permission.
  2. The WebBrowser class can only be used in threads set to single thread apartment (STA) mode.
  3. It is resource-intensive. Be sure to dispose it after it completes its work.

Display Web Page

In order to display a web page, you can simply call the Navigate() method of this control and pass the URI of the web page. The URL has to be absolute address.

webBrowser1.Navigate(new Uri(url));

Display Web Page with POST

The navigate() method has overloads that can POST to web page with data.

public void Navigate (string url, string targetFrameName, byte[] postData, string additionalHeaders);

Communication between Web Page and Windows Forms

  1. Call web page script function from Windows Forms app: let’s say you have a javascript method in the hosted web page called displayMessage(message) and you can call it inside your Windows Forms app by using webBrowser1.Document.InvokeScript("displayMessage", new string[] { "hello world!" }).
  2. Call Windows Forms method from hosted web page: You can use WebBrowser.ObjectForScripting property to enable communication between a Web page hosted by the WebBrowser control and the application that contains the WebBrowser control. The object specified for this property is available to Web page script as the window.external object. For example, <button onclick="window.external.winformMethod('trigger windows forms method')">test</button>”. Here winformMethod() is defined inside the object that assigned to the WebBrowser.ObjectForScripting property.

WebView2

The WebView2 control uses Microsoft Edge as the rendering engine to display the web content in native apps. You can install Microsoft.Web.WebView2 nuget package to use WebView2 control.

WebView2.CoreWebView2 Property

This property inside WebView2 class expose the Microsoft.Web.WebView2.Core.CoreWebView2 object. Most of the WebView2 functions are actually performed by this object. This value is null until it is initialized. You should call webview2.EnsureCoreWebView2Async() method to initialize the CoreWebView2 object before using it.

Display Web Page

Similar to WebBrowser control, you can simply call the Navigate() method of this control and pass the URI of the web page. The URL has to be absolute address.

webView2.CoreWebView2.Navigate(url);

Display Web Page with POST

You can use CoreWebView2.NavigateWithWebResourceRequest() method to navigate to a web page with post data and additional headers.

public void NavigateWithWebResourceRequest (CoreWebView2WebResourceRequest request);

Use the CoreWebView2.Environment.CreateWebResourceRequest() method to create CoreWebView2WebResourceRequest object

public Microsoft.Web.WebView2.Core.CoreWebView2WebResourceRequest CreateWebResourceRequest (string url, string method, System.IO.Stream postData, string headers);

An example:

var webview2 = new Microsoft.Web.WebView2.WinForms.WebView2();
await webview2.EnsureCoreWebView2Async(env);
string postData = "{\"payload\": \"this is a test\"}";
string url = "https://test.com";
string additionalHeaders = "Content-Type: application/json\r\nOrigin: mysite.com\r\nReferer: mysite.com"
CoreWebView2WebResourceRequest request = webview2.CoreWebView2.Environment.CreateWebResourceRequest(url, "POST", new MemoryStream(Encoding.UTF8.GetBytes(postdata)), additionalHeaders);
webview2.CoreWebView2.NavigateWithWebResourceRequest(request);

Send Message from Web Page to Windows Forms

Web content in a WebView2 control can use window.chrome.webview.postMessage() method to post data to the host.

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web View2 Demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous" />
<script type="text/javascript">
window.chrome.webview.addEventListener('message', function(e) {
document.getElementById('message').innerText = JSON.stringify(e.data);
})
function postMessageToHost() {
chrome.webview.postMessage(document.getElementById('message').innerText.trim());
}
</script>
</head>

<body>
<div class="card">
<div class="card-header">
Log
</div>
<div class="card-body">
<p class="card-text">
<div id="message">
{
"city": "NORTH YORK",
"country": "Canada",
"startDate": "2021-06-03T13:47:56.686Z",
"lastVerifiedDate": "2021-06-03",
"postalCode": "M2J3B4",
"province": "Ontario",
"streetName": "DON MILLS RD",
"streetNumber": "2600"
}
</div>
</p>
<div class="d-grid gap-8 d-md-block">
<button type="button" class="btn btn-success"
onclick="postMessageToHost()">Post Message To Host</button>&nbsp;&nbsp;&nbsp;
</div>
</div>
</div>
</body>

</html>

The host handles the message using the registered WebMessageReceived event handler.

using Microsoft.Web.WebView2.Core;
using System.Text.Json;

namespace WebView2Hosting
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeAsync();
}


async void InitializeAsync()
{
await webView2.EnsureCoreWebView2Async(null);
webView2.CoreWebView2.WebMessageReceived += DisplayMessage;
}
private void btnNavigateGet_Click(object sender, EventArgs e)
{
webView2.CoreWebView2.Navigate("http://localhost:8888/httpbridge/webview2.html");
}

private void DisplayMessage(object sender, CoreWebView2WebMessageReceivedEventArgs args)
{
string message = args.TryGetWebMessageAsString();
var address = JsonSerializer.Deserialize<Address>(message, new JsonSerializerOptions() { AllowTrailingCommas = true, PropertyNameCaseInsensitive = true });
MessageBox.Show($"Message received from the web page: {JsonSerializer.Serialize(address)}");
}

private void btnMessageFromHostToWeb_Click(object sender, EventArgs e)
{
var address = new Address()
{
City = "Toronto",
Province = "Ontario",
Country = "Canada",
StartDate = DateTime.Now,
StreetName = "YOUNG STREET",
StreetNumber = "10000",
LastVerifiedDate = DateTime.Now,
PostalCode = "M9N5R3"
};
webView2.CoreWebView2.PostWebMessageAsJson(JsonSerializer.Serialize(address));
}
}
}

Send Message from Windows Form App to Web Page

Windows Form app can use CoreWebView2.PostWebMessageAsString() or CoreWebView2.PostWebMessageAsJSON() method to send message to web page.

private void btnMessageFromHostToWeb_Click(object sender, EventArgs e)
{
var address = new Address()
{
City = "Toronto",
Province = "Ontario",
Country = "Canada",
StartDate = DateTime.Now,
StreetName = "YOUNG STREET",
StreetNumber = "10000",
LastVerifiedDate = DateTime.Now,
PostalCode = "M9N5R3"
};
webView2.CoreWebView2.PostWebMessageAsJson(JsonSerializer.Serialize(address));
}

Web page registers the handler to handle the message by using indow.chrome.webview.addEventListener(‘message’, handler). The message handler function has one parameter, the data property of the parameter is the data received. If the Windows Form app send the data using PostWebMessageAsString(), then the data property is a string; if the Windows Form app send the data using PostWebMessageAsJSON(), then the data property is a JSON object.

window.chrome.webview.addEventListener('message', function(e) {
document.getElementById('message').innerText = JSON.stringify(e.data);
})

CefSharp

CefSharp is based on Chromium Embedded Framework, the open source version of Google Chrome. You can get more information from CefSharp project official site.

Installation

For .NET 5.0 or greater Windows Forms app, install following nuget package:

CefSharp.WinForms.NETCore

Display Web Page

Similar to WebBrowser control, you can simply call the Navigate() method of this control and pass the URI of the web page. The URL has to be absolute address.

this.chromium = new CefSharp.WinForms.ChromiumWebBrowser();
chromium.LoadUrl("http://localhost:8888/httpbridge/chromium.html");

Display Web Page with POST

You can use LoadUrlWithPostData() extension method to navigate to a web page with post data.

this.chromium = new CefSharp.WinForms.ChromiumWebBrowser();
string postData = "{\"payload\": \"this is a test\"}";
chromium.LoadUrlWithPostData("http://localhost:8888/chromium.html", Encoding.UTF8.GetBytes(postdata));

Send Message from Web Page to Windows Forms

CefSharp can expose the C# object into the web page using chromium.JavascriptObjectRepository.Register() method.

internal class CefCustomObject<T>
{
private static ChromiumWebBrowser _browser = null;


public CefCustomObject(ChromiumWebBrowser browser)
{
_browser = browser;
}

public void showDevTools()
{
_browser.ShowDevTools();
}

public void postMessage(string message)
{
MessageBox.Show(message.ToString() + "\r\n" + JsonConvert.DeserializeObject<T>(message).ToString());
}
}

chromium.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
chromium.JavascriptObjectRepository.Register("cefCustomObject", new CefCustomObject<Address>(chromium), options: BindingOptions.DefaultBinder);

The first parameter in the Register() method is the JavaScript object name you can use in the web page.

Once the object is registered, inside your web page, you can simply use it like regular javascript object (postMessageToHost1() method uses registered cefCustomObject).

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web View2 Demo</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous" />
<script type="text/javascript">
function postMessageFromHost(message){
alert(`received message from host: ${message}`);
}
function postMessageToHost1() {
cefCustomObject.postMessage(document.getElementById('message').innerText.trim());
}
function postMessageToHost2() {
CefSharp.PostMessage("Hello world!");
}
cefCustomObject.showDevTools();
</script>
</head>

<body>
<div class="card" onload="cefCustomObject.showDevTools()">
<div class="card-header">
Log
</div>
<div class="card-body">
<p class="card-text">
<div id="message">
{
"city": "NORTH YORK",
"country": "Canada",
"startDate": "2021-06-03T13:47:56.686Z",
"lastVerifiedDate": "2021-06-03",
"postalCode": "M2J3B4",
"province": "Ontario",
"streetName": "DON MILLS RD",
"streetNumber": "2600"
}
</div>
</p>
<div class="d-grid gap-8 d-md-block">
<button type="button" class="btn btn-success"
onclick="postMessageToHost1()">Post Message To Host using Registered Javascript Repository</button>&nbsp;&nbsp;&nbsp;
<button type="button" class="btn btn-success"
onclick="postMessageToHost2()">Post Message To Host using JavascriptMessageReceived Event</button>&nbsp;&nbsp;&nbsp;

</div>
</div>
</div>
</body>

</html>

Another way to post message from web page to hosting app is to use the CefSharp.PostMessage(message) JavaScript method in web page (see above example). In the hosting app, register JavascriptMessageReceived event handler to handle the data.

       private void InitializeChromium()
{
CefSettings settings = new CefSettings();
Cef.Initialize(settings);
chromium.JavascriptMessageReceived += ChromeBrowser_JavascriptMessageReceived;
}

private void ChromeBrowser_JavascriptMessageReceived(object? sender, JavascriptMessageReceivedEventArgs e)
{
MessageBox.Show($"JavascriptMessageReceived event: {e.Message}");
}

Send Message from Windows Form App to Web Page

Windows Form app can execute the JavaScript function defined in web page and pass the data to the function by using ChromiumWebBrowser's ExecuteScriptAsync() extension method.

public static void ExecuteScriptAsync(
this IChromiumWebBrowserBase browser,
string javascriptMethodName,
params Object[] args
)

However, before you execute any script in web page, you need to make sure the web page is loaded. You can subscribe the LoadingStateChanged event and monitor the event arg’s IsLoading flag.

private bool _isLoading = true;

private void InitializeChromium()
{
CefSettings settings = new CefSettings();
Cef.Initialize(settings);
chromium.JavascriptMessageReceived += ChromeBrowser_JavascriptMessageReceived;
chromium.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true;
chromium.JavascriptObjectRepository.Register("cefCustomObject", new CefCustomObject<Address>(chromium), options: BindingOptions.DefaultBinder);
chromium.LoadingStateChanged += (sender, args) =>
{
_isLoading = args.IsLoading;
};
}

private void btnInvokeWebPageFunction_Click(object sender, EventArgs e)
{
if (!_isLoading)
{
//Wait for the Page to finish loading
chromium.ExecuteScriptAsync("postMessageFromHost", new string[] { "this is a test" });
}
}

Web page has the JavaScript method “postMessageFromHost()” defined.

<script type="text/javascript">
function postMessageFromHost(message){
alert(`received message from host: ${message}`);
}
</script>

Happy coding!

--

--

Jason Ge
Jason Ge

Written by Jason Ge

Software developer with over 20 years experience. Recently focus on Vue/Angular and asp.net core.

No responses yet