dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.48k stars 10.04k forks source link

JS to C# != ElementReference; C# to JS == ElementReference #17982

Closed Triggs9 closed 3 years ago

Triggs9 commented 4 years ago

Describe the bug

when calling a C# call from JS the returned object fails to resolve ElementReference to a referenced element. C# calling JS with the same object resolves the element reference correctly.

they are both resolving to JSObjects some how, just wondering if its possible for the returned object to be resolved as an ElementReference?

this is currently a blocker for me because i can't simply return the same object requested, id have to have a js callback, that i am unsure of how to implement correctly.

EDIT: Just figured out a callback solution here: https://stackoverflow.com/questions/56627649/how-can-i-get-javascript-callback-in-net-blazor

but this bug would be more simple.

To Reproduce

Failed TypeScript ElementReference Resolution code:

let newComponent: MyComponent | null = await this._dotNetInterop.invokeMethodAsync("BuildNewComponent",incomingProperties);

Output of failed: typeof: object __internalId: "50c7d062-116e-4f5b-afbd-dba94f3521f2"

Working TypeScript ElementReference Resolution code

public RecieveComponent(component: object) 
{
    console.log("recieved new component");
    console.log(component);
}

Output of working: \New Font\

_dotNetInterop is defined as:

InjectDotNetInterop(incomingInterop: DotNet.DotNetObject): Boolean
    {
        if(this._dotNetInterop == undefined)
        {
            this._dotNetInterop = incomingInterop;
        }
}

C#:

public object BuildNewComponent()
{
    var newJSComponent = new
    {
        SomeTestVariable = "Built in an anonymous type",
        Tags = myElementReference
    };
    object dynamicCast = incomingComponent.ConvertToJSComponent();
    await this._currentJSRuntime.InvokeVoidAsync("OriJSClientCalls.RecieveComponent", dynamicCast);
    return dynamicCast    
}

Got Exceptions? Include both the message and the stack trace -->

Further technical details

Runtime Environment: OS Name: Windows OS Version: 10.0.18362 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\3.0.100\

Host (useful for support): Version: 3.0.0 Commit: 7d57652f33

.NET Core SDKs installed: 2.2.402 [C:\Program Files\dotnet\sdk] 3.0.100 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed: Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.2.7 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.0.0 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET Core runtimes or SDKs: https://aka.ms/dotnet-download

pranavkm commented 4 years ago

Thank you for filing this issue. In order for us to investigate this issue, please provide a minimal project (ideally a GitHub repo) that illustrates the problem.

Triggs9 commented 4 years ago

not to keen to release my source code, and for me to built a repro repo for something this small would take to long. I assume its because this file: https://github.com/aspnet/AspNetCore/blob/master/src/Components/Web.JS/src/Rendering/ElementReferenceCapture.ts isn't being applied to objects being returned from c# calls, not sure exactly where yet, ill keep looking.

Triggs9 commented 4 years ago

https://github.com/aspnet/AspNetCore/blob/c759f5b8f82fc0ed1a5e918c6b611ffb861af84d/src/Components/Web.JS/src/Boot.Server.ts#L121

this is where the dotnet object is returned, but its as a json object, is it still possible to apply the above method (getElementByCaptureId) to that json?

Triggs9 commented 4 years ago

Here is my crude implementation that suites my needs for now:

export function BlazorIDToElement(incomingObject: any): OriComponent
{
    let elementRefKey = '__internalId';

    //https://stackoverflow.com/questions/18936915/dynamically-set-property-of-nested-object
    let setValue = (path: string, value: Element | null) => {
        var schema = incomingObject;  // a moving reference to internal objects within obj
        var pList = path.split('.');
        var len = pList.length;
        for(var i = 0; i < len-1; i++) {
            var elem = pList[i];
            if( !schema[elem] ) schema[elem] = {}
            schema = schema[elem];
        }

        schema[pList[len-1]] = value;
    };

    //https://stackoverflow.com/questions/15690706/recursively-looping-through-an-object-to-build-a-property-list
    let iterateObject = (obj: any, stack: string | null = null): any => {
        for (var property in obj) {
            if (obj.hasOwnProperty(property)) {
                if (typeof obj[property] == "object") {
                    if(stack === null) stack = property
                    else stack = stack + "." + property
                    iterateObject(obj[property],  stack);
                } else {
                    console.log("Current property: ")
                    console.log(property)
                    console.log((property === elementRefKey))
                    console.log(typeof obj[property] === 'string')
                    if((property === elementRefKey) && typeof obj[property] === 'string')
                    {
                        if(stack !== null)
                            setValue(stack, getElementByCaptureId(obj[property]))
                        //console.log(getElementByCaptureId(obj[property]))
                    }

                    console.log(incomingObject)
                    console.log(stack)
                }
            }
        }
    };

    iterateObject(incomingObject);
    return incomingObject;
}
pranavkm commented 4 years ago

@Triggs9 it's still not very clear what scenario it is that you're attempting to solve. It would help to share a minimal repro that exhibits what it is that's problematic for us to investigate this further.

