| File: WebRenderer.cs | Web Access |
| Project: src\aspnetcore\src\Components\Web\src\Microsoft.AspNetCore.Components.Web.csproj (Microsoft.AspNetCore.Components.Web) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web.Infrastructure; using Microsoft.AspNetCore.Components.Web.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.JSInterop; using static Microsoft.AspNetCore.Internal.LinkerFlags; namespace Microsoft.AspNetCore.Components.RenderTree; /// <summary> /// A <see cref="Renderer"/> that attaches its components to a browser DOM. /// </summary> public abstract class WebRenderer : Renderer { private readonly DotNetObjectReference<WebRendererInteropMethods> _interopMethodsReference; private readonly int _rendererId; /// <summary> /// Constructs an instance of <see cref="WebRenderer"/>. /// </summary> /// <param name="serviceProvider">The <see cref="IServiceProvider"/> to be used when initializing components.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> /// <param name="jsonOptions">The <see cref="JsonSerializerOptions"/>.</param> /// <param name="jsComponentInterop">The <see cref="JSComponentInterop"/>.</param> public WebRenderer( IServiceProvider serviceProvider, ILoggerFactory loggerFactory, JsonSerializerOptions jsonOptions, JSComponentInterop jsComponentInterop) : base(serviceProvider, loggerFactory) { _interopMethodsReference = DotNetObjectReference.Create( new WebRendererInteropMethods(this, jsonOptions, jsComponentInterop)); _rendererId = GetWebRendererId(); // Supply a DotNetObjectReference to JS that it can use to call us back for events etc. jsComponentInterop.AttachToRenderer(this); var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>(); AttachWebRendererInterop(jsRuntime, jsonOptions, jsComponentInterop); } /// <summary> /// Gets the identifier for the renderer. /// </summary> protected int RendererId { get => _rendererId; } /// <summary> /// Allocates an identifier for the renderer. /// </summary> protected virtual int GetWebRendererId() { // We return '0' by default, which is reserved so that classes deriving from this // type don't need to worry about allocating an ID unless they're using multiple renderers. // As soon as multiple renderers are used, this needs to return a unique identifier. return 0; } /// <summary> /// Instantiates a root component and attaches it to the browser within the specified element. /// </summary> /// <param name="componentType">The type of the component.</param> /// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param> /// <returns>The new component ID.</returns> protected internal int AddRootComponent([DynamicallyAccessedMembers(Component)] Type componentType, string domElementSelector) { var component = InstantiateComponent(componentType); var componentId = AssignRootComponentId(component); AttachRootComponentToBrowser(componentId, domElementSelector); return componentId; } /// <summary> /// Called by the framework to give a location for the specified root component in the browser DOM. /// </summary> /// <param name="componentId">The component ID.</param> /// <param name="domElementSelector">A CSS selector that uniquely identifies a DOM element.</param> protected abstract void AttachRootComponentToBrowser(int componentId, string domElementSelector); /// <summary> /// Releases the resources used by the current <see cref="WebRenderer"/> instance. /// </summary> /// <param name="disposing"><see langword="true"/> to release managed resources; otherwise, <see langword="false"/>.</param> protected override void Dispose(bool disposing) { if (disposing) { _interopMethodsReference.Dispose(); } base.Dispose(disposing); } [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")] private void AttachWebRendererInterop(IJSRuntime jsRuntime, JsonSerializerOptions jsonOptions, JSComponentInterop jsComponentInterop) { const string JSMethodIdentifier = "Blazor._internal.attachWebRendererInterop"; // These arguments should be kept in sync with WebRendererSerializerContext object[] args = [ _rendererId, _interopMethodsReference, jsComponentInterop.Configuration.JSComponentParametersByIdentifier, jsComponentInterop.Configuration.JSComponentIdentifiersByInitializer, ]; if (jsRuntime is IInternalWebJSInProcessRuntime inProcessRuntime) { // Fast path for WebAssembly: Rather than using the JSRuntime to serialize // parameters, we utilize the source-generated WebRendererSerializerContext // for a faster JsonTypeInfo resolution. // We resolve a JsonTypeInfo for DotNetObjectReference<WebRendererInteropMethods> from // the JS runtime's JsonConverters. This is because adding DotNetObjectReference<T> as // a supported type in the JsonSerializerContext generates unnecessary code to produce // JsonTypeInfo for all the types referenced by both DotNetObjectReference<T> and its // generic type argument. var newJsonOptions = new JsonSerializerOptions(jsonOptions); newJsonOptions.TypeInfoResolverChain.Clear(); newJsonOptions.TypeInfoResolverChain.Add(WebRendererSerializerContext.Default); newJsonOptions.TypeInfoResolverChain.Add(JsonConverterFactoryTypeInfoResolver<DotNetObjectReference<WebRendererInteropMethods>>.Instance); var argsJson = JsonSerializer.Serialize(args, newJsonOptions); inProcessRuntime.InvokeJS(JSMethodIdentifier, argsJson, JSCallResultType.JSVoidResult, 0); } else { jsRuntime.InvokeVoidAsync(JSMethodIdentifier, args).Preserve(); } } /// <summary> /// A collection of JS invokable methods that the JS-side code can use when it needs to /// make calls in the context of a particular renderer. This object is never exposed to /// .NET code so is only reachable via JS. /// </summary> internal sealed class WebRendererInteropMethods { private readonly WebRenderer _renderer; private readonly JsonSerializerOptions _jsonOptions; private readonly JSComponentInterop _jsComponentInterop; [DynamicDependency(nameof(DispatchEventAsync))] public WebRendererInteropMethods(WebRenderer renderer, JsonSerializerOptions jsonOptions, JSComponentInterop jsComponentInterop) { _renderer = renderer; _jsonOptions = jsonOptions; _jsComponentInterop = jsComponentInterop; } [JSInvokable] public Task DispatchEventAsync(JsonElement eventDescriptor, JsonElement eventArgs) { var webEventData = WebEventData.Parse(_renderer, _jsonOptions, eventDescriptor, eventArgs); return _renderer.DispatchEventAsync( webEventData.EventHandlerId, webEventData.EventFieldInfo, webEventData.EventArgs); } [JSInvokable] // Linker preserves this if you call RootComponents.Add public int AddRootComponent(string identifier, string domElementSelector) => _jsComponentInterop.AddRootComponent(identifier, domElementSelector); [JSInvokable] // Linker preserves this if you call RootComponents.Add public void SetRootComponentParameters(int componentId, int parameterCount, JsonElement parametersJson) => _jsComponentInterop.SetRootComponentParameters(componentId, parameterCount, parametersJson, _jsonOptions); [JSInvokable] // Linker preserves this if you call RootComponents.Add public void RemoveRootComponent(int componentId) => _jsComponentInterop.RemoveRootComponent(componentId); } } // This should be kept in sync with the argument types in the call to // 'Blazor._internal.attachWebRendererInterop' [JsonSerializable(typeof(object[]))] [JsonSerializable(typeof(int))] [JsonSerializable(typeof(Dictionary<string, JSComponentConfigurationStore.JSComponentParameter[]>))] [JsonSerializable(typeof(Dictionary<string, List<string>>))] internal sealed partial class WebRendererSerializerContext : JsonSerializerContext;