| File: Routing\ActionEndpointDataSourceBase.cs | Web Access |
| Project: src\aspnetcore\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core) |
// 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.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.Routing; internal abstract class ActionEndpointDataSourceBase : EndpointDataSource, IDisposable { private readonly IActionDescriptorCollectionProvider _actions; // The following are protected by this lock for WRITES only. This pattern is similar // to DefaultActionDescriptorChangeProvider - see comments there for details on // all of the threading behaviors. protected readonly object Lock = new object(); // Protected for READS and WRITES. protected readonly List<Action<EndpointBuilder>> Conventions; protected readonly List<Action<EndpointBuilder>> FinallyConventions; private List<Endpoint>? _endpoints; private CancellationTokenSource? _cancellationTokenSource; private IChangeToken? _changeToken; private IDisposable? _disposable; public ActionEndpointDataSourceBase(IActionDescriptorCollectionProvider actions) { _actions = actions; Conventions = new List<Action<EndpointBuilder>>(); FinallyConventions = new List<Action<EndpointBuilder>>(); } public override IReadOnlyList<Endpoint> Endpoints { get { Initialize(); Debug.Assert(_changeToken != null); Debug.Assert(_endpoints != null); return _endpoints; } } public override IReadOnlyList<Endpoint> GetGroupedEndpoints(RouteGroupContext context) { return CreateEndpoints( context.Prefix, _actions.ActionDescriptors.Items, Conventions, context.Conventions, FinallyConventions, context.FinallyConventions); } // Will be called with the lock. protected abstract List<Endpoint> CreateEndpoints( RoutePattern? groupPrefix, IReadOnlyList<ActionDescriptor> actions, IReadOnlyList<Action<EndpointBuilder>> conventions, IReadOnlyList<Action<EndpointBuilder>> groupConventions, IReadOnlyList<Action<EndpointBuilder>> finallyConventions, IReadOnlyList<Action<EndpointBuilder>> groupFinallyConventions); protected void Subscribe() { // IMPORTANT: this needs to be called by the derived class to avoid the fragile base class // problem. We can't call this in the base-class constructor because it's too early. // // It's possible for someone to override the collection provider without providing // change notifications. If that's the case we won't process changes. if (_actions is ActionDescriptorCollectionProvider collectionProviderWithChangeToken) { _disposable = ChangeToken.OnChange( collectionProviderWithChangeToken.GetChangeToken, UpdateEndpoints); } } public override IChangeToken GetChangeToken() { Initialize(); Debug.Assert(_changeToken != null); Debug.Assert(_endpoints != null); return _changeToken; } public void Dispose() { // Once disposed we won't process updates anymore, but we still allow access to the endpoints. _disposable?.Dispose(); _disposable = null; } private void Initialize() { if (_endpoints == null) { lock (Lock) { if (_endpoints == null) { UpdateEndpoints(); } } } } private void UpdateEndpoints() { lock (Lock) { var endpoints = CreateEndpoints( groupPrefix: null, _actions.ActionDescriptors.Items, conventions: Conventions, groupConventions: Array.Empty<Action<EndpointBuilder>>(), finallyConventions: FinallyConventions, groupFinallyConventions: Array.Empty<Action<EndpointBuilder>>()); // Step 1 - capture old token var oldCancellationTokenSource = _cancellationTokenSource; // Step 2 - update endpoints _endpoints = endpoints; // Step 3 - create new change token _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); // Step 4 - trigger old token oldCancellationTokenSource?.Cancel(); } } }