Merge branch 'release/3.1' => 'master' (dotnet/extensions#2368)
\n\nCommit migrated from 7433e7f07c
This commit is contained in:
commit
3ffb2ca7d2
|
|
@ -24,6 +24,9 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
private static readonly ConcurrentDictionary<AssemblyKey, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByAssembly
|
||||
= new ConcurrentDictionary<AssemblyKey, IReadOnlyDictionary<string, (MethodInfo, Type[])>>();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByType
|
||||
= new ConcurrentDictionary<Type, IReadOnlyDictionary<string, (MethodInfo, Type[])>>();
|
||||
|
||||
/// <summary>
|
||||
/// Receives a call from JS to .NET, locating and invoking the specified method.
|
||||
/// </summary>
|
||||
|
|
@ -129,9 +132,12 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
var methodIdentifier = callInfo.MethodIdentifier;
|
||||
|
||||
AssemblyKey assemblyKey;
|
||||
MethodInfo methodInfo;
|
||||
Type[] parameterTypes;
|
||||
if (objectReference is null)
|
||||
{
|
||||
assemblyKey = new AssemblyKey(assemblyName);
|
||||
(methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyKey, methodIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -147,11 +153,9 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
return default;
|
||||
}
|
||||
|
||||
assemblyKey = new AssemblyKey(objectReference.Value.GetType().Assembly);
|
||||
(methodInfo, parameterTypes) = GetCachedMethodInfo(objectReference, methodIdentifier);
|
||||
}
|
||||
|
||||
var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyKey, methodIdentifier);
|
||||
|
||||
var suppliedArgs = ParseArguments(jsRuntime, methodIdentifier, argsJson, parameterTypes);
|
||||
|
||||
try
|
||||
|
|
@ -301,7 +305,47 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"The assembly '{assemblyKey.AssemblyName}' does not contain a public method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].");
|
||||
throw new ArgumentException($"The assembly '{assemblyKey.AssemblyName}' does not contain a public invokable method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].");
|
||||
}
|
||||
}
|
||||
|
||||
private static (MethodInfo methodInfo, Type[] parameterTypes) GetCachedMethodInfo(IDotNetObjectReference objectReference, string methodIdentifier)
|
||||
{
|
||||
var type = objectReference.Value.GetType();
|
||||
var assemblyMethods = _cachedMethodsByType.GetOrAdd(type, ScanTypeForCallableMethods);
|
||||
if (assemblyMethods.TryGetValue(methodIdentifier, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"The type '{type.Name}' does not contain a public invokable method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].");
|
||||
}
|
||||
|
||||
static Dictionary<string, (MethodInfo, Type[])> ScanTypeForCallableMethods(Type type)
|
||||
{
|
||||
var result = new Dictionary<string, (MethodInfo, Type[])>(StringComparer.Ordinal);
|
||||
var invokableMethods = type
|
||||
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
|
||||
.Where(method => !method.ContainsGenericParameters && method.IsDefined(typeof(JSInvokableAttribute), inherit: false));
|
||||
|
||||
foreach (var method in invokableMethods)
|
||||
{
|
||||
var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false).Identifier ?? method.Name;
|
||||
var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -312,35 +356,22 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
var result = new Dictionary<string, (MethodInfo, Type[])>(StringComparer.Ordinal);
|
||||
var invokableMethods = GetRequiredLoadedAssembly(assemblyKey)
|
||||
.GetExportedTypes()
|
||||
.SelectMany(type => type.GetMethods(
|
||||
BindingFlags.Public |
|
||||
BindingFlags.DeclaredOnly |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Static))
|
||||
.Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false));
|
||||
.SelectMany(type => type.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
||||
.Where(method => !method.ContainsGenericParameters && method.IsDefined(typeof(JSInvokableAttribute), inherit: false));
|
||||
foreach (var method in invokableMethods)
|
||||
{
|
||||
var identifier = method.GetCustomAttribute<JSInvokableAttribute>(false).Identifier ?? method.Name;
|
||||
var parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
|
||||
|
||||
try
|
||||
if (result.ContainsKey(identifier))
|
||||
{
|
||||
result.Add(identifier, (method, parameterTypes));
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
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.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -70,7 +69,7 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
DotNetDispatcher.Invoke(new TestJSRuntime(), new DotNetInvocationInfo(thisAssemblyName, methodIdentifier, default, default), null);
|
||||
});
|
||||
|
||||
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
|
||||
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public invokable method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -355,6 +354,78 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
Assert.Equal("MY STRING", resultDto.StringVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeNonGenericInstanceMethodOnGenericType()
|
||||
{
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new GenericType<int>();
|
||||
jsRuntime.Invoke<object>("_setup",
|
||||
DotNetObjectReference.Create(targetInstance));
|
||||
var argsJson = "[\"hello world\"]";
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, nameof(GenericType<int>.EchoStringParameter), 1, default), argsJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("\"hello world\"", resultJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeMethodsThatAcceptGenericParametersOnGenericTypes()
|
||||
{
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new GenericType<string>();
|
||||
jsRuntime.Invoke<object>("_setup",
|
||||
DotNetObjectReference.Create(targetInstance));
|
||||
var argsJson = "[\"hello world\"]";
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, nameof(GenericType<string>.EchoParameter), 1, default), argsJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("\"hello world\"", resultJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeStaticOpenGenericMethods()
|
||||
{
|
||||
var methodIdentifier = "StaticGenericMethod";
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<ArgumentException>(() => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(thisAssemblyName, methodIdentifier, 0, default), "[7]"));
|
||||
Assert.Contains($"The assembly '{thisAssemblyName}' does not contain a public invokable method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeInstanceOpenGenericMethods()
|
||||
{
|
||||
var methodIdentifier = "InstanceGenericMethod";
|
||||
var targetInstance = new GenericType<int>();
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
jsRuntime.Invoke<object>("_setup",
|
||||
DotNetObjectReference.Create(targetInstance));
|
||||
var argsJson = "[\"hello world\"]";
|
||||
|
||||
// Act
|
||||
var ex = Assert.Throws<ArgumentException>(() => DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, methodIdentifier, 1, default), argsJson));
|
||||
Assert.Contains($"The type 'GenericType`1' does not contain a public invokable method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeMethodsWithGenericParameters_IfTypesDoNotMatch()
|
||||
{
|
||||
var jsRuntime = new TestJSRuntime();
|
||||
var targetInstance = new GenericType<int>();
|
||||
jsRuntime.Invoke<object>("_setup",
|
||||
DotNetObjectReference.Create(targetInstance));
|
||||
var argsJson = "[\"hello world\"]";
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<JsonException>(() =>
|
||||
DotNetDispatcher.Invoke(jsRuntime, new DotNetInvocationInfo(null, nameof(GenericType<int>.EchoParameter), 1, default), argsJson));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeWithFewerNumberOfParameters()
|
||||
{
|
||||
|
|
@ -790,6 +861,18 @@ namespace Microsoft.JSInterop.Infrastructure
|
|||
}
|
||||
}
|
||||
|
||||
public class GenericType<TValue>
|
||||
{
|
||||
[JSInvokable] public string EchoStringParameter(string input) => input;
|
||||
[JSInvokable] public TValue EchoParameter(TValue 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<object> _nextInvocationTcs = new TaskCompletionSource<object>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue