JavaScript interop v3 (#1033)

* JavaScript interop via new IJSRuntime abstraction

* CR feedback
This commit is contained in:
Steve Sanderson 2018-06-25 15:14:42 +01:00 committed by GitHub
parent dcd9cf7481
commit b275055835
81 changed files with 4029 additions and 2804 deletions

View File

@ -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}

View File

@ -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>

View File

@ -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$/, '');
}
})();
})();

View File

@ -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}";

View File

@ -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>

View File

@ -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

View File

@ -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"
}
}

View File

@ -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() {

View File

@ -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
}
};
}

View File

@ -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,
};

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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}'.`);
}
}

View File

@ -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;
},
});
}

View File

@ -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;
}
});

View File

@ -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];

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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();
}
}
}

View File

@ -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 });
}
}

View File

@ -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();
}
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -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.");
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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];
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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.");
}
}
}
}

View File

@ -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));
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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");
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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);
}
}

1
src/Microsoft.JSInterop/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
JavaScriptRuntime/dist/

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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)
{
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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));
}
}
}

View File

@ -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()));
}
}
}
}

View File

@ -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.`);
}
}
}

View File

@ -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
{

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.JSInterop.Test")]

View File

@ -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));
});
}
}
}

View File

@ -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/**"
]
}

View File

@ -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; }
}
}

View File

@ -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",

View File

@ -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!",

View File

@ -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; }
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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");
}
}

View File

@ -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}");
}
}

View File

@ -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!");
}
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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);
}
}

View File

@ -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()

View File

@ -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)

View File

@ -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();
}

View File

@ -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>

View File

@ -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!');

View File

@ -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);
}
}

View File

@ -1,3 +1,5 @@
Blazor.registerFunction('TestContentPackage.showPrompt', function (message) {
window.TestContentPackage = {
showPrompt: function (message) {
return prompt(message, "Type anything here");
});
}
};