MicrosoftEdge / WebView2Feedback

Feedback and discussions about Microsoft Edge WebView2
https://aka.ms/webview2
437 stars 51 forks source link

.NET host objects need to use deprecated AutoDual attribute #517

Open champnic opened 3 years ago

champnic commented 3 years ago

One thing that is a little disconcerting about this is that we need to use this:

image

Originally posted by @RickStrahl in https://github.com/MicrosoftEdge/WebView2Feedback/issues/199#issuecomment-632965966

AB#28585274

champnic commented 3 years ago

I've created an item on our backlog to track this, thanks for calling it out! We're working with the .NET team on the right path forwards.

cgeier commented 3 years ago

Using ClassInterfaceType.AutoDual is not required. ClassInterfaceType.None can be used. If ClassInterfaceType.None is used, one needs to create interface(s) for each class. The following code also demonstrates using an event to send data from JavaScript to C#. I believe that the maximum depth currently supported by hostObjects is 3.

For the following code, ensure that you've subscribed to the WebView2 control's WebMessageReceived event.

C#: (WebView2 v.1.0.721)

ClassFiles-CSharp.zip

Set WebView2 control settings

//set WebView2 control settings
webView21.CoreWebView2.Settings.AreHostObjectsAllowed = true;
webView21.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = true;
webView21.CoreWebView2.Settings.IsScriptEnabled = true;
webView21.CoreWebView2.Settings.IsWebMessageEnabled = true;

Create a class named: EmployeeInfoValueUpdatedArgs

EmployeeInfoValueUpdatedArgs.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace WebView2UsingHostObject
{
    [ComVisible(false)]
    public delegate void EventHandlerEmployeeInfoValueUpdated(object sender, EmployeeInfoValueUpdatedArgs e);

    [ComVisible(true)]
    public class EmployeeInfoValueUpdatedArgs : System.EventArgs

    {
        public string Data { get; private set; } = string.Empty;

        public EmployeeInfoValueUpdatedArgs(string data)
        {
            this.Data = data;
        }
    }
}

Create a class named: Employee.cs

Employee.cs

Add the following using statements:

using System.Runtime.InteropServices;
using Newtonsoft.Json;

To allow use with event(s), add the following interface (in Employee.cs):

IComEventsEmployee

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEventsEmployee
{
    void ValueUpdatedEmployee(object sender, EmployeeInfoValueUpdatedArgs e);

}

Create an interface for Employee (in Employee.cs):

IEmployee

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IEmployee
{
    //If a method / property doesn't need to be COM visible
    //(available in Javascript), don't include it in the interface

    string EmployeeId { get; set; }

    string FirstName { get; set; }

    string LastName { get; set; }

    void AddPhone(string phoneNumberType, string phoneNumber);

    string GetPhone();

    string GetPhoneAsJson();

    string GetPhoneAsJsonNewtonsoft();

    void SendUpdates(string data);

}

Add code to the Employee class (in Employee.cs):

Employee

Note: Employee implements IEmployee, but IComEventsEmployee is only specified in [ComSourceInterfaces(typeof(IComEventsEmployee))]

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEventsEmployee))]
public class Employee : IEmployee
{

    //variable name as declared in the interface (IComEventsEmployee)
    //event interested parties can register with to know 
    //when value is updated.

    public event EventHandlerEmployeeInfoValueUpdated ValueUpdatedEmployee;

    public string EmployeeId { get; set; }

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public List<PhoneInfo> Phone { get; set; } = new List<PhoneInfo>();

    public void AddPhone(string phoneNumberType, string phoneNumber)
    {
        Phone.Add(new PhoneInfo(phoneNumberType, phoneNumber));
    }

    public string GetPhone()
    {
        StringBuilder sb = new StringBuilder();

        if (Phone != null && Phone.Count > 0)
        {
            foreach (PhoneInfo p in Phone)
            {
                sb.AppendFormat("PhoneNumberType: {0}, PhoneNumber: {1}", p.PhoneNumberType, p.PhoneNumber);
            }

        }

        return sb.ToString();
    }

