| File: StaticAssetEndpointDataSource.cs | Web Access |
| Project: src\aspnetcore\src\StaticAssets\src\Microsoft.AspNetCore.StaticAssets.csproj (Microsoft.AspNetCore.StaticAssets) |
// 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; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.StaticAssets; /// <summary> /// An <see cref="EndpointDataSource"/> for static assets. /// </summary> [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] internal class StaticAssetsEndpointDataSource : EndpointDataSource { private readonly object _lock = new(); private readonly List<StaticAssetDescriptor> _descriptors; private readonly StaticAssetEndpointFactory _endpointFactory; private readonly List<Action<EndpointBuilder>> _conventions = []; private readonly List<Action<EndpointBuilder>> _finallyConventions = []; private List<Endpoint> _endpoints = null!; private CancellationTokenSource _cancellationTokenSource; private CancellationChangeToken _changeToken; internal StaticAssetsEndpointDataSource( IServiceProvider serviceProvider, StaticAssetEndpointFactory endpointFactory, string manifestName, bool isBuildManifest, List<StaticAssetDescriptor> descriptors) { ServiceProvider = serviceProvider; _descriptors = descriptors; ManifestPath = manifestName; _endpointFactory = endpointFactory; _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); if (isBuildManifest) { _conventions.Add(c => c.Metadata.Add(new BuildAssetMetadata())); } DefaultBuilder = new StaticAssetsEndpointConventionBuilder( _lock, isBuildManifest, descriptors, _conventions, _finallyConventions); } /// <summary> /// Gets the manifest name associated with this static asset endpoint data source. /// </summary> public string ManifestPath { get; } internal IReadOnlyList<StaticAssetDescriptor> Descriptors => _descriptors; /// <inheritdoc /> internal StaticAssetsEndpointConventionBuilder DefaultBuilder { get; set; } /// <inheritdoc /> 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 IServiceProvider ServiceProvider { get; } private void Initialize() { if (_endpoints == null) { lock (_lock) { if (_endpoints == null) { UpdateEndpoints(); } } } } private void UpdateEndpoints() { lock (_lock) { var endpoints = new List<Endpoint>(); foreach (var asset in _descriptors) { // At this point the descriptor becomes immutable. asset.Freeze(); endpoints.Add(_endpointFactory.Create(asset, _conventions, _finallyConventions)); } var oldCancellationTokenSource = _cancellationTokenSource; _endpoints = endpoints; _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); oldCancellationTokenSource?.Cancel(); oldCancellationTokenSource?.Dispose(); } } /// <inheritdoc /> public override IChangeToken GetChangeToken() { Initialize(); Debug.Assert(_changeToken != null); Debug.Assert(_endpoints != null); return _changeToken; } private string GetDebuggerDisplay() { return ManifestPath; } }