Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve support for methods with multiple JSInvokable attributes #59787

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -407,18 +407,21 @@ private static (MethodInfo methodInfo, Type[] parameterTypes) GetCachedMethodInf
continue;
}

var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false)!.Identifier ?? method.Name!;
var parameterTypes = GetParameterTypes(method);

if (result.ContainsKey(identifier))
foreach (var attr in method.GetCustomAttributes<JSInvokableAttribute>(false))
{
throw new InvalidOperationException($"The type {type.Name} contains more than one " +
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
"type must have different identifiers. You can pass a custom identifier as a parameter to " +
$"the [JSInvokable] attribute.");
var identifier = attr.Identifier ?? method.Name;
var parameterTypes = GetParameterTypes(method);

if (result.ContainsKey(identifier))
{
throw new InvalidOperationException($"The type {type.Name} contains more than one " +
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
"type must have different identifiers. You can pass a custom identifier as a parameter to " +
$"the [JSInvokable] attribute.");
}

result.Add(identifier, (method, parameterTypes));
}

result.Add(identifier, (method, parameterTypes));
}

return result;
Expand All @@ -443,18 +446,21 @@ private static (MethodInfo methodInfo, Type[] parameterTypes) GetCachedMethodInf
continue;
}

var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false)!.Identifier ?? method.Name;
var parameterTypes = GetParameterTypes(method);

if (result.ContainsKey(identifier))
foreach (var attr in method.GetCustomAttributes<JSInvokableAttribute>(false))
{
throw new InvalidOperationException($"The assembly '{assemblyKey.AssemblyName}' contains more than one " +
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
$"assembly must have different identifiers. You can pass a custom identifier as a parameter to " +
$"the [JSInvokable] attribute.");
var identifier = attr.Identifier ?? method.Name;
var parameterTypes = GetParameterTypes(method);

if (result.ContainsKey(identifier))
{
throw new InvalidOperationException($"The assembly '{assemblyKey.AssemblyName}' contains more than one " +
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
$"assembly must have different identifiers. You can pass a custom identifier as a parameter to " +
$"the [JSInvokable] attribute.");
}

result.Add(identifier, (method, parameterTypes));
}

result.Add(identifier, (method, parameterTypes));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,42 @@ public void CanInvokeMethodsThatAcceptGenericParametersOnGenericTypes()
Assert.Equal("\"hello world\"", resultJson);
}


[Fact]
public void CanInvokeMethodsWithMultipleIdentifiers() {
var jsRuntime = new TestJSRuntime();
var targetInstance = new MultipleJSInvokableAttributes();
jsRuntime.Invoke<object>("_setup",
DotNetObjectReference.Create(targetInstance));
var argsJson = "[\"hello Alias\"]";

// Act
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "MultipleJSInvokableAttributesInstance", 1, default), argsJson);
var resultJson2 = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "Alias", 1, default), argsJson);

// Assert
Assert.Equal("\"hello Alias\"", resultJson);
Assert.Equal("\"hello Alias\"", resultJson2);
}
[Fact]
public void CanInvokeMethodsWithMultipleIdentifiers_Static()
{
// Arrange/Act
var jsRuntime = new TestJSRuntime();
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticNonVoidAlias1", default, default), null);
var result = JsonSerializer.Deserialize<TestDTO>(resultJson, jsRuntime.JsonSerializerOptions);

var resultJson2 = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticNonVoidAlias2", default, default), null);
var result2 = JsonSerializer.Deserialize<TestDTO>(resultJson2, jsRuntime.JsonSerializerOptions);

// Assert
Assert.Equal("Test", result.StringVal);
Assert.Equal(456, result.IntVal);

Assert.Equal("Test", result2.StringVal);
Assert.Equal(456, result2.IntVal);
}

[Fact]
public void CannotInvokeStaticOpenGenericMethods()
{
Expand Down Expand Up @@ -874,6 +910,10 @@ public static void MyInvocableVoid()
public static object MyInvocableNonVoid()
=> new TestDTO { StringVal = "Test", IntVal = 123 };

[JSInvokable("InvocableStaticNonVoidAlias1"), JSInvokable("InvocableStaticNonVoidAlias2")]
public static object MyInvocableNonVoidWithAlias()
=> new TestDTO { StringVal = "Test", IntVal = 456 };

[JSInvokable("InvocableStaticWithParams")]
public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, DotNetObjectReference<TestDTO> dtoByRef)
=> new object[]
Expand Down Expand Up @@ -1017,12 +1057,17 @@ public class GenericType<TValue>
[JSInvokable] public TValue EchoParameter(TValue input) => input;
}

public class MultipleJSInvokableAttributes
{
[JSInvokable, JSInvokable("Alias")] public string MultipleJSInvokableAttributesInstance(string input) => input;
}

public class GenericMethodClass
{
[JSInvokable("StaticGenericMethod")] public static string StaticGenericMethod<TValue>(TValue input) => input.ToString();
[JSInvokable("InstanceGenericMethod")] public string GenericMethod<TValue>(TValue input) => input.ToString();
}

public class TestJSRuntime : JSInProcessRuntime
{
private TaskCompletionSource _nextInvocationTcs = new TaskCompletionSource();
Expand Down
Loading