    public string GetPhoneAsJson()
    {
        string jsonStr = string.Empty;
        string jsonInnerStr = string.Empty;

        if (Phone != null && Phone.Count > 0)
        {
            jsonStr = String.Format("[");

            foreach (PhoneInfo p in Phone)
            {
                if (!String.IsNullOrEmpty(jsonInnerStr))
                {
                    jsonInnerStr += ",";
                }

                jsonInnerStr += String.Format("{{\"PhoneNumberType\":\"{0}\",\"PhoneNumber\":\"{1}\"}}", p.PhoneNumberType, p.PhoneNumber);

            }

            jsonStr += jsonInnerStr;
            jsonStr += String.Format("]");

        }

        return jsonStr;
    }

    public string GetPhoneAsJsonNewtonsoft()
    {
        return JsonConvert.SerializeObject(Phone);
    }

    public void SendUpdates(string data)
    {
        //Important: don't raise the event unless there are subscribers

        if (ValueUpdatedEmployee != null)
        {
            //create a new instance
            //using the constructor to set the value
            EmployeeInfoValueUpdatedArgs valueArgs = new EmployeeInfoValueUpdatedArgs(data);

            //raise event 'ValueUpdatedEmployee'
            ValueUpdatedEmployee(this, valueArgs);
        }//if
    }
}

Create a class named: PhoneInfo.cs

PhoneInfo.cs

Add the following using statements:

using System.Runtime.InteropServices;
using Newtonsoft.Json;

To allow use with event(s), add the following interface (in PhoneInfo.cs):

IComEventsPhoneInfo

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEventsPhoneInfo
{
    //Todo: add event - optional

}

Create an interface for Employee (in PhoneInfo.cs):

IPhoneInfo

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IPhoneInfo
{
    //If a method / property doesn't need to be COM visible
    //(available in Javascript), don't include it in the interface

    string PhoneNumberType { get; set; }
    string PhoneNumber { get; set; }

}

Add code to the PhoneInfo class (in PhoneInfo.cs):

PhoneInfo

Note: Employee implements IPhoneInfo, but IComEventsPhoneInfo is only specified in [ComSourceInterfaces(typeof(IComEventsPhoneInfo))]

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEventsPhoneInfo))]
public class PhoneInfo : IPhoneInfo
{

    //Todo: add event - optional

    public string PhoneNumberType { get; set; } 

    public string PhoneNumber { get; set; }

    public PhoneInfo()
    {

    }

    public PhoneInfo(string phoneNumberType, string phoneNumber)
    {
        this.PhoneNumberType = phoneNumberType;
        this.PhoneNumber = phoneNumber;
    }
}

To Use

Example:

private async Task TestHostObjectEmployeeInfo()
{
    StringBuilder scriptToRun = new StringBuilder();

    //create new instance
    Employee employee = new Employee();

    //add host object
    webView21.CoreWebView2.AddHostObjectToScript("employee", employee);

    //subscribe to event(s) (listen to events)
    employee.ValueUpdatedEmployee += Employee_ValueUpdatedEmployee;

    scriptToRun = new StringBuilder();

    //#1
    scriptToRun.Append("function sendData(){ \n");
    scriptToRun.Append("  try{ \n");
    scriptToRun.Append("    chrome.webview.hostObjects.sync.employee.FirstName = 'John';\n");
    scriptToRun.Append("    let result = chrome.webview.hostObjects.sync.employee.FirstName; \n");
    scriptToRun.Append("    return result; \n");
    scriptToRun.Append("  } \n");
    scriptToRun.Append("  catch(err){ \n");
    scriptToRun.Append("    window.chrome.webview.postMessage('Error: ' + err.message); \n");
    scriptToRun.Append("  } \n\n");

    scriptToRun.Append("} \n");

    scriptToRun.AppendLine("sendData();\n");

    var result1 = await webView21.CoreWebView2.ExecuteScriptAsync(scriptToRun.ToString());

    //#2
    scriptToRun = new StringBuilder();
    scriptToRun.Append("function sendData(){ \n");
    scriptToRun.Append("  try{ \n");
    scriptToRun.Append("    chrome.webview.hostObjects.sync.employee.AddPhone('home','800-555-1234'); \n");

    scriptToRun.Append("    let result = 
    chrome.webview.hostObjects.sync.employee.GetPhoneAsJsonNewtonsoft(); \n");
    scriptToRun.Append("    window.chrome.webview.postMessage(result); \n");

    scriptToRun.Append("    return result; \n");
    scriptToRun.Append("  } \n");
    scriptToRun.Append("  catch(err){ \n");
    scriptToRun.Append("    window.chrome.webview.postMessage('Error: ' + err.message); \n");
    scriptToRun.Append("  } \n\n");
    scriptToRun.Append("} \n");

    scriptToRun.AppendLine("sendData(); \n");

    var result2 = await webView21.CoreWebView2.ExecuteScriptAsync(scriptToRun.ToString());

    //#3
    scriptToRun = new StringBuilder();
    scriptToRun.Append("function sendData(){ \n");
    scriptToRun.Append("  try{ \n");
    scriptToRun.Append("    chrome.webview.hostObjects.sync.employee.FirstName = 'Tom';\n");
    scriptToRun.Append("    let result = chrome.webview.hostObjects.sync.employee.FirstName; \n");
    scriptToRun.Append("    chrome.webview.hostObjects.sync.employee.SendUpdates(result + ' hello from JS'); \n");
    scriptToRun.Append("    return result; \n");
    scriptToRun.Append("  } \n");
    scriptToRun.Append("  catch(err){ \n");
    scriptToRun.Append("    window.chrome.webview.postMessage('Error: ' + err.message); \n");

    scriptToRun.Append("  } \n\n");

    scriptToRun.Append("} \n");

    scriptToRun.AppendLine("sendData();\n");

    //LogMsg(scriptToRun.ToString());

    var result3 = await webView21.CoreWebView2.ExecuteScriptAsync(scriptToRun.ToString());

    try
    {
        webView21.CoreWebView2.RemoveHostObjectFromScript("employee");
    }
    catch (System.Runtime.InteropServices.COMException ex)
    {
        System.Diagnostics.Debug.WriteLine("Error: (COMException) - " + ex.Message);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.Message);
    }

    //unsubscribe to event(s) (remove listener)
    employee.ValueUpdatedEmployee -= Employee_ValueUpdatedEmployee;

    employee = null;
}

Employee_ValueUpdatedEmployee

private void Employee_ValueUpdatedEmployee(object sender, EmployeeInfoValueUpdatedArgs e)
{
    System.Diagnostics.Debug.WriteLine("Employee_ValueUpdatedEmployee event raised");

    System.Diagnostics.Debug.WriteLine("Data: " + e.Data);
}

Some other things that can be specified are Guid, DispId, and ProgId. Note: Dispin is only specified in the interface(s). I believe each value should be unique among the interfaces used by a particular class. The values should also be sequential.

According to this post:

DispId: Uniquely identifies methods and properties of an interface/class to the COM Interop system. ... The ID’s that you assign in DispID need to be sequential (1, 2, 3, etc.).

Guid: Each COM visible component (Classes, Interfaces, etc.) within your assembly is identified within the Interop system through a Guid.

ComInterfaceType:

ClassInterfaceType (lets COM know whether your class supports Early Binding, Late Binding, or both.)

Note: To be available in VB6/VBA, the class may need to be registered using RegAsm.exe.

Also see:

IComEventsEmployee

[ComVisible(true)]
[Guid("your guid 1 here - containing only letters, numbers, and dashes")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEventsEmployee
{
    [DispId(0x00000001)]
    void ValueUpdatedEmployee(object sender, EmployeeInfoValueUpdatedArgs e);
}

IEmployee

[ComVisible(true)]
[Guid("your guid 2 here - containing only letters, numbers, and dashes")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IEmployee
{
    [DispId(0x10000001)]
    string EmployeeId { get; set; }

    [DispId(0x10000002)]
    string FirstName { get; set; }
        ...
}

Employee

[ComVisible(true)]
[Guid("your guid 3 here - containing only letters, numbers, and dashes")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEventsEmployee))]
[ProgId("MyNamespace.Employee")]
public class Employee : IEmployee
{
    public event EventHandlerEmployeeInfoValueUpdated ValueUpdatedEmployee;

