how to convert a C# List<object> to Javascript Array of Object with their Methods

1

In the continuity of that idea : C# WebBrowser control: window.external access sub object

i try to pass a list object from C# to a WebBrowser. And would like, in JavaScript, to access to the methods that belongs to those objects that are stored in an array.

The problem is that it can't recognize the List in javascript. And i can't use JSON because it don't pass methods. Also it's not an ASP/Server side Script. (it's a WPF)

To make it simple for the example, i replace the object with string, and let's say that i would like to call their (C#, not JS) .ToLower() methods in a loop.

Actually i have this in C# :

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IBaseObject
{
    IList<string> myList { get; }
}

[ComVisible(true)]
public class BaseObject : IBaseObject
{
    public IList<string> myList { get; private set; } = new List<string>() { "A" };
}

And instantiating it like so :

MyWebBrowser.ObjectForScripting = new BaseObject();

The first problem i encounter is on Javascript when i try to access the list itself :

                                        // - Return -
alert(window.external.myList);          // Error
alert(typeof window.external.myList);   // Unknow
alert(window.external.myList.Count);    // Error

Where i was excpecting something like Object

Any idea of what i'm doing wrong, or what did i forget?

Thanks in advance.


EDIT 1 :

I tried some solution as commented below and created my own Array Interface (inspired from IList interface) But i can't make this piece of code working :

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMyList : IEnumerable, IEnumerator
{
    [DispId(0)]
    string this[object index] { get; set; }

    void Add(string str);
}


[ComVisible(true)]
public class MyList : IMyList
{
    private string[] _aString = new string[1];

    [DispId(0)]
    public string this[object index] {
        get
        {
            Console.WriteLine("Success!");
            return _aString[(int)index];
        }
        set
        {
            _aString[(int)index] = value;
        }
    }

    public void Add(string str)
    {
        // it's just for an example intiate with "A", not need to dev.
        _aString[0] = str;
    }

    public IEnumerator GetEnumerator()
    {
        return (IEnumerator)this;
    }

    // Enumerators are positioned before the first element
    // until the first MoveNext() call.
    public int position = -1;

    public bool MoveNext()
    {
        position++;

        return (position < _aString.Length);
    }

    public void Reset()
    {
        position = -1;
    }

    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }

    public string Current
    {
        get
        {
            try
            {
                return _aString[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}


[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IBaseObject
{
    IMyList myList { get; }
}

[ComVisible(true)]
public class BaseObject : IBaseObject
{
    public BaseObject()
    {
        _myList = new MyList() { "A" };
    }

    IMyList _myList;

    public IMyList myList
    {
        get 
        {
            return _myList;
        }
    }
}

The problem is that each time that i call in Javascript :

alert(window.external.myList[0]);

I get an javascript TypeError: invalid procedure call or argument

The problem come from the indexer : public string this[int index]

Which i have pick from the IList Interface. it work in C#, but i have no idea of how to make it work through JavaScript :(


EDIT 2 :

i've added a IReflect interface to MyList to see how it was dispatched :

public class MyList : CustomDispatch, IMyList {

and here the CustomDispatch class:

[ComVisible(true)]
public class CustomDispatch : IReflect
{
    // Called by CLR to get DISPIDs and names for properties
    PropertyInfo[] IReflect.GetProperties(BindingFlags bindingAttr)
    {
        return this.GetType().GetProperties(bindingAttr);
    }

    // Called by CLR to get DISPIDs and names for fields
    FieldInfo[] IReflect.GetFields(BindingFlags bindingAttr)
    {
        return this.GetType().GetFields(bindingAttr);
    }

    // Called by CLR to get DISPIDs and names for methods
    MethodInfo[] IReflect.GetMethods(BindingFlags bindingAttr)
    {
        return this.GetType().GetMethods(bindingAttr);
    }

    // Called by CLR to invoke a member
    object IReflect.InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, System.Globalization.CultureInfo culture, string[] namedParameters)
    {
        Console.WriteLine("InvokeMember : {0}", name);

        try
        {
            // default InvokeMember
            return this.GetType().InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);;
        }
        catch (MissingMemberException ex)
        {
            // Well-known HRESULT returned by IDispatch.Invoke:
            const int DISP_E_MEMBERNOTFOUND = unchecked((int)0x80020003);
            throw new COMException(ex.Message, DISP_E_MEMBERNOTFOUND);
        }
    }

    FieldInfo IReflect.GetField(string name, BindingFlags bindingAttr)
    {
        return this.GetType().GetField(name, bindingAttr);
    }

    MemberInfo[] IReflect.GetMember(string name, BindingFlags bindingAttr)
    {
        return this.GetType().GetMember(name, bindingAttr);
    }

    MemberInfo[] IReflect.GetMembers(BindingFlags bindingAttr)
    {
        return this.GetType().GetMembers(bindingAttr);
    }

    MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr)
    {
        return this.GetType().GetMethod(name, bindingAttr);
    }

    MethodInfo IReflect.GetMethod(string name, BindingFlags bindingAttr,
    Binder binder, Type[] types, ParameterModifier[] modifiers)
    {
        return this.GetType().GetMethod(name, bindingAttr, binder, types, modifiers);
    }

    PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
    {
        return this.GetType().GetProperty(name, bindingAttr, binder, returnType, types, modifiers);
    }

    PropertyInfo IReflect.GetProperty(string name, BindingFlags bindingAttr)
    {
        return this.GetType().GetProperty(name, bindingAttr);
    }

    Type IReflect.UnderlyingSystemType
    {
        get { return this.GetType().UnderlyingSystemType; }
    }
}

And here is the results that i get with JavaScript :

// here C# IReflect call GetProperties, GetFields, GetMethods
// Then InvokeMember is called (the name parameter is = "[DISPID=0]")
alert(window.external.myList);

// here C# IReflect call GetProperties, GetFields, GetMethods
// But InvokeMember is never called
alert(window.external.myList[0]);

Then i thought that my method signature was wrong. So i've tried with this[int index] and even this[string index] with JS alert(window.external.myList["A"]);. But i could never reach the InvokeMember of IReflect


EDIT 3 :

after some research, my guess is that JavaScript expect to find a PropertyInfo with a string name "0" (the indexer value) in IReflect.GetProperties. Because in JS arr['myKey']is equal to arr.myKey. which is treat as a property. So, arr[0] is equal to arr['0'] (and is like arr.0 then) Anyway, i have no idea of how to add a PropertyInfo in PropertyInfo[]. Maybe there's more chance to do something with the IExpando / ExpandoObject stuff

javascript
c#
wpf
asked on Stack Overflow Jan 10, 2020 by Lenor • edited Jan 23, 2020 by Lenor

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0