Improved interoperability
* Add support for invoking async JavaScript functions from .NET. * Add support for invoking .NET methods from JavaScript. * Add support for invoking async .NET methods from JavaScript.
This commit is contained in:
parent
a3a76c2e9a
commit
5cb544ece8
|
|
@ -1830,15 +1830,6 @@
|
|||
"xtend": "4.0.1"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||
|
|
@ -1872,6 +1863,15 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { platform } from './Environment';
|
||||
import { platform } from './Environment';
|
||||
import { getAssemblyNameFromUrl } from './Platform/DotNet';
|
||||
import './Rendering/Renderer';
|
||||
import './Services/Http';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { platform } from './Environment'
|
||||
import { registerFunction } from './Interop/RegisteredFunction';
|
||||
import { navigateTo } from './Services/UriHelper';
|
||||
import { invokeDotNetMethod, invokeDotNetMethodAsync } from './Interop/InvokeDotNetMethodWithJsonMarshalling';
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
// When the library is loaded in a browser via a <script> element, make the
|
||||
|
|
@ -9,5 +10,7 @@ if (typeof window !== 'undefined') {
|
|||
platform,
|
||||
registerFunction,
|
||||
navigateTo,
|
||||
invokeDotNetMethod,
|
||||
invokeDotNetMethodAsync
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { invokeWithJsonMarshalling } from './InvokeWithJsonMarshalling';
|
||||
import { invokeWithJsonMarshalling, invokeWithJsonMarshallingAsync } from './InvokeJavaScriptFunctionWithJsonMarshalling';
|
||||
import { invokePromiseCallback } from './InvokeDotNetMethodWithJsonMarshalling';
|
||||
import { attachRootComponentToElement, renderBatch } from '../Rendering/Renderer';
|
||||
|
||||
/**
|
||||
|
|
@ -8,5 +9,7 @@ import { attachRootComponentToElement, renderBatch } from '../Rendering/Renderer
|
|||
export const internalRegisteredFunctions = {
|
||||
attachRootComponentToElement,
|
||||
invokeWithJsonMarshalling,
|
||||
invokeWithJsonMarshallingAsync,
|
||||
invokePromiseCallback,
|
||||
renderBatch,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,190 @@
|
|||
import { platform } from '../Environment';
|
||||
import { System_String, Pointer, MethodHandle } from '../Platform/Platform';
|
||||
import { getRegisteredFunction } from './RegisteredFunction';
|
||||
import { error } from 'util';
|
||||
|
||||
export interface MethodOptions {
|
||||
type: TypeIdentifier;
|
||||
method: MethodIdentifier;
|
||||
}
|
||||
|
||||
// Keep in sync with InvocationResult.cs
|
||||
export interface InvocationResult {
|
||||
succeeded: boolean;
|
||||
result?: any;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface MethodIdentifier {
|
||||
name: string;
|
||||
typeArguments?: { [key: string]: TypeIdentifier }
|
||||
parameterTypes?: TypeIdentifier[];
|
||||
}
|
||||
|
||||
export interface TypeIdentifier {
|
||||
assembly: string;
|
||||
name: string;
|
||||
typeArguments?: { [key: string]: TypeIdentifier };
|
||||
}
|
||||
|
||||
export function invokeDotNetMethod<T>(methodOptions: MethodOptions, ...args: any[]): (T | null) {
|
||||
return invokeDotNetMethodCore(methodOptions, null, ...args);
|
||||
}
|
||||
|
||||
const registrations = {};
|
||||
let findDotNetMethodHandle: MethodHandle;
|
||||
|
||||
function getFindDotNetMethodHandle() {
|
||||
if (findDotNetMethodHandle === undefined) {
|
||||
findDotNetMethodHandle = platform.findMethod(
|
||||
'Microsoft.AspNetCore.Blazor.Browser',
|
||||
'Microsoft.AspNetCore.Blazor.Browser.Interop',
|
||||
'InvokeDotNetFromJavaScript',
|
||||
'FindDotNetMethod');
|
||||
}
|
||||
return findDotNetMethodHandle;
|
||||
}
|
||||
|
||||
function resolveRegistration(methodOptions: MethodOptions) {
|
||||
const findDotNetMethodHandle = getFindDotNetMethodHandle();
|
||||
const assemblyEntry = registrations[methodOptions.type.assembly];
|
||||
const typeEntry = assemblyEntry && assemblyEntry[methodOptions.type.name];
|
||||
const registration = typeEntry && typeEntry[methodOptions.method.name];
|
||||
if (registration !== undefined) {
|
||||
return registration;
|
||||
} else {
|
||||
|
||||
const serializedOptions = platform.toDotNetString(JSON.stringify(methodOptions));
|
||||
const result = platform.callMethod(findDotNetMethodHandle, null, [serializedOptions]);
|
||||
const registration = platform.toJavaScriptString(result as System_String);
|
||||
|
||||
if (assemblyEntry === undefined) {
|
||||
const assembly = {};
|
||||
const type = {};
|
||||
registrations[methodOptions.type.assembly] = assembly;
|
||||
assembly[methodOptions.type.name] = type;
|
||||
type[methodOptions.method.name] = registration;
|
||||
} else if (typeEntry === undefined) {
|
||||
const type = {};
|
||||
assemblyEntry[methodOptions.type.name] = type;
|
||||
type[methodOptions.method.name] = registration;
|
||||
} else {
|
||||
typeEntry[methodOptions.method.name] = registration;
|
||||
}
|
||||
|
||||
return registration;
|
||||
}
|
||||
}
|
||||
|
||||
let invokeDotNetMethodHandle: MethodHandle;
|
||||
|
||||
function getInvokeDotNetMethodHandle() {
|
||||
if (invokeDotNetMethodHandle === undefined) {
|
||||
invokeDotNetMethodHandle = platform.findMethod(
|
||||
'Microsoft.AspNetCore.Blazor.Browser',
|
||||
'Microsoft.AspNetCore.Blazor.Browser.Interop',
|
||||
'InvokeDotNetFromJavaScript',
|
||||
'InvokeDotNetMethod');
|
||||
}
|
||||
return invokeDotNetMethodHandle;
|
||||
}
|
||||
|
||||
function invokeDotNetMethodCore<T>(methodOptions: MethodOptions, callbackId: string | null, ...args: any[]): (T | null) {
|
||||
const invokeDotNetMethodHandle = getInvokeDotNetMethodHandle();
|
||||
const registration = resolveRegistration(methodOptions);
|
||||
|
||||
const packedArgs = packArguments(args);
|
||||
|
||||
const serializedCallback = callbackId != null ? platform.toDotNetString(callbackId) : null;
|
||||
const serializedArgs = platform.toDotNetString(JSON.stringify(packedArgs));
|
||||
const serializedRegistration = platform.toDotNetString(registration);
|
||||
const serializedResult = platform.callMethod(invokeDotNetMethodHandle, null, [serializedRegistration, serializedCallback, serializedArgs]);
|
||||
|
||||
const result = JSON.parse(platform.toJavaScriptString(serializedResult as System_String));
|
||||
if (result.succeeded) {
|
||||
return result.result;
|
||||
} else {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have to worry about overflows here. Number.MAX_SAFE_INTEGER in JS is 2^53-1
|
||||
let globalId = 0;
|
||||
|
||||
export function invokeDotNetMethodAsync<T>(methodOptions: MethodOptions, ...args: any[]): Promise<T | null> {
|
||||
const callbackId = (globalId++).toString();
|
||||
|
||||
const result = new Promise<T | null>((resolve, reject) => {
|
||||
TrackedReference.track(callbackId, (invocationResult: InvocationResult) => {
|
||||
// We got invoked, so we unregister ourselves.
|
||||
TrackedReference.untrack(callbackId);
|
||||
if (invocationResult.succeeded) {
|
||||
resolve(invocationResult.result);
|
||||
} else {
|
||||
reject(new Error(invocationResult.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
invokeDotNetMethodCore(methodOptions, callbackId, ...args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function invokePromiseCallback(id: string, invocationResult: InvocationResult): void {
|
||||
const callback = TrackedReference.get(id) as Function;
|
||||
callback.call(null, invocationResult);
|
||||
}
|
||||
|
||||
function packArguments(args: any[]) {
|
||||
const result = {};
|
||||
if (args.length == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (args.length > 7) {
|
||||
for (let i = 0; i < 7; i++) {
|
||||
result[`argument${[i + 1]}`] = args[i];
|
||||
}
|
||||
result['argument8'] = packArguments(args.slice(7));
|
||||
} else {
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
result[`argument${[i + 1]}`] = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class TrackedReference {
|
||||
private static references: { [key: string]: any } = {};
|
||||
|
||||
public static track(id: string, trackedObject: any): void {
|
||||
const refs = TrackedReference.references;
|
||||
if (refs[id] !== undefined) {
|
||||
throw new Error(`An element with id '${id}' is already being tracked.`);
|
||||
}
|
||||
|
||||
refs[id] = trackedObject;
|
||||
}
|
||||
|
||||
public static untrack(id: string): void {
|
||||
const refs = TrackedReference.references;
|
||||
const result = refs[id];
|
||||
if (result === undefined) {
|
||||
throw new Error(`An element with id '${id}' is not being being tracked.`);
|
||||
}
|
||||
|
||||
refs[id] = undefined;
|
||||
}
|
||||
|
||||
public static get(id: string): any {
|
||||
const refs = TrackedReference.references;
|
||||
const result = refs[id];
|
||||
if (result === undefined) {
|
||||
throw new Error(`An element with id '${id}' is not being being tracked.`);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { platform } from '../Environment';
|
||||
import { System_String } from '../Platform/Platform';
|
||||
import { getRegisteredFunction } from './RegisteredFunction';
|
||||
import { invokeDotNetMethod, MethodOptions, InvocationResult } from './InvokeDotNetMethodWithJsonMarshalling';
|
||||
import { getElementByCaptureId } from '../Rendering/ElementReferenceCapture';
|
||||
import { System } from 'typescript';
|
||||
import { error } from 'util';
|
||||
|
||||
const elementRefKey = '_blazorElementRef'; // Keep in sync with ElementRef.cs
|
||||
|
||||
export function invokeWithJsonMarshalling(identifier: System_String, ...argsJson: System_String[]) {
|
||||
let result: InvocationResult;
|
||||
const identifierJsString = platform.toJavaScriptString(identifier);
|
||||
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json), jsonReviver));
|
||||
|
||||
try {
|
||||
result = { succeeded: true, result: invokeWithJsonMarshallingCore(identifierJsString, ...args) };
|
||||
} catch (e) {
|
||||
result = { succeeded: false, message: e instanceof Error ? `${e.message}\n${e.stack}` : (e ? e.toString() : null) };
|
||||
}
|
||||
|
||||
const resultJson = JSON.stringify(result);
|
||||
return platform.toDotNetString(resultJson);
|
||||
}
|
||||
|
||||
function invokeWithJsonMarshallingCore(identifier: string, ...args: any[]) {
|
||||
const funcInstance = getRegisteredFunction(identifier);
|
||||
const result = funcInstance.apply(null, args);
|
||||
if (result !== null && result !== undefined) {
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const invokeDotNetCallback: MethodOptions = {
|
||||
type: {
|
||||
assembly: 'Microsoft.AspNetCore.Blazor.Browser',
|
||||
name: 'Microsoft.AspNetCore.Blazor.Browser.Interop.TaskCallback'
|
||||
},
|
||||
method: {
|
||||
name: 'InvokeTaskCallback'
|
||||
}
|
||||
};
|
||||
|
||||
export function invokeWithJsonMarshallingAsync<T>(identifier: string, callbackId: string, ...argsJson: string[]) {
|
||||
const result = invokeWithJsonMarshallingCore(identifier, ...argsJson) as Promise<any>;
|
||||
|
||||
result
|
||||
.then(res => invokeDotNetMethod(invokeDotNetCallback, callbackId, JSON.stringify({ succeeded: true, result: res })))
|
||||
.catch(reason => invokeDotNetMethod(
|
||||
invokeDotNetCallback,
|
||||
callbackId,
|
||||
JSON.stringify({ succeeded: false, message: (reason && reason.message) || (reason && reason.toString && reason.toString()) })));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function jsonReviver(key: string, value: any): any {
|
||||
if (value && typeof value === 'object' && value.hasOwnProperty(elementRefKey) && typeof value[elementRefKey] === 'number') {
|
||||
return getElementByCaptureId(value[elementRefKey]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import { platform } from '../Environment';
|
||||
import { System_String } from '../Platform/Platform';
|
||||
import { getRegisteredFunction } from './RegisteredFunction';
|
||||
import { getElementByCaptureId } from '../Rendering/ElementReferenceCapture';
|
||||
|
||||
const elementRefKey = '_blazorElementRef'; // Keep in sync with ElementRef.cs
|
||||
|
||||
export function invokeWithJsonMarshalling(identifier: System_String, ...argsJson: System_String[]) {
|
||||
const identifierJsString = platform.toJavaScriptString(identifier);
|
||||
const funcInstance = getRegisteredFunction(identifierJsString);
|
||||
const args = argsJson.map(json => JSON.parse(platform.toJavaScriptString(json), jsonReviver));
|
||||
const result = funcInstance.apply(null, args);
|
||||
if (result !== null && result !== undefined) {
|
||||
const resultJson = JSON.stringify(result);
|
||||
return platform.toDotNetString(resultJson);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function jsonReviver(key: string, value: any): any {
|
||||
if (value && typeof value === 'object' && value.hasOwnProperty(elementRefKey) && typeof value[elementRefKey] === 'number') {
|
||||
return getElementByCaptureId(value[elementRefKey]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
|
||||
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
|
||||
import { getAssemblyNameFromUrl } from '../DotNet';
|
||||
import { getRegisteredFunction } from '../../Interop/RegisteredFunction';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export interface Platform {
|
||||
export interface Platform {
|
||||
start(loadAssemblyUrls: string[]): Promise<void>;
|
||||
|
||||
callEntryPoint(assemblyName: string, entrypointMethod: string, args: (System_Object | null)[]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const path = require('path');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,260 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
internal class ArgumentList
|
||||
{
|
||||
private const BindingFlags DeserializeFlags = BindingFlags.Static | BindingFlags.NonPublic;
|
||||
|
||||
public static ArgumentList Instance { get; } = new ArgumentList();
|
||||
private static ConcurrentDictionary<Type, Func<string, ArgumentList>> _deserializers = new ConcurrentDictionary<Type, Func<string, ArgumentList>>();
|
||||
|
||||
public static Type GetArgumentClass(Type[] arguments)
|
||||
{
|
||||
switch (arguments.Length)
|
||||
{
|
||||
case 0:
|
||||
return typeof(ArgumentList);
|
||||
case 1:
|
||||
return typeof(ArgumentList<>).MakeGenericType(arguments);
|
||||
case 2:
|
||||
return typeof(ArgumentList<,>).MakeGenericType(arguments);
|
||||
case 3:
|
||||
return typeof(ArgumentList<,,>).MakeGenericType(arguments);
|
||||
case 4:
|
||||
return typeof(ArgumentList<,,,>).MakeGenericType(arguments);
|
||||
case 5:
|
||||
return typeof(ArgumentList<,,,,>).MakeGenericType(arguments);
|
||||
case 6:
|
||||
return typeof(ArgumentList<,,,,,>).MakeGenericType(arguments);
|
||||
case 7:
|
||||
return typeof(ArgumentList<,,,,,,>).MakeGenericType(arguments);
|
||||
default:
|
||||
return GetArgumentsClassCore(arguments, 0);
|
||||
}
|
||||
|
||||
Type GetArgumentsClassCore(Type[] args, int position)
|
||||
{
|
||||
var rest = args.Length - position;
|
||||
switch (rest)
|
||||
{
|
||||
case 0:
|
||||
// We handle this case in the preamble. If there are more than 7 arguments, we pack the
|
||||
// remaining arguments in nested argument list types, with at least one argument.
|
||||
throw new InvalidOperationException("We shouldn't get here!");
|
||||
case 1:
|
||||
return typeof(ArgumentList<>).MakeGenericType(args.Skip(position).Take(rest).ToArray());
|
||||
case 2:
|
||||
return typeof(ArgumentList<,>).MakeGenericType(args.Skip(position).Take(rest).ToArray());
|
||||
case 3:
|
||||
return typeof(ArgumentList<,,>).MakeGenericType(args.Skip(position).Take(rest).ToArray());
|
||||
case 4:
|
||||
return typeof(ArgumentList<,,,>).MakeGenericType(args.Skip(position).Take(rest).ToArray());
|
||||
case 5:
|
||||
return typeof(ArgumentList<,,,,>).MakeGenericType(args.Skip(position).Take(rest).ToArray());
|
||||
case 6:
|
||||
return typeof(ArgumentList<,,,,,>).MakeGenericType(args.Skip(position).Take(rest).ToArray());
|
||||
case 7:
|
||||
return typeof(ArgumentList<,,,,,,>).MakeGenericType(args.Skip(position).Take(rest).ToArray());
|
||||
case 8:
|
||||
// When there are more than 7 arguments, we transparently package more arguments in a nested arguments type.
|
||||
// {
|
||||
// argument1: ...,
|
||||
// argument2: ...,
|
||||
// argument3: ...,
|
||||
// argument4: ...,
|
||||
// argument5: ...,
|
||||
// argument6: ...,
|
||||
// argument7: ...,
|
||||
// argument8: {
|
||||
// argument1: ..., // Actually argument 8
|
||||
// }
|
||||
// }
|
||||
|
||||
var typeArguments = args
|
||||
.Skip(position)
|
||||
.Take(7)
|
||||
.Concat(new[] { GetArgumentsClassCore(args, position + 7) }).ToArray();
|
||||
return typeof(ArgumentList<,,,,,,,>).MakeGenericType(typeArguments);
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported number of arguments '{arguments.Length}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Func<string, ArgumentList> GetDeserializer(Type deserializedType)
|
||||
{
|
||||
return _deserializers.GetOrAdd(deserializedType, DeserializerFactory);
|
||||
|
||||
Func<string, ArgumentList> DeserializerFactory(Type type)
|
||||
{
|
||||
switch (deserializedType.GetGenericArguments().Length)
|
||||
{
|
||||
case 0:
|
||||
return JsonDeserialize;
|
||||
case 1:
|
||||
return (Func<string, ArgumentList>)deserializedType.GetMethod("JsonDeserialize1", DeserializeFlags)
|
||||
.CreateDelegate(typeof(Func<string, ArgumentList>));
|
||||
case 2:
|
||||
return (Func<string, ArgumentList>)deserializedType.GetMethod("JsonDeserialize2", DeserializeFlags)
|
||||
.CreateDelegate(typeof(Func<string, ArgumentList>));
|
||||
case 3:
|
||||
return (Func<string, ArgumentList>)deserializedType.GetMethod("JsonDeserialize3", DeserializeFlags)
|
||||
.CreateDelegate(typeof(Func<string, ArgumentList>));
|
||||
case 4:
|
||||
return (Func<string, ArgumentList>)deserializedType.GetMethod("JsonDeserialize4", DeserializeFlags)
|
||||
.CreateDelegate(typeof(Func<string, ArgumentList>));
|
||||
case 5:
|
||||
return (Func<string, ArgumentList>)deserializedType.GetMethod("JsonDeserialize5", DeserializeFlags)
|
||||
.CreateDelegate(typeof(Func<string, ArgumentList>));
|
||||
case 6:
|
||||
return (Func<string, ArgumentList>)deserializedType.GetMethod("JsonDeserialize6", DeserializeFlags)
|
||||
.CreateDelegate(typeof(Func<string, ArgumentList>));
|
||||
case 7:
|
||||
return (Func<string, ArgumentList>)deserializedType.GetMethod("JsonDeserialize7", DeserializeFlags)
|
||||
.CreateDelegate(typeof(Func<string, ArgumentList>));
|
||||
case 8:
|
||||
return (Func<string, ArgumentList>)deserializedType.GetMethod("JsonDeserialize8", DeserializeFlags)
|
||||
.CreateDelegate(typeof(Func<string, ArgumentList>));
|
||||
default:
|
||||
throw new InvalidOperationException("Shouldn't have gotten here!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ArgumentList JsonDeserialize(string item) => Instance;
|
||||
|
||||
public virtual object[] ToArray() => Array.Empty<object>();
|
||||
}
|
||||
|
||||
internal class ArgumentList<T1> : ArgumentList
|
||||
{
|
||||
public T1 Argument1 { get; set; }
|
||||
|
||||
internal static ArgumentList<T1> JsonDeserialize1(string item) =>
|
||||
JsonUtil.Deserialize<ArgumentList<T1>>(item);
|
||||
|
||||
public override object[] ToArray() => new object[] { Argument1 };
|
||||
}
|
||||
|
||||
internal class ArgumentList<T1, T2> : ArgumentList
|
||||
{
|
||||
public T1 Argument1 { get; set; }
|
||||
public T2 Argument2 { get; set; }
|
||||
|
||||
internal static ArgumentList<T1, T2> JsonDeserialize2(string item) =>
|
||||
JsonUtil.Deserialize<ArgumentList<T1, T2>>(item);
|
||||
|
||||
public override object[] ToArray() => new object[] { Argument1, Argument2 };
|
||||
}
|
||||
|
||||
internal class ArgumentList<T1, T2, T3> : ArgumentList
|
||||
{
|
||||
public T1 Argument1 { get; set; }
|
||||
public T2 Argument2 { get; set; }
|
||||
public T3 Argument3 { get; set; }
|
||||
|
||||
internal static ArgumentList<T1, T2, T3> JsonDeserialize3(string item) =>
|
||||
JsonUtil.Deserialize<ArgumentList<T1, T2, T3>>(item);
|
||||
|
||||
public override object[] ToArray() => new object[] { Argument1, Argument2, Argument3 };
|
||||
}
|
||||
|
||||
internal class ArgumentList<T1, T2, T3, T4> : ArgumentList
|
||||
{
|
||||
public T1 Argument1 { get; set; }
|
||||
public T2 Argument2 { get; set; }
|
||||
public T3 Argument3 { get; set; }
|
||||
public T4 Argument4 { get; set; }
|
||||
|
||||
internal static ArgumentList<T1, T2, T3, T4> JsonDeserialize4(string item) =>
|
||||
JsonUtil.Deserialize<ArgumentList<T1, T2, T3, T4>>(item);
|
||||
|
||||
public override object[] ToArray() => new object[] { Argument1, Argument2, Argument3, Argument4 };
|
||||
}
|
||||
|
||||
internal class ArgumentList<T1, T2, T3, T4, T5> : ArgumentList
|
||||
{
|
||||
public T1 Argument1 { get; set; }
|
||||
public T2 Argument2 { get; set; }
|
||||
public T3 Argument3 { get; set; }
|
||||
public T4 Argument4 { get; set; }
|
||||
public T5 Argument5 { get; set; }
|
||||
|
||||
internal static ArgumentList<T1, T2, T3, T4, T5> JsonDeserialize5(string item) =>
|
||||
JsonUtil.Deserialize<ArgumentList<T1, T2, T3, T4, T5>>(item);
|
||||
|
||||
public override object[] ToArray() => new object[] { Argument1, Argument2, Argument3, Argument4, Argument5 };
|
||||
}
|
||||
|
||||
internal class ArgumentList<T1, T2, T3, T4, T5, T6> : ArgumentList
|
||||
{
|
||||
public T1 Argument1 { get; set; }
|
||||
public T2 Argument2 { get; set; }
|
||||
public T3 Argument3 { get; set; }
|
||||
public T4 Argument4 { get; set; }
|
||||
public T5 Argument5 { get; set; }
|
||||
public T6 Argument6 { get; set; }
|
||||
|
||||
internal static ArgumentList<T1, T2, T3, T4, T5, T6> JsonDeserialize6(string item) =>
|
||||
JsonUtil.Deserialize<ArgumentList<T1, T2, T3, T4, T5, T6>>(item);
|
||||
|
||||
public override object[] ToArray() => new object[] { Argument1, Argument2, Argument3, Argument4, Argument5, Argument6 };
|
||||
}
|
||||
|
||||
internal class ArgumentList<T1, T2, T3, T4, T5, T6, T7> : ArgumentList
|
||||
{
|
||||
public T1 Argument1 { get; set; }
|
||||
public T2 Argument2 { get; set; }
|
||||
public T3 Argument3 { get; set; }
|
||||
public T4 Argument4 { get; set; }
|
||||
public T5 Argument5 { get; set; }
|
||||
public T6 Argument6 { get; set; }
|
||||
public T7 Argument7 { get; set; }
|
||||
|
||||
internal static ArgumentList<T1, T2, T3, T4, T5, T6, T7> JsonDeserialize7(string item) =>
|
||||
JsonUtil.Deserialize<ArgumentList<T1, T2, T3, T4, T5, T6, T7>>(item);
|
||||
|
||||
public override object[] ToArray() => new object[] { Argument1, Argument2, Argument3, Argument4, Argument5, Argument6, Argument7 };
|
||||
}
|
||||
|
||||
internal class ArgumentList<T1, T2, T3, T4, T5, T6, T7, T8> : ArgumentList
|
||||
{
|
||||
public T1 Argument1 { get; set; }
|
||||
public T2 Argument2 { get; set; }
|
||||
public T3 Argument3 { get; set; }
|
||||
public T4 Argument4 { get; set; }
|
||||
public T5 Argument5 { get; set; }
|
||||
public T6 Argument6 { get; set; }
|
||||
public T7 Argument7 { get; set; }
|
||||
public T8 Argument8 { get; set; }
|
||||
|
||||
internal static ArgumentList<T1, T2, T3, T4, T5, T6, T7, T8> JsonDeserialize8(string item) =>
|
||||
JsonUtil.Deserialize<ArgumentList<T1, T2, T3, T4, T5, T6, T7, T8>>(item);
|
||||
|
||||
public override object[] ToArray()
|
||||
{
|
||||
if (Argument8 == null)
|
||||
{
|
||||
throw new InvalidOperationException("Argument8 can't be null!");
|
||||
}
|
||||
|
||||
if (!(Argument8 is ArgumentList rest))
|
||||
{
|
||||
throw new InvalidOperationException("Argument 8 must be an ArgumentList");
|
||||
}
|
||||
if (rest.GetType().GetGenericArguments().Length < 1)
|
||||
{
|
||||
throw new InvalidOperationException("Argument 8 must contain an inner parameter!");
|
||||
}
|
||||
|
||||
return new object[] { Argument1, Argument2, Argument3, Argument4, Argument5, Argument6, Argument7 }.Concat(rest.ToArray()).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
internal class InvocationResult<TRes>
|
||||
{
|
||||
// Whether the method call succeeded or threw an exception.
|
||||
public bool Succeeded { get; set; }
|
||||
|
||||
// The result of the method call if any.
|
||||
public TRes Result { get; set; }
|
||||
|
||||
// The message from the captured exception in case there was an error.
|
||||
public string Message { get; set; }
|
||||
|
||||
public static string Success(TRes result) =>
|
||||
JsonUtil.Serialize(new InvocationResult<TRes> { Result = result, Succeeded = true });
|
||||
|
||||
public static string Fail(Exception exception) =>
|
||||
JsonUtil.Serialize(new InvocationResult<object> { Message = exception.Message, Succeeded = false });
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
internal class InvokeDotNetFromJavaScript
|
||||
{
|
||||
private static int NextFunction = 0;
|
||||
private static readonly ConcurrentDictionary<string, string> ResolvedFunctionRegistrations = new ConcurrentDictionary<string, string>();
|
||||
private static readonly ConcurrentDictionary<string, object> ResolvedFunctions = new ConcurrentDictionary<string, object>();
|
||||
|
||||
private const string InvokePromiseCallback = "invokePromiseCallback";
|
||||
|
||||
public static string FindDotNetMethod(string methodOptions)
|
||||
{
|
||||
var result = ResolvedFunctionRegistrations.GetOrAdd(methodOptions, opts =>
|
||||
{
|
||||
var options = JsonUtil.Deserialize<MethodInvocationOptions>(methodOptions);
|
||||
var argumentDeserializer = GetOrCreateArgumentDeserializer(options);
|
||||
var invoker = GetOrCreateInvoker(options, argumentDeserializer);
|
||||
|
||||
var invokerRegistration = NextFunction.ToString();
|
||||
NextFunction++;
|
||||
if (!ResolvedFunctions.TryAdd(invokerRegistration, invoker))
|
||||
{
|
||||
throw new InvalidOperationException($"A function with registration '{invokerRegistration}' was already registered");
|
||||
}
|
||||
|
||||
return invokerRegistration;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string InvokeDotNetMethod(string registration, string callbackId, string methodArguments)
|
||||
{
|
||||
// We invoke the dotnet method and wrap either the result or the exception produced by
|
||||
// an error into an invocation result type. This invocation result is just a discriminated
|
||||
// union with either success or failure.
|
||||
try
|
||||
{
|
||||
return InvocationResult<object>.Success(InvokeDotNetMethodCore(registration, callbackId, methodArguments));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var exception = e;
|
||||
while (exception.InnerException != null)
|
||||
{
|
||||
exception = exception.InnerException;
|
||||
}
|
||||
|
||||
return InvocationResult<object>.Fail(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static object InvokeDotNetMethodCore(string registration, string callbackId, string methodArguments)
|
||||
{
|
||||
if (!ResolvedFunctions.TryGetValue(registration, out var registeredFunction))
|
||||
{
|
||||
throw new InvalidOperationException($"No method exists with registration number '{registration}'.");
|
||||
}
|
||||
|
||||
if (!(registeredFunction is Func<string, object> invoker))
|
||||
{
|
||||
throw new InvalidOperationException($"The registered invoker has the wrong signature.");
|
||||
}
|
||||
|
||||
var result = invoker(methodArguments);
|
||||
if (callbackId != null && !(result is Task))
|
||||
{
|
||||
var methodSpec = ResolvedFunctionRegistrations.Single(kvp => kvp.Value == registration);
|
||||
var options = JsonUtil.Deserialize<MethodInvocationOptions>(methodSpec.Key);
|
||||
throw new InvalidOperationException($"'{options.Method.Name}' in '{options.Type.Name}' must return a Task.");
|
||||
}
|
||||
|
||||
if (result is Task && callbackId == null)
|
||||
{
|
||||
var methodSpec = ResolvedFunctionRegistrations.Single(kvp => kvp.Value == registration);
|
||||
var options = JsonUtil.Deserialize<MethodInvocationOptions>(methodSpec.Key);
|
||||
throw new InvalidOperationException($"'{options.Method.Name}' in '{options.Type.Name}' must not return a Task.");
|
||||
}
|
||||
|
||||
if (result is Task taskResult)
|
||||
{
|
||||
// For async work, we just setup the callback on the returned task to invoke the appropiate callback in JavaScript.
|
||||
SetupResultCallback(callbackId, taskResult);
|
||||
|
||||
// We just return null here as the proper result will be returned through invoking a JavaScript callback when the
|
||||
// task completes.
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupResultCallback(string callbackId, Task taskResult)
|
||||
{
|
||||
taskResult.ContinueWith(task =>
|
||||
{
|
||||
if (task.Status == TaskStatus.RanToCompletion)
|
||||
{
|
||||
if (task.GetType() == typeof(Task))
|
||||
{
|
||||
RegisteredFunction.Invoke<bool>(
|
||||
InvokePromiseCallback,
|
||||
callbackId,
|
||||
new InvocationResult<object> { Succeeded = true, Result = null });
|
||||
}
|
||||
else
|
||||
{
|
||||
var returnValue = TaskResultUtil.GetTaskResult(task);
|
||||
RegisteredFunction.Invoke<bool>(
|
||||
InvokePromiseCallback,
|
||||
callbackId,
|
||||
new InvocationResult<object> { Succeeded = true, Result = returnValue });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Exception exception = task.Exception;
|
||||
while (exception is AggregateException || exception.InnerException is TargetInvocationException)
|
||||
{
|
||||
exception = exception.InnerException;
|
||||
}
|
||||
|
||||
RegisteredFunction.Invoke<bool>(
|
||||
InvokePromiseCallback,
|
||||
callbackId,
|
||||
new InvocationResult<object> { Succeeded = false, Message = exception.Message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
internal static Func<string, object> GetOrCreateInvoker(MethodInvocationOptions options, Func<string, object[]> argumentDeserializer)
|
||||
{
|
||||
var method = options.GetMethodOrThrow();
|
||||
return (string args) => method.Invoke(null, argumentDeserializer(args));
|
||||
}
|
||||
|
||||
private static Func<string, object[]> GetOrCreateArgumentDeserializer(MethodInvocationOptions options)
|
||||
{
|
||||
var info = options.GetMethodOrThrow();
|
||||
var argsClass = ArgumentList.GetArgumentClass(info.GetParameters().Select(p => p.ParameterType).ToArray());
|
||||
var deserializeMethod = ArgumentList.GetDeserializer(argsClass);
|
||||
|
||||
return Deserialize;
|
||||
|
||||
object[] Deserialize(string arguments)
|
||||
{
|
||||
var argsInstance = deserializeMethod(arguments);
|
||||
return argsInstance.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
internal class MethodIdentifier
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required if the method is generic.
|
||||
/// </summary>
|
||||
public IDictionary<string, TypeIdentifier> TypeArguments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required if the method has overloads.
|
||||
/// </summary>
|
||||
public TypeIdentifier[] ParameterTypes { get; set; }
|
||||
|
||||
internal MethodInfo GetMethodOrThrow(Type type)
|
||||
{
|
||||
var result = type.GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => string.Equals(m.Name, Name, StringComparison.Ordinal)).ToArray();
|
||||
|
||||
if (result.Length == 1)
|
||||
{
|
||||
// The method doesn't have overloads, we just return the method found by name.
|
||||
return result[0];
|
||||
}
|
||||
|
||||
result = result.Where(r => r.GetParameters().Length == (ParameterTypes?.Length ?? 0)).ToArray();
|
||||
|
||||
if (result.Length == 1)
|
||||
{
|
||||
// The method has only a single method with the given number of parameter types.
|
||||
return result[0];
|
||||
}
|
||||
|
||||
if (result.Length == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Couldn't find a method with name '{Name}' in '{type.FullName}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Multiple methods with name '{Name}' and '{ParameterTypes.Length}' arguments found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
internal class MethodInvocationOptions
|
||||
{
|
||||
public TypeIdentifier Type { get; set; }
|
||||
public MethodIdentifier Method { get; set; }
|
||||
|
||||
internal MethodInfo GetMethodOrThrow()
|
||||
{
|
||||
var type = Type.GetTypeOrThrow();
|
||||
var method = Method.GetMethodOrThrow(type);
|
||||
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using WebAssembly;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
|
|
@ -23,10 +24,74 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
|||
{
|
||||
// This is a low-perf convenience method that bypasses the need to deal with
|
||||
// .NET memory and data structures on the JS side
|
||||
var argsJson = args.Select(JsonUtil.Serialize);
|
||||
var resultJson = InvokeUnmarshalled<string>("invokeWithJsonMarshalling",
|
||||
argsJson.Prepend(identifier).ToArray());
|
||||
return JsonUtil.Deserialize<TRes>(resultJson);
|
||||
var argsJson = new string[args.Length + 1];
|
||||
|
||||
argsJson[0] = identifier;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
argsJson[i + 1] = JsonUtil.Serialize(args[i]);
|
||||
}
|
||||
|
||||
var resultJson = InvokeUnmarshalled<string>("invokeWithJsonMarshalling", argsJson);
|
||||
|
||||
var result = JsonUtil.Deserialize<InvocationResult<TRes>>(resultJson);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return result.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JavaScriptException(result.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// Arguments and return values are marshalled via JSON serialization.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type. This type must be JSON deserializable.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="args">The arguments to pass, each of which must be JSON serializable.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public static Task<TRes> InvokeAsync<TRes>(string identifier, params object[] args)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<TRes>();
|
||||
var callbackId = Guid.NewGuid().ToString();
|
||||
var argsJson = new string[args.Length + 2];
|
||||
|
||||
argsJson[0] = identifier;
|
||||
argsJson[1] = callbackId;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
argsJson[i + 2] = JsonUtil.Serialize(args[i]);
|
||||
}
|
||||
|
||||
TaskCallbacks.Track(callbackId, new Action<string>(r =>
|
||||
{
|
||||
var res = JsonUtil.Deserialize<InvocationResult<TRes>>(r);
|
||||
TaskCallbacks.Untrack(callbackId);
|
||||
if (res.Succeeded)
|
||||
{
|
||||
tcs.SetResult(res.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.SetException(new JavaScriptException(res.Message));
|
||||
}
|
||||
}));
|
||||
|
||||
try
|
||||
{
|
||||
var result = Invoke<object>("invokeWithJsonMarshallingAsync", argsJson);
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
TaskCallbacks.Untrack(callbackId);
|
||||
throw;
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -102,4 +167,13 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
|||
: result;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TaskCallback
|
||||
{
|
||||
public static void InvokeTaskCallback(string id, string result)
|
||||
{
|
||||
var callback = TaskCallbacks.Get(id);
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
internal static class TaskCallbacks
|
||||
{
|
||||
private static IDictionary<string, Action<string>> References { get; } =
|
||||
new Dictionary<string, Action<string>>();
|
||||
|
||||
public static void Track(string id, Action<string> reference)
|
||||
{
|
||||
if (References.ContainsKey(id))
|
||||
{
|
||||
throw new InvalidOperationException($"An element with id '{id}' is already being tracked.");
|
||||
}
|
||||
|
||||
References.Add(id, reference);
|
||||
}
|
||||
|
||||
public static void Untrack(string id)
|
||||
{
|
||||
if (!References.ContainsKey(id))
|
||||
{
|
||||
throw new InvalidOperationException($"An element with id '{id}' is not being tracked.");
|
||||
}
|
||||
|
||||
References.Remove(id);
|
||||
}
|
||||
|
||||
public static Action<string> Get(string id)
|
||||
{
|
||||
if (!References.ContainsKey(id))
|
||||
{
|
||||
throw new InvalidOperationException($"An element with id '{id}' is not being tracked.");
|
||||
}
|
||||
|
||||
return References[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal class TaskResultUtil
|
||||
{
|
||||
private static ConcurrentDictionary<Type, ITaskResultGetter> _cachedGetters = new ConcurrentDictionary<Type, ITaskResultGetter>();
|
||||
|
||||
private interface ITaskResultGetter
|
||||
{
|
||||
object GetResult(Task task);
|
||||
}
|
||||
|
||||
private class TaskResultGetter<T> : ITaskResultGetter
|
||||
{
|
||||
public object GetResult(Task task) => ((Task<T>)task).Result;
|
||||
}
|
||||
|
||||
public static object GetTaskResult(Task task)
|
||||
{
|
||||
var getter = _cachedGetters.GetOrAdd(task.GetType(), taskType =>
|
||||
{
|
||||
var resultType = taskType.GetGenericArguments().Single();
|
||||
return (ITaskResultGetter)Activator.CreateInstance(
|
||||
typeof(TaskResultGetter<>).MakeGenericType(resultType));
|
||||
});
|
||||
return getter.GetResult(task);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
internal class TypeIdentifier
|
||||
{
|
||||
public string Assembly { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public IDictionary<string, TypeIdentifier> TypeArguments { get; set; }
|
||||
|
||||
internal Type GetTypeOrThrow()
|
||||
{
|
||||
return Type.GetType($"{Name}, {Assembly}", throwOnError: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,429 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
public class JavaScriptInvokeTests
|
||||
{
|
||||
public static TheoryData<object> ResolveMethodPropertyData
|
||||
{
|
||||
get
|
||||
{
|
||||
var result = new TheoryData<object>();
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidParameterless)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithOneParameter)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithTwoParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithThreeParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithFourParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithFiveParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithSixParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithSevenParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithEightParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.ReturnArray)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoOneParameter)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoTwoParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoThreeParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoFourParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoFiveParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoSixParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoSevenParameters)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoEightParameters)));
|
||||
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidParameterlessAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithOneParameterAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithTwoParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithThreeParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithFourParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithFiveParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithSixParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithSevenParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.VoidWithEightParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.ReturnArrayAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoOneParameterAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoTwoParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoThreeParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoFourParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoFiveParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoSixParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoSevenParametersAsync)));
|
||||
result.Add(CreateMethodOptions(nameof(JavaScriptInterop.EchoEightParametersAsync)));
|
||||
|
||||
return result;
|
||||
|
||||
MethodInvocationOptions CreateMethodOptions(string methodName) =>
|
||||
new MethodInvocationOptions
|
||||
{
|
||||
Type = new TypeIdentifier
|
||||
{
|
||||
Assembly = typeof(JavaScriptInterop).Assembly.GetName().Name,
|
||||
Name = typeof(JavaScriptInterop).FullName
|
||||
},
|
||||
Method = new MethodIdentifier
|
||||
{
|
||||
Name = methodName
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ResolveMethodPropertyData))]
|
||||
public void ResolveMethod(object optionsObject)
|
||||
{
|
||||
var options = optionsObject as MethodInvocationOptions;
|
||||
|
||||
var resolvedMethod = options.GetMethodOrThrow();
|
||||
|
||||
Assert.NotNull(resolvedMethod);
|
||||
Assert.Equal(options.Method.Name, resolvedMethod.Name);
|
||||
}
|
||||
}
|
||||
|
||||
internal class JavaScriptInterop
|
||||
{
|
||||
public static IDictionary<string, object[]> Invocations = new Dictionary<string, object[]>();
|
||||
|
||||
public static void VoidParameterless()
|
||||
{
|
||||
Invocations[nameof(VoidParameterless)] = new object[0];
|
||||
}
|
||||
|
||||
public static void VoidWithOneParameter(ComplexParameter parameter1)
|
||||
{
|
||||
Invocations[nameof(VoidWithOneParameter)] = new object[] { parameter1 };
|
||||
}
|
||||
|
||||
public static void VoidWithTwoParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
{
|
||||
Invocations[nameof(VoidWithTwoParameters)] = new object[] { parameter1, parameter2 };
|
||||
}
|
||||
|
||||
public static void VoidWithThreeParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3)
|
||||
{
|
||||
Invocations[nameof(VoidWithThreeParameters)] = new object[] { parameter1, parameter2, parameter3 };
|
||||
}
|
||||
|
||||
public static void VoidWithFourParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
Invocations[nameof(VoidWithFourParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
}
|
||||
|
||||
public static void VoidWithFiveParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5)
|
||||
{
|
||||
Invocations[nameof(VoidWithFiveParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
||||
}
|
||||
|
||||
public static void VoidWithSixParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6)
|
||||
{
|
||||
Invocations[nameof(VoidWithSixParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
||||
}
|
||||
|
||||
public static void VoidWithSevenParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7)
|
||||
{
|
||||
Invocations[nameof(VoidWithSevenParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
||||
}
|
||||
|
||||
public static void VoidWithEightParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
Segment parameter8)
|
||||
{
|
||||
Invocations[nameof(VoidWithEightParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||
}
|
||||
|
||||
public static decimal[] ReturnArray()
|
||||
{
|
||||
return new decimal[] { 0.1M, 0.2M };
|
||||
}
|
||||
|
||||
public static object[] EchoOneParameter(ComplexParameter parameter1)
|
||||
{
|
||||
return new object[] { parameter1 };
|
||||
}
|
||||
|
||||
public static object[] EchoTwoParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
{
|
||||
return new object[] { parameter1, parameter2 };
|
||||
}
|
||||
|
||||
public static object[] EchoThreeParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3 };
|
||||
}
|
||||
|
||||
public static object[] EchoFourParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
}
|
||||
|
||||
public static object[] EchoFiveParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
||||
}
|
||||
|
||||
public static object[] EchoSixParameters(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
||||
}
|
||||
|
||||
public static object[] EchoSevenParameters(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
||||
}
|
||||
|
||||
public static object[] EchoEightParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
Segment parameter8)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||
}
|
||||
|
||||
public static Task VoidParameterlessAsync()
|
||||
{
|
||||
Invocations[nameof(VoidParameterlessAsync)] = new object[0];
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithOneParameterAsync(ComplexParameter parameter1)
|
||||
{
|
||||
Invocations[nameof(VoidParameterless)] = new object[] { parameter1 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithTwoParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
{
|
||||
Invocations[nameof(VoidParameterless)] = new object[] { parameter1, parameter2 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithThreeParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3)
|
||||
{
|
||||
Invocations[nameof(VoidWithThreeParameters)] = new object[] { parameter1, parameter2, parameter3 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithFourParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
Invocations[nameof(VoidWithFourParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithFiveParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5)
|
||||
{
|
||||
Invocations[nameof(VoidWithFiveParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithSixParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6)
|
||||
{
|
||||
Invocations[nameof(VoidWithSixParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithSevenParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7)
|
||||
{
|
||||
Invocations[nameof(VoidWithSevenParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithEightParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
Segment parameter8)
|
||||
{
|
||||
Invocations[nameof(VoidWithEightParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task<decimal[]> ReturnArrayAsync()
|
||||
{
|
||||
return Task.FromResult(new decimal[] { 0.1M, 0.2M });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoOneParameterAsync(ComplexParameter parameter1)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoTwoParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoThreeParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoFourParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoFiveParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoSixParametersAsync(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoSevenParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoEightParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
Segment parameter8)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 });
|
||||
}
|
||||
}
|
||||
|
||||
public struct Segment
|
||||
{
|
||||
public string Source { get; set; }
|
||||
public int Start { get; set; }
|
||||
public int Length { get; set; }
|
||||
}
|
||||
|
||||
public class ComplexParameter
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public bool IsValid { get; set; }
|
||||
public Segment Data { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using BasicTestApp;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure;
|
||||
using Microsoft.AspNetCore.Blazor.E2ETest.Infrastructure.ServerFixtures;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
||||
{
|
||||
public class InteropTest : BasicTestAppTestBase
|
||||
{
|
||||
public InteropTest(
|
||||
BrowserFixture browserFixture,
|
||||
DevHostServerFixture<Program> serverFixture,
|
||||
ITestOutputHelper output)
|
||||
: base(browserFixture, serverFixture, output)
|
||||
{
|
||||
Navigate(ServerPathBase, noReload: true);
|
||||
MountTestComponent<InteropComponent>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeDotNetMethods()
|
||||
{
|
||||
// Arrange
|
||||
var expectedValues = new Dictionary<string, string>
|
||||
{
|
||||
["VoidParameterless"] = "[]",
|
||||
["VoidWithOneParameter"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
||||
["VoidWithTwoParameters"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
||||
["VoidWithThreeParameters"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
||||
["VoidWithFourParameters"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
||||
["VoidWithFiveParameters"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,20,40]",
|
||||
["VoidWithSixParameters"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,12,24,48,6.25]",
|
||||
["VoidWithSevenParameters"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,14,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||
["VoidWithEightParameters"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,16,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||
["VoidParameterlessAsync"] = "[]",
|
||||
["VoidWithOneParameterAsync"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
||||
["VoidWithTwoParametersAsync"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
||||
["VoidWithThreeParametersAsync"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
||||
["VoidWithFourParametersAsync"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
||||
["VoidWithFiveParametersAsync"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,20,40]",
|
||||
["VoidWithSixParametersAsync"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,12,24,48,6.25]",
|
||||
["VoidWithSevenParametersAsync"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,14,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||
["VoidWithEightParametersAsync"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,16,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||
["result1"] = @"[0.1,0.2]",
|
||||
["result2"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
||||
["result3"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
||||
["result4"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
||||
["result5"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
||||
["result6"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,20,40]",
|
||||
["result7"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,12,24,48,6.25]",
|
||||
["result8"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,14,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||
["result9"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,16,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||
["result1Async"] = @"[0.1,0.2]",
|
||||
["result2Async"] = @"[{""id"":1,""isValid"":false,""data"":{""source"":""Some random text with at least 1 characters"",""start"":1,""length"":1}}]",
|
||||
["result3Async"] = @"[{""id"":2,""isValid"":true,""data"":{""source"":""Some random text with at least 2 characters"",""start"":2,""length"":2}},2]",
|
||||
["result4Async"] = @"[{""id"":3,""isValid"":false,""data"":{""source"":""Some random text with at least 3 characters"",""start"":3,""length"":3}},3,6]",
|
||||
["result5Async"] = @"[{""id"":4,""isValid"":true,""data"":{""source"":""Some random text with at least 4 characters"",""start"":4,""length"":4}},4,8,16]",
|
||||
["result6Async"] = @"[{""id"":5,""isValid"":false,""data"":{""source"":""Some random text with at least 5 characters"",""start"":5,""length"":5}},5,10,20,40]",
|
||||
["result7Async"] = @"[{""id"":6,""isValid"":true,""data"":{""source"":""Some random text with at least 6 characters"",""start"":6,""length"":6}},6,12,24,48,6.25]",
|
||||
["result8Async"] = @"[{""id"":7,""isValid"":false,""data"":{""source"":""Some random text with at least 7 characters"",""start"":7,""length"":7}},7,14,28,56,7.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5]]",
|
||||
["result9Async"] = @"[{""id"":8,""isValid"":true,""data"":{""source"":""Some random text with at least 8 characters"",""start"":8,""length"":8}},8,16,32,64,8.25,[0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5],{""source"":""Some random text with at least 7 characters"",""start"":9,""length"":9}]",
|
||||
["ThrowException"] = @"""Threw an exception!""",
|
||||
["AsyncThrowSyncException"] = @"""Threw a sync exception!""",
|
||||
["AsyncThrowAsyncException"] = @"""Threw an async exception!""",
|
||||
["ExceptionFromSyncMethod"] = "Function threw an exception!",
|
||||
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
|
||||
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
|
||||
};
|
||||
var actualValues = new Dictionary<string, string>();
|
||||
|
||||
// Act
|
||||
var interopButton = Browser.FindElement(By.Id("btn-interop"));
|
||||
interopButton.Click();
|
||||
|
||||
var wait = new WebDriverWait(Browser, TimeSpan.FromSeconds(10))
|
||||
.Until(d => d.FindElement(By.Id("done-with-interop")));
|
||||
|
||||
foreach (var expectedValue in expectedValues)
|
||||
{
|
||||
var currentValue = Browser.FindElement(By.Id(expectedValue.Key));
|
||||
actualValues.Add(expectedValue.Key, currentValue.Text);
|
||||
}
|
||||
|
||||
// Assert
|
||||
foreach (var expectedValue in expectedValues)
|
||||
{
|
||||
if (expectedValue.Key.Contains("Exception"))
|
||||
{
|
||||
Assert.StartsWith(expectedValue.Value, actualValues[expectedValue.Key]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(expectedValue.Value, actualValues[expectedValue.Key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
@using Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
@using BasicTestApp.InteropTest
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
|
||||
<button id="btn-interop" onclick="@InvokeInteropAsync">Invoke interop!</button>
|
||||
|
||||
<div>
|
||||
<h1>Invocations</h1>
|
||||
@foreach (var invocation in Invocations)
|
||||
{
|
||||
<h2>@invocation.Key</h2>
|
||||
<p id="@invocation.Key">@invocation.Value</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>Return values and exceptions thrown from .NET</h1>
|
||||
@foreach (var returnValue in ReturnValues)
|
||||
{
|
||||
<h2>@returnValue.Key</h2>
|
||||
<p id="@returnValue.Key">@returnValue.Value</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h1>Exceptions thrown from JavaScript</h1>
|
||||
<h2>@nameof(ExceptionFromSyncMethod)</h2>
|
||||
<p id="@nameof(ExceptionFromSyncMethod)">@ExceptionFromSyncMethod?.Message</p>
|
||||
<h2>@nameof(SyncExceptionFromAsyncMethod)</h2>
|
||||
<p id="@nameof(SyncExceptionFromAsyncMethod)">@SyncExceptionFromAsyncMethod?.Message</p>
|
||||
<h2>@nameof(AsyncExceptionFromAsyncMethod)</h2>
|
||||
<p id="@nameof(AsyncExceptionFromAsyncMethod)">@AsyncExceptionFromAsyncMethod?.Message</p>
|
||||
</div>
|
||||
@if (DoneWithInterop)
|
||||
{
|
||||
<p id="done-with-interop">Done with interop.</p>
|
||||
}
|
||||
|
||||
@functions {
|
||||
|
||||
public IDictionary<string, string> ReturnValues { get; set; } = new Dictionary<string, string>();
|
||||
public IDictionary<string, string> Invocations { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
public JavaScriptException ExceptionFromSyncMethod { get; set; }
|
||||
public JavaScriptException SyncExceptionFromAsyncMethod { get; set; }
|
||||
public JavaScriptException AsyncExceptionFromAsyncMethod { get; set; }
|
||||
|
||||
public bool DoneWithInterop { get; set; }
|
||||
|
||||
public async Task InvokeInteropAsync()
|
||||
{
|
||||
Console.WriteLine("Starting interop invocations.");
|
||||
await RegisteredFunction.InvokeAsync<object>("BasicTestApp.Interop.InvokeDotNetInteropMethodsAsync");
|
||||
Console.WriteLine("Showing interop invocation results.");
|
||||
var collectResults = RegisteredFunction.Invoke<Dictionary<string,string>>("BasicTestApp.Interop.CollectResults");
|
||||
|
||||
ReturnValues = collectResults.ToDictionary(kvp => kvp.Key,kvp => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(kvp.Value)));
|
||||
|
||||
var invocations = new Dictionary<string, string>();
|
||||
foreach (var interopResult in JavaScriptInterop.Invocations)
|
||||
{
|
||||
var interopResultValue = JsonUtil.Serialize(interopResult.Value);
|
||||
invocations[interopResult.Key] = interopResultValue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RegisteredFunction.Invoke<object>("BasicTestApp.Interop.FunctionThrows");
|
||||
}
|
||||
catch (JavaScriptException e)
|
||||
{
|
||||
ExceptionFromSyncMethod = e;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RegisteredFunction.InvokeAsync<object>("BasicTestApp.Interop.AsyncFunctionThrowsSyncException");
|
||||
}
|
||||
catch (JavaScriptException e)
|
||||
{
|
||||
SyncExceptionFromAsyncMethod = e;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RegisteredFunction.InvokeAsync<object>("BasicTestApp.Interop.AsyncFunctionThrowsAsyncException");
|
||||
}
|
||||
catch (JavaScriptException e)
|
||||
{
|
||||
AsyncExceptionFromAsyncMethod = e;
|
||||
}
|
||||
|
||||
Invocations = invocations;
|
||||
DoneWithInterop = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace BasicTestApp.InteropTest
|
||||
{
|
||||
public class ComplexParameter
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public bool IsValid { get; set; }
|
||||
|
||||
public Segment Data { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,360 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BasicTestApp.InteropTest
|
||||
{
|
||||
public class JavaScriptInterop
|
||||
{
|
||||
public static IDictionary<string, object[]> Invocations = new Dictionary<string, object[]>();
|
||||
|
||||
public static void ThrowException() => throw new InvalidOperationException("Threw an exception!");
|
||||
|
||||
public static Task AsyncThrowSyncException() => throw new InvalidOperationException("Threw a sync exception!");
|
||||
|
||||
public static Task AsyncThrowAsyncException()
|
||||
{
|
||||
TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
|
||||
var timer = new Timer(
|
||||
state =>
|
||||
{
|
||||
tcs.SetException(new InvalidOperationException("Threw an async exception!"));
|
||||
},
|
||||
null,
|
||||
3000,
|
||||
Timeout.Infinite);
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public static void VoidParameterless()
|
||||
{
|
||||
Invocations[nameof(VoidParameterless)] = new object[0];
|
||||
}
|
||||
|
||||
public static void VoidWithOneParameter(ComplexParameter parameter1)
|
||||
{
|
||||
Invocations[nameof(VoidWithOneParameter)] = new object[] { parameter1 };
|
||||
}
|
||||
|
||||
public static void VoidWithTwoParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
{
|
||||
Invocations[nameof(VoidWithTwoParameters)] = new object[] { parameter1, parameter2 };
|
||||
}
|
||||
|
||||
public static void VoidWithThreeParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3)
|
||||
{
|
||||
Invocations[nameof(VoidWithThreeParameters)] = new object[] { parameter1, parameter2, parameter3 };
|
||||
}
|
||||
|
||||
public static void VoidWithFourParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
Invocations[nameof(VoidWithFourParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
}
|
||||
|
||||
public static void VoidWithFiveParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5)
|
||||
{
|
||||
Invocations[nameof(VoidWithFiveParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
||||
}
|
||||
|
||||
public static void VoidWithSixParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6)
|
||||
{
|
||||
Invocations[nameof(VoidWithSixParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
||||
}
|
||||
|
||||
public static void VoidWithSevenParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7)
|
||||
{
|
||||
Invocations[nameof(VoidWithSevenParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
||||
}
|
||||
|
||||
public static void VoidWithEightParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
Segment parameter8)
|
||||
{
|
||||
Invocations[nameof(VoidWithEightParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||
}
|
||||
|
||||
public static decimal[] ReturnArray()
|
||||
{
|
||||
return new decimal[] { 0.1M, 0.2M };
|
||||
}
|
||||
|
||||
public static object[] EchoOneParameter(ComplexParameter parameter1)
|
||||
{
|
||||
return new object[] { parameter1 };
|
||||
}
|
||||
|
||||
public static object[] EchoTwoParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
{
|
||||
return new object[] { parameter1, parameter2 };
|
||||
}
|
||||
|
||||
public static object[] EchoThreeParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3 };
|
||||
}
|
||||
|
||||
public static object[] EchoFourParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
}
|
||||
|
||||
public static object[] EchoFiveParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
||||
}
|
||||
|
||||
public static object[] EchoSixParameters(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
||||
}
|
||||
|
||||
public static object[] EchoSevenParameters(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
||||
}
|
||||
|
||||
public static object[] EchoEightParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
Segment parameter8)
|
||||
{
|
||||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||
}
|
||||
|
||||
public static Task VoidParameterlessAsync()
|
||||
{
|
||||
Invocations[nameof(VoidParameterlessAsync)] = new object[0];
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithOneParameterAsync(ComplexParameter parameter1)
|
||||
{
|
||||
Invocations[nameof(VoidWithOneParameterAsync)] = new object[] { parameter1 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithTwoParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
{
|
||||
Invocations[nameof(VoidWithTwoParametersAsync)] = new object[] { parameter1, parameter2 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithThreeParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3)
|
||||
{
|
||||
Invocations[nameof(VoidWithThreeParametersAsync)] = new object[] { parameter1, parameter2, parameter3 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithFourParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
Invocations[nameof(VoidWithFourParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithFiveParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5)
|
||||
{
|
||||
Invocations[nameof(VoidWithFiveParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithSixParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6)
|
||||
{
|
||||
Invocations[nameof(VoidWithSixParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithSevenParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7)
|
||||
{
|
||||
Invocations[nameof(VoidWithSevenParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task VoidWithEightParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
Segment parameter8)
|
||||
{
|
||||
Invocations[nameof(VoidWithEightParametersAsync)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public static Task<decimal[]> ReturnArrayAsync()
|
||||
{
|
||||
return Task.FromResult(new decimal[] { 0.1M, 0.2M });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoOneParameterAsync(ComplexParameter parameter1)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoTwoParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoThreeParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoFourParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoFiveParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoSixParametersAsync(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoSevenParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 });
|
||||
}
|
||||
|
||||
public static Task<object[]> EchoEightParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
int parameter4,
|
||||
long parameter5,
|
||||
float parameter6,
|
||||
List<double> parameter7,
|
||||
Segment parameter8)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace BasicTestApp.InteropTest
|
||||
{
|
||||
public struct Segment
|
||||
{
|
||||
public string Source { get; set; }
|
||||
public int Start { get; set; }
|
||||
public int Length { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Browser.Http;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
Select test:
|
||||
<select onchange="mountTestComponent(event.target.value)">
|
||||
<option value="">Choose...</option>
|
||||
<option value="BasicTestApp.InteropComponent">Interop component</option>
|
||||
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
|
||||
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
|
||||
<option value="BasicTestApp.CounterComponent">Counter</option>
|
||||
|
|
@ -47,6 +48,8 @@
|
|||
<app>Loading...</app>
|
||||
|
||||
<script type="blazor-boot"></script>
|
||||
<!-- Used for testing interop scenarios between JS and .NET -->
|
||||
<script type="text/javascript" src="js/jsinteroptests.js"></script>
|
||||
<script>
|
||||
// The client-side .NET code calls this when it is ready to be called from test code
|
||||
// The Xunit test code polls until it sees the flag is set
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
|
||||
// We'll store the results from the tests here
|
||||
var results = {};
|
||||
|
||||
function invokeDotNetInteropMethodsAsync() {
|
||||
console.log('Invoking void sync methods.');
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidParameterless'));
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidWithOneParameter'), ...createArgumentList(1));
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidWithTwoParameters'), ...createArgumentList(2));
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidWithThreeParameters'), ...createArgumentList(3));
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidWithFourParameters'), ...createArgumentList(4));
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidWithFiveParameters'), ...createArgumentList(5));
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidWithSixParameters'), ...createArgumentList(6));
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidWithSevenParameters'), ...createArgumentList(7));
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('VoidWithEightParameters'), ...createArgumentList(8));
|
||||
|
||||
console.log('Invoking returning sync methods.');
|
||||
results['result1'] = Blazor.invokeDotNetMethod(createMethodOptions('ReturnArray'));
|
||||
results['result2'] = Blazor.invokeDotNetMethod(createMethodOptions('EchoOneParameter'), ...createArgumentList(1));
|
||||
results['result3'] = Blazor.invokeDotNetMethod(createMethodOptions('EchoTwoParameters'), ...createArgumentList(2));
|
||||
results['result4'] = Blazor.invokeDotNetMethod(createMethodOptions('EchoThreeParameters'), ...createArgumentList(3));
|
||||
results['result5'] = Blazor.invokeDotNetMethod(createMethodOptions('EchoFourParameters'), ...createArgumentList(4));
|
||||
results['result6'] = Blazor.invokeDotNetMethod(createMethodOptions('EchoFiveParameters'), ...createArgumentList(5));
|
||||
results['result7'] = Blazor.invokeDotNetMethod(createMethodOptions('EchoSixParameters'), ...createArgumentList(6));
|
||||
results['result8'] = Blazor.invokeDotNetMethod(createMethodOptions('EchoSevenParameters'), ...createArgumentList(7));
|
||||
results['result9'] = Blazor.invokeDotNetMethod(createMethodOptions('EchoEightParameters'), ...createArgumentList(8));
|
||||
|
||||
console.log('Invoking void async methods.');
|
||||
return Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidParameterlessAsync'))
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidWithOneParameterAsync'), ...createArgumentList(1)))
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidWithTwoParametersAsync'), ...createArgumentList(2)))
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidWithThreeParametersAsync'), ...createArgumentList(3)))
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidWithFourParametersAsync'), ...createArgumentList(4)))
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidWithFiveParametersAsync'), ...createArgumentList(5)))
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidWithSixParametersAsync'), ...createArgumentList(6)))
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidWithSevenParametersAsync'), ...createArgumentList(7)))
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('VoidWithEightParametersAsync'), ...createArgumentList(8)))
|
||||
.then(() => {
|
||||
console.log('Invoking returning async methods.');
|
||||
return Blazor.invokeDotNetMethodAsync(createMethodOptions('ReturnArrayAsync'))
|
||||
.then(r => results['result1Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoOneParameterAsync'), ...createArgumentList(1)))
|
||||
.then(r => results['result2Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoTwoParametersAsync'), ...createArgumentList(2)))
|
||||
.then(r => results['result3Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoThreeParametersAsync'), ...createArgumentList(3)))
|
||||
.then(r => results['result4Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoFourParametersAsync'), ...createArgumentList(4)))
|
||||
.then(r => results['result5Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoFiveParametersAsync'), ...createArgumentList(5)))
|
||||
.then(r => results['result6Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoSixParametersAsync'), ...createArgumentList(6)))
|
||||
.then(r => results['result7Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoSevenParametersAsync'), ...createArgumentList(7)))
|
||||
.then(r => results['result8Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoEightParametersAsync'), ...createArgumentList(8)))
|
||||
.then(r => results['result9Async'] = r);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Invoking methods that throw exceptions');
|
||||
try {
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('ThrowException'))
|
||||
} catch (e) {
|
||||
results['ThrowException'] = e.message;
|
||||
}
|
||||
|
||||
try {
|
||||
Blazor.invokeDotNetMethodAsync(createMethodOptions('AsyncThrowSyncException'));
|
||||
} catch (e) {
|
||||
results['AsyncThrowSyncException'] = e.message;
|
||||
}
|
||||
|
||||
return Blazor.invokeDotNetMethodAsync(createMethodOptions('AsyncThrowAsyncException'))
|
||||
.catch(e => {
|
||||
results['AsyncThrowAsyncException'] = e.message;
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => console.log('Done invoking interop methods'));
|
||||
});
|
||||
}
|
||||
|
||||
function createMethodOptions(methodName) {
|
||||
return {
|
||||
type: {
|
||||
assembly: 'BasicTestApp',
|
||||
name: 'BasicTestApp.InteropTest.JavaScriptInterop'
|
||||
},
|
||||
method: {
|
||||
name: methodName
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createArgumentList(argumentNumber){
|
||||
const array = new Array(argumentNumber);
|
||||
if (argumentNumber === 0) {
|
||||
return [];
|
||||
}
|
||||
for (var i = 0; i < argumentNumber; i++) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
array[i] = {
|
||||
id: argumentNumber,
|
||||
isValid: argumentNumber % 2 === 0,
|
||||
data: {
|
||||
source: `Some random text with at least ${argumentNumber} characters`,
|
||||
start: argumentNumber,
|
||||
length: argumentNumber
|
||||
}
|
||||
};
|
||||
break;
|
||||
case 1:
|
||||
array[i] = argumentNumber;
|
||||
break;
|
||||
case 2:
|
||||
array[i] = argumentNumber * 2;
|
||||
break;
|
||||
case 3:
|
||||
array[i] = argumentNumber * 4;
|
||||
break;
|
||||
case 4:
|
||||
array[i] = argumentNumber * 8;
|
||||
break;
|
||||
case 5:
|
||||
array[i] = argumentNumber + 0.25;
|
||||
break;
|
||||
case 6:
|
||||
array[i] = Array.apply(null, Array(argumentNumber)).map((v, i) => i + 0.5);
|
||||
break;
|
||||
case 7:
|
||||
array[i] = {
|
||||
source: `Some random text with at least ${i} characters`,
|
||||
start: argumentNumber + 1,
|
||||
length: argumentNumber + 1
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(i);
|
||||
throw new Error('Invalid argument count!');
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
Blazor.registerFunction('BasicTestApp.Interop.InvokeDotNetInteropMethodsAsync', invokeDotNetInteropMethodsAsync);
|
||||
Blazor.registerFunction('BasicTestApp.Interop.CollectResults', collectInteropResults);
|
||||
|
||||
Blazor.registerFunction('BasicTestApp.Interop.FunctionThrows', functionThrowsException);
|
||||
Blazor.registerFunction('BasicTestApp.Interop.AsyncFunctionThrowsSyncException', asyncFunctionThrowsSyncException);
|
||||
Blazor.registerFunction('BasicTestApp.Interop.AsyncFunctionThrowsAsyncException', asyncFunctionThrowsAsyncException);
|
||||
|
||||
function functionThrowsException() {
|
||||
throw new Error('Function threw an exception!');
|
||||
}
|
||||
|
||||
function asyncFunctionThrowsSyncException() {
|
||||
throw new Error('Function threw a sync exception!');
|
||||
}
|
||||
|
||||
function asyncFunctionThrowsAsyncException() {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => reject(new Error('Function threw an async exception!')), 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function collectInteropResults() {
|
||||
let result = {};
|
||||
let properties = Object.getOwnPropertyNames(results);
|
||||
for (let i = 0; i < properties.length; i++) {
|
||||
let property = properties[i];
|
||||
result[property] = btoa(JSON.stringify(results[property]));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Loading…
Reference in New Issue