    public string EmployeeId { get; set; }

    public string FirstName { get; set; }
        ...
}

Regasm.exe: When deploying your dll to other machines, use RegAsm.exe. It exports a type library (.tlb file) and registers it for COM. It’s okay if you overwrite an existing type library for the same assembly. The RegAsm.exe utility replaces the traditional RegSvr32.exe registration utility.

RegAsm.exe can be found in: %SystemRoot%\Microsoft.NET\Framework64\<version> or (%SystemRoot%\Microsoft.NET\Framework\<version>)

See

cgeier commented 3 years ago

Here's a version for VB .NET that uses ClassInterfaceType.None instead of the ClassInterfaceType.AutoDual. If ClassInterfaceType.None is used, one needs to create interface(s) for each class. The following code also demonstrates using an event to send data from JavaScript to VB .NET. (maximum depth currently supported by hostObjects is 3).

For the following code, ensure that you've subscribed to the WebView2 control's WebMessageReceived event. The instructions below are for VS 2019. However, you can also use VS 2017.

VB .NET (WebView2 v1.0.721)

ClassFiles-VB.NET.zip

Set WebView2 control settings

'set WebView2 control settings
WebView21.CoreWebView2.Settings.AreHostObjectsAllowed = True
WebView21.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = True
WebView21.CoreWebView2.Settings.IsScriptEnabled = True
WebView21.CoreWebView2.Settings.IsWebMessageEnabled = True

Create a class named: EmployeeInfoValueUpdatedArgs

EmployeeInfoValueUpdatedArgs

Option Strict Off
Option Explicit On
Imports System.Runtime.InteropServices
Imports Newtonsoft.Json

<ComVisible(False)>
Public Delegate Sub EventHandlerEmployeeInfoValueUpdated(sender As Object, e As EmployeeInfoValueUpdatedArgs)
Public Class EmployeeInfoValueUpdatedArgs

    Public Data As String

    Public Sub New()

    End Sub

    Public Sub New(userData As String)
        Data = userData
    End Sub

End Class

Create a class named: EmployeeInfo.vb

EmployeeInfo.vb

Add the following statements:

Option Strict Off
Option Explicit On
Imports System.Text
Imports System.Runtime.InteropServices
Imports Newtonsoft.Json

To allow use with event(s), add the following interface (in EmployeeInfo.vb):

<ComVisible(True)>
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)>
Public Interface IComEventsEmployeeInfo
    'ToDo: add event(s) - optional

    Event ValueUpdatedEmployeeInfo()
End Interface

Create an interface for EmployeeInfo (in EmployeeInfo.vb)

IEmployeeInfo

<ComVisible(True)>
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)>
Public Interface IEmployeeInfo

    Property EmployeeId() As String
    Property FirstName() As String
    Property LastName() As String

    Sub AddComputer(compAssetNumber As String, compDescription As String, compMake As String, compModel As String)

    Sub AddPhone(phoneNumberType As String, phoneNumber As String)

    Function GetPhone()

    Function GetPhoneAsJson()

    Function GetPhoneAsJsonNewtonsoft()

    Sub SendUpdates(data As String)

End Interface

Add code to the EmployeeInfo class (in EmployeeInfo.vb)

EmployeeInfo

Note: EmployeeInfo implements IEmployeeInfo, but IComEventsEmployeeInfo is only specified in <ComSourceInterfaces(GetType(IComEventsEmployeeInfo))>

