This repo has the experimental .NET MAUI HybridWebView control, which enables hosting arbitrary HTML/JS/CSS content in a WebView and enables communication between the code in the WebView (JavaScript) and the code that hosts the WebView (C#/.NET). For example, if you have an existing React JS application, you could host it in a cross-platform .NET MAUI native application, and build the back-end of the application using C# and .NET.
Example usage of the control:
<hwv:HybridWebView
HybridAssetRoot="hybrid_root"
MainFile="hybrid_app.html"
RawMessageReceived="OnHybridWebViewRawMessageReceived" />
And here's how .NET code can call a JavaScript method:
var sum = await myHybridWebView.InvokeJsMethodAsync<int>("JsAddNumbers", 123, 456);
And the reverse, JavaScript code calling a .NET method:
HybridWebView.SendInvokeMessageToDotNet("CallMeFromScript", ["msg from js", 987]);
With JavaScript you can also asynchronously call a .NET method and get a result:
HybridWebView.SendInvokeMessageToDotNetAsync("CallMeFromScriptAsync", ["msg from js", 987])
.then(result => console.log("Got result from .NET: " + result));
or
const result = await HybridWebView.SendInvokeMessageToDotNetAsync("CallMeFromScriptAsync", ["msg from js", 987]);
In addition to method invocation, sending "raw" messages is also supported.
Projects in this repo:
See the main discussion topic here: https://github.com/dotnet/maui/discussions/12009
Or please log an issue in this repo for any other topics.
To get started, you'll need a .NET MAUI 8 project, then add the HybridWebView control, and add some web content to it.
Note: If you'd like to check out an already completed sample, go to https://github.com/Eilon/SampleMauiHybridWebViewProject
EJL.MauiHybridWebView
package (NuGet package):
ejl.mauihybridwebview
HybridWebView
control to a page in your app:
MauiProgram.cs
add the line builder.Services.AddHybridWebView();
to register the HybridWebView
componentsMainPage.xaml
file<ScrollView>
control and all of its contentsxmlns:ejl="clr-namespace:HybridWebView;assembly=HybridWebView"
declaration to the top-level <ContentPage ....>
tag<ejl:HybridWebView HybridAssetRoot="hybrid_root" RawMessageReceived="OnHybridWebViewRawMessageReceived" />
inside the <ContentPage>
tagMainPage.xaml.cs
filecount
field, and the OnCounterClicked
method, and replace it with the following code:
private async void OnHybridWebViewRawMessageReceived(object sender, HybridWebView.HybridWebViewRawMessageReceivedEventArgs e)
{
await Dispatcher.DispatchAsync(async () =>
{
await DisplayAlert("JavaScript message", e.Message, "OK");
});
}
Now add some web content to the app:
hybrid_root
In the hybrid_root
folder add a new file called index.html
and replace its contents with:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://github.com/Eilon/MauiHybridWebView/raw/main/_hwv/HybridWebView.js"></script>
<script src="https://github.com/Eilon/MauiHybridWebView/raw/main/myapp.js"></script>
</head>
<body>
<div>
Your message: <input type="text" id="messageInput" />
</div>
<div>
<button onclick="SendMessageToCSharp()">Send to C#!</button>
</div>
</body>
</html>
myapp.js
with the following contents:
function SendMessageToCSharp() {
var message = document.getElementById('messageInput').value;
HybridWebView.SendRawMessageToDotNet(message);
}
The HybridWebView
control can redirect URL requests to native code, and allow custom responses streams to be set.
This allows scenarios such as dynamically generating content, loading content from compressed files like ZIP or SQLite, or loading content from the internet that doesn't support CORS.
To use this feature, handle the ProxyRequestReceived
event in the HybridWebView
control.
When the event handler is called set the ResponseStream
and optionally the ResponseContentType
of the HybridWebViewProxyEventArgs
object received in the OnProxyRequest
method.
The HybridWebViewProxyEventArgs
has the following properties:
Property | Type | Description |
---|---|---|
QueryParams |
IDictionary<string, string> |
The query string parameters of the request. Note that all values will be strings. |
ResponseContentType |
string |
The content type of the response body. Default: "text/plain" |
ResponseStream |
Stream |
The stream to use as the response body. |
Url |
string |
The full URL that was requested. |
myWebView.ProxyRequestReceived += async (args) =>
{
//Use the query string parameters to determine what to do.
if (args.QueryParams.TryGetValue("myParameter", out var myParameter))
{
//Add your logic to determine what to do.
if (myParameter == "myValue")
{
//Add logic to get your content (e.g. from a database, or generate it).
//Can be anything that can be turned into a stream.
//Set the response stream and optionally the content type.
args.ResponseStream = new MemoryStream(Encoding.UTF8.GetBytes("This is the file content"));
args.ResponseContentType = "text/plain";
}
}
};
In your web app, you can make requests to the proxy URL by either using relative paths like /proxy?myParameter=myValue
or an absolute path by appending the relative path tot he pages origin location window.location.origin + '/proxy?myParameter=myValue'
.
Be sure to encode the query string parameters so that they are properly handled. Here are some ways to implement proxy URLs in your web app:
<img src="https://github.com/Eilon/MauiHybridWebView/raw/main/proxy?myParameter=myValue" />
Use proxy URLs in JavaScript.
var request = window.location.origin + '/proxy?myParameter=' + encodeURIComponent('myValue');
fetch(request)
.then(response => response.text())
.then(data => console.log(data));
NOTE: Data from the webview can only be set in the proxy query string. POST body data is not supported as the native WebView
in platforms do not support it.
To run this app you need to have Visual Studio for Windows or Mac, including the .NET MAUI workload. Then clone this repo, open the solution, and run one of the sample projects.
The MauiReactJSHybridApp sample contains portions of a pre-existing Todo App built using React JS.
The original React JS Todo app sample used here is based on this sample: https://github.com/mdn/todo-react. I created a fork at https://github.com/Eilon/todo-react that incorporates some small changes to call the .NET API from JavaScript to synchronize the Todo list between the two parts of the app.
To make changes to the fork and update the .NET MAUI app, here's what I do:
yarn
to ensure the JavaScript dependencies are installedset PUBLIC_URL=/
to establish the root of the app as /
because that's the root of the .NET MAUI HybridWebView appnpm run build
to compile the app and produce a static version of it
Error: error:0308010C:digital envelope routines::unsupported
set NODE_OPTIONS=--openssl-legacy-provider
npm run build
./build
folderMauiReactJSHybridApp
's Resources/Raw/ReactTodoApp
folder and delete all the existing files, and replace with the files from the previous step's ./build
folderThis project is licensed under the MIT License. However, portions of the incorporated source code may be subject to other licenses:
MauiReactJSHybridApp/Resources/Raw/ReactTodoApp
folder is the output of a build from https://github.com/Eilon/todo-react, which is a modified fork of https://github.com/mdn/todo-react, which is licensed under the Mozilla Public License 2.0.