aspnetcore/src/Components/Server/src/Circuits/RemoteJSRuntime.cs

152 lines
7.1 KiB
C#

// 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.Text.Json;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.JSInterop;
using Microsoft.JSInterop.Infrastructure;
namespace Microsoft.AspNetCore.Components.Server.Circuits
{
internal class RemoteJSRuntime : JSRuntime
{
private readonly CircuitOptions _options;
private readonly ILogger<RemoteJSRuntime> _logger;
private CircuitClientProxy _clientProxy;
public ElementReferenceContext ElementReferenceContext { get; }
public RemoteJSRuntime(IOptions<CircuitOptions> options, ILogger<RemoteJSRuntime> logger)
{
_options = options.Value;
_logger = logger;
DefaultAsyncTimeout = _options.JSInteropDefaultCallTimeout;
ElementReferenceContext = new WebElementReferenceContext(this);
JsonSerializerOptions.Converters.Add(new ElementReferenceJsonConverter(ElementReferenceContext));
}
internal void Initialize(CircuitClientProxy clientProxy)
{
_clientProxy = clientProxy ?? throw new ArgumentNullException(nameof(clientProxy));
}
protected override void EndInvokeDotNet(DotNetInvocationInfo invocationInfo, in DotNetInvocationResult invocationResult)
{
if (!invocationResult.Success)
{
Log.InvokeDotNetMethodException(_logger, invocationInfo, invocationResult.Exception);
string errorMessage;
if (_options.DetailedErrors)
{
errorMessage = invocationResult.Exception.ToString();
}
else
{
errorMessage = $"There was an exception invoking '{invocationInfo.MethodIdentifier}'";
if (invocationInfo.AssemblyName != null)
{
errorMessage += $" on assembly '{invocationInfo.AssemblyName}'";
}
errorMessage += $". For more details turn on detailed exceptions in '{nameof(CircuitOptions)}.{nameof(CircuitOptions.DetailedErrors)}'";
}
EndInvokeDotNetCore(invocationInfo.CallId, success: false, errorMessage);
}
else
{
Log.InvokeDotNetMethodSuccess(_logger, invocationInfo);
EndInvokeDotNetCore(invocationInfo.CallId, success: true, invocationResult.Result);
}
}
private void EndInvokeDotNetCore(string callId, bool success, object resultOrError)
{
_clientProxy.SendAsync(
"JS.EndInvokeDotNet",
JsonSerializer.Serialize(new[] { callId, success, resultOrError }, JsonSerializerOptions));
}
protected override void BeginInvokeJS(long asyncHandle, string identifier, string argsJson, JSCallResultType resultType, long targetInstanceId)
{
if (_clientProxy is null)
{
throw new InvalidOperationException(
"JavaScript interop calls cannot be issued at this time. This is because the component is being " +
$"statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed " +
$"during the OnAfterRenderAsync lifecycle method.");
}
Log.BeginInvokeJS(_logger, asyncHandle, identifier);
_clientProxy.SendAsync("JS.BeginInvokeJS", asyncHandle, identifier, argsJson, (int)resultType, targetInstanceId);
}
public static class Log
{
private static readonly Action<ILogger, long, string, Exception> _beginInvokeJS =
LoggerMessage.Define<long, string>(
LogLevel.Debug,
new EventId(1, "BeginInvokeJS"),
"Begin invoke JS interop '{AsyncHandle}': '{FunctionIdentifier}'");
private static readonly Action<ILogger, string, string, string, Exception> _invokeStaticDotNetMethodException =
LoggerMessage.Define<string, string, string>(
LogLevel.Debug,
new EventId(2, "InvokeDotNetMethodException"),
"There was an error invoking the static method '[{AssemblyName}]::{MethodIdentifier}' with callback id '{CallbackId}'.");
private static readonly Action<ILogger, string, long, string, Exception> _invokeInstanceDotNetMethodException =
LoggerMessage.Define<string, long, string>(
LogLevel.Debug,
new EventId(2, "InvokeDotNetMethodException"),
"There was an error invoking the instance method '{MethodIdentifier}' on reference '{DotNetObjectReference}' with callback id '{CallbackId}'.");
private static readonly Action<ILogger, string, string, string, Exception> _invokeStaticDotNetMethodSuccess =
LoggerMessage.Define<string, string, string>(
LogLevel.Debug,
new EventId(3, "InvokeDotNetMethodSuccess"),
"Invocation of '[{AssemblyName}]::{MethodIdentifier}' with callback id '{CallbackId}' completed successfully.");
private static readonly Action<ILogger, string, long, string, Exception> _invokeInstanceDotNetMethodSuccess =
LoggerMessage.Define<string, long, string>(
LogLevel.Debug,
new EventId(3, "InvokeDotNetMethodSuccess"),
"Invocation of '{MethodIdentifier}' on reference '{DotNetObjectReference}' with callback id '{CallbackId}' completed successfully.");
internal static void BeginInvokeJS(ILogger logger, long asyncHandle, string identifier) =>
_beginInvokeJS(logger, asyncHandle, identifier, null);
internal static void InvokeDotNetMethodException(ILogger logger, in DotNetInvocationInfo invocationInfo , Exception exception)
{
if (invocationInfo.AssemblyName != null)
{
_invokeStaticDotNetMethodException(logger, invocationInfo.AssemblyName, invocationInfo.MethodIdentifier, invocationInfo.CallId, exception);
}
else
{
_invokeInstanceDotNetMethodException(logger, invocationInfo.MethodIdentifier, invocationInfo.DotNetObjectId, invocationInfo.CallId, exception);
}
}
internal static void InvokeDotNetMethodSuccess(ILogger<RemoteJSRuntime> logger, in DotNetInvocationInfo invocationInfo)
{
if (invocationInfo.AssemblyName != null)
{
_invokeStaticDotNetMethodSuccess(logger, invocationInfo.AssemblyName, invocationInfo.MethodIdentifier, invocationInfo.CallId, null);
}
else
{
_invokeInstanceDotNetMethodSuccess(logger, invocationInfo.MethodIdentifier, invocationInfo.DotNetObjectId, invocationInfo.CallId, null);
}
}
}
}
}