<ComVisible(True)>
<ClassInterface(ClassInterfaceType.None)>
<ComDefaultInterface(GetType(IEmployeeInfo))>
<ComSourceInterfaces(GetType(IComEventsEmployeeInfo))>
Public Class EmployeeInfo
    Implements IEmployeeInfo

    'ToDo: add event(s) - optional

    Public Event ValueUpdatedEmployee As EventHandlerEmployeeInfoValueUpdated

    Private empId As String = String.Empty
    Private fName As String = String.Empty
    Private lName As String = String.Empty

    Public Property CompanyAssets As List(Of CompanyAssetInfo) = New List(Of CompanyAssetInfo)

    Public Property EmployeeId() As String Implements IEmployeeInfo.EmployeeId
        Get
            Return empId
        End Get

        Set(ByVal value As String)
            empId = value
        End Set
    End Property

    Public Property FirstName() As String Implements IEmployeeInfo.FirstName
        Get
            Return fName
        End Get

        Set(ByVal value As String)
            fName = value
        End Set
    End Property

    Public Property LastName() As String Implements IEmployeeInfo.LastName
        Get
            Return lName
        End Get

        Set(ByVal value As String)
            lName = value
        End Set
    End Property

    Public Property Phone As List(Of PhoneInfo) = New List(Of PhoneInfo)

    Public Sub AddComputer(compAssetNumber As String, compDescription As String, compMake As String, compModel As String) Implements IEmployeeInfo.AddComputer

        Dim assetInfo = New CompanyAssetInfo()
        assetInfo.AddComputer(compAssetNumber, compDescription, compMake, compModel)

        'add to list
        Me.CompanyAssets.Add(assetInfo)
    End Sub

    Public Sub AddPhone(phoneNumberType As String, phoneNumber As String) Implements IEmployeeInfo.AddPhone
        Phone.Add(New PhoneInfo(phoneNumberType, phoneNumber))
    End Sub

    Public Function GetPhone() Implements IEmployeeInfo.GetPhone

        Dim sb As New StringBuilder

        If Phone IsNot Nothing AndAlso Phone.Count > 0 Then
            For Each pInfo As PhoneInfo In Phone
                sb.AppendFormat("PhoneNumberType: {0}, PhoneNumber: {1}", pInfo.PhoneNumberType, pInfo.PhoneNumber)
            Next
        End If

        Return sb.ToString()
    End Function

    Public Function GetPhoneAsJson() Implements IEmployeeInfo.GetPhoneAsJson
        Dim jsonStr As String = String.Empty
        Dim jsonInnerStr As String = String.Empty

        If Phone IsNot Nothing AndAlso Phone.Count > 0 Then
            jsonStr = String.Format("[")

            For Each pInfo As PhoneInfo In Phone
                jsonInnerStr += String.Format("{{""PhoneNumberType"":""{0}"",""PhoneNumber"":""{1}""}}", pInfo.PhoneNumberType, pInfo.PhoneNumber)
            Next

            jsonStr += jsonInnerStr
            jsonStr += String.Format("]")
        End If

        Return jsonStr
    End Function

    Public Function GetPhoneAsJsonNewtonsoft() Implements IEmployeeInfo.GetPhoneAsJsonNewtonsoft
        Return JsonConvert.SerializeObject(Phone)
    End Function

    Public Sub SendUpdates(data As String) Implements IEmployeeInfo.SendUpdates

        Dim valueArgs As EmployeeInfoValueUpdatedArgs = New EmployeeInfoValueUpdatedArgs(data)

        RaiseEvent ValueUpdatedEmployee(Me, valueArgs)

    End Sub
End Class

Create a class named: PhoneInfo.vb

PhoneInfo.vb

Add the following statements:

Option Strict Off
Option Explicit On
Imports System.Runtime.InteropServices
Imports Newtonsoft.Json

To allow use with event(s), add the following interface (in PhoneInfo.vb):

IComEventsPhoneInfo

<ComVisible(True)>
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)>
Public Interface IComEventsPhoneInfo
    'ToDo: add event(s) - optional
End Interface

Create an interface for EmployeeInfo (in PhoneInfo.cs):

IPhoneInfo

<ComVisible(True)>
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)>
Public Interface IPhoneInfo

    Property ImeiNumber As String
    Property Manufacturer As String
    Property Model As String
    Property PhoneNumber As String
    Property PhoneNumberType As String

End Interface

Add code to the PhoneInfo class (in PhoneInfo.vb):

Note: In PhoneInfo, I show another way of declaring a property.

PhoneInfo

