maui icon indicating copy to clipboard operation
maui copied to clipboard

[Enhancement] (HybridWebView) - Invoke C# code from JavaScript

Open Sergtek opened this issue 2 years ago • 43 comments

Description

In Xamarin.Forms to invoke C# code from JavaScript in a WebView there is official documentation from Microsoft: https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/hybridwebview

This suggestion is to add new documentation on HybridWebView for .NET Maui as custom renderers are used in the Xamarin.Forms documentation and now with .NET Maui custom renderers are deprecated so it would be interesting to add examples using Handlers and in the Xamarin.Forms the Windows sample supports UWP but now with .NET Maui we need a WinUI 3 compatible sample, and Microsoft now officially supports macOS, so an example should be added for this platform as well.

Public API Changes

Create HybridWebView for .NET Maui using Handlers instead of custom renderers and adding examples for WinUI 3 and macOS.

Intended Use-Case

Invoking C# code from JavaScript in a WebView is very common in many applications and Xamarin.Forms has documentation from Microsoft covering this topic so it stands to reason that .NET Maui which is the successor technology to Xamarin.Forms will receive the same support in all officially supported platforms: Android, iOS, WinUI 3 and macOS.

Sergtek avatar Apr 23 '22 15:04 Sergtek

I am almost there. The only issue is this one https://gitlab.com/fairking/mauiwebviewsample

Also the Blazor is an interesting approach one: https://gitlab.com/fairking/mauiblazorwebview

More info here: https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-javascript-from-dotnet

fairking avatar Apr 23 '22 18:04 fairking

We have some ideas to make this type of IPC a lot easier: https://github.com/dotnet/maui/issues/1148

But it's not currently in the plans.

We do have BlazorWebView, which enables running Razor components in .NET MAUI apps on all supported platforms, which includes C#/JS interop.

Eilon avatar Apr 28 '22 18:04 Eilon

We do have BlazorWebView, which enables running Razor components in .NET MAUI apps on all supported platforms, which includes C#/JS interop.

  1. C# to Js calls are not available https://github.com/dotnet/aspnetcore/issues/41339
  2. Thinking of hot reload for other pwa frameworks like vue (not just Blazor).

fairking avatar Apr 29 '22 17:04 fairking

We have some ideas to make this type of IPC a lot easier: #1148

But it's not currently in the plans.

We do have BlazorWebView, which enables running Razor components in .NET MAUI apps on all supported platforms, which includes C#/JS interop.

But that doesn't cover the following use case: Hosting a Blazor Server app in a MAUI shell. Like that you can have an app that runs in a browser, with a MAUI version if you need enhanced support for local resources (e.g. local applications). In the end we just need a way to send messages from the Blazor Server app to the MAUI shell. At the moment this is not possible, correcct?

luetm avatar May 19 '22 07:05 luetm

@luetm not directly, but that's what this feature proposal would make easier. For now you could use a regular .NET MAUI WebView (not BlazorWebView) and register JavaScript callbacks to communicate between the code in the web view (which would be your Blazor Server app, which is really just a web app), and the .NET code running in .NET MAUI. But it's all manual.

Eilon avatar May 19 '22 16:05 Eilon

Yes, we really need a Blazor server app to access mobile resources through calling C# from JavaScript too. Is "Customizing a WebView" available in MAUI?

jameslin0312 avatar May 25 '22 08:05 jameslin0312

Yes, we really need a Blazor server app to access mobile resources through calling C# from JavaScript too. Is "Customizing a WebView" available in MAUI?

This is already available in BlazorWebView. See https://gitlab.com/fairking/mauiblazorwebview/-/blob/master/MauiApp1/wwwroot/index.html#L43

fairking avatar May 25 '22 09:05 fairking

@fairking Thanks for your reply. I will test it. By the way, is there any documentation about "DotNet" usage? Can I use HostPage with external url?

jameslin0312 avatar May 25 '22 10:05 jameslin0312

@fairking Thanks for your reply. I will test it. By the way, is there any documentation about "DotNet" usage? Can I use HostPage with external url?

https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/call-dotnet-from-javascript

fairking avatar May 25 '22 16:05 fairking

@fairking It doesn't seem to resolve my problem. I need a WebView or BlazorWebView to connect remote Blazor server web site, and the JavaScript loaded from the web site needs to call C# functions to access device resource. I have tried setting remote url to HostPage of BlazorWebView without any RootCimponents. But the app shows "There is no content at".

jameslin0312 avatar May 27 '22 08:05 jameslin0312

@fairking It doesn't seem to resolve my problem. I need a WebView or BlazorWebView to connect remote Blazor server web site, and the JavaScript loaded from the web site needs to call C# functions to access device resource. I have tried setting remote url to HostPage of BlazorWebView without any RootCimponents. But the app shows "There is no content at".

