-
Notifications
You must be signed in to change notification settings - Fork 9.9k
/
EndpointDataSource.cs
174 lines (147 loc) · 6.88 KB
/
EndpointDataSource.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Routing;
/// <summary>
/// Provides a collection of <see cref="Endpoint"/> instances.
/// </summary>
public abstract class EndpointDataSource
{
/// <summary>
/// Gets a <see cref="IChangeToken"/> used to signal invalidation of cached <see cref="Endpoint"/>
/// instances.
/// </summary>
/// <returns>The <see cref="IChangeToken"/>.</returns>
public abstract IChangeToken GetChangeToken();
/// <summary>
/// Returns a read-only collection of <see cref="Endpoint"/> instances.
/// </summary>
public abstract IReadOnlyList<Endpoint> Endpoints { get; }
/// <summary>
/// Get the <see cref="Endpoint"/> instances for this <see cref="EndpointDataSource"/> given the specified <see cref="RouteGroupContext.Prefix"/> and <see cref="RouteGroupContext.Conventions"/>.
/// </summary>
/// <param name="context">Details about how the returned <see cref="Endpoint"/> instances should be grouped and a reference to application services.</param>
/// <returns>
/// Returns a read-only collection of <see cref="Endpoint"/> instances given the specified group <see cref="RouteGroupContext.Prefix"/> and <see cref="RouteGroupContext.Conventions"/>.
/// </returns>
public virtual IReadOnlyList<Endpoint> GetGroupedEndpoints(RouteGroupContext context)
{
// Only evaluate Endpoints once per call.
var endpoints = Endpoints;
var wrappedEndpoints = new RouteEndpoint[endpoints.Count];
for (int i = 0; i < endpoints.Count; i++)
{
var endpoint = endpoints[i];
// Endpoint does not provide a RoutePattern but RouteEndpoint does. So it's impossible to apply a prefix for custom Endpoints.
// Supporting arbitrary Endpoints just to add group metadata would require changing the Endpoint type breaking any real scenario.
if (endpoint is not RouteEndpoint routeEndpoint)
{
throw new NotSupportedException(Resources.FormatMapGroup_CustomEndpointUnsupported(endpoint.GetType()));
}
// Make the full route pattern visible to IEndpointConventionBuilder extension methods called on the group.
// This includes patterns from any parent groups.
var fullRoutePattern = RoutePatternFactory.Combine(context.Prefix, routeEndpoint.RoutePattern);
var routeEndpointBuilder = new RouteEndpointBuilder(routeEndpoint.RequestDelegate, fullRoutePattern, routeEndpoint.Order)
{
DisplayName = routeEndpoint.DisplayName,
ApplicationServices = context.ApplicationServices,
};
// Apply group conventions to each endpoint in the group at a lower precedent than metadata already on the endpoint.
foreach (var convention in context.Conventions)
{
convention(routeEndpointBuilder);
}
// Any metadata already on the RouteEndpoint must have been applied directly to the endpoint or to a nested group.
// This makes the metadata more specific than what's being applied to this group. So add it after this group's conventions.
foreach (var metadata in routeEndpoint.Metadata)
{
routeEndpointBuilder.Metadata.Add(metadata);
}
foreach (var finallyConvention in context.FinallyConventions)
{
finallyConvention(routeEndpointBuilder);
}
// The RoutePattern, RequestDelegate, Order and DisplayName can all be overridden by non-group-aware conventions.
// Unlike with metadata, if a convention is applied to a group that changes any of these, I would expect these
// to be overridden as there's no reasonable way to merge these properties.
wrappedEndpoints[i] = (RouteEndpoint)routeEndpointBuilder.Build();
}
return wrappedEndpoints;
}
// We don't implement DebuggerDisplay directly on the EndpointDataSource base type because this could have side effects.
internal static string GetDebuggerDisplayStringForEndpoints(IReadOnlyList<Endpoint>? endpoints)
{
if (endpoints is null || endpoints.Count == 0)
{
return "No endpoints";
}
var sb = new StringBuilder();
foreach (var endpoint in endpoints)
{
if (endpoint is RouteEndpoint routeEndpoint)
{
var template = routeEndpoint.RoutePattern.RawText;
template = string.IsNullOrEmpty(template) ? "\"\"" : template;
sb.Append(template);
sb.Append(", Defaults: new { ");
FormatValues(sb, routeEndpoint.RoutePattern.Defaults);
sb.Append(" }");
var routeNameMetadata = routeEndpoint.Metadata.GetMetadata<IRouteNameMetadata>();
sb.Append(", Route Name: ");
sb.Append(routeNameMetadata?.RouteName);
var routeValues = routeEndpoint.RoutePattern.RequiredValues;
if (routeValues.Count > 0)
{
sb.Append(", Required Values: new { ");
FormatValues(sb, routeValues);
sb.Append(" }");
}
sb.Append(", Order: ");
sb.Append(routeEndpoint.Order);
var httpMethodMetadata = routeEndpoint.Metadata.GetMetadata<IHttpMethodMetadata>();
if (httpMethodMetadata is not null)
{
sb.Append(", Http Methods: ");
sb.AppendJoin(", ", httpMethodMetadata.HttpMethods);
}
sb.Append(", Display Name: ");
}
else
{
sb.Append("Non-RouteEndpoint. DisplayName: ");
}
sb.AppendLine(endpoint.DisplayName);
}
return sb.ToString();
static void FormatValues(StringBuilder sb, IEnumerable<KeyValuePair<string, object?>> values)
{
var isFirst = true;
foreach (var (key, value) in values)
{
if (isFirst)
{
isFirst = false;
}
else
{
sb.Append(", ");
}
sb.Append(key);
sb.Append(" = ");
if (value is null)
{
sb.Append("null");
}
else
{
sb.Append('\"');
sb.Append(value);
sb.Append('\"');
}
}
}
}
}