Public Class PhoneInfo
    Implements IPhoneInfo

    Public Property ImeiNumber As String Implements IPhoneInfo.ImeiNumber

    Public Property Manufacturer As String Implements IPhoneInfo.Manufacturer

    Public Property Model As String Implements IPhoneInfo.Model

    Public Property PhoneNumber As String Implements IPhoneInfo.PhoneNumber

    Public Property PhoneNumberType As String Implements IPhoneInfo.PhoneNumberType

    Public Sub New()

    End Sub

    Public Sub New(phoneNumber As String, phoneNumberType As String)
        Me.PhoneNumber = phoneNumber
        Me.PhoneNumberType = phoneNumberType
    End Sub
    Public Sub New(imeiNumber As String, manufacturer As String, model As String, phoneNumber As String, phoneNumberType As String)
        Me.ImeiNumber = imeiNumber
        Me.Manufacturer = manufacturer
        Me.Model = model
        Me.PhoneNumber = phoneNumber
        Me.PhoneNumberType = phoneNumberType
    End Sub

End Class

To Use:

Example:

Private Async Function TestHostObjectEmployeeInfo() As Task
    Dim scriptToRun As New System.Text.StringBuilder
    Dim employee As EmployeeInfo = New EmployeeInfo()

    'subscribe to event(s)
    AddHandler employee.ValueUpdatedEmployee, AddressOf Employee_ValueUpdatedEmployee

    'add host object
    WebView21.CoreWebView2.AddHostObjectToScript("employee", employee)

    '#1 
    scriptToRun = New StringBuilder()
    scriptToRun.AppendFormat("function sendData(){{ {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  try{{  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    window.chrome.webview.postMessage('hello 1a');  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    chrome.webview.hostObjects.sync.employee.FirstName = 'John'; {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    let result = chrome.webview.hostObjects.sync.employee.FirstName;  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    return result;  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  }}  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  catch(err){{  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    window.chrome.webview.postMessage('Error: ' + err.message);  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  }}  {0}", System.Environment.NewLine)

    scriptToRun.AppendFormat("}}  {0}", System.Environment.NewLine)

    scriptToRun.AppendFormat("sendData(); {0}", System.Environment.NewLine)

    System.Diagnostics.Debug.WriteLine(scriptToRun.ToString())

    Dim result1 = Await WebView21.CoreWebView2.ExecuteScriptAsync(scriptToRun.ToString())

    System.Diagnostics.Debug.WriteLine("Info: result1: " & result1)

    '#2
    scriptToRun = New StringBuilder()
    scriptToRun.AppendFormat("function sendData(){{ {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  try{{  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    window.chrome.webview.postMessage('hello 2a');  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    chrome.webview.hostObjects.sync.employee.AddPhone('home','800-555-1234'); {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    let result = chrome.webview.hostObjects.sync.employee.GetPhoneAsJsonNewtonsoft();  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    window.chrome.webview.postMessage(result);  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    return result;  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  }}  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  catch(err){{  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    window.chrome.webview.postMessage('Error: ' + err.message);  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  }}  {0}", System.Environment.NewLine)

    scriptToRun.AppendFormat("}}  {0}", System.Environment.NewLine)

    scriptToRun.AppendFormat("sendData(); {0}", System.Environment.NewLine)

    Dim result2 = Await WebView21.CoreWebView2.ExecuteScriptAsync(scriptToRun.ToString())

    System.Diagnostics.Debug.WriteLine("Info: result2: " & result2)

    '#3
    scriptToRun = New StringBuilder()
    scriptToRun.AppendFormat("function sendData(){{ {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  try{{  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    window.chrome.webview.postMessage('hello 1a');  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    chrome.webview.hostObjects.sync.employee.FirstName = 'John'; {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    let result = chrome.webview.hostObjects.sync.employee.FirstName;  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    chrome.webview.hostObjects.sync.employee.SendUpdates(result +  ' hello from JS');  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    return result;  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  }}  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  catch(err){{  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("    window.chrome.webview.postMessage('Error: ' + err.message);  {0}", System.Environment.NewLine)
    scriptToRun.AppendFormat("  }}  {0}", System.Environment.NewLine)

    scriptToRun.AppendFormat("}}  {0}", System.Environment.NewLine)

    scriptToRun.AppendFormat("sendData(); {0}", System.Environment.NewLine)

    Dim result3 = Await WebView21.CoreWebView2.ExecuteScriptAsync(scriptToRun.ToString())

    System.Diagnostics.Debug.WriteLine("Info: result3: " & result3)

    Try
        'remove host object
        WebView21.CoreWebView2.RemoveHostObjectFromScript("employee")
    Catch ex As System.Runtime.InteropServices.COMException
        System.Diagnostics.Debug.WriteLine("Error (COMException): " & ex.Message)
    Catch ex As Exception
        System.Diagnostics.Debug.WriteLine("Error: " & ex.Message)
    End Try

    'unsubscribe from event(s)
    RemoveHandler employee.ValueUpdatedEmployee, AddressOf Employee_ValueUpdatedEmployee

    employee = Nothing
End Sub

Employee_ValueUpdatedEmployee

Private Sub Employee_ValueUpdatedEmployee(sender As Object, e As EmployeeInfoValueUpdatedArgs)
    System.Diagnostics.Debug.WriteLine("Info: Employee_ValueUpdatedEmployee event raised")
    System.Diagnostics.Debug.WriteLine("Info: e.Data: " & e.Data)
End Sub

Some other things that can be specified are Guid, DispId, and ProgId. Note: Dispin is only specified in the interface(s). Dispin numbers should be sequential.

According to this post:

DispId: Uniquely identifies methods and properties of an interface/class to the COM Interop system. ... The ID’s that you assign in DispID need to be sequential (1, 2, 3, etc.).

Guid: Each COM visible component (Classes, Interfaces, etc.) within your assembly is identified within the Interop system through a Guid.

ComInterfaceType:

ClassInterfaceType (lets COM know whether your class supports Early Binding, Late Binding, or both.)

Note: To be available in VB6/VBA, the class may need to be registered using RegAsm.exe.

Also see:

IComEventsEmployeeInfo

<ComVisible(True)>
<Guid("your guid 1 here - containing only letters, numbers, and dashes")>
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)>
Public Interface IComEventsEmployeeInfo
    'ToDo: add event(s) - optional

    Event ValueUpdatedEmployeeInfo()