Sorry, I don't understand what you are trying to achieve. Do you want to call backed (c#) from WebView (JS) in Desktop/Mobile application? That link I provided is exactly for such purpose. The only issue with BlazorWebView is there is no communication in opposite way (to call frontend from backend).

You say "Blazor server web site" which for me is simply a client running in BlazorWebView (using WebAssembly to compile) and communicating with the server via HTTP as any other SPA application. Unless I am wrong. Please someone correct me as I don't use Blazor. I prefer Vuejs/Typescript.

fairking avatar May 27 '22 12:05 fairking

@fairking Thanks for your help. I have a ASP. NET Core server implemented already, and I want to have a MAUI hyprid app to load web page on WebView from the server. On the web page, I need to use the Javascript to call client C# to access device resources, such as scanning QR Code or getting GPS. I can do this using Xamarin forms by customizing WebRender.

jameslin0312 avatar May 27 '22 15:05 jameslin0312

@fairking Thanks for your help. I have a ASP. NET Core server implemented already, and I want to have a MAUI hyprid app to load web page on WebView from the server. On the web page, I need to use the Javascript to call client C# to access device resources, such as scanning QR Code or getting GPS. I can do this using Xamarin forms by customizing WebRender.

I am not sure if you can communicate with Backend from ASP.NET renderer perspective (when you generate html), but you can definitely communicate with Backend from BlazorWebView client via javascript: MainPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:b="clr-namespace:Microsoft.AspNetCore.Components.WebView.Maui;assembly=Microsoft.AspNetCore.Components.WebView.Maui"
             xmlns:local="clr-namespace:MyProject"
             x:Class="MyProject.MainPage"
             BackgroundColor="{DynamicResource PageBackgroundColor}">

    <b:BlazorWebView HostPage="wwwroot/index.html"></b:BlazorWebView>

</ContentPage>

index.html

<button onclick="callCsharp()" type="button">Click Me</button>

<script>
    function callCsharp() {
        window
            .DotNet.invokeMethodAsync('MyProject', 'CallMeFromJs', "ABC")
            .then((data) => { console.log(data); }); // It will print "ABC OK" in browser console or VS output window
    }
</script>

MauiProgram.cs

using Microsoft.JSInterop;

namespace MyProject
{
    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            return MauiApp.CreateBuilder()
                .UseMauiApp<App>()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                })
                .Services.AddMauiBlazorWebView()
                .Services.AddBlazorWebViewDeveloperTools()
                .Build();
        }

        [JSInvokable]
        public static async Task<object> CallMeFromJs(JsRequest request)
        {
            var result = (string)request + " OK";
            return result;
        }
    }
}

Note: MyProject is your assembly name in window.DotNet.invokeMethodAsync('MyProject', 'CallMeFromJs', "ABC").

Please see this sample: https://gitlab.com/fairking/mauiblazorwebview

I hope it helps.

fairking avatar May 27 '22 17:05 fairking

@fairking Thanks for your sample. I need the HostPage of BlazorWebBrowser to be https://MyWebServer/, not local index.html file. This seems not supported in BlazorWebView. I can use WebView to load a remote url, but I can't find the way to call from Javascript to client C#.

jameslin0312 avatar May 28 '22 00:05 jameslin0312

I am in a similar boat where I am using a webview around an existing angular app and need to have way for angular code call C# in .Net Maui. I had it working in Xamarin forms.

fras2560 avatar Jun 27 '22 17:06 fras2560

@fairking @Eilon can you provide more support for cases where BlazorWebView is not an option and need to use WebView due to files being served by a web server?

fras2560 avatar Jul 20 '22 20:07 fras2560

@fairking @Eilon can you provide more support for cases where BlazorWebView is not an option and need to use WebView due to files being served by a web server?

It's a very interesting scenario but right now we don't have a specific plan for it. BTW even in BlazorWebView there is support for remote and local files, but it's still focused on using Blazor-based apps (for example, it wouldn't work well for React).

Eilon avatar Jul 21 '22 18:07 Eilon

@Eilon This is a long story. Long time ago I published a post on microsoft community forum about how cool would be to use MAUI as a cross platform framework to build Hybrid applications. The reason many people would love it because there is a huge market of SPA web applications (Angular/React/Vue) which companies and people want to bring into Desktop/Mobile/UWP world with a device API support (eg. Camera, Storage or Sqlite to be able to keep data offline). At the moment there is Electron and NativeScript but their backend is JS/TS, no C#/.NET support. So those developers can use C#/.NET as their backend.

I started digging and found out it is not possible at the moment to move SPA app into MAUI project.

