| File: Builder\RazorComponentEndpointDataSource.cs | Web Access |
| Project: src\aspnetcore\src\Components\Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj (Microsoft.AspNetCore.Components.Endpoints) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Components.Discovery; using Microsoft.AspNetCore.Components.Endpoints.Infrastructure; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; using static Microsoft.AspNetCore.Internal.LinkerFlags; namespace Microsoft.AspNetCore.Components.Endpoints; internal class RazorComponentEndpointDataSource<[DynamicallyAccessedMembers(Component)] TRootComponent> : EndpointDataSource { private readonly object _lock = new(); private readonly List<Action<EndpointBuilder>> _conventions = []; private readonly List<Action<EndpointBuilder>> _finallyConventions = []; private readonly List<Action<ComponentApplicationBuilder>> _componentApplicationBuilderActions = []; private readonly RazorComponentDataSourceOptions _options = new(); private readonly IEndpointRouteBuilder _endpointRouteBuilder; private readonly ResourceCollectionResolver _resourceCollectionResolver; private readonly RenderModeEndpointProvider[] _renderModeEndpointProviders; private readonly RazorComponentEndpointFactory _factory; private readonly HotReloadService? _hotReloadService; private List<Endpoint>? _endpoints; private CancellationTokenSource _cancellationTokenSource; private IChangeToken _changeToken; private IDisposable? _disposableChangeToken; // THREADING: protected by _lock // Internal for testing. internal List<Action<EndpointBuilder>> Conventions => _conventions; internal List<Action<ComponentApplicationBuilder>> ComponentApplicationBuilderActions => _componentApplicationBuilderActions; internal CancellationTokenSource ChangeTokenSource => _cancellationTokenSource; public RazorComponentEndpointDataSource( IEnumerable<RenderModeEndpointProvider> renderModeEndpointProviders, IEndpointRouteBuilder endpointRouteBuilder, RazorComponentEndpointFactory factory, HotReloadService? hotReloadService = null) { _endpointRouteBuilder = endpointRouteBuilder; _resourceCollectionResolver = new ResourceCollectionResolver(endpointRouteBuilder); _renderModeEndpointProviders = renderModeEndpointProviders.ToArray(); _factory = factory; _hotReloadService = hotReloadService; DefaultBuilder = new RazorComponentsEndpointConventionBuilder( _lock, endpointRouteBuilder, _options, _conventions, _finallyConventions, _componentApplicationBuilderActions); _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); } internal RazorComponentsEndpointConventionBuilder DefaultBuilder { get; } public override IReadOnlyList<Endpoint> Endpoints { get { // Note it is important that this is lazy, since we only want to create the endpoints after the user had a chance to populate // the list of conventions. // The order is as follows: // * MapRazorComponents gets called and the data source gets created. // * The RazorComponentEndpointConventionBuilder is returned and the user gets a chance to call on it to add conventions. // * The first request arrives and the DfaMatcherBuilder accesses the data sources to get the endpoints. // * The endpoints get created and the conventions get applied. Initialize(); Debug.Assert(_changeToken != null); Debug.Assert(_endpoints != null); return _endpoints; } } internal RazorComponentDataSourceOptions Options => _options; private void Initialize() { if (_endpoints == null) { lock (_lock) { if (_endpoints == null) { UpdateEndpoints(); } } } } private void UpdateEndpoints() { const string ResourceCollectionKey = "__ResourceCollectionKey"; lock (_lock) { _disposableChangeToken?.Dispose(); _disposableChangeToken = null; var endpoints = new List<Endpoint>(); var componentApplicationBuilder = new ComponentApplicationBuilder(); foreach (var action in ComponentApplicationBuilderActions) { action?.Invoke(componentApplicationBuilder); } var context = componentApplicationBuilder.Build(); var configuredRenderModesMetadata = new ConfiguredRenderModesMetadata( [.. Options.ConfiguredRenderModes]); var endpointContext = new RazorComponentEndpointUpdateContext(endpoints, _options); DefaultBuilder.OnBeforeCreateEndpoints(endpointContext); AddBlazorWebEndpoints(endpoints); foreach (var definition in context.Pages) { _factory.AddEndpoints( endpoints, typeof(TRootComponent), definition, _conventions, _finallyConventions, configuredRenderModesMetadata); } // Extract the endpoint collection from any of the endpoints var resourceCollection = endpoints.Count > 0 ? endpoints[^1].Metadata.GetMetadata<ResourceAssetCollection>() : null; ICollection<IComponentRenderMode> renderModes = Options.ConfiguredRenderModes; foreach (var renderMode in renderModes) { var found = false; foreach (var provider in _renderModeEndpointProviders) { var builder = _endpointRouteBuilder.CreateApplicationBuilder(); builder.Properties[ResourceCollectionKey] = resourceCollection; if (provider.Supports(renderMode)) { found = true; RenderModeEndpointProvider.AddEndpoints( endpoints, typeof(TRootComponent), provider.GetEndpointBuilders(renderMode, builder), renderMode, _conventions, _finallyConventions); } } if (!found) { throw new InvalidOperationException($"Unable to find a provider for the render mode: {renderMode.GetType().FullName}. This generally " + "means that a call to 'AddInteractiveWebAssemblyComponents' or 'AddInteractiveServerComponents' is missing. " + "For example, change builder.Services.AddRazorComponents() to builder.Services.AddRazorComponents().AddInteractiveServerComponents()."); } } var oldCancellationTokenSource = _cancellationTokenSource; _endpoints = endpoints; _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); oldCancellationTokenSource?.Cancel(); oldCancellationTokenSource?.Dispose(); if (_hotReloadService is { MetadataUpdateSupported: true }) { _disposableChangeToken = ChangeToken.OnChange(_hotReloadService.GetChangeToken, UpdateEndpoints); } } } private void AddBlazorWebEndpoints(List<Endpoint> endpoints) { List<EndpointBuilder> blazorWebEndpoints = [ OpaqueRedirection.GetBlazorOpaqueRedirectionEndpoint()]; foreach (var endpoint in blazorWebEndpoints) { foreach (var convention in _conventions) { convention(endpoint); } foreach (var convention in _finallyConventions) { convention(endpoint); } endpoints.Add(endpoint.Build()); } } public override IChangeToken GetChangeToken() { Initialize(); Debug.Assert(_changeToken != null); Debug.Assert(_endpoints != null); return _changeToken; } }