-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Add MapGroup #41265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Add MapGroup #41265
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
b85faba
Fix EndpointMetadataCollection nullability
halter73 902fd4c
Add MapGroup
halter73 0dee84c
*REMOVED*
halter73 c2510aa
Add test
halter73 c84506f
Fix PublicAPI for EndpointMetadataCollection type forward
halter73 6547110
Apply suggestions from code review
halter73 71489f7
Set RouteEndpointBuilder.ServiceProvider
halter73 2debe27
s/prefixPattern/prefix/
halter73 493c5d0
Add MapGroup_DataSourceFiresChangeToken_WhenInnerDataSourceFiresChang…
halter73 e9405d7
Add RoutingGroupsTest to Mvc.FunctionalTests
halter73 44c9b04
Add MapGroup_ChangingMostEndpointBuilderPropertiesInConvention_Works
halter73 017d0b6
Add MapGroup_SupportsMultipleEndpoints
halter73 77fa894
Move new tests to GroupTest.cs
halter73 38b20d5
Clean StartupForGroups
halter73 b5b6eba
Fix nullable reference warning in GroupTests.cs
halter73 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
#nullable enable | ||
Microsoft.AspNetCore.Http.EndpointMetadataCollection.GetRequiredMetadata<T>() -> T! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) | ||
*REMOVED*Microsoft.AspNetCore.Http.EndpointMetadataCollection.Enumerator.Current.get -> object? (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) | ||
Microsoft.AspNetCore.Http.EndpointMetadataCollection.Enumerator.Current.get -> object! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) | ||
Microsoft.AspNetCore.Http.EndpointMetadataCollection.GetRequiredMetadata<T>() -> T! (forwarded, contained in Microsoft.AspNetCore.Http.Abstractions) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Routing.Patterns; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Microsoft.AspNetCore.Routing; | ||
|
||
/// <summary> | ||
/// A builder for defining groups of endpoints with a common prefix that implements both the <see cref="IEndpointRouteBuilder"/> | ||
/// and <see cref="IEndpointConventionBuilder"/> interfaces. This can be used to add endpoints with the given <see cref="GroupPrefix"/>, | ||
/// and to customize those endpoints using conventions. | ||
/// </summary> | ||
public sealed class GroupRouteBuilder : IEndpointRouteBuilder, IEndpointConventionBuilder | ||
{ | ||
private readonly IEndpointRouteBuilder _outerEndpointRouteBuilder; | ||
private readonly RoutePattern _pattern; | ||
|
||
private readonly List<EndpointDataSource> _dataSources = new(); | ||
private readonly List<Action<EndpointBuilder>> _conventions = new(); | ||
|
||
internal GroupRouteBuilder(IEndpointRouteBuilder outerEndpointRouteBuilder, RoutePattern pattern) | ||
{ | ||
_outerEndpointRouteBuilder = outerEndpointRouteBuilder; | ||
_pattern = pattern; | ||
|
||
if (outerEndpointRouteBuilder is GroupRouteBuilder outerGroup) | ||
{ | ||
GroupPrefix = RoutePatternFactory.Combine(outerGroup.GroupPrefix, pattern); | ||
} | ||
else | ||
{ | ||
GroupPrefix = pattern; | ||
} | ||
|
||
_outerEndpointRouteBuilder.DataSources.Add(new GroupDataSource(this)); | ||
} | ||
|
||
/// <summary> | ||
/// The <see cref="RoutePattern"/> prefixing all endpoints defined using this <see cref="GroupRouteBuilder"/>. | ||
/// This accounts for nested groups and gives the full group prefix, not just the prefix supplied to the last call to | ||
/// <see cref="EndpointRouteBuilderExtensions.MapGroup(IEndpointRouteBuilder, RoutePattern)"/>. | ||
/// </summary> | ||
public RoutePattern GroupPrefix { get; } | ||
|
||
IServiceProvider IEndpointRouteBuilder.ServiceProvider => _outerEndpointRouteBuilder.ServiceProvider; | ||
IApplicationBuilder IEndpointRouteBuilder.CreateApplicationBuilder() => _outerEndpointRouteBuilder.CreateApplicationBuilder(); | ||
ICollection<EndpointDataSource> IEndpointRouteBuilder.DataSources => _dataSources; | ||
void IEndpointConventionBuilder.Add(Action<EndpointBuilder> convention) => _conventions.Add(convention); | ||
|
||
private bool IsRoot => ReferenceEquals(GroupPrefix, _pattern); | ||
|
||
private sealed class GroupDataSource : EndpointDataSource | ||
{ | ||
private readonly GroupRouteBuilder _groupRouteBuilder; | ||
|
||
public GroupDataSource(GroupRouteBuilder groupRouteBuilder) | ||
{ | ||
_groupRouteBuilder = groupRouteBuilder; | ||
} | ||
|
||
public override IReadOnlyList<Endpoint> Endpoints | ||
{ | ||
get | ||
{ | ||
var list = new List<Endpoint>(); | ||
|
||
foreach (var dataSource in _groupRouteBuilder._dataSources) | ||
{ | ||
foreach (var endpoint in dataSource.Endpoints) | ||
{ | ||
// 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(_groupRouteBuilder.GroupPrefix, routeEndpoint.RoutePattern); | ||
|
||
// RequestDelegate can never be null on a RouteEndpoint. The nullability carries over from Endpoint. | ||
var routeEndpointBuilder = new RouteEndpointBuilder(routeEndpoint.RequestDelegate!, fullRoutePattern, routeEndpoint.Order) | ||
{ | ||
DisplayName = routeEndpoint.DisplayName, | ||
ServiceProvider = _groupRouteBuilder._outerEndpointRouteBuilder.ServiceProvider, | ||
}; | ||
|
||
// Apply group conventions to each endpoint in the group at a lower precedent than metadata already on the endpoint. | ||
foreach (var convention in _groupRouteBuilder._conventions) | ||
{ | ||
convention(routeEndpointBuilder); | ||
} | ||
|
||
// If we supported mutating the route pattern via a group convention, RouteEndpointBuilder.RoutePattern would have | ||
// to be the partialRoutePattern (below) instead of the fullRoutePattern (above) since that's all we can control. We cannot | ||
// change a parent prefix. In order to allow to conventions to read the fullRoutePattern, we do not support mutation. | ||
if (!ReferenceEquals(fullRoutePattern, routeEndpointBuilder.RoutePattern)) | ||
{ | ||
throw new NotSupportedException(Resources.FormatMapGroup_ChangingRoutePatternUnsupported( | ||
fullRoutePattern.RawText, routeEndpointBuilder.RoutePattern.RawText)); | ||
} | ||
|
||
// 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. | ||
// | ||
// REVIEW: This means group conventions don't get visibility into endpoint-specific metadata nor the ability to override it. | ||
// We should consider allowing group-aware conventions the ability to read and mutate this metadata in future releases. | ||
foreach (var metadata in routeEndpoint.Metadata) | ||
{ | ||
routeEndpointBuilder.Metadata.Add(metadata); | ||
} | ||
|
||
// Use _pattern instead of GroupPrefix when we're calculating an intermediate RouteEndpoint. | ||
var partialRoutePattern = _groupRouteBuilder.IsRoot | ||
? fullRoutePattern : RoutePatternFactory.Combine(_groupRouteBuilder._pattern, routeEndpoint.RoutePattern); | ||
|
||
// The 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. | ||
list.Add(new RouteEndpoint( | ||
// Again, RequestDelegate can never be null given a RouteEndpoint. | ||
routeEndpointBuilder.RequestDelegate!, | ||
partialRoutePattern, | ||
routeEndpointBuilder.Order, | ||
new(routeEndpointBuilder.Metadata), | ||
routeEndpointBuilder.DisplayName)); | ||
} | ||
} | ||
|
||
return list; | ||
} | ||
} | ||
|
||
public override IChangeToken GetChangeToken() => new CompositeEndpointDataSource(_groupRouteBuilder._dataSources).GetChangeToken(); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.