Triggs9 commented 4 years ago

I refuse to create a repo for some who refuses to read and even slightly attempt to understand the bug being reported and implements a blanket "i need this to do my job" as a machine/robot would. I have even gone to the extent of solving the bug for you. don't comment on this thread again please.

mrpmorris commented 4 years ago

You shouldn't pass an ElementReference to JS and then pass it back, because by the time the JS calls C# the element may have been re-rendered and have a new ID.

What do you want to achieve?

Triggs9 commented 4 years ago

So the ElementReference is always being generated by CS code, and passed to JS, the means by which its being passed are changing.

One is through a JS parameter: TS Method

public RecieveComponent(component: object)
{
let NewComponent = component
}

invoked in CSharp with:

this._currentJSRuntime.InvokeVoidAsync("RecieveComponent", newComponent);

the other is through the DotNet Interop Object and a return value from that CS Method call CSharp Method

public object RecieveNewComponent(component: object) 

invoked in TypeScript with:

let NewComponent = this._dotNetInterop.invokeMethodAsync("RecieveNewComponent");

the latter call made in typescript fails to resolve the id to the actual html tag, while the first call does not. the exact same object represented in C# is used for both calls, again always originating from CS

when your blazor engine returns the js object, as the result of the returned C# object, its not being checked for elementreferences

mrpmorris commented 4 years ago

You aren't supposed to pass element references from javascript to C#. They should only be passed the other way, so javascript can perform an operation immediately and then discard the reference.

Can you tell me your goal, in case there is a better way to do what you want?

Triggs9 commented 4 years ago

I am not, as stated above: "the ElementReference is always being generated by CS code, and passed to JS". I might create a reproduction repo if i get time, but as stated above also: "Here is my crude implementation that suites my needs for now"

mrpmorris commented 4 years ago

I'm sorry but I agree with @pranavkm that your issue needs more information.

I'm struggling to work out exactly what you are trying to do, snippets of failing code aren't helping, and a long complex JS function that solves the problem doesn't help to clear it up.

Please submit a very minimal project.

shawty commented 4 years ago

@Triggs9 People here are trying to help you, but your being very abrasive and in no way attempting to help us to help you, we can see what your trying to ask, but your not answering the questions being asked by others, and thus we can't help you.

As @mrpmorris states there are MANY ways of doing what your trying to achieve, and if you explain your goals, then we might be able to tell you a BETTER way to do what your trying to do.

If you don't explain, then there's little we can do to help, other than WAIT until what you describe as a bug to be investigated, and that's simply not going to happen overnight.

If you want to try to reach a solution now, then help us... to help you!

shawty commented 4 years ago

Ok I've read through this several times now and it appears that what your trying to do is pass in an existing HTML elements ID, then get a list of all it's properties back to your C# code in something like a Dictionary<string, object>

But... your trying to do this in an async and/or call back way, EG: make the call, then a short while later get a "callback" with the data in that you expect.

Correct?

Triggs9 commented 4 years ago

Created a reproduction repo here:

https://github.com/Triggs9/BlazorElementReferenceBug

it has alot of code i have been working on for a while now, so i copyright it as much as positing something publicly on github will allow ha ha, but in any case there are three variables if you open the web browser console and type in: WorkingThroughJSParam WorkingComponent FailedComponent

WorkingThroughJSParam:

WorkingComponent:

FailedComponent:

the TextTag parameter should result in a JS Element, not just a string ID of the element for all 3 objects

my proposal for a fix, is that the object is always handled by the same handler in C# whether as a returned object or passed to js as a parameter

let me know if you need more information

Triggs9 commented 4 years ago

an npm install command as well as a npm run script build:devis required before running dotnet run

shawty commented 4 years ago

I don't have time to look now, it's getting very late here in the UK, but I'm sure others will get a look in before I get chance tomorrow.

Was I correct in my last comment about what I thought you where trying to achieve?

Triggs9 commented 4 years ago

no, its all one way right now all C# to JS, the interop can work as two entities, DotNetIntrop in JS and JSInterop in C# and still pass data one way, or both if desired. This one is Entirely C# to JS one way, no returned data to C#.

Triggs9 commented 4 years ago

I can see where alot of C# guys get confused, and assume that i want everything done in C#. But JS has alot of animation libraries like GSAP. That C# could probably never provide purely due to its complexity involved with typing everything. I want the elements in TS so i can handle the visual side of things in typescript, i want C# to handle the actual data structure of my components as thats what it does better than C#. Thats the goal atleast.

shawty commented 4 years ago

I've not had chance to follow up on this yet... "work explosion" dunno if anyone else has though.

ghost commented 3 years ago

Thank you for contacting us. Due to a lack of activity on this discussion issue we're closing it in an effort to keep our backlog clean. If you believe there is a concern related to the ASP.NET Core framework, which hasn't been addressed yet, please file a new issue.

This issue will be locked after 30 more days of inactivity. If you still wish to discuss this subject after then, please create a new issue!