End Interface

IEmployeeInfo

<ComVisible(True)>
<Guid("your guid 2 here - containing only letters, numbers, and dashes")>
<InterfaceType(ComInterfaceType.InterfaceIsIDispatch)>
Public Interface IEmployeeInfo

    <DispId(&H10000000)>
    Property EmployeeId() As String

    <DispId(&H10000001)>
    Property FirstName() As String

    <DispId(&H10000003)>
    Property LastName() As String

            ...

End Interface

EmployeeInfo

<ComVisible(True)>
<Guid("your guid 3 here - containing only letters, numbers, and dashes")>
<ClassInterface(ClassInterfaceType.None), ProgId("your progId")>
<ComDefaultInterface(GetType(IEmployeeInfo))>
<ComSourceInterfaces(GetType(IComEventsEmployeeInfo))>
Public Class EmployeeInfo
    Implements IEmployeeInfo

    'ToDo: add event(s) - optional

    Public Event ValueUpdatedEmployee As EventHandlerEmployeeInfoValueUpdated

    Private empId As String = String.Empty
    Private fName As String = String.Empty

              ...

End Class

Note: According to Exposing COM interfaces of a .NET class library for Late Binding, this is the same ProgId that you'd use in VB6 or vbscript

set myobject = createobject ("your progId")

Resources

KalleOlaviNiemitalo commented 1 year ago

Although the recommendation in CA1408: Do not use AutoDual ClassInterfaceType applies only to ClassInterfaceType.AutoDual, the [Obsolete("Support for IDispatch may be unavailable in future releases")] attribute is on both ClassInterfaceType.AutoDispatch and ClassInterfaceType.AutoDual: https://github.com/dotnet/runtime/blob/1af80ba017f6f7644305e1781d8cc9845a92b5f8/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs#L153-L162

There is no ObsoleteAttribute on ComInterfaceType.InterfaceIsIDispatch.