Skip to content

Commit df7b408

Browse files
authored
Improve support for methods with multiple JSInvokable attributes (#59787)
* * Improve support for methods with multiple JSInvokable * Add tests. * * Improve support for methods with multiple JSInvokable * Add tests.
1 parent 979382a commit df7b408

File tree

2 files changed

+72
-21
lines changed

2 files changed

+72
-21
lines changed

src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -407,18 +407,21 @@ private static (MethodInfo methodInfo, Type[] parameterTypes) GetCachedMethodInf
407407
continue;
408408
}
409409

410-
var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false)!.Identifier ?? method.Name!;
411-
var parameterTypes = GetParameterTypes(method);
412-
413-
if (result.ContainsKey(identifier))
410+
foreach (var attr in method.GetCustomAttributes<JSInvokableAttribute>(false))
414411
{
415-
throw new InvalidOperationException($"The type {type.Name} contains more than one " +
416-
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
417-
"type must have different identifiers. You can pass a custom identifier as a parameter to " +
418-
$"the [JSInvokable] attribute.");
412+
var identifier = attr.Identifier ?? method.Name;
413+
var parameterTypes = GetParameterTypes(method);
414+
415+
if (result.ContainsKey(identifier))
416+
{
417+
throw new InvalidOperationException($"The type {type.Name} contains more than one " +
418+
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
419+
"type must have different identifiers. You can pass a custom identifier as a parameter to " +
420+
$"the [JSInvokable] attribute.");
421+
}
422+
423+
result.Add(identifier, (method, parameterTypes));
419424
}
420-
421-
result.Add(identifier, (method, parameterTypes));
422425
}
423426

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

446-
var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false)!.Identifier ?? method.Name;
447-
var parameterTypes = GetParameterTypes(method);
448-
449-
if (result.ContainsKey(identifier))
449+
foreach (var attr in method.GetCustomAttributes<JSInvokableAttribute>(false))
450450
{
451-
throw new InvalidOperationException($"The assembly '{assemblyKey.AssemblyName}' contains more than one " +
452-
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
453-
$"assembly must have different identifiers. You can pass a custom identifier as a parameter to " +
454-
$"the [JSInvokable] attribute.");
451+
var identifier = attr.Identifier ?? method.Name;
452+
var parameterTypes = GetParameterTypes(method);
453+
454+
if (result.ContainsKey(identifier))
455+
{
456+
throw new InvalidOperationException($"The assembly '{assemblyKey.AssemblyName}' contains more than one " +
457+
$"[JSInvokable] method with identifier '{identifier}'. All [JSInvokable] methods within the same " +
458+
$"assembly must have different identifiers. You can pass a custom identifier as a parameter to " +
459+
$"the [JSInvokable] attribute.");
460+
}
461+
462+
result.Add(identifier, (method, parameterTypes));
455463
}
456-
457-
result.Add(identifier, (method, parameterTypes));
458464
}
459465
}
460466

src/JSInterop/Microsoft.JSInterop/test/Infrastructure/DotNetDispatcherTest.cs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,42 @@ public void CanInvokeMethodsThatAcceptGenericParametersOnGenericTypes()
496496
Assert.Equal("\"hello world\"", resultJson);
497497
}
498498

499+
500+
[Fact]
501+
public void CanInvokeMethodsWithMultipleIdentifiers() {
502+
var jsRuntime = new TestJSRuntime();
503+
var targetInstance = new MultipleJSInvokableAttributes();
504+
jsRuntime.Invoke<object>("_setup",
505+
DotNetObjectReference.Create(targetInstance));
506+
var argsJson = "[\"hello Alias\"]";
507+
508+
// Act
509+
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "MultipleJSInvokableAttributesInstance", 1, default), argsJson);
510+
var resultJson2 = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, "Alias", 1, default), argsJson);
511+
512+
// Assert
513+
Assert.Equal("\"hello Alias\"", resultJson);
514+
Assert.Equal("\"hello Alias\"", resultJson2);
515+
}
516+
[Fact]
517+
public void CanInvokeMethodsWithMultipleIdentifiers_Static()
518+
{
519+
// Arrange/Act
520+
var jsRuntime = new TestJSRuntime();
521+
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticNonVoidAlias1", default, default), null);
522+
var result = JsonSerializer.Deserialize<TestDTO>(resultJson, jsRuntime.JsonSerializerOptions);
523+
524+
var resultJson2 = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, "InvocableStaticNonVoidAlias2", default, default), null);
525+
var result2 = JsonSerializer.Deserialize<TestDTO>(resultJson2, jsRuntime.JsonSerializerOptions);
526+
527+
// Assert
528+
Assert.Equal("Test", result.StringVal);
529+
Assert.Equal(456, result.IntVal);
530+
531+
Assert.Equal("Test", result2.StringVal);
532+
Assert.Equal(456, result2.IntVal);
533+
}
534+
499535
[Fact]
500536
public void CannotInvokeStaticOpenGenericMethods()
501537
{
@@ -874,6 +910,10 @@ public static void MyInvocableVoid()
874910
public static object MyInvocableNonVoid()
875911
=> new TestDTO { StringVal = "Test", IntVal = 123 };
876912

913+
[JSInvokable("InvocableStaticNonVoidAlias1"), JSInvokable("InvocableStaticNonVoidAlias2")]
914+
public static object MyInvocableNonVoidWithAlias()
915+
=> new TestDTO { StringVal = "Test", IntVal = 456 };
916+
877917
[JSInvokable("InvocableStaticWithParams")]
878918
public static object[] MyInvocableWithParams(TestDTO dtoViaJson, int[] incrementAmounts, DotNetObjectReference<TestDTO> dtoByRef)
879919
=> new object[]
@@ -1017,12 +1057,17 @@ public class GenericType<TValue>
10171057
[JSInvokable] public TValue EchoParameter(TValue input) => input;
10181058
}
10191059

1060+
public class MultipleJSInvokableAttributes
1061+
{
1062+
[JSInvokable, JSInvokable("Alias")] public string MultipleJSInvokableAttributesInstance(string input) => input;
1063+
}
1064+
10201065
public class GenericMethodClass
10211066
{
10221067
[JSInvokable("StaticGenericMethod")] public static string StaticGenericMethod<TValue>(TValue input) => input.ToString();
10231068
[JSInvokable("InstanceGenericMethod")] public string GenericMethod<TValue>(TValue input) => input.ToString();
10241069
}
1025-
1070+
10261071
public class TestJSRuntime : JSInProcessRuntime
10271072
{
10281073
private TaskCompletionSource _nextInvocationTcs = new TaskCompletionSource();

0 commit comments

Comments
 (0)