-
Notifications
You must be signed in to change notification settings - Fork 9.8k
/
ActionEndpointDataSourceBase.cs
141 lines (122 loc) · 4.75 KB
/
ActionEndpointDataSourceBase.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 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 constuctor 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();
}
}
}