First with WebView there is a problem with loading local files. Also it wasn't possible just to point to local index.html and have relative paths. It required to load index.html manually using FileStream and fix all relative paths of the js and css assets/vendors.js => /spa/assets/vendors.js because the BaseUrl wasn't working properly. I cannot see there is a progress. It is still in development so far.

Regarding the BlazorWebView the thing with pointing to index.html and loading assets works as expected, but on the other hand there is a Blazor.start() which is not working properly on Android (windows is fine). I tried the latest release but looks like same issue.

There is a hot reload thing which does not exist for hybrid applications in MAUI, but I have no problem with that. I develop and debug UI running the app in web browser (npm run serve), and repoint the backend api to the web api. On PreBuild event I can do something like npm run build && xcopy /spa/dist/*.* /maui/wwwroot/*.*.

If one of those WebViews could be fixed I may be able to start working on it, but if not then I will consider to try Tauri which is not mature yet and the Android support is in development but it looks very promising as a framework for hybrid multiplatform applications.

Also I am waiting for NFC support for MAUI apps. But this is completely different story I assume.

fairking avatar Jul 21 '22 20:07 fairking

BlazorWebView

@Eilon How does BlazorWebView support remote files? It seems like using HostPage is not working for me.

fras2560 avatar Aug 02 '22 20:08 fras2560

@fras2560 the HostPage has to be a local file, but you can reference scripts, images, etc. that are remote.

Eilon avatar Aug 02 '22 23:08 Eilon

WebView could have a interop way to communicate to javascript... Javascript -> C# and C# -> Javascript. Thats the biggest problem why we are using reactnative right now and not maui/.net for apps.

danilobreda avatar Sep 21 '22 03:09 danilobreda

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

ghost avatar Nov 10 '22 20:11 ghost

So calling C# from javascript in .NET MAUI is not supported for .NET 7?

jjxtra avatar Nov 18 '22 19:11 jjxtra

There is a good tutorial of .NET Maui Blazor with JS calling .net https://www.youtube.com/watch?v=o7iVk5XFPd8&ab_channel=ProgressTelerik , this is a .NET MAUI Blazor loading an Angular project and calling C# functions in the BLazor project from that angular Project

PTAHume avatar Nov 27 '22 03:11 PTAHume

I guess we are stuck with using navigation events / urls, which is slow unfortunately and limited on data that can be passed

jjxtra avatar Nov 28 '22 20:11 jjxtra

I found a solution that works, to be fair this is in the GitHub example that is mentioned above but at first, I thought it didn't work till I realised you have to make sure that you get the project reference name correct: So if you use:

DotNet.invokeMethodAsync('MyMauiApp', 'CallMeFromJs', 'SomeString')

You DO NOT need to register this in the @code or codebase e.g. in the "OnInitializedAsync() or OnAfterRenderAsync(bool firstRender)"

All you need to do is add it to a js file that is referenced by index.html or directly to that page header, as a JS function that you call at some point.

The important part of this is 'MyMauiApp' this MUST be the NAME of the project NOT the default namespace that's created from that name.

For example, if the project was called "FOO-BAR" the default namespace would be called "Foo_Bar" but the application name would be "FOO-BAR" and that's what you would need to use, in the js function to call c#

Usually, the two are the same unless you use crazy naming conventions like I tend to.

This will the C# function CallMeFromJs and pass the pram 'SomeString' :)

The CS is as normal then

   [JSInvokable]
    public static async Task<bool> CallMeFromJs(string somePram)

This works as per usual with Blazor but inside Maui and allows you to call a C# function on a razor page from JS.

This will work for Android and Windows as I have tested and prove that locally. I have not yet been able to test this on ISO or Mac as I need to set up a mac VM and iPhone emulator first but I suspect it will do.

It seems the key is using "DotNet.invokeMethodAsync" as opposed to registering a js function and passing a reference of the project to it like you might normally do: THIS WILL NOT WORK await JSRuntime.InvokeAsync<bool>("CallMeFromJs", DotNetObjectReference.Create(this));

I hope this helps others

PTAHume avatar Nov 29 '22 14:11 PTAHume

Does the above pattern work outside of Blazor?

jjxtra avatar Nov 29 '22 14:11 jjxtra

yes it does, so long as the child project is within the blazor project it seems, if you watch that Microsoft youtube video I posted above they do exactly that, they load an angular projected into blazor inside of net MAUI and are able to call C# functions in blazor from angular

PTAHume avatar Nov 29 '22 14:11 PTAHume

Thanks. Can't use blazor for my particular Maui app, so will continue with the url navigation hook pattern.

jjxtra avatar Nov 29 '22 14:11 jjxtra

@PTAHume are you saying if I am using a webview in a dotnet maui project I can call

DotNet.invokeMethodAsync('MyMauiApp', 'CallMeFromJs', 'SomeString')

fras2560 avatar Nov 29 '22 15:11 fras2560