Skip to content

Commit

Permalink
Call .NET methods from JS (#538)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidbritch authored Nov 14, 2024
1 parent 6854972 commit c4d697f
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AA468903-C211-421B-A09A-81F3745FD28B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA468903-C211-421B-A09A-81F3745FD28B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA468903-C211-421B-A09A-81F3745FD28B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{AA468903-C211-421B-A09A-81F3745FD28B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA468903-C211-421B-A09A-81F3745FD28B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using System.Diagnostics;
using System.Text.Json.Serialization;

namespace HybridWebViewDemo;

Expand All @@ -9,6 +10,7 @@ public partial class MainPage : ContentPage
public MainPage()
{
InitializeComponent();
hybridWebView.SetInvokeJavaScriptTarget<DotNetMethods>(new DotNetMethods(this));
}

private void OnSendMessageButtonClicked(object sender, EventArgs e)
Expand All @@ -22,7 +24,7 @@ private async void OnInvokeJSMethodButtonClicked(object sender, EventArgs e)
double x = 123d;
double y = 321d;

ComputationResult result = await hybridWebView.InvokeJavaScriptAsync<ComputationResult>(
ComputationResult? result = await hybridWebView.InvokeJavaScriptAsync<ComputationResult>(
"AddNumbers", // JavaScript method name
HybridSampleJSContext.Default.ComputationResult, // JSON serialization info for return type
[x, y], // Parameter values
Expand All @@ -40,7 +42,7 @@ private async void OnInvokeAsyncJSMethodButtonClicked(object sender, EventArgs e
{
string statusResult = string.Empty;

Dictionary<string,string> asyncResult = await hybridWebView.InvokeJavaScriptAsync<Dictionary<string, string>>(
Dictionary<string,string>? asyncResult = await hybridWebView.InvokeJavaScriptAsync<Dictionary<string, string>>(
"EvaluateMeWithParamsAndAsyncReturn", // JavaScript method name
HybridSampleJSContext.Default.DictionaryStringString, // JSON serialization info for return type
["new_key", "new_value"], // Parameter values
Expand Down Expand Up @@ -75,4 +77,46 @@ internal partial class HybridSampleJSContext : JsonSerializerContext
// This type's attributes specify JSON serialization info to preserve type structure
// for trimmed builds.
}

private class DotNetMethods
{
MainPage _mainPage;

public DotNetMethods(MainPage mainPage)
{
_mainPage = mainPage;
}

public void DoSyncWork()
{
Debug.WriteLine("DoSyncWork");
}

public void DoSyncWorkParams(int i, string s)
{
Debug.WriteLine($"DoSyncWorkParams: {i}, {s}");
}

public string DoSyncWorkReturn()
{
Debug.WriteLine("DoSyncWorkReturn");
return "Hello from C#!";
}

public SyncReturn DoSyncWorkParamsReturn(int i, string s)
{
Debug.WriteLine($"DoSyncWorkParamReturn: {i}, {s}");
return new SyncReturn
{
Message = "Hello from C#!" + s,
Value = i
};
}
}

public class SyncReturn
{
public string? Message { get; set; }
public int Value { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public static MauiApp CreateMauiApp()
});

#if DEBUG
builder.Logging.AddDebug();
builder.Services.AddHybridWebViewDeveloperTools();
builder.Logging.AddDebug();
#endif

return builder.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
<link rel="stylesheet" href="styles/app.css">
<script src="scripts/HybridWebView.js"></script>
<script>
function LogMessage(msg) {
var messageLog = document.getElementById("messageLog");
messageLog.value += '\r\n' + msg;
}

window.addEventListener(
"HybridWebViewMessageReceived",
function (e) {
var messageFromCSharp = document.getElementById("messageFromCSharp");
messageFromCSharp.value += '\r\n' + e.detail.message;
LogMessage("Raw message: " + e.detail.message);
});

function AddNumbers(a, b) {
Expand All @@ -28,7 +32,7 @@
async function EvaluateMeWithParamsAndAsyncReturn(s1, s2) {
const response = await fetch("/asyncdata.txt");
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
throw new Error(`HTTP error: ${response.status}`);
}
var jsonData = await response.json();

Expand All @@ -39,6 +43,32 @@

return jsonData;
}


async function InvokeDoSyncWork() {
LogMessage("Invoking DoSyncWork");
await window.HybridWebView.InvokeDotNet('DoSyncWork');
LogMessage("Invoked DoSyncWork");
}

async function InvokeDoSyncWorkParams() {
LogMessage("Invoking DoSyncWorkParams");
await window.HybridWebView.InvokeDotNet('DoSyncWorkParams', [123, 'hello']);
LogMessage("Invoked DoSyncWorkParams");
}

async function InvokeDoSyncWorkReturn() {
LogMessage("Invoking DoSyncWorkReturn");
const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkReturn');
LogMessage("Invoked DoSyncWorkReturn, return value: " + retValue);
}

async function InvokeDoSyncWorkParamsReturn() {
LogMessage("Invoking DoSyncWorkParamsReturn");
const retValue = await window.HybridWebView.InvokeDotNet('DoSyncWorkParamsReturn', [123, 'hello']);
LogMessage("Invoked DoSyncWorkParamsReturn, return value: message=" + retValue.Message + ", value=" + retValue.Value);
}

</script>
</head>
<body>
Expand All @@ -49,10 +79,16 @@
<button onclick="window.HybridWebView.SendRawMessage('Message from JS! ' + (count++))">Send message to C#</button>
</div>
<div>
Message from C#: <textarea readonly id="messageFromCSharp" style="width: 80%; height: 10em;"></textarea>
<button onclick="InvokeDoSyncWork()">Call C# sync method (no params)</button>
<button onclick="InvokeDoSyncWorkParams()">Call C# sync method (params)</button>
<button onclick="InvokeDoSyncWorkReturn()">Call C# method (no params) and get simple return value</button>
<button onclick="InvokeDoSyncWorkParamsReturn()">Call C# method (params) and get complex return value</button>
</div>
<div>
Log: <textarea readonly id="messageLog" style="width: 80%; height: 10em;"></textarea>
</div>
<div>
Consider examining this PDF: <a href="docs/sample.pdf">sample.pdf</a>
Consider checking out this PDF: <a href="docs/sample.pdf">sample.pdf</a>
</div>
</body>
</html>
</html>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
window.HybridWebView = {
"Init": function () {
"Init": function Init() {
function DispatchHybridWebViewMessage(message) {
const event = new CustomEvent("HybridWebViewMessageReceived", { detail: { message: message } });
window.dispatchEvent(event);
Expand Down Expand Up @@ -27,11 +27,53 @@
}
},

"SendRawMessage": function (message) {
window.HybridWebView.__SendMessageInternal('RawMessage', message);
"SendRawMessage": function SendRawMessage(message) {
window.HybridWebView.__SendMessageInternal('__RawMessage', message);
},

"__SendMessageInternal": function (type, message) {
"InvokeDotNet": async function InvokeDotNetAsync(methodName, paramValues) {
const body = {
MethodName: methodName
};

if (typeof paramValues !== 'undefined') {
if (!Array.isArray(paramValues)) {
paramValues = [paramValues];
}

for (var i = 0; i < paramValues.length; i++) {
paramValues[i] = JSON.stringify(paramValues[i]);
}

if (paramValues.length > 0) {
body.ParamValues = paramValues;
}
}

const message = JSON.stringify(body);

var requestUrl = `${window.location.origin}/__hwvInvokeDotNet?data=${encodeURIComponent(message)}`;

const rawResponse = await fetch(requestUrl, {
method: 'GET',
headers: {
'Accept': 'application/json'
}
});
const response = await rawResponse.json();

if (response) {
if (response.IsJson) {
return JSON.parse(response.Result);
}

return response.Result;
}

return null;
},

"__SendMessageInternal": function __SendMessageInternal(type, message) {

const messageToSend = type + '|' + message;

Expand All @@ -49,7 +91,7 @@
}
},

"InvokeMethod": function (taskId, methodName, args) {
"__InvokeJavaScript": function __InvokeJavaScript(taskId, methodName, args) {
if (methodName[Symbol.toStringTag] === 'AsyncFunction') {
// For async methods, we need to call the method and then trigger the callback when it's done
const asyncPromise = methodName(...args);
Expand All @@ -65,14 +107,14 @@
}
},

"__TriggerAsyncCallback": function (taskId, result) {
"__TriggerAsyncCallback": function __TriggerAsyncCallback(taskId, result) {
// Make sure the result is a string
if (result && typeof (result) !== 'string') {
result = JSON.stringify(result);
}

window.HybridWebView.__SendMessageInternal('InvokeMethodCompleted', taskId + '|' + result);
window.HybridWebView.__SendMessageInternal('__InvokeJavaScriptCompleted', taskId + '|' + result);
}
}

window.HybridWebView.Init();
window.HybridWebView.Init();

0 comments on commit c4d697f

Please sign in to comment.