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"
|
"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": {
|
"string-width": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
"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": {
|
"strip-ansi": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
"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 { getAssemblyNameFromUrl } from './Platform/DotNet';
|
||||||
import './Rendering/Renderer';
|
import './Rendering/Renderer';
|
||||||
import './Services/Http';
|
import './Services/Http';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { platform } from './Environment'
|
import { platform } from './Environment'
|
||||||
import { registerFunction } from './Interop/RegisteredFunction';
|
import { registerFunction } from './Interop/RegisteredFunction';
|
||||||
import { navigateTo } from './Services/UriHelper';
|
import { navigateTo } from './Services/UriHelper';
|
||||||
|
import { invokeDotNetMethod, invokeDotNetMethodAsync } from './Interop/InvokeDotNetMethodWithJsonMarshalling';
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
// When the library is loaded in a browser via a <script> element, make the
|
// When the library is loaded in a browser via a <script> element, make the
|
||||||
|
|
@ -9,5 +10,7 @@ if (typeof window !== 'undefined') {
|
||||||
platform,
|
platform,
|
||||||
registerFunction,
|
registerFunction,
|
||||||
navigateTo,
|
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';
|
import { attachRootComponentToElement, renderBatch } from '../Rendering/Renderer';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -8,5 +9,7 @@ import { attachRootComponentToElement, renderBatch } from '../Rendering/Renderer
|
||||||
export const internalRegisteredFunctions = {
|
export const internalRegisteredFunctions = {
|
||||||
attachRootComponentToElement,
|
attachRootComponentToElement,
|
||||||
invokeWithJsonMarshalling,
|
invokeWithJsonMarshalling,
|
||||||
|
invokeWithJsonMarshallingAsync,
|
||||||
|
invokePromiseCallback,
|
||||||
renderBatch,
|
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 { getAssemblyNameFromUrl } from '../DotNet';
|
||||||
import { getRegisteredFunction } from '../../Interop/RegisteredFunction';
|
import { getRegisteredFunction } from '../../Interop/RegisteredFunction';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export interface Platform {
|
export interface Platform {
|
||||||
start(loadAssemblyUrls: string[]): Promise<void>;
|
start(loadAssemblyUrls: string[]): Promise<void>;
|
||||||
|
|
||||||
callEntryPoint(assemblyName: string, entrypointMethod: string, args: (System_Object | null)[]);
|
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');
|
const webpack = require('webpack');
|
||||||
|
|
||||||
module.exports = {
|
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.
|
// 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;
|
using WebAssembly;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
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
|
// This is a low-perf convenience method that bypasses the need to deal with
|
||||||
// .NET memory and data structures on the JS side
|
// .NET memory and data structures on the JS side
|
||||||
var argsJson = args.Select(JsonUtil.Serialize);
|
var argsJson = new string[args.Length + 1];
|
||||||
var resultJson = InvokeUnmarshalled<string>("invokeWithJsonMarshalling",
|
|
||||||
argsJson.Prepend(identifier).ToArray());
|
argsJson[0] = identifier;
|
||||||
return JsonUtil.Deserialize<TRes>(resultJson);
|
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>
|
/// <summary>
|
||||||
|
|
@ -102,4 +167,13 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||||
: result;
|
: 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Blazor.Browser.Http;
|
using Microsoft.AspNetCore.Blazor.Browser.Http;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
Select test:
|
Select test:
|
||||||
<select onchange="mountTestComponent(event.target.value)">
|
<select onchange="mountTestComponent(event.target.value)">
|
||||||
<option value="">Choose...</option>
|
<option value="">Choose...</option>
|
||||||
|
<option value="BasicTestApp.InteropComponent">Interop component</option>
|
||||||
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
|
<option value="BasicTestApp.AsyncEventHandlerComponent">Async event handlers</option>
|
||||||
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
|
<option value="BasicTestApp.AddRemoveChildComponents">Add/remove child components</option>
|
||||||
<option value="BasicTestApp.CounterComponent">Counter</option>
|
<option value="BasicTestApp.CounterComponent">Counter</option>
|
||||||
|
|
@ -47,6 +48,8 @@
|
||||||
<app>Loading...</app>
|
<app>Loading...</app>
|
||||||
|
|
||||||
<script type="blazor-boot"></script>
|
<script type="blazor-boot"></script>
|
||||||
|
<!-- Used for testing interop scenarios between JS and .NET -->
|
||||||
|
<script type="text/javascript" src="js/jsinteroptests.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// The client-side .NET code calls this when it is ready to be called from test code
|
// 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
|
// 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