public async Task BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { try { AssertInitialized(); if (assemblyName == "Microsoft.AspNetCore.Components.Web" && methodIdentifier == "DispatchEvent") { Log.DispatchEventTroughJSInterop(_logger); return; } await Renderer.Dispatcher.InvokeAsync(() => { SetCurrentCircuitHost(this); Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId); DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); }); } catch (Exception ex) { // We don't expect any of this code to actually throw, because DotNetDispatcher.BeginInvoke doesn't throw // however, we still want this to get logged if we do. UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } }
public async Task SetCertificateAsync_should_get_certificate_thumbprint() { string relyingPartyId = await CreateEntity(null); CreateTestHost("Alice Smith", SharedConstants.WRITER, relyingPartyId, out TestHost host, out RenderedComponent <App> component, out MockHttpMessageHandler _, true); var jsRuntime = host.ServiceProvider.GetRequiredService <JSRuntimeImpl>(); WaitForLoaded(host, component); WaitForContains(host, component, "filtered"); jsRuntime.Called.WaitOne(); await Task.Delay(100).ConfigureAwait(false); DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, "NotifyChange", 1, default), "[[{ \"name\": \"test.crt\" }]]"); host.WaitForNextRender(); string markup = component.GetMarkup(); Assert.Contains("Invalid file", markup); }
// BeginInvokeDotNetFromJS is used in a fire-and-forget context, so it's responsible for its own // error handling. public async Task BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { AssertInitialized(); AssertNotDisposed(); try { await Renderer.Dispatcher.InvokeAsync(() => { SetCurrentCircuitHost(this); Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId); DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); }); } catch (Exception ex) { // We don't expect any of this code to actually throw, because DotNetDispatcher.BeginInvoke doesn't throw // however, we still want this to get logged if we do. Log.BeginInvokeDotNetFailed(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId, ex); if (Client.Connected) { await NotifyClientError(Client, "Interop call failed."); } UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } }
// EndInvokeJSFromDotNet is used in a fire-and-forget context, so it's responsible for its own // error handling. public async Task EndInvokeJSFromDotNet(long asyncCall, bool succeeded, string arguments) { AssertInitialized(); AssertNotDisposed(); try { await Renderer.Dispatcher.InvokeAsync(() => { if (!succeeded) { // We can log the arguments here because it is simply the JS error with the call stack. Log.EndInvokeJSFailed(_logger, asyncCall, arguments); } else { Log.EndInvokeJSSucceeded(_logger, asyncCall); } DotNetDispatcher.EndInvokeJS(JSRuntime, arguments); }); } catch (Exception ex) { // An error completing JS interop means that the user sent invalid data, a well-behaved // client won't do this. Log.EndInvokeDispatchException(_logger, ex); await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Invalid interop arguments.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } }
private static void BeginInvokeDotNet(PageContext pageContext, string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { DotNetDispatcher.BeginInvokeDotNet( pageContext.JSRuntime, new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId), argsJson); }
public async Task EndInvokeJSFromDotNet(long asyncCall, bool succeded, string arguments) { try { AssertInitialized(); await Renderer.Dispatcher.InvokeAsync(() => { SetCurrentCircuitHost(this); if (!succeded) { // We can log the arguments here because it is simply the JS error with the call stack. Log.EndInvokeJSFailed(_logger, asyncCall, arguments); } else { Log.EndInvokeJSSucceeded(_logger, asyncCall); } DotNetDispatcher.EndInvoke(arguments); }); } catch (Exception ex) { Log.EndInvokeDispatchException(_logger, ex); } }
private static void AttachJsInterop(IPC ipc, CancellationToken appLifetime) { var desktopSynchronizationContext = new DesktopSynchronizationContext(appLifetime); SynchronizationContext.SetSynchronizationContext(desktopSynchronizationContext); ipc.On("BeginInvokeDotNetFromJS", args => { desktopSynchronizationContext.Send(state => { var argsArray = (object[])state; DotNetDispatcher.BeginInvokeDotNet( DesktopJSRuntime, new DotNetInvocationInfo( assemblyName: ((JsonElement)argsArray[1]).GetString(), methodIdentifier: ((JsonElement)argsArray[2]).GetString(), dotNetObjectId: ((JsonElement)argsArray[3]).GetInt64(), callId: ((JsonElement)argsArray[0]).GetString()), ((JsonElement)argsArray[4]).GetString()); }, args); }); ipc.On("EndInvokeJSFromDotNet", args => { desktopSynchronizationContext.Send(state => { var argsArray = (object[])state; DotNetDispatcher.EndInvokeJS( DesktopJSRuntime, ((JsonElement)argsArray[2]).GetString()); }, args); }); }
private Task <InteropHandshakeResult> AttachInteropAsync() { var resultTcs = new TaskCompletionSource <InteropHandshakeResult>(); // These hacks can go away once there's a proper IPC channel for event notifications etc. var selfAsDotNetObjectReference = typeof(DotNetObjectReference).GetMethod(nameof(DotNetObjectReference.Create)) .MakeGenericMethod(GetType()) .Invoke(null, new[] { this }); var selfAsDotnetObjectReferenceId = (long)typeof(JSRuntime).GetMethod("TrackObjectReference", BindingFlags.NonPublic | BindingFlags.Instance) .MakeGenericMethod(GetType()) .Invoke(_jsRuntime, new[] { selfAsDotNetObjectReference }); _ipc.Once("components:init", args => { var argsArray = (object[])args; var initialUriAbsolute = ((JsonElement)argsArray[0]).GetString(); var baseUriAbsolute = ((JsonElement)argsArray[1]).GetString(); resultTcs.TrySetResult(new InteropHandshakeResult(baseUriAbsolute, initialUriAbsolute)); }); _ipc.On("BeginInvokeDotNetFromJS", args => { var argsArray = (object[])args; var assemblyName = argsArray[1] != null ? ((JsonElement)argsArray[1]).GetString() : null; var methodIdentifier = ((JsonElement)argsArray[2]).GetString(); var dotNetObjectId = ((JsonElement)argsArray[3]).GetInt64(); var callId = ((JsonElement)argsArray[0]).GetString(); var argsJson = ((JsonElement)argsArray[4]).GetString(); // As a temporary hack, intercept blazor.desktop.js's JS interop calls for event notifications, // and direct them to our own instance. This is to avoid needing a static BlazorHybridRenderer.Instance. // Similar temporary hack for navigation notifications // TODO: Change blazor.desktop.js to use a dedicated IPC call for these calls, not JS interop. if (assemblyName == "Microsoft.MobileBlazorBindings.WebView") { assemblyName = null; dotNetObjectId = selfAsDotnetObjectReferenceId; } DotNetDispatcher.BeginInvokeDotNet( _jsRuntime, new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId), argsJson); }); _ipc.On("EndInvokeJSFromDotNet", args => { var argsArray = (object[])args; DotNetDispatcher.EndInvokeJS( _jsRuntime, ((JsonElement)argsArray[2]).GetString()); }); _webView.Source = new XF.UrlWebViewSource { Url = $"{BlazorAppScheme}://0.0.0.0/" }; return(resultTcs.Task); }
// Invoked via Mono's JS interop mechanism (invoke_method) public static void EndInvokeJS(string argsJson) { WebAssemblyCallQueue.Schedule(argsJson, static argsJson => { // This is not expected to throw, as it takes care of converting any unhandled user code // exceptions into a failure on the Task that was returned when calling InvokeAsync. DotNetDispatcher.EndInvokeJS(Instance, argsJson); }); }
[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); }
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); }
public void CanInvokeStaticVoidMethod() { // Arrange/Act SomePublicType.DidInvokeMyInvocableVoid = false; var resultJson = DotNetDispatcher.Invoke(thisAssemblyName, "InvocableStaticVoid", null); // Assert Assert.Null(resultJson); Assert.True(SomePublicType.DidInvokeMyInvocableVoid); }
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); }
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); }
private void EndInvokeJS(PageContext pageContext, long asyncHandle, bool succeeded, string argumentsOrError) { if (succeeded) { DotNetDispatcher.EndInvokeJS(pageContext.JSRuntime, argumentsOrError); } else { throw new InvalidOperationException(argumentsOrError); } }
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); }
public void HandleFileSelected_should_report_importation_result() { CreateTestHost("Alice Smith", SharedConstants.WRITER, out RenderedComponent <App> component); WaitForLoaded(component); var jsRuntime = _host.ServiceProvider.GetRequiredService <JSRuntimeImpl>(); DotNetDispatcher.BeginInvokeDotNet(jsRuntime, new DotNetInvocationInfo(null, nameof(InputFile.NotifyChange), 1, default), "[[{ \"name\": \"test.json\" }]]"); var markup = _host.WaitForContains(component, "text-success"); Assert.Contains("text-success", markup); }
public async void BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { AssertInitialized(); try { await Renderer.InvokeAsync(() => { SetCurrentCircuitHost(this); DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); }); } catch (Exception ex) { UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } }
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); }
public async Task SetCertificateAsync_should_get_certificate_thumbprint() { string relyingPartyId = await CreateEntity(null); var component = CreateComponent("Alice Smith", SharedConstants.WRITERPOLICY, relyingPartyId); var inputFile = component.FindComponent <InputFile>(); await component.InvokeAsync(() => inputFile.Instance.OnChange.InvokeAsync(new InputFileChangeEventArgs(new List <IBrowserFile> { new FakeBrowserFile() })).ConfigureAwait(false)).ConfigureAwait(false); DotNetDispatcher.BeginInvokeDotNet(new JSRuntimeImpl(), new DotNetInvocationInfo(null, "NotifyChange", 1, default), "[[{ \"name\": \"test.crt\" }]]"); component.WaitForState(() => component.Markup.Contains("Invalid file")); Assert.Contains("Invalid file", component.Markup); }
// Invoked via Mono's JS interop mechanism (invoke_method) private static void BeginInvokeDotNet(string callId, string assemblyNameOrDotNetObjectId, string methodIdentifier, string argsJson) { // Figure out whether 'assemblyNameOrDotNetObjectId' is the assembly name or the instance ID // We only need one for any given call. This helps to work around the limitation that we can // only pass a maximum of 4 args in a call from JS to Mono WebAssembly. string assemblyName; long dotNetObjectId; if (char.IsDigit(assemblyNameOrDotNetObjectId[0])) { dotNetObjectId = long.Parse(assemblyNameOrDotNetObjectId); assemblyName = null; } else { dotNetObjectId = default; assemblyName = assemblyNameOrDotNetObjectId; } DotNetDispatcher.BeginInvoke(callId, assemblyName, methodIdentifier, dotNetObjectId, argsJson); }
// ReceiveByteArray is used in a fire-and-forget context, so it's responsible for its own // error handling. internal async Task ReceiveByteArray(int id, byte[] data) { AssertInitialized(); AssertNotDisposed(); try { await Renderer.Dispatcher.InvokeAsync(() => { Log.ReceiveByteArraySuccess(_logger, id); DotNetDispatcher.ReceiveByteArray(JSRuntime, id, data); }); } catch (Exception ex) { // An error completing JS interop means that the user sent invalid data, a well-behaved // client won't do this. Log.ReceiveByteArrayException(_logger, id, ex); await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Invalid byte array.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } }
// BeginInvokeDotNetFromJS is used in a fire-and-forget context, so it's responsible for its own // error handling. public async Task BeginInvokeDotNetFromJS(string callId, string assemblyName, string methodIdentifier, long dotNetObjectId, string argsJson) { AssertInitialized(); AssertNotDisposed(); try { await Renderer.Dispatcher.InvokeAsync(() => { Log.BeginInvokeDotNet(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId); var invocationInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId, callId); DotNetDispatcher.BeginInvokeDotNet(JSRuntime, invocationInfo, argsJson); }); } catch (Exception ex) { // We don't expect any of this code to actually throw, because DotNetDispatcher.BeginInvoke doesn't throw // however, we still want this to get logged if we do. Log.BeginInvokeDotNetFailed(_logger, callId, assemblyName, methodIdentifier, dotNetObjectId, ex); await TryNotifyClientErrorAsync(Client, GetClientErrorMessage(ex, "Interop call failed.")); UnhandledException?.Invoke(this, new UnhandledExceptionEventArgs(ex, isTerminating: false)); } }
public void CannotInvokeWithEmptyAssemblyName() { var ex = Assert.Throws <ArgumentException>(() => { DotNetDispatcher.Invoke(" ", "SomeMethod", default, "[]");
// Invoked via Mono's JS interop mechanism (invoke_method) public static void EndInvokeJS(string argsJson) => DotNetDispatcher.EndInvokeJS(Instance, argsJson);
// The following methods are invoke via Mono's JS interop mechanism (invoke_method) public static string?InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) { var callInfo = new DotNetInvocationInfo(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId, CultureInfo.InvariantCulture), callId: null); return(DotNetDispatcher.Invoke(Instance, callInfo, argsJson)); }
// Invoked via Mono's JS interop mechanism (invoke_method) private static string InvokeDotNet(string assemblyName, string methodIdentifier, string dotNetObjectId, string argsJson) => DotNetDispatcher.Invoke(assemblyName, methodIdentifier, dotNetObjectId == null ? default : long.Parse(dotNetObjectId), argsJson);
private static void ReceiveByteArrayFromJS(PageContext pageContext, int id, byte[] data) { DotNetDispatcher.ReceiveByteArray(pageContext.JSRuntime, id, data); }
private static void EndInvokeJS(PageContext pageContext, string argumentsOrError) { DotNetDispatcher.EndInvokeJS(pageContext.JSRuntime, argumentsOrError); }
private void EndInvokeJS(PageContext pageContext, long asyncHandle, bool succeeded, string argumentsOrError) { DotNetDispatcher.EndInvokeJS(pageContext.JSRuntime, argumentsOrError); }