From d8a66bcc6f8f659a7a58121af381b59ac148c34a Mon Sep 17 00:00:00 2001 From: rolandVi Date: Thu, 10 Jul 2025 15:04:26 +0200 Subject: [PATCH 1/6] Enable IJSObjectReference to handle null/undefined values --- .../src/src/Microsoft.JSInterop.ts | 38 +++++++++++-------- .../src/test/CallDispatcher.test.ts | 23 ++++++++++- .../JSObjectReferenceJsonConverter.cs | 6 +++ .../JSObjectReferenceJsonConverterTest.cs | 13 +++++++ 4 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts index fdd7a4ed65ec..f674d5c688e2 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/src/Microsoft.JSInterop.ts @@ -155,6 +155,12 @@ export module DotNet { * @throws Error if the given value is not an Object. */ export function createJSObjectReference(jsObject: any): any { + if (jsObject === null || jsObject === undefined) { + return { + [jsObjectIdKey]: -1 + }; + } + if (jsObject && (typeof jsObject === "object" || jsObject instanceof Function)) { cachedJSObjectsById[nextJsObjectId] = new JSObject(jsObject); @@ -220,7 +226,7 @@ export module DotNet { export function disposeJSObjectReference(jsObjectReference: any): void { const id = jsObjectReference && jsObjectReference[jsObjectIdKey]; - if (typeof id === "number") { + if (typeof id === "number" && id !== -1) { disposeJSObjectReferenceById(id); } } @@ -573,7 +579,7 @@ export module DotNet { } /** Traverses the object hierarchy to find an object member specified by the identifier. - * + * * @param obj Root object to search in. * @param identifier Complete identifier of the member to find, e.g. "document.location.href". * @returns A tuple containing the immediate parent of the member and the member name. @@ -586,19 +592,19 @@ export module DotNet { // Error handling in case of undefined last key depends on the type of operation. for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; - + if (current && typeof current === 'object' && key in current) { current = current[key]; } else { throw new Error(`Could not find '${identifier}' ('${key}' was undefined).`); } } - + return [current, keys[keys.length - 1]]; } /** Takes an object member and a call type and returns a function that performs the operation specified by the call type on the member. - * + * * @param parent Immediate parent of the accessed object member. * @param memberName Name (key) of the accessed member. * @param callType The type of the operation to perform on the member. @@ -640,50 +646,50 @@ export module DotNet { if (!(propName in obj)) { return false; } - + // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain. while (obj !== undefined) { const descriptor = Object.getOwnPropertyDescriptor(obj, propName); - + if (descriptor) { // Return true for data property if (descriptor.hasOwnProperty('value')) { return true } - + // Return true for accessor property with defined getter. return descriptor.hasOwnProperty('get') && typeof descriptor.get === 'function'; } - + obj = Object.getPrototypeOf(obj); } - + return false; } - + function isWritableProperty(obj: any, propName: string) { // Return true for missing property if the property can be added. if (!(propName in obj)) { return Object.isExtensible(obj); } - + // If the property is present we examine its descriptor, potentially needing to walk up the prototype chain. while (obj !== undefined) { const descriptor = Object.getOwnPropertyDescriptor(obj, propName); - + if (descriptor) { // Return true for writable data property. if (descriptor.hasOwnProperty('value') && descriptor.writable) { return true; } - + // Return true for accessor property with defined setter. return descriptor.hasOwnProperty('set') && typeof descriptor.set === 'function'; } - + obj = Object.getPrototypeOf(obj); } - + return false; } diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts index 38ee0cc07996..65a8c41efed2 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts @@ -395,4 +395,25 @@ describe("CallDispatcher", () => { expect(result2).toBe("30"); }); -}); \ No newline at end of file + + test("createJSObjectReference: Handles null values without throwing", () => { + const nullRef = DotNet.createJSObjectReference(null); + expect(nullRef).toEqual({ [jsObjectId]: -1 }); + }); + + test("createJSObjectReference: Handles undefined values without throwing", () => { + const undefinedRef = DotNet.createJSObjectReference(undefined); + expect(undefinedRef).toEqual({ [jsObjectId]: -1 }); + }); + + test("disposeJSObjectReference: Safely handles null reference disposal", () => { + const nullRef = DotNet.createJSObjectReference(null); + expect(() => DotNet.disposeJSObjectReference(nullRef)).not.toThrow(); + }); + + test("createJSObjectReference: Still throws for invalid types", () => { + expect(() => DotNet.createJSObjectReference("string")).toThrow(); + expect(() => DotNet.createJSObjectReference(123)).toThrow(); + expect(() => DotNet.createJSObjectReference(true)).toThrow(); + }); +}); diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSObjectReferenceJsonConverter.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSObjectReferenceJsonConverter.cs index 563291822a4a..e4bf9db0c409 100644 --- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSObjectReferenceJsonConverter.cs +++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/JSObjectReferenceJsonConverter.cs @@ -22,6 +22,12 @@ public override bool CanConvert(Type typeToConvert) public override IJSObjectReference? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var id = JSObjectReferenceJsonWorker.ReadJSObjectReferenceIdentifier(ref reader); + + if (id == -1) + { + return null; + } + return new JSObjectReference(_jsRuntime, id); } diff --git a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/JSObjectReferenceJsonConverterTest.cs b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/JSObjectReferenceJsonConverterTest.cs index 63b5a889782a..383c06819317 100644 --- a/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/JSObjectReferenceJsonConverterTest.cs +++ b/src/JSInterop/Microsoft.JSInterop/test/Infrastructure/JSObjectReferenceJsonConverterTest.cs @@ -87,4 +87,17 @@ public void Write_WritesValidJson() // Assert Assert.Equal($"{{\"__jsObjectId\":{jsObjectRef.Id}}}", json); } + + [Fact] + public void Read_ReturnsNull_WhenJSObjectIdIsMinusOne() + { + // Arrange + var json = "{\"__jsObjectId\":-1}"; + + // Act + var deserialized = JsonSerializer.Deserialize(json, JsonSerializerOptions); + + // Assert + Assert.Null(deserialized); + } } From f73ca659d03c8499facfb4524a79b615ffdedf5d Mon Sep 17 00:00:00 2001 From: rolandVi Date: Fri, 11 Jul 2025 17:27:00 +0200 Subject: [PATCH 2/6] Changing E2E tests --- AspNetCore.slnx | 5 +- .../test/E2ETest/Tests/InteropTest.cs | 10 +-- .../BasicTestApp/InteropComponent.razor | 61 ++++++++++++++----- .../BasicTestApp/wwwroot/js/jsinteroptests.js | 3 +- .../src/test/CallDispatcher.test.ts | 30 +++++++++ 5 files changed, 88 insertions(+), 21 deletions(-) diff --git a/AspNetCore.slnx b/AspNetCore.slnx index 2b5b7180956c..d36d49ee4765 100644 --- a/AspNetCore.slnx +++ b/AspNetCore.slnx @@ -10,6 +10,9 @@ + + + @@ -1200,8 +1203,8 @@ - + diff --git a/src/Components/test/E2ETest/Tests/InteropTest.cs b/src/Components/test/E2ETest/Tests/InteropTest.cs index ef7d9fe18963..92fa18abc94e 100644 --- a/src/Components/test/E2ETest/Tests/InteropTest.cs +++ b/src/Components/test/E2ETest/Tests/InteropTest.cs @@ -89,8 +89,9 @@ public void CanInvokeInteropMethods() ["invokeVoidAsyncReturnsWithoutSerializing"] = "Success", ["invokeVoidAsyncReturnsWithoutSerializingInJSObjectReference"] = "Success", ["invokeAsyncThrowsSerializingCircularStructure"] = "Success", - ["invokeAsyncThrowsUndefinedJSObjectReference"] = "Success", - ["invokeAsyncThrowsNullJSObjectReference"] = "Success", + ["invokeAsyncUndefinedJSObjectReference"] = "Success", + ["invokeAsyncNullJSObjectReference"] = "Success", + ["invokeAsyncNullFromVariableJSObjectReference"] = "Success", ["disposeJSObjectReferenceAsync"] = "Success", // GetValue tests ["getValueFromDataPropertyAsync"] = "10", @@ -148,8 +149,9 @@ public void CanInvokeInteropMethods() ["invokeVoidReturnsWithoutSerializingIJSInProcessRuntime"] = "Success", ["invokeVoidReturnsWithoutSerializingInIJSInProcessObjectReference"] = "Success", ["invokeThrowsSerializingCircularStructure"] = "Success", - ["invokeThrowsUndefinedJSObjectReference"] = "Success", - ["invokeThrowsNullJSObjectReference"] = "Success", + ["invokeUndefinedJSObjectReference"] = "Success", + ["invokeNullJSObjectReference"] = "Success", + ["invokeNullFromVariableJSObjectReference"] = "Success", ["stringValueUpperSync"] = "MY STRING", ["testDtoNonSerializedValueSync"] = "99999", ["testDtoSync"] = "Same", diff --git a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor index eb7839539d54..fd2f5bb3c262 100644 --- a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor @@ -170,29 +170,43 @@ try { var undefinedJsObjectReference = await JSRuntime.InvokeAsync("returnUndefined"); - ReturnValues["invokeAsyncThrowsUndefinedJSObjectReference"] = undefinedJsObjectReference is null ? "Failure: null" : "Failure: not null"; + ReturnValues["invokeAsyncUndefinedJSObjectReference"] = undefinedJsObjectReference is null ? "Success" : "Failure: not null"; } - catch (JSException) + catch (JSException ex) { - ReturnValues["invokeAsyncThrowsUndefinedJSObjectReference"] = "Success"; + ReturnValues["invokeAsyncUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; } catch (Exception ex) { - ReturnValues["invokeAsyncThrowsUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; + ReturnValues["invokeAsyncUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; } try { var nullJsObjectReference = await JSRuntime.InvokeAsync("returnNull"); - ReturnValues["invokeAsyncThrowsNullJSObjectReference"] = nullJsObjectReference is null ? "Failure: null" : "Failure: not null"; + ReturnValues["invokeAsyncNullJSObjectReference"] = nullJsObjectReference is null ? "Success" : "Failure: not null"; + } + catch (JSException ex) + { + ReturnValues["invokeAsyncNullJSObjectReference"] = $"Failure: {ex.Message}"; + } + catch (Exception ex) + { + ReturnValues["invokeAsyncNullJSObjectReference"] = $"Failure: {ex.Message}"; + } + + try + { + var nullVariableJsObjectReference = await JSRuntime.GetValueAsync("jsInteropTests.testObject.nullProperty"); + ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = nullVariableJsObjectReference is null ? "Success" : "Failure: not null"; } catch (JSException) { - ReturnValues["invokeAsyncThrowsNullJSObjectReference"] = "Success"; + ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = "Failure"; } catch (Exception ex) { - ReturnValues["invokeAsyncThrowsNullJSObjectReference"] = $"Failure: {ex.Message}"; + ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; } var jsObjectReference = await JSRuntime.InvokeAsync("returnJSObjectReference"); @@ -397,29 +411,46 @@ try { var undefinedJsObjectReference = inProcRuntime.Invoke("returnUndefined"); - ReturnValues["invokeThrowsUndefinedJSObjectReference"] = undefinedJsObjectReference is null ? "Failure: null" : "Failure: not null"; + ReturnValues["invokeUndefinedJSObjectReference"] = undefinedJsObjectReference is null ? "Success" : "Failure: not null"; + Console.WriteLine($"Received undefined JSObjectReference: {undefinedJsObjectReference}"); } - catch (JSException) + catch (JSException ex) { - ReturnValues["invokeThrowsUndefinedJSObjectReference"] = "Success"; + ReturnValues["invokeUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; } catch (Exception ex) { - ReturnValues["invokeThrowsUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; + ReturnValues["invokeUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; } try { var nullJsObjectReference = inProcRuntime.Invoke("returnNull"); - ReturnValues["invokeThrowsNullJSObjectReference"] = nullJsObjectReference is null ? "Failure: null" : "Failure: not null"; + ReturnValues["invokeNullJSObjectReference"] = nullJsObjectReference is null ? "Success" : "Failure: not null"; + Console.WriteLine($"Received undefined JSObjectReference: {nullJsObjectReference}"); } - catch (JSException) + catch (JSException ex) + { + ReturnValues["invokeNullJSObjectReference"] = $"Failure: {ex.Message}"; + } + catch (Exception ex) + { + ReturnValues["invokeNullJSObjectReference"] = $"Failure: {ex.Message}"; + } + + try + { + var nullVariableJsObjectReference = inProcRuntime.GetValue("jsInteropTests.testObject.nullProperty"); + ReturnValues["invokeNullFromVariableJSObjectReference"] = nullVariableJsObjectReference is null ? "Success" : "Failure: not null"; + Console.WriteLine($"Received undefined JSObjectReference: {nullVariableJsObjectReference}"); + } + catch (JSException ex) { - ReturnValues["invokeThrowsNullJSObjectReference"] = "Success"; + ReturnValues["invokeNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; } catch (Exception ex) { - ReturnValues["invokeThrowsNullJSObjectReference"] = $"Failure: {ex.Message}"; + ReturnValues["invokeNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; } var jsInProcObjectReference = inProcRuntime.Invoke("returnJSObjectReference"); diff --git a/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js b/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js index 2a22736a58e2..3faee4235ecb 100644 --- a/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js +++ b/src/Components/test/testassets/BasicTestApp/wwwroot/js/jsinteroptests.js @@ -234,7 +234,8 @@ const testObject = { }, set setOnlyProperty(value) { this.num = value; - } + }, + nullProperty: null } window.jsInteropTests = { diff --git a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts index 901c4a75a864..fbf2b23261b0 100644 --- a/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts +++ b/src/JSInterop/Microsoft.JSInterop.JS/src/test/CallDispatcher.test.ts @@ -416,4 +416,34 @@ describe("CallDispatcher", () => { expect(() => DotNet.createJSObjectReference(123)).toThrow(); expect(() => DotNet.createJSObjectReference(true)).toThrow(); }); + + test("GetValue: Returns JSObjectReference with sentinel value for null property", () => { + const testObject = { nullProp: null }; + const objectId = getObjectReferenceId(testObject); + + const result = dispatcher.invokeJSFromDotNet( + "nullProp", + "[]", + DotNet.JSCallResultType.JSObjectReference, + objectId, + DotNet.JSCallType.GetValue + ); + + expect(result).toBe('{"__jsObjectId":-1}'); + }); + + test("GetValue: Returns JSObjectReference with sentinel value for undefined property", () => { + const testObject = { undefinedProp: undefined }; + const objectId = getObjectReferenceId(testObject); + + const result = dispatcher.invokeJSFromDotNet( + "undefinedProp", + "[]", + DotNet.JSCallResultType.JSObjectReference, + objectId, + DotNet.JSCallType.GetValue + ); + + expect(result).toBe('{"__jsObjectId":-1}'); + }); }); From 514e875684dcb8528e0a8f6c6df202fd753c0fa5 Mon Sep 17 00:00:00 2001 From: rolandVi Date: Mon, 14 Jul 2025 13:50:24 +0200 Subject: [PATCH 3/6] Added missing WebAssemblyJSObjectReferenceJsonConverter fucntionality + fixed E2E tests --- ...bAssemblyJSObjectReferenceJsonConverter.cs | 6 + .../JSObjectReferenceJsonConverterTest.cs | 13 ++ .../test/E2ETest/Tests/InteropTest.cs | 20 +- .../BasicTestApp/InteropComponent.razor | 210 +++++++++++------- 4 files changed, 155 insertions(+), 94 deletions(-) diff --git a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSObjectReferenceJsonConverter.cs b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSObjectReferenceJsonConverter.cs index ff294cef2648..38ff95ea0c74 100644 --- a/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSObjectReferenceJsonConverter.cs +++ b/src/Components/WebAssembly/JSInterop/src/WebAssemblyJSObjectReferenceJsonConverter.cs @@ -26,6 +26,12 @@ public override bool CanConvert(Type typeToConvert) public override IJSObjectReference? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var id = JSObjectReferenceJsonWorker.ReadJSObjectReferenceIdentifier(ref reader); + + if (id == -1) + { + return null; + } + return new WebAssemblyJSObjectReference(_jsRuntime, id); } diff --git a/src/Components/WebAssembly/WebAssembly/test/JSObjectReferenceJsonConverterTest.cs b/src/Components/WebAssembly/WebAssembly/test/JSObjectReferenceJsonConverterTest.cs index 65aeb2acf662..0467e1ac326e 100644 --- a/src/Components/WebAssembly/WebAssembly/test/JSObjectReferenceJsonConverterTest.cs +++ b/src/Components/WebAssembly/WebAssembly/test/JSObjectReferenceJsonConverterTest.cs @@ -44,4 +44,17 @@ public void Read_ReadsJson_IJSInProcessObjectReference() // Assert Assert.Equal(expectedId, deserialized?.Id); } + + [Fact] + public void Read_ReturnsNull_WhenIdIsMinusOne() + { + // Arrange + var json = "{\"__jsObjectId\":-1}"; + + // Act + var deserialized = JsonSerializer.Deserialize(json, JsonSerializerOptions); + + // Assert + Assert.Null(deserialized); + } } diff --git a/src/Components/test/E2ETest/Tests/InteropTest.cs b/src/Components/test/E2ETest/Tests/InteropTest.cs index 92fa18abc94e..07d54f0fa3d8 100644 --- a/src/Components/test/E2ETest/Tests/InteropTest.cs +++ b/src/Components/test/E2ETest/Tests/InteropTest.cs @@ -89,9 +89,6 @@ public void CanInvokeInteropMethods() ["invokeVoidAsyncReturnsWithoutSerializing"] = "Success", ["invokeVoidAsyncReturnsWithoutSerializingInJSObjectReference"] = "Success", ["invokeAsyncThrowsSerializingCircularStructure"] = "Success", - ["invokeAsyncUndefinedJSObjectReference"] = "Success", - ["invokeAsyncNullJSObjectReference"] = "Success", - ["invokeAsyncNullFromVariableJSObjectReference"] = "Success", ["disposeJSObjectReferenceAsync"] = "Success", // GetValue tests ["getValueFromDataPropertyAsync"] = "10", @@ -109,7 +106,12 @@ public void CanInvokeInteropMethods() ["invokeConstructorWithClassConstructorAsync.function"] = "6", ["invokeConstructorWithNonConstructorAsync"] = "Success", // Function reference tests - ["changeFunctionViaObjectReferenceAsync"] = "42" + ["changeFunctionViaObjectReferenceAsync"] = "42", + // JS Object Nullable reference tests + ["invokeAsyncUndefinedJSObjectReference"] = "Success", + ["invokeAsyncNullJSObjectReference"] = "Success", + ["invokeAsyncNullFromVariableJSObjectReference"] = "Success", + ["invokeAsyncNonExistentJSObjectReference"] = "Success", }; var expectedSyncValues = new Dictionary @@ -149,9 +151,6 @@ public void CanInvokeInteropMethods() ["invokeVoidReturnsWithoutSerializingIJSInProcessRuntime"] = "Success", ["invokeVoidReturnsWithoutSerializingInIJSInProcessObjectReference"] = "Success", ["invokeThrowsSerializingCircularStructure"] = "Success", - ["invokeUndefinedJSObjectReference"] = "Success", - ["invokeNullJSObjectReference"] = "Success", - ["invokeNullFromVariableJSObjectReference"] = "Success", ["stringValueUpperSync"] = "MY STRING", ["testDtoNonSerializedValueSync"] = "99999", ["testDtoSync"] = "Same", @@ -176,7 +175,12 @@ public void CanInvokeInteropMethods() ["invokeConstructorWithClassConstructor.function"] = "6", ["invokeConstructorWithNonConstructor"] = "Success", // Function reference tests - ["changeFunctionViaObjectReference"] = "42" + ["changeFunctionViaObjectReference"] = "42", + // JS Object Nullable reference tests + ["invokeUndefinedJSObjectReference"] = "Success", + ["invokeNullJSObjectReference"] = "Success", + ["invokeNullFromVariableJSObjectReference"] = "Success", + ["invokeNonExistentJSObjectReference"] = "Success", }; // Include the sync assertions only when running under WebAssembly diff --git a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor index fd2f5bb3c262..f446453be60d 100644 --- a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor @@ -167,47 +167,6 @@ ReturnValues["invokeAsyncThrowsSerializingCircularStructure"] = $"Failure: {ex.Message}"; } - try - { - var undefinedJsObjectReference = await JSRuntime.InvokeAsync("returnUndefined"); - ReturnValues["invokeAsyncUndefinedJSObjectReference"] = undefinedJsObjectReference is null ? "Success" : "Failure: not null"; - } - catch (JSException ex) - { - ReturnValues["invokeAsyncUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; - } - catch (Exception ex) - { - ReturnValues["invokeAsyncUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; - } - - try - { - var nullJsObjectReference = await JSRuntime.InvokeAsync("returnNull"); - ReturnValues["invokeAsyncNullJSObjectReference"] = nullJsObjectReference is null ? "Success" : "Failure: not null"; - } - catch (JSException ex) - { - ReturnValues["invokeAsyncNullJSObjectReference"] = $"Failure: {ex.Message}"; - } - catch (Exception ex) - { - ReturnValues["invokeAsyncNullJSObjectReference"] = $"Failure: {ex.Message}"; - } - - try - { - var nullVariableJsObjectReference = await JSRuntime.GetValueAsync("jsInteropTests.testObject.nullProperty"); - ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = nullVariableJsObjectReference is null ? "Success" : "Failure: not null"; - } - catch (JSException) - { - ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = "Failure"; - } - catch (Exception ex) - { - ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; - } var jsObjectReference = await JSRuntime.InvokeAsync("returnJSObjectReference"); ReturnValues["jsObjectReference.identity"] = await jsObjectReference.InvokeAsync("identity", "Invoked from JSObjectReference"); @@ -322,6 +281,12 @@ FunctionReferenceTests(); } + await JSObjectReferenceAsyncTests(); + if (shouldSupportSyncInterop) + { + JSObjectReferenceTests(); + } + Invocations = invocations; DoneWithInterop = true; } @@ -408,51 +373,6 @@ ReturnValues["invokeThrowsSerializingCircularStructure"] = $"Failure: {ex.Message}"; } - try - { - var undefinedJsObjectReference = inProcRuntime.Invoke("returnUndefined"); - ReturnValues["invokeUndefinedJSObjectReference"] = undefinedJsObjectReference is null ? "Success" : "Failure: not null"; - Console.WriteLine($"Received undefined JSObjectReference: {undefinedJsObjectReference}"); - } - catch (JSException ex) - { - ReturnValues["invokeUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; - } - catch (Exception ex) - { - ReturnValues["invokeUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; - } - - try - { - var nullJsObjectReference = inProcRuntime.Invoke("returnNull"); - ReturnValues["invokeNullJSObjectReference"] = nullJsObjectReference is null ? "Success" : "Failure: not null"; - Console.WriteLine($"Received undefined JSObjectReference: {nullJsObjectReference}"); - } - catch (JSException ex) - { - ReturnValues["invokeNullJSObjectReference"] = $"Failure: {ex.Message}"; - } - catch (Exception ex) - { - ReturnValues["invokeNullJSObjectReference"] = $"Failure: {ex.Message}"; - } - - try - { - var nullVariableJsObjectReference = inProcRuntime.GetValue("jsInteropTests.testObject.nullProperty"); - ReturnValues["invokeNullFromVariableJSObjectReference"] = nullVariableJsObjectReference is null ? "Success" : "Failure: not null"; - Console.WriteLine($"Received undefined JSObjectReference: {nullVariableJsObjectReference}"); - } - catch (JSException ex) - { - ReturnValues["invokeNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; - } - catch (Exception ex) - { - ReturnValues["invokeNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; - } - var jsInProcObjectReference = inProcRuntime.Invoke("returnJSObjectReference"); ReturnValues["jsInProcessObjectReference.identity"] = jsInProcObjectReference.Invoke("identity", "Invoked from JSInProcessObjectReference"); @@ -657,6 +577,124 @@ ReturnValues["changeFunctionViaObjectReference"] = testClassRef.Invoke("getTextLength").ToString(); } + private async Task JSObjectReferenceAsyncTests() + { + try + { + var undefinedJsObjectReference = await JSRuntime.InvokeAsync("jsInteropTests.returnUndefined"); + ReturnValues["invokeAsyncUndefinedJSObjectReference"] = undefinedJsObjectReference is null ? "Success" : $"Failure: not null (type: {undefinedJsObjectReference.GetType().FullName})"; + } + catch (JSException ex) + { + ReturnValues["invokeAsyncUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; + } + catch (Exception ex) + { + ReturnValues["invokeAsyncUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; + } + + try + { + var nullJsObjectReference = await JSRuntime.InvokeAsync("jsInteropTests.returnNull"); + ReturnValues["invokeAsyncNullJSObjectReference"] = nullJsObjectReference is null ? "Success" : $"Failure: not null (type: {nullJsObjectReference.GetType().FullName})"; + } + catch (JSException ex) + { + ReturnValues["invokeAsyncNullJSObjectReference"] = $"Failure: {ex.Message}"; + } + catch (Exception ex) + { + ReturnValues["invokeAsyncNullJSObjectReference"] = $"Failure: {ex.Message}"; + } + + try + { + var nullVariableJsObjectReference = await JSRuntime.GetValueAsync("jsInteropTests.testObject.nullProperty"); + ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = nullVariableJsObjectReference is null ? "Success" : $"Failure: not null (type: {nullVariableJsObjectReference.GetType().FullName})"; + } + catch (JSException ex) + { + ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; + } + catch (Exception ex) + { + ReturnValues["invokeAsyncNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; + } + + try + { + await JSRuntime.GetValueAsync("nonexistend"); + } + catch (JSException) + { + ReturnValues["invokeAsyncNonExistentJSObjectReference"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["invokeAsyncNonExistentJSObjectReference"] = "Failure"; + } + } + + private async Task JSObjectReferenceTests() + { + var inProcRuntime = ((IJSInProcessRuntime)JSRuntime); + + try + { + var undefinedJsObjectReference = inProcRuntime.Invoke("returnUndefined"); + ReturnValues["invokeUndefinedJSObjectReference"] = undefinedJsObjectReference is null ? "Success" : $"Failure: not null (type: {undefinedJsObjectReference.GetType().FullName})"; + } + catch (JSException ex) + { + ReturnValues["invokeUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; + } + catch (Exception ex) + { + ReturnValues["invokeUndefinedJSObjectReference"] = $"Failure: {ex.Message}"; + } + + try + { + var nullJsObjectReference = inProcRuntime.Invoke("returnNull"); + ReturnValues["invokeNullJSObjectReference"] = nullJsObjectReference is null ? "Success" : $"Failure: not null (type: {nullJsObjectReference.GetType().FullName})"; + } + catch (JSException ex) + { + ReturnValues["invokeNullJSObjectReference"] = $"Failure: {ex.Message}"; + } + catch (Exception ex) + { + ReturnValues["invokeNullJSObjectReference"] = $"Failure: {ex.Message}"; + } + + try + { + var nullVariableJsObjectReference = inProcRuntime.GetValue("jsInteropTests.testObject.nullProperty"); + ReturnValues["invokeNullFromVariableJSObjectReference"] = nullVariableJsObjectReference is null ? "Success" : $"Failure: not null (type: {nullVariableJsObjectReference.GetType().FullName})"; + } + catch (JSException ex) + { + ReturnValues["invokeNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; + } + catch (Exception ex) + { + ReturnValues["invokeNullFromVariableJSObjectReference"] = $"Failure: {ex.Message}"; + } + + try + { + inProcRuntime.GetValue("nonexistend"); + } + catch (JSException) + { + ReturnValues["invokeNonExistentJSObjectReference"] = "Success"; + } + catch (Exception ex) + { + ReturnValues["invokeNonExistentJSObjectReference"] = "Failure"; + } + } + public class PassDotNetObjectByRefArgs { public string StringValue { get; set; } From ee28eb43a95618b11aeef73793ea0a52731a3b1e Mon Sep 17 00:00:00 2001 From: Roland Vizner <148648143+rolandVi@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:56:00 +0200 Subject: [PATCH 4/6] Update AspNetCore.slnx Removed unwanted `AspNetCore.slnx` entry --- AspNetCore.slnx | 3 --- 1 file changed, 3 deletions(-) diff --git a/AspNetCore.slnx b/AspNetCore.slnx index d36d49ee4765..cb8c39406b92 100644 --- a/AspNetCore.slnx +++ b/AspNetCore.slnx @@ -10,9 +10,6 @@ - - - From d96f3bbcea2e1d27dff8182b70f8f506abc02da6 Mon Sep 17 00:00:00 2001 From: rolandVi Date: Mon, 14 Jul 2025 14:06:43 +0200 Subject: [PATCH 5/6] Reverted AspNetCore.slnx to main state --- AspNetCore.slnx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/AspNetCore.slnx b/AspNetCore.slnx index d36d49ee4765..2b5b7180956c 100644 --- a/AspNetCore.slnx +++ b/AspNetCore.slnx @@ -10,9 +10,6 @@ - - - @@ -1203,8 +1200,8 @@ - + From 6375104b7670796cd951d8ccafa82dc4202e49f3 Mon Sep 17 00:00:00 2001 From: rolandVi Date: Mon, 14 Jul 2025 15:03:20 +0200 Subject: [PATCH 6/6] Fixed build --- .../test/testassets/BasicTestApp/InteropComponent.razor | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor index f446453be60d..3cafad7b577b 100644 --- a/src/Components/test/testassets/BasicTestApp/InteropComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/InteropComponent.razor @@ -282,6 +282,7 @@ } await JSObjectReferenceAsyncTests(); + if (shouldSupportSyncInterop) { JSObjectReferenceTests(); @@ -631,11 +632,11 @@ } catch (Exception ex) { - ReturnValues["invokeAsyncNonExistentJSObjectReference"] = "Failure"; + ReturnValues["invokeAsyncNonExistentJSObjectReference"] = $"Failure: {ex.Message}"; } } - private async Task JSObjectReferenceTests() + private void JSObjectReferenceTests() { var inProcRuntime = ((IJSInProcessRuntime)JSRuntime); @@ -691,7 +692,7 @@ } catch (Exception ex) { - ReturnValues["invokeNonExistentJSObjectReference"] = "Failure"; + ReturnValues["invokeNonExistentJSObjectReference"] = $"Failure: {ex.Message}"; } }