JavaScript interop v3 (#1033)
* JavaScript interop via new IJSRuntime abstraction * CR feedback
This commit is contained in:
parent
dcd9cf7481
commit
b275055835
33
Blazor.sln
33
Blazor.sln
|
|
@ -95,6 +95,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Blazor.Analyzers.Test", "test\Microsoft.AspNetCore.Blazor.Analyzers.Test\Microsoft.AspNetCore.Blazor.Analyzers.Test.csproj", "{CF3B5990-7A05-4993-AACA-D2C8D7AFF6E6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop", "src\Microsoft.JSInterop\Microsoft.JSInterop.csproj", "{C866B19D-AFFF-45B7-8DAB-71805F39D516}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.JSInterop.Test", "test\Microsoft.JSInterop.Test\Microsoft.JSInterop.Test.csproj", "{BA1CE1FD-89D8-423F-A21B-6B212674EB39}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.WebAssembly.Interop", "src\mono\Mono.WebAssembly.Interop\Mono.WebAssembly.Interop.csproj", "{C56873E6-8F49-476E-AF51-B5D187832CF5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -338,6 +344,30 @@ Global
|
|||
{CF3B5990-7A05-4993-AACA-D2C8D7AFF6E6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CF3B5990-7A05-4993-AACA-D2C8D7AFF6E6}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF3B5990-7A05-4993-AACA-D2C8D7AFF6E6}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -380,6 +410,9 @@ Global
|
|||
{3A457B14-D91B-4FFF-A81A-8F350BDB911F} = {E8EBA72C-D555-43AE-BC98-F0B2D05F6A07}
|
||||
{6DDD6A29-0A3E-417F-976C-5FE3FDA74055} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{CF3B5990-7A05-4993-AACA-D2C8D7AFF6E6} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{C866B19D-AFFF-45B7-8DAB-71805F39D516} = {B867E038-B3CE-43E3-9292-61568C46CDEB}
|
||||
{BA1CE1FD-89D8-423F-A21B-6B212674EB39} = {ADA3AE29-F6DE-49F6-8C7C-B321508CAE8E}
|
||||
{C56873E6-8F49-476E-AF51-B5D187832CF5} = {7B5CAAB1-A3EB-44F7-87E3-A13ED89FC17D}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {504DA352-6788-4DC0-8705-82167E72A4D3}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mono sanity check</title>
|
||||
|
|
@ -139,32 +139,21 @@
|
|||
throw new Error('This is a JavaScript exception.');
|
||||
}
|
||||
|
||||
// Normally, applications would use the higher-level APIs for registering invocable
|
||||
// functions, and for invoking them with automatic argument/result marshalling.
|
||||
// But since this project is trying to test low-level Mono runtime capabilities,
|
||||
// we implement our own marshalling here.
|
||||
window.Blazor = {
|
||||
platform: {
|
||||
monoGetRegisteredFunction: function (name) { return blazorRegisteredFunctions[name]; }
|
||||
}
|
||||
};
|
||||
var blazorRegisteredFunctions = {
|
||||
evaluateJsExpression: function (dotNetStringExpression) {
|
||||
var result = eval(dotnetStringToJavaScriptString(dotNetStringExpression));
|
||||
return result === null || result === undefined
|
||||
? result // Pass through null/undefined so we can verify this is handled upstream
|
||||
: javaScriptStringToDotNetString(result.toString());
|
||||
},
|
||||
function evaluateJsExpression(dotNetStringExpression) {
|
||||
var result = eval(dotnetStringToJavaScriptString(dotNetStringExpression));
|
||||
return result === null || result === undefined
|
||||
? result // Pass through null/undefined so we can verify this is handled upstream
|
||||
: javaScriptStringToDotNetString(result.toString());
|
||||
}
|
||||
|
||||
divideNumbersUnmarshalled: function (a, b) {
|
||||
// In this example, neither the arguments nor return value are boxed
|
||||
// -- we expect to receive and return numbers directly
|
||||
if (b === 0) {
|
||||
throw new Error('Division by zero');
|
||||
}
|
||||
return a / b;
|
||||
function divideNumbersUnmarshalled(a, b) {
|
||||
// In this example, neither the arguments nor return value are boxed
|
||||
// -- we expect to receive and return numbers directly
|
||||
if (b === 0) {
|
||||
throw new Error('Division by zero');
|
||||
}
|
||||
};
|
||||
return a / b;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,14 @@
|
|||
(function () {
|
||||
(function () {
|
||||
// Implement just enough of the DotNet.* API surface for unmarshalled interop calls to work
|
||||
// in the cases used in this project
|
||||
window.DotNet = {
|
||||
jsCallDispatcher: {
|
||||
findJSFunction: function (identifier) {
|
||||
return window[identifier];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.initMono = function initMono(loadAssemblyUrls, onReadyCallback) {
|
||||
window.Browser = {
|
||||
init: function () { },
|
||||
|
|
@ -158,4 +168,4 @@
|
|||
return filename.replace(/\.dll$/, '');
|
||||
}
|
||||
|
||||
})();
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Mono.WebAssembly.Interop;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using WebAssembly;
|
||||
|
||||
namespace MonoSanityClient
|
||||
{
|
||||
|
|
@ -32,8 +32,7 @@ namespace MonoSanityClient
|
|||
|
||||
public static string EvaluateJavaScript(string expression)
|
||||
{
|
||||
// For tests that call this method, we'll exercise the 'BlazorInvokeJSArray' code path
|
||||
var result = Runtime.BlazorInvokeJSArray<string>(out var exceptionMessage, "evaluateJsExpression", expression, null, null);
|
||||
var result = InternalCalls.InvokeJSUnmarshalled<string, string, object, object>(out var exceptionMessage, "evaluateJsExpression", expression, null, null);
|
||||
if (exceptionMessage != null)
|
||||
{
|
||||
return $".NET got exception: {exceptionMessage}";
|
||||
|
|
@ -46,7 +45,7 @@ namespace MonoSanityClient
|
|||
{
|
||||
// For tests that call this method, we'll exercise the 'BlazorInvokeJS' code path
|
||||
// since that doesn't box the params
|
||||
var result = Runtime.BlazorInvokeJS<int, int, object, int>(out var exceptionMessage, "divideNumbersUnmarshalled", numberA, numberB, null);
|
||||
var result = InternalCalls.InvokeJSUnmarshalled<int, int, object, int>(out var exceptionMessage, "divideNumbersUnmarshalled", numberA, numberB, null);
|
||||
if (exceptionMessage != null)
|
||||
{
|
||||
return $".NET got exception: {exceptionMessage}";
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
<Import Project="..\..\src\Microsoft.AspNetCore.Blazor.Build\ReferenceFromSource.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Share the WebAssembly.Runtime.cs source here so we get access to the same interop externs -->
|
||||
<Compile Include="..\..\src\Microsoft.AspNetCore.Blazor.Browser\Interop\WebAssembly.Runtime.cs" />
|
||||
<!-- Share the InternalCalls.cs source here so we get access to the same interop externs -->
|
||||
<Compile Include="..\..\src\mono\Mono.WebAssembly.Interop\InternalCalls.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="2.0.0" />
|
||||
<WebpackInputs Include="**\*.ts" Exclude="node_modules\**" />
|
||||
<WebpackInputs Include="..\Microsoft.JSInterop\TypeScript\src\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\Microsoft.AspNetCore.Blazor.BuildTools\ReferenceFromSource.props" />
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -4,13 +4,14 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"build": "webpack --mode development",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/emscripten": "0.0.31",
|
||||
"typescript": "^2.6.1",
|
||||
"webpack": "^3.8.1",
|
||||
"ts-loader": "^3.2.0"
|
||||
"ts-loader": "^4.4.1",
|
||||
"typescript": "^2.9.2",
|
||||
"webpack": "^4.12.0",
|
||||
"webpack-cli": "^3.0.8"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import '../../Microsoft.JSInterop/JavaScriptRuntime/src/Microsoft.JSInterop';
|
||||
import { platform } from './Environment';
|
||||
import { getAssemblyNameFromUrl } from './Platform/DotNet';
|
||||
import './Rendering/Renderer';
|
||||
import './Services/Http';
|
||||
import './Services/UriHelper';
|
||||
import './GlobalExports';
|
||||
|
||||
async function boot() {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
import { platform } from './Environment'
|
||||
import { registerFunction } from './Interop/RegisteredFunction';
|
||||
import { navigateTo } from './Services/UriHelper';
|
||||
import { invokeDotNetMethod, invokeDotNetMethodAsync } from './Interop/InvokeDotNetMethodWithJsonMarshalling';
|
||||
import { platform } from './Environment';
|
||||
import { navigateTo, internalFunctions as uriHelperInternalFunctions } from './Services/UriHelper';
|
||||
import { internalFunctions as httpInternalFunctions } from './Services/Http';
|
||||
import { attachRootComponentToElement, renderBatch } from './Rendering/Renderer';
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
// When the library is loaded in a browser via a <script> element, make the
|
||||
// following APIs available in global scope for invocation from JS
|
||||
window['Blazor'] = {
|
||||
platform,
|
||||
registerFunction,
|
||||
navigateTo,
|
||||
invokeDotNetMethod,
|
||||
invokeDotNetMethodAsync
|
||||
|
||||
_internal: {
|
||||
attachRootComponentToElement,
|
||||
renderBatch,
|
||||
http: httpInternalFunctions,
|
||||
uriHelper: uriHelperInternalFunctions
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
import { invokeWithJsonMarshalling, invokeWithJsonMarshallingAsync } from './InvokeJavaScriptFunctionWithJsonMarshalling';
|
||||
import { invokePromiseCallback } from './InvokeDotNetMethodWithJsonMarshalling';
|
||||
import { attachRootComponentToElement, renderBatch } from '../Rendering/Renderer';
|
||||
|
||||
/**
|
||||
* The definitive list of internal functions invokable from .NET code.
|
||||
* These function names are treated as 'reserved' and cannot be passed to registerFunction.
|
||||
*/
|
||||
export const internalRegisteredFunctions = {
|
||||
attachRootComponentToElement,
|
||||
invokeWithJsonMarshalling,
|
||||
invokeWithJsonMarshallingAsync,
|
||||
invokePromiseCallback,
|
||||
renderBatch,
|
||||
};
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
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,25 +0,0 @@
|
|||
import { internalRegisteredFunctions } from './InternalRegisteredFunction';
|
||||
|
||||
const registeredFunctions: { [identifier: string]: Function | undefined } = {};
|
||||
|
||||
export function registerFunction(identifier: string, implementation: Function) {
|
||||
if (internalRegisteredFunctions.hasOwnProperty(identifier)) {
|
||||
throw new Error(`The function identifier '${identifier}' is reserved and cannot be registered.`);
|
||||
}
|
||||
|
||||
if (registeredFunctions.hasOwnProperty(identifier)) {
|
||||
throw new Error(`A function with the identifier '${identifier}' has already been registered.`);
|
||||
}
|
||||
|
||||
registeredFunctions[identifier] = implementation;
|
||||
}
|
||||
|
||||
export function getRegisteredFunction(identifier: string): Function {
|
||||
// By prioritising the internal ones, we ensure you can't override them
|
||||
const result = internalRegisteredFunctions[identifier] || registeredFunctions[identifier];
|
||||
if (result) {
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(`Could not find registered function with name '${identifier}'.`);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { MethodHandle, System_Object, System_String, System_Array, Pointer, Platform } from '../Platform';
|
||||
import { getAssemblyNameFromUrl } from '../DotNet';
|
||||
import { getRegisteredFunction } from '../../Interop/RegisteredFunction';
|
||||
|
||||
const assemblyHandleCache: { [assemblyName: string]: number } = {};
|
||||
const typeHandleCache: { [fullyQualifiedTypeName: string]: number } = {};
|
||||
|
|
@ -134,10 +133,6 @@ export const monoPlatform: Platform = {
|
|||
},
|
||||
};
|
||||
|
||||
// Bypass normal type checking to add this extra function. It's only intended to be called from
|
||||
// the JS code in Mono's driver.c. It's never intended to be called from TypeScript.
|
||||
(monoPlatform as any).monoGetRegisteredFunction = getRegisteredFunction;
|
||||
|
||||
function findAssembly(assemblyName: string): number {
|
||||
let assemblyHandle = assemblyHandleCache[assemblyName];
|
||||
if (!assemblyHandle) {
|
||||
|
|
@ -229,6 +224,7 @@ function createEmscriptenModuleInstance(loadAssemblyUrls: string[], onReady: ()
|
|||
module.postRun.push(() => {
|
||||
const load_runtime = Module.cwrap('mono_wasm_load_runtime', null, ['string']);
|
||||
load_runtime('appBinDir');
|
||||
attachInteropInvoker();
|
||||
onReady();
|
||||
});
|
||||
|
||||
|
|
@ -254,3 +250,30 @@ function asyncLoad(url, onload, onerror) {
|
|||
function getArrayDataPointer<T>(array: System_Array<T>): number {
|
||||
return <number><any>array + 12; // First byte from here is length, then following bytes are entries
|
||||
}
|
||||
|
||||
function attachInteropInvoker() {
|
||||
const dotNetDispatcherInvokeMethodHandle = findMethod('Microsoft.JSInterop', 'Microsoft.JSInterop', 'DotNetDispatcher', 'Invoke');
|
||||
const dotNetDispatcherBeginInvokeMethodHandle = findMethod('Microsoft.JSInterop', 'Microsoft.JSInterop', 'DotNetDispatcher', 'BeginInvoke');
|
||||
|
||||
DotNet.attachDispatcher({
|
||||
beginInvokeDotNetFromJS: (callId, assemblyName, methodIdentifier, argsJson) => {
|
||||
monoPlatform.callMethod(dotNetDispatcherBeginInvokeMethodHandle, null, [
|
||||
callId ? monoPlatform.toDotNetString(callId.toString()) : null,
|
||||
monoPlatform.toDotNetString(assemblyName),
|
||||
monoPlatform.toDotNetString(methodIdentifier),
|
||||
monoPlatform.toDotNetString(argsJson)
|
||||
]);
|
||||
},
|
||||
|
||||
invokeDotNetFromJS: (assemblyName, methodIdentifier, argsJson) => {
|
||||
const resultJsonStringPtr = monoPlatform.callMethod(dotNetDispatcherInvokeMethodHandle, null, [
|
||||
monoPlatform.toDotNetString(assemblyName),
|
||||
monoPlatform.toDotNetString(methodIdentifier),
|
||||
monoPlatform.toDotNetString(argsJson)
|
||||
]) as System_String;
|
||||
return resultJsonStringPtr
|
||||
? JSON.parse(monoPlatform.toJavaScriptString(resultJsonStringPtr))
|
||||
: null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
export function applyCaptureIdToElement(element: Element, referenceCaptureId: number) {
|
||||
export function applyCaptureIdToElement(element: Element, referenceCaptureId: number) {
|
||||
element.setAttribute(getCaptureIdAttributeName(referenceCaptureId), '');
|
||||
}
|
||||
|
||||
export function getElementByCaptureId(referenceCaptureId: number) {
|
||||
function getElementByCaptureId(referenceCaptureId: number) {
|
||||
const selector = `[${getCaptureIdAttributeName(referenceCaptureId)}]`;
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
function getCaptureIdAttributeName(referenceCaptureId: number) {
|
||||
return `_bl_${referenceCaptureId}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Support receiving ElementRef instances as args in interop calls
|
||||
const elementRefKey = '_blazorElementRef'; // Keep in sync with ElementRef.cs
|
||||
DotNet.attachReviver((key, value) => {
|
||||
if (value && typeof value === 'object' && value.hasOwnProperty(elementRefKey) && typeof value[elementRefKey] === 'number') {
|
||||
return getElementByCaptureId(value[elementRefKey]);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { System_Object, System_String, System_Array, MethodHandle, Pointer } from '../Platform/Platform';
|
||||
import { System_Object, System_String, System_Array, MethodHandle, Pointer } from '../Platform/Platform';
|
||||
import { platform } from '../Environment';
|
||||
import { renderBatch as renderBatchStruct, arrayRange, arraySegment, renderTreeDiffStructLength, renderTreeDiff, RenderBatchPointer, RenderTreeDiffPointer } from './RenderBatch';
|
||||
import { BrowserRenderer } from './BrowserRenderer';
|
||||
|
|
@ -6,11 +6,10 @@ import { BrowserRenderer } from './BrowserRenderer';
|
|||
type BrowserRendererRegistry = { [browserRendererId: number]: BrowserRenderer };
|
||||
const browserRenderers: BrowserRendererRegistry = {};
|
||||
|
||||
export function attachRootComponentToElement(browserRendererId: number, elementSelector: System_String, componentId: number) {
|
||||
const elementSelectorJs = platform.toJavaScriptString(elementSelector);
|
||||
const element = document.querySelector(elementSelectorJs);
|
||||
export function attachRootComponentToElement(browserRendererId: number, elementSelector: string, componentId: number) {
|
||||
const element = document.querySelector(elementSelector);
|
||||
if (!element) {
|
||||
throw new Error(`Could not find any element matching selector '${elementSelectorJs}'.`);
|
||||
throw new Error(`Could not find any element matching selector '${elementSelector}'.`);
|
||||
}
|
||||
|
||||
let browserRenderer = browserRenderers[browserRendererId];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { registerFunction } from '../Interop/RegisteredFunction';
|
||||
import { platform } from '../Environment';
|
||||
import { MethodHandle, System_String, System_Array } from '../Platform/Platform';
|
||||
const httpClientAssembly = 'Microsoft.AspNetCore.Blazor.Browser';
|
||||
|
|
@ -8,9 +7,10 @@ const httpClientFullTypeName = `${httpClientNamespace}.${httpClientTypeName}`;
|
|||
let receiveResponseMethod: MethodHandle;
|
||||
let allocateArrayMethod: MethodHandle;
|
||||
|
||||
registerFunction(`${httpClientFullTypeName}.Send`, (id: number, body: System_Array<any>, jsonFetchArgs: System_String) => {
|
||||
sendAsync(id, body, jsonFetchArgs);
|
||||
});
|
||||
// These are the functions we're making available for invocation from .NET
|
||||
export const internalFunctions = {
|
||||
sendAsync
|
||||
}
|
||||
|
||||
async function sendAsync(id: number, body: System_Array<any>, jsonFetchArgs: System_String) {
|
||||
let response: Response;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import { registerFunction } from '../Interop/RegisteredFunction';
|
||||
import { platform } from '../Environment';
|
||||
import { MethodHandle, System_String } from '../Platform/Platform';
|
||||
const registeredFunctionPrefix = 'Microsoft.AspNetCore.Blazor.Browser.Services.BrowserUriHelper';
|
||||
let notifyLocationChangedMethod: MethodHandle;
|
||||
let hasRegisteredEventListeners = false;
|
||||
|
||||
registerFunction(`${registeredFunctionPrefix}.getLocationHref`,
|
||||
() => platform.toDotNetString(location.href));
|
||||
// These are the functions we're making available for invocation from .NET
|
||||
export const internalFunctions = {
|
||||
enableNavigationInterception,
|
||||
navigateTo,
|
||||
getBaseURI: () => document.baseURI,
|
||||
getLocationHref: () => location.href,
|
||||
}
|
||||
|
||||
registerFunction(`${registeredFunctionPrefix}.getBaseURI`,
|
||||
() => document.baseURI ? platform.toDotNetString(document.baseURI) : null);
|
||||
|
||||
registerFunction(`${registeredFunctionPrefix}.enableNavigationInterception`, () => {
|
||||
function enableNavigationInterception() {
|
||||
if (hasRegisteredEventListeners) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -35,11 +36,7 @@ registerFunction(`${registeredFunctionPrefix}.enableNavigationInterception`, ()
|
|||
});
|
||||
|
||||
window.addEventListener('popstate', handleInternalNavigation);
|
||||
});
|
||||
|
||||
registerFunction(`${registeredFunctionPrefix}.navigateTo`, (uriDotNetString: System_String) => {
|
||||
navigateTo(platform.toJavaScriptString(uriDotNetString));
|
||||
});
|
||||
}
|
||||
|
||||
export function navigateTo(uri: string) {
|
||||
const absoluteUri = toAbsoluteUri(uri);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Browser.Interop;
|
||||
using Microsoft.JSInterop;
|
||||
using Mono.WebAssembly.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
@ -64,11 +65,18 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Http
|
|||
|
||||
options.RequestUri = request.RequestUri.ToString();
|
||||
|
||||
RegisteredFunction.InvokeUnmarshalled<int, byte[], string, object>(
|
||||
$"{typeof(BrowserHttpMessageHandler).FullName}.Send",
|
||||
id,
|
||||
request.Content == null ? null : await request.Content.ReadAsByteArrayAsync(),
|
||||
JsonUtil.Serialize(options));
|
||||
if (JSRuntime.Current is MonoWebAssemblyJSRuntime mono)
|
||||
{
|
||||
mono.InvokeUnmarshalled<int, byte[], string, object>(
|
||||
"Blazor._internal.http.sendAsync",
|
||||
id,
|
||||
request.Content == null ? null : await request.Content.ReadAsByteArrayAsync(),
|
||||
Json.Serialize(options));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("BrowserHttpMessageHandler only supports running under Mono WebAssembly.");
|
||||
}
|
||||
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
|
@ -98,7 +106,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Http
|
|||
}
|
||||
else
|
||||
{
|
||||
var responseDescriptor = JsonUtil.Deserialize<ResponseDescriptor>(responseDescriptorJson);
|
||||
var responseDescriptor = Json.Deserialize<ResponseDescriptor>(responseDescriptorJson);
|
||||
var responseContent = responseBodyData == null ? null : new ByteArrayContent(responseBodyData);
|
||||
var responseMessage = responseDescriptor.ToResponseMessage(responseContent);
|
||||
tcs.SetResult(responseMessage);
|
||||
|
|
|
|||
|
|
@ -1,260 +0,0 @@
|
|||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
// 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 });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur during an interop call from .NET to JavaScript.
|
||||
/// </summary>
|
||||
public class JavaScriptException : Exception
|
||||
{
|
||||
internal JavaScriptException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
// 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,179 +0,0 @@
|
|||
// 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.Threading.Tasks;
|
||||
using WebAssembly;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for invoking preregistered JavaScript functions from .NET code.
|
||||
/// </summary>
|
||||
public static class RegisteredFunction
|
||||
{
|
||||
/// <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 TRes Invoke<TRes>(string identifier, params object[] args)
|
||||
{
|
||||
// This is a low-perf convenience method that bypasses the need to deal with
|
||||
// .NET memory and data structures on the JS side
|
||||
var argsJson = new string[args.Length + 1];
|
||||
|
||||
argsJson[0] = identifier;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
argsJson[i + 1] = JsonUtil.Serialize(args[i]);
|
||||
}
|
||||
|
||||
var resultJson = InvokeUnmarshalled<string>("invokeWithJsonMarshalling", argsJson);
|
||||
|
||||
var result = JsonUtil.Deserialize<InvocationResult<TRes>>(resultJson);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return result.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JavaScriptException(result.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// Arguments and return values are marshalled via JSON serialization.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type. This type must be JSON deserializable.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="args">The arguments to pass, each of which must be JSON serializable.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public static Task<TRes> InvokeAsync<TRes>(string identifier, params object[] args)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<TRes>();
|
||||
var callbackId = Guid.NewGuid().ToString();
|
||||
var argsJson = new string[args.Length + 2];
|
||||
|
||||
argsJson[0] = identifier;
|
||||
argsJson[1] = callbackId;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
argsJson[i + 2] = JsonUtil.Serialize(args[i]);
|
||||
}
|
||||
|
||||
TaskCallbacks.Track(callbackId, new Action<string>(r =>
|
||||
{
|
||||
var res = JsonUtil.Deserialize<InvocationResult<TRes>>(r);
|
||||
TaskCallbacks.Untrack(callbackId);
|
||||
if (res.Succeeded)
|
||||
{
|
||||
tcs.SetResult(res.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.SetException(new JavaScriptException(res.Message));
|
||||
}
|
||||
}));
|
||||
|
||||
try
|
||||
{
|
||||
var result = Invoke<object>("invokeWithJsonMarshallingAsync", argsJson);
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
TaskCallbacks.Untrack(callbackId);
|
||||
throw;
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
///
|
||||
/// When using this overload, all arguments will be supplied as <see cref="System.Object" />
|
||||
/// references, meaning that any reference types will be boxed. If you are passing
|
||||
/// 3 or fewer arguments, it is preferable to instead call the overload that
|
||||
/// specifies generic type arguments for each argument.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="args">The arguments to pass, each of which will be supplied as a <see cref="System.Object" /> instance.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public static TRes InvokeUnmarshalled<TRes>(string identifier, params object[] args)
|
||||
{
|
||||
var result = Runtime.BlazorInvokeJSArray<TRes>(out var exception, identifier, args);
|
||||
return exception != null
|
||||
? throw new JavaScriptException(exception)
|
||||
: result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public static TRes InvokeUnmarshalled<TRes>(string identifier)
|
||||
=> InvokeUnmarshalled<object, object, object, TRes>(identifier, null, null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="T0">The type of the first argument.</typeparam>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="arg0">The first argument.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public static TRes InvokeUnmarshalled<T0, TRes>(string identifier, T0 arg0)
|
||||
=> InvokeUnmarshalled<T0, object, object, TRes>(identifier, arg0, null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="T0">The type of the first argument.</typeparam>
|
||||
/// <typeparam name="T1">The type of the second argument.</typeparam>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="arg0">The first argument.</param>
|
||||
/// <param name="arg1">The second argument.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public static TRes InvokeUnmarshalled<T0, T1, TRes>(string identifier, T0 arg0, T1 arg1)
|
||||
=> InvokeUnmarshalled<T0, T1, object, TRes>(identifier, arg0, arg1, null);
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the JavaScript function registered with the specified identifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="T0">The type of the first argument.</typeparam>
|
||||
/// <typeparam name="T1">The type of the second argument.</typeparam>
|
||||
/// <typeparam name="T2">The type of the third argument.</typeparam>
|
||||
/// <typeparam name="TRes">The .NET type corresponding to the function's return value type.</typeparam>
|
||||
/// <param name="identifier">The identifier used when registering the target function.</param>
|
||||
/// <param name="arg0">The first argument.</param>
|
||||
/// <param name="arg1">The second argument.</param>
|
||||
/// <param name="arg2">The third argument.</param>
|
||||
/// <returns>The result of the function invocation.</returns>
|
||||
public static TRes InvokeUnmarshalled<T0, T1, T2, TRes>(string identifier, T0 arg0, T1 arg1, T2 arg2)
|
||||
{
|
||||
var result = Runtime.BlazorInvokeJS<T0, T1, T2, TRes>(out var exception, identifier, arg0, arg1, arg2);
|
||||
return exception != null
|
||||
? throw new JavaScriptException(exception)
|
||||
: result;
|
||||
}
|
||||
}
|
||||
|
||||
internal class TaskCallback
|
||||
{
|
||||
public static void InvokeTaskCallback(string id, string result)
|
||||
{
|
||||
var callback = TaskCallbacks.Get(id);
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// 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.Runtime.CompilerServices;
|
||||
|
||||
namespace WebAssembly
|
||||
{
|
||||
internal static class Runtime
|
||||
{
|
||||
// The exact namespace, type, and method names must match the corresponding entry in
|
||||
// driver.c in the Mono distribution
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern TRes BlazorInvokeJS<T0, T1, T2, TRes>(out string exception, string funcName, T0 arg0, T1 arg1, T2 arg2);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern TRes BlazorInvokeJSArray<TRes>(out string exception, string funcName, params object[] args);
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Blazor\Microsoft.AspNetCore.Blazor.csproj" />
|
||||
<ProjectReference Include="..\mono\Mono.WebAssembly.Interop\Mono.WebAssembly.Interop.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Browser.Interop;
|
||||
using Microsoft.AspNetCore.Blazor.Browser.Services;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.Rendering;
|
||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||
using Microsoft.JSInterop;
|
||||
using Mono.WebAssembly.Interop;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
||||
|
|
@ -59,11 +59,18 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
|||
{
|
||||
var component = InstantiateComponent(componentType);
|
||||
var componentId = AssignComponentId(component);
|
||||
RegisteredFunction.InvokeUnmarshalled<int, string, int, object>(
|
||||
"attachRootComponentToElement",
|
||||
|
||||
// The only reason we're calling this synchronously is so that, if it throws,
|
||||
// we get the exception back *before* attempting the first UpdateDisplay
|
||||
// (otherwise the logged exception will come from UpdateDisplay instead of here)
|
||||
// When implementing support for out-of-process runtimes, we'll need to call this
|
||||
// asynchronously and ensure we surface any exceptions correctly.
|
||||
((IJSInProcessRuntime)JSRuntime.Current).Invoke<object>(
|
||||
"Blazor._internal.attachRootComponentToElement",
|
||||
_browserRendererId,
|
||||
domElementSelector,
|
||||
componentId);
|
||||
|
||||
component.SetParameters(ParameterCollection.Empty);
|
||||
}
|
||||
|
||||
|
|
@ -78,10 +85,19 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
|||
/// <inheritdoc />
|
||||
protected override void UpdateDisplay(in RenderBatch batch)
|
||||
{
|
||||
RegisteredFunction.InvokeUnmarshalled<int, RenderBatch, object>(
|
||||
"renderBatch",
|
||||
_browserRendererId,
|
||||
batch);
|
||||
if (JSRuntime.Current is MonoWebAssemblyJSRuntime mono)
|
||||
{
|
||||
mono.InvokeUnmarshalled<int, RenderBatch, object>(
|
||||
"Blazor._internal.renderBatch",
|
||||
_browserRendererId,
|
||||
batch);
|
||||
}
|
||||
else
|
||||
{
|
||||
// When implementing support for an out-of-process JS runtime, we'll need to
|
||||
// do something here to serialize and transmit the RenderBatch efficiently.
|
||||
throw new NotImplementedException("TODO: Support BrowserRenderer.UpdateDisplay on other runtimes.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Browser.Interop;
|
||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
||||
|
|
@ -19,7 +18,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
|||
// This can be simplified in the future when the Mono WASM runtime is enhanced.
|
||||
public static void DispatchEvent(string eventDescriptorJson, string eventArgsJson)
|
||||
{
|
||||
var eventDescriptor = JsonUtil.Deserialize<BrowserEventDescriptor>(eventDescriptorJson);
|
||||
var eventDescriptor = Json.Deserialize<BrowserEventDescriptor>(eventDescriptorJson);
|
||||
var eventArgs = ParseEventArgsJson(eventDescriptor.EventArgsType, eventArgsJson);
|
||||
var browserRenderer = BrowserRendererRegistry.Find(eventDescriptor.BrowserRendererId);
|
||||
browserRenderer.DispatchBrowserEvent(
|
||||
|
|
@ -33,29 +32,29 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Rendering
|
|||
switch (eventArgsType)
|
||||
{
|
||||
case "change":
|
||||
return JsonUtil.Deserialize<UIChangeEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIChangeEventArgs>(eventArgsJson);
|
||||
case "clipboard":
|
||||
return JsonUtil.Deserialize<UIClipboardEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIClipboardEventArgs>(eventArgsJson);
|
||||
case "drag":
|
||||
return JsonUtil.Deserialize<UIDragEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIDragEventArgs>(eventArgsJson);
|
||||
case "error":
|
||||
return JsonUtil.Deserialize<UIErrorEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIErrorEventArgs>(eventArgsJson);
|
||||
case "focus":
|
||||
return JsonUtil.Deserialize<UIFocusEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIFocusEventArgs>(eventArgsJson);
|
||||
case "keyboard":
|
||||
return JsonUtil.Deserialize<UIKeyboardEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIKeyboardEventArgs>(eventArgsJson);
|
||||
case "mouse":
|
||||
return JsonUtil.Deserialize<UIMouseEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIMouseEventArgs>(eventArgsJson);
|
||||
case "pointer":
|
||||
return JsonUtil.Deserialize<UIPointerEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIPointerEventArgs>(eventArgsJson);
|
||||
case "progress":
|
||||
return JsonUtil.Deserialize<UIProgressEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIProgressEventArgs>(eventArgsJson);
|
||||
case "touch":
|
||||
return JsonUtil.Deserialize<UITouchEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UITouchEventArgs>(eventArgsJson);
|
||||
case "unknown":
|
||||
return JsonUtil.Deserialize<UIEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIEventArgs>(eventArgsJson);
|
||||
case "wheel":
|
||||
return JsonUtil.Deserialize<UIWheelEventArgs>(eventArgsJson);
|
||||
return Json.Deserialize<UIWheelEventArgs>(eventArgsJson);
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported value '{eventArgsType}'.", nameof(eventArgsType));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 Microsoft.JSInterop;
|
||||
using Mono.WebAssembly.Interop;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Temporary mechanism for registering the Mono JS runtime. Developers do not need to
|
||||
/// use this directly, and it will be removed shortly.
|
||||
/// </summary>
|
||||
public class ActivateMonoJSRuntime
|
||||
{
|
||||
static object ensureActivatedReturnValue = new object();
|
||||
|
||||
static ActivateMonoJSRuntime()
|
||||
{
|
||||
// Temporarily enable MonoWebAssemblyJSRuntime on this class constructor
|
||||
// Later it will become part of the app startup and config mechanism
|
||||
JSRuntime.SetCurrentJSRuntime(new MonoWebAssemblyJSRuntime());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temporary mechanism for registering the Mono JS runtime. Developers do not need to
|
||||
/// use this directly, and it will be removed shortly.
|
||||
/// The return value is intended to be used with GC.KeepAlive purely as a way of ensuring
|
||||
/// the invocation doesn't get stripped out by the linker (if it's smart enough to do so).
|
||||
/// </summary>
|
||||
public static object EnsureActivated()
|
||||
=> ensureActivatedReturnValue;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,12 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
|||
{
|
||||
private readonly IServiceProvider _underlyingProvider;
|
||||
|
||||
static BrowserServiceProvider()
|
||||
{
|
||||
// TODO: Remove once we make this part of the app startup mechanism
|
||||
GC.KeepAlive(ActivateMonoJSRuntime.EnsureActivated());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="BrowserServiceProvider"/>.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Browser.Interop;
|
||||
using Microsoft.AspNetCore.Blazor.Services;
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
||||
|
|
@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
|||
// that's fine too - they will just share their internal state.
|
||||
// This class will never be used during server-side prerendering, so we don't have thread-
|
||||
// safety concerns due to the static state.
|
||||
static readonly string _functionPrefix = typeof(BrowserUriHelper).FullName;
|
||||
const string _functionPrefix = "Blazor._internal.uriHelper.";
|
||||
static bool _hasEnabledNavigationInterception;
|
||||
static string _cachedAbsoluteUri;
|
||||
static EventHandler<string> _onLocationChanged;
|
||||
|
|
@ -58,8 +58,14 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
|||
{
|
||||
if (_cachedAbsoluteUri == null)
|
||||
{
|
||||
var newUri = RegisteredFunction.InvokeUnmarshalled<string>(
|
||||
$"{_functionPrefix}.getLocationHref");
|
||||
// BrowserUriHelper is only intended for client-side (Mono) use, so it's OK
|
||||
// to rely on synchrony here. When we come to implement IUriHelper for
|
||||
// out-of-process cases, we can't use all the statics either, so this whole
|
||||
// service needs to be rebuilt. It will most likely require you to supply
|
||||
// the current URL and base href as constructor parameters so it has that
|
||||
// info synchronously.
|
||||
var newUri = ((IJSInProcessRuntime)JSRuntime.Current)
|
||||
.Invoke<string>(_functionPrefix + "getLocationHref");
|
||||
|
||||
if (_hasEnabledNavigationInterception)
|
||||
{
|
||||
|
|
@ -113,7 +119,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
|||
throw new ArgumentNullException(nameof(uri));
|
||||
}
|
||||
|
||||
RegisteredFunction.InvokeUnmarshalled<object>($"{_functionPrefix}.navigateTo", uri);
|
||||
JSRuntime.Current.InvokeAsync<object>(_functionPrefix + "navigateTo", uri);
|
||||
}
|
||||
|
||||
private static void EnsureBaseUriPopulated()
|
||||
|
|
@ -121,8 +127,11 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
|||
// The <base href> is fixed for the lifetime of the page, so just cache it
|
||||
if (_baseUriStringWithTrailingSlash == null)
|
||||
{
|
||||
var baseUriAbsolute = RegisteredFunction.InvokeUnmarshalled<string>(
|
||||
$"{_functionPrefix}.getBaseURI");
|
||||
// As described in other comment block above, BrowserUriHelper is only for
|
||||
// client -side (Mono) use, so it's OK to rely on synchrony here.
|
||||
var baseUriAbsolute = ((IJSInProcessRuntime)JSRuntime.Current)
|
||||
.Invoke<string>(_functionPrefix + "getBaseURI");
|
||||
|
||||
_baseUriStringWithTrailingSlash = ToBaseUri(baseUriAbsolute);
|
||||
_baseUriWithTrailingSlash = new Uri(_baseUriStringWithTrailingSlash);
|
||||
}
|
||||
|
|
@ -142,8 +151,7 @@ namespace Microsoft.AspNetCore.Blazor.Browser.Services
|
|||
if (!_hasEnabledNavigationInterception)
|
||||
{
|
||||
_hasEnabledNavigationInterception = true;
|
||||
RegisteredFunction.InvokeUnmarshalled<object>(
|
||||
$"{_functionPrefix}.enableNavigationInterception");
|
||||
JSRuntime.Current.InvokeAsync<object>(_functionPrefix + "enableNavigationInterception");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Json;
|
||||
using Microsoft.JSInterop.Internal;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor
|
||||
{
|
||||
// TODO: Once we no longer need the Razor base class hacks, rename this from 'JsonUtil'
|
||||
// to 'Json', because it's a better name. Currently we can't call it 'Json' because the
|
||||
// fake Razor base class already has a property called 'Json'.
|
||||
|
||||
/// <summary>
|
||||
/// Provides mechanisms for converting between .NET objects and JSON strings.
|
||||
/// </summary>
|
||||
[Obsolete("Use Microsoft.JSInterop.Json instead.")]
|
||||
public static class JsonUtil
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -17,8 +17,9 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <returns>The JSON string.</returns>
|
||||
[Obsolete("Use Microsoft.JSInterop.Json.Serialize instead.")]
|
||||
public static string Serialize(object value)
|
||||
=> SimpleJson.SimpleJson.SerializeObject(value);
|
||||
=> Json.Serialize(value);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the JSON string, creating an object of the specified generic type.
|
||||
|
|
@ -26,7 +27,8 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
/// <typeparam name="T">The type of object to create.</typeparam>
|
||||
/// <param name="json">The JSON string.</param>
|
||||
/// <returns>An object of the specified type.</returns>
|
||||
[Obsolete("Use Microsoft.JSInterop.Json.Deserialize<T> instead.")]
|
||||
public static T Deserialize<T>(string json)
|
||||
=> SimpleJson.SimpleJson.DeserializeObject<T>(json);
|
||||
=> Json.Deserialize<T>(json);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.JSInterop\Microsoft.JSInterop.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.JSInterop;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -23,7 +24,7 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string requestUri)
|
||||
{
|
||||
var responseJson = await httpClient.GetStringAsync(requestUri);
|
||||
return JsonUtil.Deserialize<T>(responseJson);
|
||||
return Json.Deserialize<T>(responseJson);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -94,7 +95,7 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
/// <returns>The response parsed as an object of the generic type.</returns>
|
||||
public static async Task<T> SendJsonAsync<T>(this HttpClient httpClient, HttpMethod method, string requestUri, object content)
|
||||
{
|
||||
var requestJson = JsonUtil.Serialize(content);
|
||||
var requestJson = Json.Serialize(content);
|
||||
var response = await httpClient.SendAsync(new HttpRequestMessage(method, requestUri)
|
||||
{
|
||||
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
|
||||
|
|
@ -107,7 +108,7 @@ namespace Microsoft.AspNetCore.Blazor
|
|||
else
|
||||
{
|
||||
var responseJson = await response.Content.ReadAsStringAsync();
|
||||
return JsonUtil.Deserialize<T>(responseJson);
|
||||
return Json.Deserialize<T>(responseJson);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
JavaScriptRuntime/dist/
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods that receive incoming calls from JS to .NET.
|
||||
/// </summary>
|
||||
public static class DotNetDispatcher
|
||||
{
|
||||
private static ConcurrentDictionary<string, IReadOnlyDictionary<string, (MethodInfo, Type[])>> _cachedMethodsByAssembly
|
||||
= new ConcurrentDictionary<string, IReadOnlyDictionary<string, (MethodInfo, Type[])>>();
|
||||
|
||||
/// <summary>
|
||||
/// Receives a call from JS to .NET, locating and invoking the specified method.
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
||||
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||
public static string Invoke(string assemblyName, string methodIdentifier, string argsJson)
|
||||
{
|
||||
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
||||
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
||||
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
||||
// because there would be nobody to police that. This method *is* the police.
|
||||
|
||||
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, argsJson);
|
||||
return syncResult == null ? null : Json.Serialize(syncResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives a call from JS to .NET, locating and invoking the specified method asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="callId">A value identifying the asynchronous call that should be passed back with the result, or null if no result notification is required.</param>
|
||||
/// <param name="assemblyName">The assembly containing the method to be invoked.</param>
|
||||
/// <param name="methodIdentifier">The identifier of the method to be invoked. The method must be annotated with a <see cref="JSInvokableAttribute"/> matching this identifier string.</param>
|
||||
/// <param name="argsJson">A JSON representation of the parameters.</param>
|
||||
/// <returns>A JSON representation of the return value, or null.</returns>
|
||||
public static void BeginInvoke(string callId, string assemblyName, string methodIdentifier, string argsJson)
|
||||
{
|
||||
// This method doesn't need [JSInvokable] because the platform is responsible for having
|
||||
// some way to dispatch calls here. The logic inside here is the thing that checks whether
|
||||
// the targeted method has [JSInvokable]. It is not itself subject to that restriction,
|
||||
// because there would be nobody to police that. This method *is* the police.
|
||||
|
||||
var syncResult = InvokeSynchronously(assemblyName, methodIdentifier, argsJson);
|
||||
|
||||
// If there was no callId, the caller does not want to be notified about the result
|
||||
if (callId != null)
|
||||
{
|
||||
// Invoke and coerce the result to a Task so the caller can use the same async API
|
||||
// for both synchronous and asynchronous methods
|
||||
var task = syncResult is Task syncResultTask ? syncResultTask : Task.FromResult(syncResult);
|
||||
task.ContinueWith(completedTask =>
|
||||
{
|
||||
// DotNetDispatcher only works with JSRuntimeBase instances.
|
||||
// If the developer wants to use a totally custom IJSRuntime, then their JS-side
|
||||
// code has to implement its own way of returning async results.
|
||||
var jsRuntimeBaseInstance = (JSRuntimeBase)JSRuntime.Current;
|
||||
|
||||
try
|
||||
{
|
||||
var result = TaskGenericsUtil.GetTaskResult(completedTask);
|
||||
jsRuntimeBaseInstance.EndInvokeDotNet(callId, true, result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex = UnwrapException(ex);
|
||||
jsRuntimeBaseInstance.EndInvokeDotNet(callId, false, ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static object InvokeSynchronously(string assemblyName, string methodIdentifier, string argsJson)
|
||||
{
|
||||
var (methodInfo, parameterTypes) = GetCachedMethodInfo(assemblyName, methodIdentifier);
|
||||
|
||||
// There's no direct way to say we want to deserialize as an array with heterogenous
|
||||
// entry types (e.g., [string, int, bool]), so we need to deserialize in two phases.
|
||||
// First we deserialize as object[], for which SimpleJson will supply JsonObject
|
||||
// instances for nonprimitive values.
|
||||
var suppliedArgs = (object[])null;
|
||||
var suppliedArgsLength = 0;
|
||||
if (argsJson != null)
|
||||
{
|
||||
suppliedArgs = Json.Deserialize<SimpleJson.JsonArray>(argsJson).ToArray<object>();
|
||||
suppliedArgsLength = suppliedArgs.Length;
|
||||
}
|
||||
if (suppliedArgsLength != parameterTypes.Length)
|
||||
{
|
||||
throw new ArgumentException($"In call to '{methodIdentifier}', expected {parameterTypes.Length} parameters but received {suppliedArgsLength}.");
|
||||
}
|
||||
|
||||
// Second, convert each supplied value to the type expected by the method
|
||||
var serializerStrategy = SimpleJson.SimpleJson.CurrentJsonSerializerStrategy;
|
||||
for (var i = 0; i < suppliedArgsLength; i++)
|
||||
{
|
||||
suppliedArgs[i] = serializerStrategy.DeserializeObject(
|
||||
suppliedArgs[i], parameterTypes[i]);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return methodInfo.Invoke(null, suppliedArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw UnwrapException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receives notification that a call from .NET to JS has finished, marking the
|
||||
/// associated <see cref="Task"/> as completed.
|
||||
/// </summary>
|
||||
/// <param name="asyncHandle">The identifier for the function invocation.</param>
|
||||
/// <param name="succeeded">A flag to indicate whether the invocation succeeded.</param>
|
||||
/// <param name="resultOrException">If <paramref name="succeeded"/> is <c>true</c>, specifies the invocation result. If <paramref name="succeeded"/> is <c>false</c>, gives the <see cref="Exception"/> corresponding to the invocation failure.</param>
|
||||
[JSInvokable(nameof(DotNetDispatcher) + "." + nameof(EndInvoke))]
|
||||
public static void EndInvoke(long asyncHandle, bool succeeded, object resultOrException)
|
||||
=> ((JSRuntimeBase)JSRuntime.Current).EndInvokeJS(asyncHandle, succeeded, resultOrException);
|
||||
|
||||
private static (MethodInfo, Type[]) GetCachedMethodInfo(string assemblyName, string methodIdentifier)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(assemblyName))
|
||||
{
|
||||
throw new ArgumentException("Cannot be null, empty, or whitespace.", nameof(assemblyName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(methodIdentifier))
|
||||
{
|
||||
throw new ArgumentException("Cannot be null, empty, or whitespace.", nameof(methodIdentifier));
|
||||
}
|
||||
|
||||
var assemblyMethods = _cachedMethodsByAssembly.GetOrAdd(assemblyName, ScanAssemblyForCallableMethods);
|
||||
if (assemblyMethods.TryGetValue(methodIdentifier, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"The assembly '{assemblyName}' does not contain a public method with [{nameof(JSInvokableAttribute)}(\"{methodIdentifier}\")].");
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, (MethodInfo, Type[])> ScanAssemblyForCallableMethods(string assemblyName)
|
||||
{
|
||||
// TODO: Consider looking first for assembly-level attributes (i.e., if there are any,
|
||||
// only use those) to avoid scanning, especially for framework assemblies.
|
||||
return GetRequiredLoadedAssembly(assemblyName)
|
||||
.GetExportedTypes()
|
||||
.SelectMany(type => type.GetMethods())
|
||||
.Where(method => method.IsDefined(typeof(JSInvokableAttribute), inherit: false))
|
||||
.ToDictionary(
|
||||
method => method.GetCustomAttribute<JSInvokableAttribute>(false).Identifier,
|
||||
method => (method, method.GetParameters().Select(p => p.ParameterType).ToArray())
|
||||
);
|
||||
}
|
||||
|
||||
private static Assembly GetRequiredLoadedAssembly(string assemblyName)
|
||||
{
|
||||
// We don't want to load assemblies on demand here, because we don't necessarily trust
|
||||
// "assemblyName" to be something the developer intended to load. So only pick from the
|
||||
// set of already-loaded assemblies.
|
||||
// In some edge cases this might force developers to explicitly call something on the
|
||||
// target assembly (from .NET) before they can invoke its allowed methods from JS.
|
||||
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
return loadedAssemblies.FirstOrDefault(a => a.GetName().Name.Equals(assemblyName, StringComparison.Ordinal))
|
||||
?? throw new ArgumentException($"There is no loaded assembly with the name '{assemblyName}'.");
|
||||
}
|
||||
|
||||
private static Exception UnwrapException(Exception ex)
|
||||
{
|
||||
while ((ex is AggregateException || ex is TargetInvocationException) && ex.InnerException != null)
|
||||
{
|
||||
ex = ex.InnerException;
|
||||
}
|
||||
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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 Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an instance of a JavaScript runtime to which calls may be dispatched.
|
||||
/// </summary>
|
||||
public interface IJSInProcessRuntime : IJSRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function synchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
T Invoke<T>(string identifier, params object[] args);
|
||||
}
|
||||
}
|
||||
|
|
@ -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.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an instance of a JavaScript runtime to which calls may be dispatched.
|
||||
/// </summary>
|
||||
public interface IJSRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
Task<T> InvokeAsync<T>(string identifier, params object[] args);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents errors that occur during an interop call from .NET to JavaScript.
|
||||
/// </summary>
|
||||
public class JSException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="JSException"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message.</param>
|
||||
public JSException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// 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 Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for an in-process JavaScript runtime.
|
||||
/// </summary>
|
||||
public abstract class JSInProcessRuntimeBase : JSRuntimeBase, IJSInProcessRuntime
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function synchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public T Invoke<T>(string identifier, params object[] args)
|
||||
{
|
||||
var resultJson = InvokeJS(identifier, Json.Serialize(args));
|
||||
return Json.Deserialize<T>(resultJson);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a synchronous function invocation.
|
||||
/// </summary>
|
||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||
/// <returns>A JSON representation of the result.</returns>
|
||||
protected abstract string InvokeJS(string identifier, string argsJson);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// 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.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Identifies a .NET method as allowing invocation from JavaScript code.
|
||||
/// Any method marked with this attribute may receive arbitrary parameter values
|
||||
/// from untrusted callers. All inputs should be validated carefully.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class JSInvokableAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the identifier for the method. The identifier must be unique within the scope
|
||||
/// of an assembly.
|
||||
/// </summary>
|
||||
public string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="JSInvokableAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="identifier">An identifier for the method, which must be unique within the scope of the assembly.</param>
|
||||
public JSInvokableAttribute(string identifier)
|
||||
{
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
{
|
||||
throw new ArgumentException("Cannot be null or empty", nameof(identifier));
|
||||
}
|
||||
|
||||
Identifier = identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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.Threading;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides mechanisms for accessing the current <see cref="IJSRuntime"/>.
|
||||
/// </summary>
|
||||
public static class JSRuntime
|
||||
{
|
||||
private static AsyncLocal<IJSRuntime> _currentJSRuntime
|
||||
= new AsyncLocal<IJSRuntime>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current <see cref="IJSRuntime"/>, if any.
|
||||
/// </summary>
|
||||
public static IJSRuntime Current => _currentJSRuntime.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current JS runtime to the supplied instance.
|
||||
///
|
||||
/// This is intended for framework use. Developers should not normally need to call this method.
|
||||
/// </summary>
|
||||
/// <param name="instance">The new current <see cref="IJSRuntime"/>.</param>
|
||||
public static void SetCurrentJSRuntime(IJSRuntime instance)
|
||||
{
|
||||
_currentJSRuntime.Value = instance
|
||||
?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
// 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.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract base class for a JavaScript runtime.
|
||||
/// </summary>
|
||||
public abstract class JSRuntimeBase : IJSRuntime
|
||||
{
|
||||
private long _nextPendingTaskId = 1; // Start at 1 because zero signals "no response needed"
|
||||
private readonly ConcurrentDictionary<long, object> _pendingTasks
|
||||
= new ConcurrentDictionary<long, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified JavaScript function asynchronously.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The JSON-serializable return type.</typeparam>
|
||||
/// <param name="identifier">An identifier for the function to invoke. For example, the value <code>"someScope.someFunction"</code> will invoke the function <code>window.someScope.someFunction</code>.</param>
|
||||
/// <param name="args">JSON-serializable arguments.</param>
|
||||
/// <returns>An instance of <typeparamref name="T"/> obtained by JSON-deserializing the return value.</returns>
|
||||
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
||||
{
|
||||
// We might consider also adding a default timeout here in case we don't want to
|
||||
// risk a memory leak in the scenario where the JS-side code is failing to complete
|
||||
// the operation.
|
||||
|
||||
var taskId = Interlocked.Increment(ref _nextPendingTaskId);
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
_pendingTasks[taskId] = tcs;
|
||||
|
||||
try
|
||||
{
|
||||
BeginInvokeJS(taskId, identifier, args?.Length > 0 ? Json.Serialize(args) : null);
|
||||
return tcs.Task;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_pendingTasks.TryRemove(taskId, out _);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begins an asynchronous function invocation.
|
||||
/// </summary>
|
||||
/// <param name="asyncHandle">The identifier for the function invocation, or zero if no async callback is required.</param>
|
||||
/// <param name="identifier">The identifier for the function to invoke.</param>
|
||||
/// <param name="argsJson">A JSON representation of the arguments.</param>
|
||||
protected abstract void BeginInvokeJS(long asyncHandle, string identifier, string argsJson);
|
||||
|
||||
internal void EndInvokeDotNet(string callId, bool success, object resultOrException)
|
||||
{
|
||||
// For failures, the common case is to call EndInvokeDotNet with the Exception object.
|
||||
// For these we'll serialize as something that's useful to receive on the JS side.
|
||||
// If the value is not an Exception, we'll just rely on it being directly JSON-serializable.
|
||||
if (!success && resultOrException is Exception)
|
||||
{
|
||||
resultOrException = resultOrException.ToString();
|
||||
}
|
||||
|
||||
// We pass 0 as the async handle because we don't want the JS-side code to
|
||||
// send back any notification (we're just providing a result for an existing async call)
|
||||
BeginInvokeJS(0, "DotNet.jsCallDispatcher.endInvokeDotNetFromJS", Json.Serialize(new[] {
|
||||
callId,
|
||||
success,
|
||||
resultOrException
|
||||
}));
|
||||
}
|
||||
|
||||
internal void EndInvokeJS(long asyncHandle, bool succeeded, object resultOrException)
|
||||
{
|
||||
if (!_pendingTasks.TryRemove(asyncHandle, out var tcs))
|
||||
{
|
||||
throw new ArgumentException($"There is no pending task with handle '{asyncHandle}'.");
|
||||
}
|
||||
|
||||
if (succeeded)
|
||||
{
|
||||
TaskGenericsUtil.SetTaskCompletionSourceResult(tcs, resultOrException);
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskGenericsUtil.SetTaskCompletionSourceException(tcs, new JSException(resultOrException.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
// This is a single-file self-contained module to avoid the need for a Webpack build
|
||||
|
||||
module DotNet {
|
||||
(window as any).DotNet = DotNet; // Ensure reachable from anywhere
|
||||
|
||||
export type JsonReviver = ((key: any, value: any) => any);
|
||||
const jsonRevivers: JsonReviver[] = [];
|
||||
|
||||
const pendingAsyncCalls: { [id: number]: PendingAsyncCall<any> } = {};
|
||||
const cachedJSFunctions: { [identifier: string]: Function } = {};
|
||||
let nextAsyncCallId = 1; // Start at 1 because zero signals "no response needed"
|
||||
|
||||
let dotNetDispatcher: DotNetCallDispatcher | null = null;
|
||||
|
||||
/**
|
||||
* Sets the specified .NET call dispatcher as the current instance so that it will be used
|
||||
* for future invocations.
|
||||
*
|
||||
* @param dispatcher An object that can dispatch calls from JavaScript to a .NET runtime.
|
||||
*/
|
||||
export function attachDispatcher(dispatcher: DotNetCallDispatcher) {
|
||||
dotNetDispatcher = dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a JSON reviver callback that will be used when parsing arguments received from .NET.
|
||||
* @param reviver The reviver to add.
|
||||
*/
|
||||
export function attachReviver(reviver: JsonReviver) {
|
||||
jsonRevivers.push(reviver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the specified .NET public method synchronously. Not all hosting scenarios support
|
||||
* synchronous invocation, so if possible use invokeMethodAsync instead.
|
||||
*
|
||||
* @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method.
|
||||
* @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
|
||||
* @param args Arguments to pass to the method, each of which must be JSON-serializable.
|
||||
* @returns The result of the operation.
|
||||
*/
|
||||
export function invokeMethod<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): T {
|
||||
const dispatcher = getRequiredDispatcher();
|
||||
if (dispatcher.invokeDotNetFromJS) {
|
||||
const argsJson = JSON.stringify(args);
|
||||
return dispatcher.invokeDotNetFromJS(assemblyName, methodIdentifier, argsJson);
|
||||
} else {
|
||||
throw new Error('The current dispatcher does not support synchronous calls from JS to .NET. Use invokeAsync instead.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the specified .NET public method asynchronously.
|
||||
*
|
||||
* @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method.
|
||||
* @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
|
||||
* @param args Arguments to pass to the method, each of which must be JSON-serializable.
|
||||
* @returns A promise representing the result of the operation.
|
||||
*/
|
||||
export function invokeMethodAsync<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise<T> {
|
||||
const asyncCallId = nextAsyncCallId++;
|
||||
const resultPromise = new Promise<T>((resolve, reject) => {
|
||||
pendingAsyncCalls[asyncCallId] = { resolve, reject };
|
||||
});
|
||||
|
||||
try {
|
||||
const argsJson = JSON.stringify(args);
|
||||
getRequiredDispatcher().beginInvokeDotNetFromJS(asyncCallId, assemblyName, methodIdentifier, argsJson);
|
||||
} catch(ex) {
|
||||
// Synchronous failure
|
||||
completePendingCall(asyncCallId, false, ex);
|
||||
}
|
||||
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
function getRequiredDispatcher(): DotNetCallDispatcher {
|
||||
if (dotNetDispatcher !== null) {
|
||||
return dotNetDispatcher;
|
||||
}
|
||||
|
||||
throw new Error('No .NET call dispatcher has been set.');
|
||||
}
|
||||
|
||||
function completePendingCall(asyncCallId: number, success: boolean, resultOrError: any) {
|
||||
if (!pendingAsyncCalls.hasOwnProperty(asyncCallId)) {
|
||||
throw new Error(`There is no pending async call with ID ${asyncCallId}.`);
|
||||
}
|
||||
|
||||
const asyncCall = pendingAsyncCalls[asyncCallId];
|
||||
delete pendingAsyncCalls[asyncCallId];
|
||||
if (success) {
|
||||
asyncCall.resolve(resultOrError);
|
||||
} else {
|
||||
asyncCall.reject(resultOrError);
|
||||
}
|
||||
}
|
||||
|
||||
interface PendingAsyncCall<T> {
|
||||
resolve: (value?: T | PromiseLike<T>) => void;
|
||||
reject: (reason?: any) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the ability to dispatch calls from JavaScript to a .NET runtime.
|
||||
*/
|
||||
export interface DotNetCallDispatcher {
|
||||
/**
|
||||
* Optional. If implemented, invoked by the runtime to perform a synchronous call to a .NET method.
|
||||
*
|
||||
* @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke.
|
||||
* @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
|
||||
* @param argsJson JSON representation of arguments to pass to the method.
|
||||
* @returns The result of the invocation.
|
||||
*/
|
||||
invokeDotNetFromJS?(assemblyName: string, methodIdentifier: string, argsJson: string): any;
|
||||
|
||||
/**
|
||||
* Invoked by the runtime to begin an asynchronous call to a .NET method.
|
||||
*
|
||||
* @param callId A value identifying the asynchronous operation. This value should be passed back in a later call from .NET to JS.
|
||||
* @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly holding the method to invoke.
|
||||
* @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
|
||||
* @param argsJson JSON representation of arguments to pass to the method.
|
||||
*/
|
||||
beginInvokeDotNetFromJS(callId: number, assemblyName: string, methodIdentifier: string, argsJson: string): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives incoming calls from .NET and dispatches them to JavaScript.
|
||||
*/
|
||||
export const jsCallDispatcher = {
|
||||
/**
|
||||
* Finds the JavaScript function matching the specified identifier.
|
||||
*
|
||||
* @param identifier Identifies the globally-reachable function to be returned.
|
||||
* @returns A Function instance.
|
||||
*/
|
||||
findJSFunction,
|
||||
|
||||
/**
|
||||
* Invokes the specified synchronous JavaScript function.
|
||||
*
|
||||
* @param identifier Identifies the globally-reachable function to invoke.
|
||||
* @param argsJson JSON representation of arguments to be passed to the function.
|
||||
* @returns JSON representation of the invocation result.
|
||||
*/
|
||||
invokeJSFromDotNet: (identifier: string, argsJson: string) => {
|
||||
const result = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson));
|
||||
return result === null || result === undefined
|
||||
? null
|
||||
: JSON.stringify(result);
|
||||
},
|
||||
|
||||
/**
|
||||
* Invokes the specified synchronous or asynchronous JavaScript function.
|
||||
*
|
||||
* @param asyncHandle A value identifying the asynchronous operation. This value will be passed back in a later call to endInvokeJSFromDotNet.
|
||||
* @param identifier Identifies the globally-reachable function to invoke.
|
||||
* @param argsJson JSON representation of arguments to be passed to the function.
|
||||
*/
|
||||
beginInvokeJSFromDotNet: (asyncHandle: number, identifier: string, argsJson: string): void => {
|
||||
// Coerce synchronous functions into async ones, plus treat
|
||||
// synchronous exceptions the same as async ones
|
||||
const promise = new Promise<any>(resolve => {
|
||||
const synchronousResultOrPromise = findJSFunction(identifier).apply(null, parseJsonWithRevivers(argsJson));
|
||||
resolve(synchronousResultOrPromise);
|
||||
});
|
||||
|
||||
// We only listen for a result if the caller wants to be notified about it
|
||||
if (asyncHandle) {
|
||||
// On completion, dispatch result back to .NET
|
||||
// Not using "await" because it codegens a lot of boilerplate
|
||||
promise.then(
|
||||
result => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', JSON.stringify([asyncHandle, true, result])),
|
||||
error => getRequiredDispatcher().beginInvokeDotNetFromJS(0, 'Microsoft.JSInterop', 'DotNetDispatcher.EndInvoke', JSON.stringify([asyncHandle, false, formatError(error)]))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Receives notification that an async call from JS to .NET has completed.
|
||||
* @param asyncCallId The identifier supplied in an earlier call to beginInvokeDotNetFromJS.
|
||||
* @param success A flag to indicate whether the operation completed successfully.
|
||||
* @param resultOrExceptionMessage Either the operation result or an error message.
|
||||
*/
|
||||
endInvokeDotNetFromJS: (asyncCallId: string, success: boolean, resultOrExceptionMessage: any): void => {
|
||||
const resultOrError = success ? resultOrExceptionMessage : new Error(resultOrExceptionMessage);
|
||||
completePendingCall(parseInt(asyncCallId), success, resultOrError);
|
||||
}
|
||||
}
|
||||
|
||||
function parseJsonWithRevivers(json: string): any {
|
||||
return json ? JSON.parse(json, (key, initialValue) => {
|
||||
// Invoke each reviver in order, passing the output from the previous reviver,
|
||||
// so that each one gets a chance to transform the value
|
||||
return jsonRevivers.reduce(
|
||||
(latestValue, reviver) => reviver(key, latestValue),
|
||||
initialValue
|
||||
);
|
||||
}) : null;
|
||||
}
|
||||
|
||||
function formatError(error: any): string {
|
||||
if (error instanceof Error) {
|
||||
return `${error.message}\n${error.stack}`;
|
||||
} else {
|
||||
return error ? error.toString() : 'null';
|
||||
}
|
||||
}
|
||||
|
||||
function findJSFunction(identifier: string): Function {
|
||||
if (cachedJSFunctions.hasOwnProperty(identifier)) {
|
||||
return cachedJSFunctions[identifier];
|
||||
}
|
||||
|
||||
let result: any = window;
|
||||
let resultIdentifier = 'window';
|
||||
identifier.split('.').forEach(segment => {
|
||||
if (segment in result) {
|
||||
result = result[segment];
|
||||
resultIdentifier += '.' + segment;
|
||||
} else {
|
||||
throw new Error(`Could not find '${segment}' in '${resultIdentifier}'.`);
|
||||
}
|
||||
});
|
||||
|
||||
if (result instanceof Function) {
|
||||
return result;
|
||||
} else {
|
||||
throw new Error(`The value '${resultIdentifier}' is not a function.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Json
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
internal static class CamelCase
|
||||
{
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Json
|
||||
namespace Microsoft.JSInterop.Internal
|
||||
{
|
||||
// This is internal because we're trying to avoid expanding JsonUtil into a sophisticated
|
||||
// This is "soft" internal because we're trying to avoid expanding JsonUtil into a sophisticated
|
||||
// API. Developers who want that would be better served by using a different JSON package
|
||||
// instead. Also the perf implications of the ICustomJsonSerializer approach aren't ideal
|
||||
// (it forces structs to be boxed, and returning a dictionary means lots more allocations
|
||||
// and boxing of any value-typed properties).
|
||||
internal interface ICustomJsonSerializer
|
||||
|
||||
/// <summary>
|
||||
/// Internal. Intended for framework use only.
|
||||
/// </summary>
|
||||
public interface ICustomJsonSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Supplies a representation suitable for JSON serialization. For example, the
|
||||
/// return value may be a string->object dictionary containing properties to
|
||||
/// serialize, or simply a string.
|
||||
/// Internal. Intended for framework use only.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
object ToJsonPrimitive();
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides mechanisms for converting between .NET objects and JSON strings for use
|
||||
/// when making calls to JavaScript functions via <see cref="IJSRuntime"/>.
|
||||
///
|
||||
/// Warning: This is not intended as a general-purpose JSON library. It is only intended
|
||||
/// for use when making calls via <see cref="IJSRuntime"/>. Eventually its implementation
|
||||
/// will be replaced by something more general-purpose.
|
||||
/// </summary>
|
||||
public static class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes the value as a JSON string.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to serialize.</param>
|
||||
/// <returns>The JSON string.</returns>
|
||||
public static string Serialize(object value)
|
||||
=> SimpleJson.SimpleJson.SerializeObject(value);
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the JSON string, creating an object of the specified generic type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of object to create.</typeparam>
|
||||
/// <param name="json">The JSON string.</param>
|
||||
/// <returns>An object of the specified type.</returns>
|
||||
public static T Deserialize<T>(string json)
|
||||
=> SimpleJson.SimpleJson.DeserializeObject<T>(json);
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +66,8 @@ using System.Globalization;
|
|||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Blazor.Json;
|
||||
using Microsoft.JSInterop;
|
||||
using Microsoft.JSInterop.Internal;
|
||||
using SimpleJson.Reflection;
|
||||
|
||||
// ReSharper disable LoopCanBeConvertedToQuery
|
||||
|
|
@ -926,18 +927,18 @@ namespace SimpleJson
|
|||
|
||||
static void EatWhitespace(char[] json, ref int index)
|
||||
{
|
||||
for (; index < json.Length; index++) {
|
||||
switch (json[index]) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\b':
|
||||
case '\f':
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
for (; index < json.Length; index++) {
|
||||
switch (json[index]) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\b':
|
||||
case '\f':
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="2.9.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.JSInterop.Test")]
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
// 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;
|
||||
|
||||
namespace Microsoft.JSInterop
|
||||
{
|
||||
internal static class TaskGenericsUtil
|
||||
{
|
||||
private static ConcurrentDictionary<Type, ITaskResultGetter> _cachedResultGetters
|
||||
= new ConcurrentDictionary<Type, ITaskResultGetter>();
|
||||
|
||||
private static ConcurrentDictionary<Type, ITcsResultSetter> _cachedResultSetters
|
||||
= new ConcurrentDictionary<Type, ITcsResultSetter>();
|
||||
|
||||
public static void SetTaskCompletionSourceResult(object taskCompletionSource, object result)
|
||||
=> CreateResultSetter(taskCompletionSource).SetResult(taskCompletionSource, result);
|
||||
|
||||
public static void SetTaskCompletionSourceException(object taskCompletionSource, Exception exception)
|
||||
=> CreateResultSetter(taskCompletionSource).SetException(taskCompletionSource, exception);
|
||||
|
||||
public static object GetTaskResult(Task task)
|
||||
{
|
||||
var getter = _cachedResultGetters.GetOrAdd(task.GetType(), taskType =>
|
||||
{
|
||||
if (taskType.IsGenericType)
|
||||
{
|
||||
var resultType = taskType.GetGenericArguments().Single();
|
||||
return (ITaskResultGetter)Activator.CreateInstance(
|
||||
typeof(TaskResultGetter<>).MakeGenericType(resultType));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new VoidTaskResultGetter();
|
||||
}
|
||||
});
|
||||
return getter.GetResult(task);
|
||||
}
|
||||
|
||||
interface ITcsResultSetter
|
||||
{
|
||||
void SetResult(object taskCompletionSource, object result);
|
||||
void SetException(object taskCompletionSource, Exception exception);
|
||||
}
|
||||
|
||||
private interface ITaskResultGetter
|
||||
{
|
||||
object GetResult(Task task);
|
||||
}
|
||||
|
||||
private class TaskResultGetter<T> : ITaskResultGetter
|
||||
{
|
||||
public object GetResult(Task task) => ((Task<T>)task).Result;
|
||||
}
|
||||
|
||||
private class VoidTaskResultGetter : ITaskResultGetter
|
||||
{
|
||||
public object GetResult(Task task)
|
||||
{
|
||||
task.Wait(); // Throw if the task failed
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class TcsResultSetter<T> : ITcsResultSetter
|
||||
{
|
||||
public void SetResult(object tcs, object result)
|
||||
{
|
||||
var typedTcs = (TaskCompletionSource<T>)tcs;
|
||||
typedTcs.SetResult((T)result);
|
||||
}
|
||||
|
||||
public void SetException(object tcs, Exception exception)
|
||||
{
|
||||
var typedTcs = (TaskCompletionSource<T>)tcs;
|
||||
typedTcs.SetException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static ITcsResultSetter CreateResultSetter(object taskCompletionSource)
|
||||
{
|
||||
return _cachedResultSetters.GetOrAdd(taskCompletionSource.GetType(), tcsType =>
|
||||
{
|
||||
var resultType = tcsType.GetGenericArguments().Single();
|
||||
return (ITcsResultSetter)Activator.CreateInstance(
|
||||
typeof(TcsResultSetter<>).MakeGenericType(resultType));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"noEmitOnError": true,
|
||||
"removeComments": false,
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"lib": ["es2015", "dom", "es2015.promise"],
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"outDir": "JavaScriptRuntime/dist"
|
||||
},
|
||||
"include": [
|
||||
"JavaScriptRuntime/src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"JavaScriptRuntime/dist/**"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,429 +0,0 @@
|
|||
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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
|
@ -26,7 +26,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
"Microsoft.AspNetCore.Blazor.Browser.dll",
|
||||
"Microsoft.AspNetCore.Blazor.dll",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"Microsoft.Extensions.DependencyInjection.dll"
|
||||
"Microsoft.Extensions.DependencyInjection.dll",
|
||||
"Microsoft.JSInterop.dll",
|
||||
"Mono.WebAssembly.Interop.dll",
|
||||
}.Select(a => hintPaths.Single(p => Path.GetFileName(p) == a))
|
||||
.ToArray();
|
||||
|
||||
|
|
@ -59,7 +61,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
"Microsoft.AspNetCore.Blazor.dll",
|
||||
"Microsoft.Extensions.DependencyInjection.Abstractions.dll",
|
||||
"Microsoft.Extensions.DependencyInjection.dll",
|
||||
"Microsoft.JSInterop.dll",
|
||||
"Mono.Security.dll",
|
||||
"Mono.WebAssembly.Interop.dll",
|
||||
"mscorlib.dll",
|
||||
"netstandard.dll",
|
||||
"StandaloneApp.dll",
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@ namespace Microsoft.AspNetCore.Blazor.E2ETest.Tests
|
|||
["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!""",
|
||||
["ThrowException"] = @"""System.InvalidOperationException: Threw an exception!",
|
||||
["AsyncThrowSyncException"] = @"""System.InvalidOperationException: Threw a sync exception!",
|
||||
["AsyncThrowAsyncException"] = @"""System.InvalidOperationException: Threw an async exception!",
|
||||
["ExceptionFromSyncMethod"] = "Function threw an exception!",
|
||||
["SyncExceptionFromAsyncMethod"] = "Function threw a sync exception!",
|
||||
["AsyncExceptionFromAsyncMethod"] = "Function threw an async exception!",
|
||||
|
|
|
|||
|
|
@ -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.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Test
|
||||
{
|
||||
public class DotNetDispatcherTest
|
||||
{
|
||||
private readonly static string thisAssemblyName
|
||||
= typeof(DotNetDispatcherTest).Assembly.GetName().Name;
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeWithEmptyAssemblyName()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(" ", "SomeMethod", "[]");
|
||||
});
|
||||
|
||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||
Assert.Equal("assemblyName", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeWithEmptyMethodIdentifier()
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke("SomeAssembly", " ", "[]");
|
||||
});
|
||||
|
||||
Assert.StartsWith("Cannot be null, empty, or whitespace.", ex.Message);
|
||||
Assert.Equal("methodIdentifier", ex.ParamName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeMethodsOnUnloadedAssembly()
|
||||
{
|
||||
var assemblyName = "Some.Fake.Assembly";
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(assemblyName, "SomeMethod", null);
|
||||
});
|
||||
|
||||
Assert.Equal($"There is no loaded assembly with the name '{assemblyName}'.", ex.Message);
|
||||
}
|
||||
|
||||
// Note: Currently it's also not possible to invoke instance or generic methods.
|
||||
// That's not something determined by DotNetDispatcher, but rather by the fact that we
|
||||
// don't pass any 'target' or close over the generics in the reflection code.
|
||||
// Not defining this behavior through unit tests because the default outcome is
|
||||
// fine (an exception stating what info is missing), plus we're likely to add support
|
||||
// for invoking instance methods in the near future.
|
||||
|
||||
[Theory]
|
||||
[InlineData("MethodOnInternalType")]
|
||||
[InlineData("PrivateMethod")]
|
||||
[InlineData("ProtectedMethod")]
|
||||
[InlineData("MethodWithoutAttribute")] // That's not really its identifier; just making the point that there's no way to invoke it
|
||||
public void CannotInvokeUnsuitableMethods(string methodIdentifier)
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(thisAssemblyName, methodIdentifier, null);
|
||||
});
|
||||
|
||||
Assert.Equal($"The assembly '{thisAssemblyName}' does not contain a public method with [JSInvokableAttribute(\"{methodIdentifier}\")].", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeStaticVoidMethod()
|
||||
{
|
||||
// Arrange/Act
|
||||
SomePublicType.DidInvokeMyInvocableVoid = false;
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", null);
|
||||
|
||||
// Assert
|
||||
Assert.Null(resultJson);
|
||||
Assert.True(SomePublicType.DidInvokeMyInvocableVoid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeStaticNonVoidMethod()
|
||||
{
|
||||
// Arrange/Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticNonVoid", null);
|
||||
var result = Json.Deserialize<TestDTO>(resultJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Test", result.StringVal);
|
||||
Assert.Equal(123, result.IntVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanInvokeStaticWithParams()
|
||||
{
|
||||
// Arrange
|
||||
var argsJson = Json.Serialize(new object[] {
|
||||
new TestDTO { StringVal = "Another string", IntVal = 456 },
|
||||
new[] { 100, 200 }
|
||||
});
|
||||
|
||||
// Act
|
||||
var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", argsJson);
|
||||
var result = Json.Deserialize<TestDTO>(resultJson);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ANOTHER STRING", result.StringVal);
|
||||
Assert.Equal(756, result.IntVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotInvokeWithIncorrectNumberOfParams()
|
||||
{
|
||||
// Arrange
|
||||
var argsJson = Json.Serialize(new object[] { 1, 2, 3, 4 });
|
||||
|
||||
// Act/Assert
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticWithParams", argsJson);
|
||||
});
|
||||
|
||||
Assert.Equal("In call to 'InvocableStaticWithParams', expected 2 parameters but received 4.", ex.Message);
|
||||
}
|
||||
|
||||
internal class SomeInteralType
|
||||
{
|
||||
[JSInvokable("MethodOnInternalType")] public void MyMethod() { }
|
||||
}
|
||||
|
||||
public class SomePublicType
|
||||
{
|
||||
public static bool DidInvokeMyInvocableVoid;
|
||||
|
||||
[JSInvokable("PrivateMethod")] private void MyPrivateMethod() { }
|
||||
[JSInvokable("ProtectedMethod")] protected void MyProtectedMethod() { }
|
||||
protected void MethodWithoutAttribute() { }
|
||||
|
||||
[JSInvokable("InvocableStaticVoid")] public static void MyInvocableVoid()
|
||||
{
|
||||
DidInvokeMyInvocableVoid = true;
|
||||
}
|
||||
|
||||
[JSInvokable("InvocableStaticNonVoid")]
|
||||
public static object MyInvocableNonVoid()
|
||||
=> new TestDTO { StringVal = "Test", IntVal = 123 };
|
||||
|
||||
[JSInvokable("InvocableStaticWithParams")]
|
||||
public static TestDTO MyInvocableWithParams(TestDTO dto, int[] incrementAmounts)
|
||||
=> new TestDTO { StringVal = dto.StringVal.ToUpperInvariant(), IntVal = dto.IntVal + incrementAmounts.Sum() };
|
||||
}
|
||||
|
||||
public class TestDTO
|
||||
{
|
||||
public string StringVal { get; set; }
|
||||
public int IntVal { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
// 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 Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Test
|
||||
{
|
||||
public class JSRuntimeBaseTest
|
||||
{
|
||||
[Fact]
|
||||
public void DispatchesAsyncCallsWithDistinctAsyncHandles()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Act
|
||||
runtime.InvokeAsync<object>("test identifier 1", "arg1", 123, true );
|
||||
runtime.InvokeAsync<object>("test identifier 2", "some other arg");
|
||||
|
||||
// Assert
|
||||
Assert.Collection(runtime.BeginInvokeCalls,
|
||||
call =>
|
||||
{
|
||||
Assert.Equal("test identifier 1", call.Identifier);
|
||||
Assert.Equal("[\"arg1\",123,true]", call.ArgsJson);
|
||||
},
|
||||
call =>
|
||||
{
|
||||
Assert.Equal("test identifier 2", call.Identifier);
|
||||
Assert.Equal("[\"some other arg\"]", call.ArgsJson);
|
||||
Assert.NotEqual(runtime.BeginInvokeCalls[0].AsyncHandle, call.AsyncHandle);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCompleteAsyncCallsAsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Act/Assert: Tasks not initially completed
|
||||
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
||||
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||
Assert.False(unrelatedTask.IsCompleted);
|
||||
Assert.False(task.IsCompleted);
|
||||
|
||||
// Act/Assert: Task can be completed
|
||||
runtime.OnEndInvoke(
|
||||
runtime.BeginInvokeCalls[1].AsyncHandle,
|
||||
/* succeeded: */ true,
|
||||
"my result");
|
||||
Assert.False(unrelatedTask.IsCompleted);
|
||||
Assert.True(task.IsCompleted);
|
||||
Assert.Equal("my result", task.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanCompleteAsyncCallsAsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Act/Assert: Tasks not initially completed
|
||||
var unrelatedTask = runtime.InvokeAsync<string>("unrelated call", Array.Empty<object>());
|
||||
var task = runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||
Assert.False(unrelatedTask.IsCompleted);
|
||||
Assert.False(task.IsCompleted);
|
||||
|
||||
// Act/Assert: Task can be failed
|
||||
runtime.OnEndInvoke(
|
||||
runtime.BeginInvokeCalls[1].AsyncHandle,
|
||||
/* succeeded: */ false,
|
||||
"This is a test exception");
|
||||
Assert.False(unrelatedTask.IsCompleted);
|
||||
Assert.True(task.IsCompleted);
|
||||
|
||||
Assert.IsType<AggregateException>(task.Exception);
|
||||
Assert.IsType<JSException>(task.Exception.InnerException);
|
||||
Assert.Equal("This is a test exception", ((JSException)task.Exception.InnerException).Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CannotCompleteSameAsyncCallMoreThanOnce()
|
||||
{
|
||||
// Arrange
|
||||
var runtime = new TestJSRuntime();
|
||||
|
||||
// Act/Assert
|
||||
runtime.InvokeAsync<string>("test identifier", Array.Empty<object>());
|
||||
var asyncHandle = runtime.BeginInvokeCalls[0].AsyncHandle;
|
||||
runtime.OnEndInvoke(asyncHandle, true, null);
|
||||
var ex = Assert.Throws<ArgumentException>(() =>
|
||||
{
|
||||
// Second "end invoke" will fail
|
||||
runtime.OnEndInvoke(asyncHandle, true, null);
|
||||
});
|
||||
Assert.Equal($"There is no pending task with handle '{asyncHandle}'.", ex.Message);
|
||||
}
|
||||
|
||||
class TestJSRuntime : JSRuntimeBase
|
||||
{
|
||||
public List<BeginInvokeAsyncArgs> BeginInvokeCalls = new List<BeginInvokeAsyncArgs>();
|
||||
|
||||
public class BeginInvokeAsyncArgs
|
||||
{
|
||||
public long AsyncHandle { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public string ArgsJson { get; set; }
|
||||
}
|
||||
|
||||
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson)
|
||||
{
|
||||
BeginInvokeCalls.Add(new BeginInvokeAsyncArgs
|
||||
{
|
||||
AsyncHandle = asyncHandle,
|
||||
Identifier = identifier,
|
||||
ArgsJson = argsJson,
|
||||
});
|
||||
}
|
||||
|
||||
public void OnEndInvoke(long asyncHandle, bool succeeded, object resultOrException)
|
||||
=> EndInvokeJS(asyncHandle, succeeded, resultOrException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.JSInterop.Test
|
||||
{
|
||||
public class JSRuntimeTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task CanHaveDistinctJSRuntimeInstancesInEachAsyncContext()
|
||||
{
|
||||
var tasks = Enumerable.Range(0, 20).Select(async _ =>
|
||||
{
|
||||
var jsRuntime = new FakeJSRuntime();
|
||||
JSRuntime.SetCurrentJSRuntime(jsRuntime);
|
||||
await Task.Delay(50).ConfigureAwait(false);
|
||||
Assert.Same(jsRuntime, JSRuntime.Current);
|
||||
});
|
||||
|
||||
await Task.WhenAll(tasks);
|
||||
Assert.Null(JSRuntime.Current);
|
||||
}
|
||||
|
||||
private class FakeJSRuntime : IJSRuntime
|
||||
{
|
||||
public Task<T> InvokeAsync<T>(string identifier, params object[] args)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Blazor.Json;
|
||||
using Microsoft.JSInterop.Internal;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Test
|
||||
namespace Microsoft.JSInterop.Test
|
||||
{
|
||||
public class JsonUtilTest
|
||||
{
|
||||
|
|
@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
[InlineData(true, "true")]
|
||||
public void CanSerializePrimitivesToJson(object value, string expectedJson)
|
||||
{
|
||||
Assert.Equal(expectedJson, JsonUtil.Serialize(value));
|
||||
Assert.Equal(expectedJson, Json.Serialize(value));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
[InlineData("true", true)]
|
||||
public void CanDeserializePrimitivesFromJson(string json, object expectedValue)
|
||||
{
|
||||
Assert.Equal(expectedValue, JsonUtil.Deserialize<object>(json));
|
||||
Assert.Equal(expectedValue, Json.Deserialize<object>(json));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
// Act/Assert
|
||||
Assert.Equal(
|
||||
"{\"id\":1844,\"name\":\"Athos\",\"pets\":[\"Aramis\",\"Porthos\",\"D'Artagnan\"],\"hobby\":2,\"nicknames\":[\"Comte de la Fère\",\"Armand\"],\"birthInstant\":\"1825-08-06T18:45:21.0000000-06:00\",\"age\":\"7665.01:30:00\",\"allergies\":{\"Ducks\":true,\"Geese\":false}}",
|
||||
JsonUtil.Serialize(person));
|
||||
Json.Serialize(person));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var json = "{\"id\":1844,\"name\":\"Athos\",\"pets\":[\"Aramis\",\"Porthos\",\"D'Artagnan\"],\"hobby\":2,\"nicknames\":[\"Comte de la Fère\",\"Armand\"],\"birthInstant\":\"1825-08-06T18:45:21.0000000-06:00\",\"age\":\"7665.01:30:00\",\"allergies\":{\"Ducks\":true,\"Geese\":false}}";
|
||||
|
||||
// Act
|
||||
var person = JsonUtil.Deserialize<Person>(json);
|
||||
var person = Json.Deserialize<Person>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1844, person.Id);
|
||||
|
|
@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var json = "{\"ID\":1844,\"NamE\":\"Athos\"}";
|
||||
|
||||
// Act
|
||||
var person = JsonUtil.Deserialize<Person>(json);
|
||||
var person = Json.Deserialize<Person>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1844, person.Id);
|
||||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var json = "{\"member1\":\"Hello\"}";
|
||||
|
||||
// Act
|
||||
var person = JsonUtil.Deserialize<PrefersPropertiesOverFields>(json);
|
||||
var person = Json.Deserialize<PrefersPropertiesOverFields>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Hello", person.Member1);
|
||||
|
|
@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
};
|
||||
|
||||
// Act
|
||||
var result = JsonUtil.Serialize(commandResult);
|
||||
var result = Json.Serialize(commandResult);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}", result);
|
||||
|
|
@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
var json = "{\"stringProperty\":\"Test\",\"boolProperty\":true,\"nullableIntProperty\":1}";
|
||||
|
||||
//Act
|
||||
var simpleError = JsonUtil.Deserialize<SimpleStruct>(json);
|
||||
var simpleError = Json.Deserialize<SimpleStruct>(json);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Test", simpleError.StringProperty);
|
||||
|
|
@ -149,7 +149,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
{
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
JsonUtil.Deserialize<ClashingProperties>("{}");
|
||||
Json.Deserialize<ClashingProperties>("{}");
|
||||
});
|
||||
|
||||
Assert.Equal($"The type '{typeof(ClashingProperties).FullName}' contains multiple public properties " +
|
||||
|
|
@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
{
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
JsonUtil.Deserialize<ClashingFields>("{}");
|
||||
Json.Deserialize<ClashingFields>("{}");
|
||||
});
|
||||
|
||||
Assert.Equal($"The type '{typeof(ClashingFields).FullName}' contains multiple public fields " +
|
||||
|
|
@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
JsonUtil.Deserialize<NonEmptyConstructorPoco>(json);
|
||||
Json.Deserialize<NonEmptyConstructorPoco>(json);
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
|
@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Blazor.Test
|
|||
public void SupportsInternalCustomSerializer()
|
||||
{
|
||||
// Arrange/Act
|
||||
var json = JsonUtil.Serialize(new WithCustomSerializer());
|
||||
var json = Json.Serialize(new WithCustomSerializer());
|
||||
|
||||
// Asssert
|
||||
Assert.Equal("{\"key1\":\"value1\",\"key2\":123}", json);
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.JSInterop\Microsoft.JSInterop.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
@using Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
@using Microsoft.JSInterop
|
||||
|
||||
<input ref="myInput" value="Value set during render" />
|
||||
|
||||
|
|
@ -7,6 +7,6 @@
|
|||
|
||||
protected override void OnAfterRender()
|
||||
{
|
||||
RegisteredFunction.Invoke<object>("setElementValue", myInput, "Value set after render");
|
||||
JSRuntime.Current.InvokeAsync<object>("setElementValue", myInput, "Value set after render");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@using Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
@using Microsoft.JSInterop
|
||||
|
||||
<h1>Element capture</h1>
|
||||
|
||||
|
|
@ -29,8 +29,8 @@
|
|||
bool _toggleCapturedElementPresence = true;
|
||||
ElementRef _myInput;
|
||||
|
||||
void MakeInteropCall()
|
||||
async Task MakeInteropCall()
|
||||
{
|
||||
RegisteredFunction.Invoke<object>("setElementValue", _myInput, $"Clicks: {++_count}");
|
||||
await JSRuntime.Current.InvokeAsync<object>("setElementValue", _myInput, $"Clicks: {++_count}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@addTagHelper *, TestContentPackage
|
||||
@addTagHelper *, TestContentPackage
|
||||
@using TestContentPackage
|
||||
|
||||
<h1>Functionality and content from an external package</h1>
|
||||
|
|
@ -31,8 +31,8 @@
|
|||
{
|
||||
string result;
|
||||
|
||||
void ShowJavaScriptPrompt()
|
||||
async Task ShowJavaScriptPrompt()
|
||||
{
|
||||
result = MyPrompt.Show("Hello!");
|
||||
result = await MyPrompt.Show("Hello!");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
@using Microsoft.AspNetCore.Blazor.Browser.Interop
|
||||
@using Microsoft.JSInterop
|
||||
@using BasicTestApp.InteropTest
|
||||
@using Microsoft.AspNetCore.Blazor
|
||||
|
||||
<button id="btn-interop" onclick="@InvokeInteropAsync">Invoke interop!</button>
|
||||
|
||||
|
|
@ -41,51 +40,53 @@
|
|||
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 JSException ExceptionFromSyncMethod { get; set; }
|
||||
public JSException SyncExceptionFromAsyncMethod { get; set; }
|
||||
public JSException AsyncExceptionFromAsyncMethod { get; set; }
|
||||
|
||||
public bool DoneWithInterop { get; set; }
|
||||
|
||||
public async Task InvokeInteropAsync()
|
||||
{
|
||||
var inProcRuntime = ((IJSInProcessRuntime)JSRuntime.Current);
|
||||
|
||||
Console.WriteLine("Starting interop invocations.");
|
||||
await RegisteredFunction.InvokeAsync<object>("BasicTestApp.Interop.InvokeDotNetInteropMethodsAsync");
|
||||
await JSRuntime.Current.InvokeAsync<object>("jsInteropTests.invokeDotNetInteropMethodsAsync");
|
||||
Console.WriteLine("Showing interop invocation results.");
|
||||
var collectResults = RegisteredFunction.Invoke<Dictionary<string,string>>("BasicTestApp.Interop.CollectResults");
|
||||
var collectResults = inProcRuntime.Invoke<Dictionary<string,string>>("jsInteropTests.collectInteropResults");
|
||||
|
||||
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);
|
||||
var interopResultValue = Json.Serialize(interopResult.Value);
|
||||
invocations[interopResult.Key] = interopResultValue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
RegisteredFunction.Invoke<object>("BasicTestApp.Interop.FunctionThrows");
|
||||
inProcRuntime.Invoke<object>("jsInteropTests.functionThrowsException");
|
||||
}
|
||||
catch (JavaScriptException e)
|
||||
catch (JSException e)
|
||||
{
|
||||
ExceptionFromSyncMethod = e;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RegisteredFunction.InvokeAsync<object>("BasicTestApp.Interop.AsyncFunctionThrowsSyncException");
|
||||
await JSRuntime.Current.InvokeAsync<object>("jsInteropTests.asyncFunctionThrowsSyncException");
|
||||
}
|
||||
catch (JavaScriptException e)
|
||||
catch (JSException e)
|
||||
{
|
||||
SyncExceptionFromAsyncMethod = e;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RegisteredFunction.InvokeAsync<object>("BasicTestApp.Interop.AsyncFunctionThrowsAsyncException");
|
||||
await JSRuntime.Current.InvokeAsync<object>("jsInteropTests.asyncFunctionThrowsAsyncException");
|
||||
}
|
||||
catch (JavaScriptException e)
|
||||
catch (JSException e)
|
||||
{
|
||||
AsyncExceptionFromAsyncMethod = e;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
|
@ -12,35 +13,33 @@ namespace BasicTestApp.InteropTest
|
|||
{
|
||||
public static IDictionary<string, object[]> Invocations = new Dictionary<string, object[]>();
|
||||
|
||||
[JSInvokable(nameof(ThrowException))]
|
||||
public static void ThrowException() => throw new InvalidOperationException("Threw an exception!");
|
||||
|
||||
public static Task AsyncThrowSyncException() => throw new InvalidOperationException("Threw a sync exception!");
|
||||
[JSInvokable(nameof(AsyncThrowSyncException))]
|
||||
public static Task AsyncThrowSyncException()
|
||||
=> throw new InvalidOperationException("Threw a sync exception!");
|
||||
|
||||
public static Task AsyncThrowAsyncException()
|
||||
[JSInvokable(nameof(AsyncThrowAsyncException))]
|
||||
public static async 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;
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("Threw an async exception!");
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidParameterless))]
|
||||
public static void VoidParameterless()
|
||||
{
|
||||
Invocations[nameof(VoidParameterless)] = new object[0];
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithOneParameter))]
|
||||
public static void VoidWithOneParameter(ComplexParameter parameter1)
|
||||
{
|
||||
Invocations[nameof(VoidWithOneParameter)] = new object[] { parameter1 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithTwoParameters))]
|
||||
public static void VoidWithTwoParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
|
|
@ -48,6 +47,7 @@ namespace BasicTestApp.InteropTest
|
|||
Invocations[nameof(VoidWithTwoParameters)] = new object[] { parameter1, parameter2 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithThreeParameters))]
|
||||
public static void VoidWithThreeParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -56,6 +56,7 @@ namespace BasicTestApp.InteropTest
|
|||
Invocations[nameof(VoidWithThreeParameters)] = new object[] { parameter1, parameter2, parameter3 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithFourParameters))]
|
||||
public static void VoidWithFourParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -65,6 +66,7 @@ namespace BasicTestApp.InteropTest
|
|||
Invocations[nameof(VoidWithFourParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithFiveParameters))]
|
||||
public static void VoidWithFiveParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -75,6 +77,7 @@ namespace BasicTestApp.InteropTest
|
|||
Invocations[nameof(VoidWithFiveParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithSixParameters))]
|
||||
public static void VoidWithSixParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -86,6 +89,7 @@ namespace BasicTestApp.InteropTest
|
|||
Invocations[nameof(VoidWithSixParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithSevenParameters))]
|
||||
public static void VoidWithSevenParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -98,6 +102,7 @@ namespace BasicTestApp.InteropTest
|
|||
Invocations[nameof(VoidWithSevenParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithEightParameters))]
|
||||
public static void VoidWithEightParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -111,16 +116,19 @@ namespace BasicTestApp.InteropTest
|
|||
Invocations[nameof(VoidWithEightParameters)] = new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(ReturnArray))]
|
||||
public static decimal[] ReturnArray()
|
||||
{
|
||||
return new decimal[] { 0.1M, 0.2M };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoOneParameter))]
|
||||
public static object[] EchoOneParameter(ComplexParameter parameter1)
|
||||
{
|
||||
return new object[] { parameter1 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoTwoParameters))]
|
||||
public static object[] EchoTwoParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
|
|
@ -128,6 +136,7 @@ namespace BasicTestApp.InteropTest
|
|||
return new object[] { parameter1, parameter2 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoThreeParameters))]
|
||||
public static object[] EchoThreeParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -136,6 +145,7 @@ namespace BasicTestApp.InteropTest
|
|||
return new object[] { parameter1, parameter2, parameter3 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoFourParameters))]
|
||||
public static object[] EchoFourParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -145,6 +155,7 @@ namespace BasicTestApp.InteropTest
|
|||
return new object[] { parameter1, parameter2, parameter3, parameter4 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoFiveParameters))]
|
||||
public static object[] EchoFiveParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -155,6 +166,7 @@ namespace BasicTestApp.InteropTest
|
|||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoSixParameters))]
|
||||
public static object[] EchoSixParameters(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
|
|
@ -165,6 +177,7 @@ namespace BasicTestApp.InteropTest
|
|||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoSevenParameters))]
|
||||
public static object[] EchoSevenParameters(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
|
|
@ -176,6 +189,7 @@ namespace BasicTestApp.InteropTest
|
|||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoEightParameters))]
|
||||
public static object[] EchoEightParameters(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -189,18 +203,21 @@ namespace BasicTestApp.InteropTest
|
|||
return new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7, parameter8 };
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidParameterlessAsync))]
|
||||
public static Task VoidParameterlessAsync()
|
||||
{
|
||||
Invocations[nameof(VoidParameterlessAsync)] = new object[0];
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithOneParameterAsync))]
|
||||
public static Task VoidWithOneParameterAsync(ComplexParameter parameter1)
|
||||
{
|
||||
Invocations[nameof(VoidWithOneParameterAsync)] = new object[] { parameter1 };
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithTwoParametersAsync))]
|
||||
public static Task VoidWithTwoParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
|
|
@ -209,6 +226,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithThreeParametersAsync))]
|
||||
public static Task VoidWithThreeParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -218,6 +236,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithFourParametersAsync))]
|
||||
public static Task VoidWithFourParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -228,6 +247,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithFiveParametersAsync))]
|
||||
public static Task VoidWithFiveParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -239,6 +259,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithSixParametersAsync))]
|
||||
public static Task VoidWithSixParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -251,6 +272,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithSevenParametersAsync))]
|
||||
public static Task VoidWithSevenParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -264,6 +286,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(VoidWithEightParametersAsync))]
|
||||
public static Task VoidWithEightParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -278,16 +301,19 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(ReturnArrayAsync))]
|
||||
public static Task<decimal[]> ReturnArrayAsync()
|
||||
{
|
||||
return Task.FromResult(new decimal[] { 0.1M, 0.2M });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoOneParameterAsync))]
|
||||
public static Task<object[]> EchoOneParameterAsync(ComplexParameter parameter1)
|
||||
{
|
||||
return Task.FromResult(new object[] { parameter1 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoTwoParametersAsync))]
|
||||
public static Task<object[]> EchoTwoParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2)
|
||||
|
|
@ -295,6 +321,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.FromResult(new object[] { parameter1, parameter2 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoThreeParametersAsync))]
|
||||
public static Task<object[]> EchoThreeParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -303,6 +330,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoFourParametersAsync))]
|
||||
public static Task<object[]> EchoFourParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -312,6 +340,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoFiveParametersAsync))]
|
||||
public static Task<object[]> EchoFiveParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -322,6 +351,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoSixParametersAsync))]
|
||||
public static Task<object[]> EchoSixParametersAsync(ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
short parameter3,
|
||||
|
|
@ -332,6 +362,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoSevenParametersAsync))]
|
||||
public static Task<object[]> EchoSevenParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
@ -344,6 +375,7 @@ namespace BasicTestApp.InteropTest
|
|||
return Task.FromResult(new object[] { parameter1, parameter2, parameter3, parameter4, parameter5, parameter6, parameter7 });
|
||||
}
|
||||
|
||||
[JSInvokable(nameof(EchoEightParametersAsync))]
|
||||
public static Task<object[]> EchoEightParametersAsync(
|
||||
ComplexParameter parameter1,
|
||||
byte parameter2,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@using System.Collections.Generic
|
||||
@using Microsoft.JSInterop
|
||||
|
||||
Type here: <input onkeypress=@OnKeyPressed />
|
||||
<ul>
|
||||
|
|
@ -13,7 +13,7 @@ Type here: <input onkeypress=@OnKeyPressed />
|
|||
|
||||
void OnKeyPressed(UIKeyboardEventArgs eventArgs)
|
||||
{
|
||||
Console.WriteLine(JsonUtil.Serialize(eventArgs));
|
||||
Console.WriteLine(Json.Serialize(eventArgs));
|
||||
keysPressed.Add(eventArgs.Key);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@using System.Collections.Generic
|
||||
@using Microsoft.JSInterop
|
||||
|
||||
<div>
|
||||
<h2>Mouse position</h2>
|
||||
|
|
@ -64,7 +65,7 @@
|
|||
|
||||
void DumpEvent(UIMouseEventArgs e)
|
||||
{
|
||||
Console.WriteLine(JsonUtil.Serialize(e));
|
||||
Console.WriteLine(Json.Serialize(e));
|
||||
}
|
||||
|
||||
void Clear()
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
// 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.Interop;
|
||||
using Microsoft.AspNetCore.Blazor.Browser.Rendering;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.Browser.Services;
|
||||
using Microsoft.JSInterop;
|
||||
using System;
|
||||
|
||||
namespace BasicTestApp
|
||||
|
|
@ -18,7 +18,8 @@ namespace BasicTestApp
|
|||
BrowserHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
|
||||
|
||||
// Signal to tests that we're ready
|
||||
RegisteredFunction.Invoke<object>("testReady");
|
||||
GC.KeepAlive(ActivateMonoJSRuntime.EnsureActivated());
|
||||
JSRuntime.Current.InvokeAsync<object>("testReady");
|
||||
}
|
||||
|
||||
public static void MountTestComponent(string componentTypeName)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
@using System.Collections.Generic
|
||||
@using Microsoft.JSInterop
|
||||
|
||||
<div>
|
||||
<h2>Touch position</h2>
|
||||
|
|
@ -27,7 +28,7 @@
|
|||
void OnTouch(UITouchEventArgs e)
|
||||
{
|
||||
message += e.Type;
|
||||
Console.WriteLine(JsonUtil.Serialize(e));
|
||||
Console.WriteLine(Json.Serialize(e));
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@
|
|||
<script>
|
||||
// The client-side .NET code calls this when it is ready to be called from test code
|
||||
// The Xunit test code polls until it sees the flag is set
|
||||
Blazor.registerFunction('testReady', function () {
|
||||
window.isTestReady = true;
|
||||
document.getElementsByTagName('APP')[0].textContent = '';
|
||||
document.getElementById('test-selector').style.display = 'block';
|
||||
});
|
||||
function testReady() {
|
||||
window.isTestReady = true;
|
||||
document.getElementsByTagName('APP')[0].textContent = '';
|
||||
document.getElementById('test-selector').style.display = 'block';
|
||||
}
|
||||
|
||||
// The Xunit test code calls this when setting up tests for specific components
|
||||
function mountTestComponent(typeName) {
|
||||
|
|
@ -67,9 +67,9 @@
|
|||
}
|
||||
|
||||
// Used by ElementRefComponent
|
||||
Blazor.registerFunction('setElementValue', function (element, newValue) {
|
||||
function setElementValue(element, newValue) {
|
||||
element.value = newValue;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,94 +1,82 @@
|
|||
/// <reference path="../../../../../src/microsoft.jsinterop/javascriptruntime/dist/microsoft.jsinterop.d.ts" />
|
||||
|
||||
// We'll store the results from the tests here
|
||||
var results = {};
|
||||
var assemblyName = 'BasicTestApp';
|
||||
|
||||
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));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidParameterless');
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithOneParameter', ...createArgumentList(1));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithTwoParameters', ...createArgumentList(2));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithThreeParameters', ...createArgumentList(3));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithFourParameters', ...createArgumentList(4));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithFiveParameters', ...createArgumentList(5));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithSixParameters', ...createArgumentList(6));
|
||||
DotNet.invokeMethod(assemblyName, 'VoidWithSevenParameters', ...createArgumentList(7));
|
||||
DotNet.invokeMethod(assemblyName, '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));
|
||||
results['result1'] = DotNet.invokeMethod(assemblyName, 'ReturnArray');
|
||||
results['result2'] = DotNet.invokeMethod(assemblyName, 'EchoOneParameter', ...createArgumentList(1));
|
||||
results['result3'] = DotNet.invokeMethod(assemblyName, 'EchoTwoParameters', ...createArgumentList(2));
|
||||
results['result4'] = DotNet.invokeMethod(assemblyName, 'EchoThreeParameters', ...createArgumentList(3));
|
||||
results['result5'] = DotNet.invokeMethod(assemblyName, 'EchoFourParameters', ...createArgumentList(4));
|
||||
results['result6'] = DotNet.invokeMethod(assemblyName, 'EchoFiveParameters', ...createArgumentList(5));
|
||||
results['result7'] = DotNet.invokeMethod(assemblyName, 'EchoSixParameters', ...createArgumentList(6));
|
||||
results['result8'] = DotNet.invokeMethod(assemblyName, 'EchoSevenParameters', ...createArgumentList(7));
|
||||
results['result9'] = DotNet.invokeMethod(assemblyName, '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)))
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'VoidParameterlessAsync')
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithOneParameterAsync', ...createArgumentList(1)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithTwoParametersAsync', ...createArgumentList(2)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithThreeParametersAsync', ...createArgumentList(3)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFourParametersAsync', ...createArgumentList(4)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithFiveParametersAsync', ...createArgumentList(5)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSixParametersAsync', ...createArgumentList(6)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithSevenParametersAsync', ...createArgumentList(7)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'VoidWithEightParametersAsync', ...createArgumentList(8)))
|
||||
.then(() => {
|
||||
console.log('Invoking returning async methods.');
|
||||
return Blazor.invokeDotNetMethodAsync(createMethodOptions('ReturnArrayAsync'))
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'ReturnArrayAsync')
|
||||
.then(r => results['result1Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoOneParameterAsync'), ...createArgumentList(1)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoOneParameterAsync', ...createArgumentList(1)))
|
||||
.then(r => results['result2Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoTwoParametersAsync'), ...createArgumentList(2)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoTwoParametersAsync', ...createArgumentList(2)))
|
||||
.then(r => results['result3Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoThreeParametersAsync'), ...createArgumentList(3)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoThreeParametersAsync', ...createArgumentList(3)))
|
||||
.then(r => results['result4Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoFourParametersAsync'), ...createArgumentList(4)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFourParametersAsync', ...createArgumentList(4)))
|
||||
.then(r => results['result5Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoFiveParametersAsync'), ...createArgumentList(5)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoFiveParametersAsync', ...createArgumentList(5)))
|
||||
.then(r => results['result6Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoSixParametersAsync'), ...createArgumentList(6)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSixParametersAsync', ...createArgumentList(6)))
|
||||
.then(r => results['result7Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoSevenParametersAsync'), ...createArgumentList(7)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoSevenParametersAsync', ...createArgumentList(7)))
|
||||
.then(r => results['result8Async'] = r)
|
||||
.then(() => Blazor.invokeDotNetMethodAsync(createMethodOptions('EchoEightParametersAsync'), ...createArgumentList(8)))
|
||||
.then(() => DotNet.invokeMethodAsync(assemblyName, 'EchoEightParametersAsync', ...createArgumentList(8)))
|
||||
.then(r => results['result9Async'] = r);
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Invoking methods that throw exceptions');
|
||||
try {
|
||||
Blazor.invokeDotNetMethod(createMethodOptions('ThrowException'))
|
||||
DotNet.invokeMethod(assemblyName, 'ThrowException');
|
||||
} catch (e) {
|
||||
results['ThrowException'] = e.message;
|
||||
}
|
||||
|
||||
try {
|
||||
Blazor.invokeDotNetMethodAsync(createMethodOptions('AsyncThrowSyncException'));
|
||||
} catch (e) {
|
||||
results['AsyncThrowSyncException'] = e.message;
|
||||
}
|
||||
|
||||
return Blazor.invokeDotNetMethodAsync(createMethodOptions('AsyncThrowAsyncException'))
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'AsyncThrowSyncException')
|
||||
.catch(e => {
|
||||
results['AsyncThrowAsyncException'] = e.message;
|
||||
return Promise.resolve();
|
||||
})
|
||||
.then(() => console.log('Done invoking interop methods'));
|
||||
});
|
||||
}
|
||||
results['AsyncThrowSyncException'] = e.message;
|
||||
|
||||
function createMethodOptions(methodName) {
|
||||
return {
|
||||
type: {
|
||||
assembly: 'BasicTestApp',
|
||||
name: 'BasicTestApp.InteropTest.JavaScriptInterop'
|
||||
},
|
||||
method: {
|
||||
name: methodName
|
||||
}
|
||||
};
|
||||
return DotNet.invokeMethodAsync(assemblyName, 'AsyncThrowAsyncException');
|
||||
}).catch(e => {
|
||||
results['AsyncThrowAsyncException'] = e.message;
|
||||
|
||||
console.log('Done invoking interop methods');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createArgumentList(argumentNumber){
|
||||
|
|
@ -143,12 +131,13 @@ function createArgumentList(argumentNumber){
|
|||
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);
|
||||
window.jsInteropTests = {
|
||||
invokeDotNetInteropMethodsAsync: invokeDotNetInteropMethodsAsync,
|
||||
collectInteropResults: collectInteropResults,
|
||||
functionThrowsException: functionThrowsException,
|
||||
asyncFunctionThrowsSyncException: asyncFunctionThrowsSyncException,
|
||||
asyncFunctionThrowsAsyncException: asyncFunctionThrowsAsyncException
|
||||
};
|
||||
|
||||
function functionThrowsException() {
|
||||
throw new Error('Function threw an exception!');
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
using Microsoft.AspNetCore.Blazor.Browser.Interop;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace TestContentPackage
|
||||
{
|
||||
public static class MyPrompt
|
||||
{
|
||||
// Keep in sync with the identifier in the .js file
|
||||
const string ShowPromptIdentifier = "TestContentPackage.showPrompt";
|
||||
|
||||
public static string Show(string message)
|
||||
public static Task<string> Show(string message)
|
||||
{
|
||||
return RegisteredFunction.Invoke<string>(
|
||||
ShowPromptIdentifier,
|
||||
return JSRuntime.Current.InvokeAsync<string>(
|
||||
"TestContentPackage.showPrompt", // Keep in sync with identifiers in the.js file
|
||||
message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
Blazor.registerFunction('TestContentPackage.showPrompt', function (message) {
|
||||
window.TestContentPackage = {
|
||||
showPrompt: function (message) {
|
||||
return prompt(message, "Type anything here");
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue