File: StaticAssetEndpointDataSource.cs
Web Access
Project: src\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;
    }
}