| File: RouteGroupBuilder.cs | Web Access |
| Project: src\aspnetcore\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing) |
// 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.FileProviders; 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 prefix defined by /// <see cref="EndpointRouteBuilderExtensions.MapGroup(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder, RoutePattern)"/> /// and to customize those endpoints using conventions. /// </summary> public sealed class RouteGroupBuilder : IEndpointRouteBuilder, IEndpointConventionBuilder { private readonly IEndpointRouteBuilder _outerEndpointRouteBuilder; private readonly RoutePattern _partialPrefix; private readonly List<EndpointDataSource> _dataSources = new(); private readonly List<Action<EndpointBuilder>> _conventions = new(); private readonly List<Action<EndpointBuilder>> _finallyConventions = new(); internal RouteGroupBuilder(IEndpointRouteBuilder outerEndpointRouteBuilder, RoutePattern partialPrefix) { _outerEndpointRouteBuilder = outerEndpointRouteBuilder; _partialPrefix = partialPrefix; _outerEndpointRouteBuilder.DataSources.Add(new GroupEndpointDataSource(this)); } IServiceProvider IEndpointRouteBuilder.ServiceProvider => _outerEndpointRouteBuilder.ServiceProvider; IApplicationBuilder IEndpointRouteBuilder.CreateApplicationBuilder() => _outerEndpointRouteBuilder.CreateApplicationBuilder(); ICollection<EndpointDataSource> IEndpointRouteBuilder.DataSources => _dataSources; void IEndpointConventionBuilder.Add(Action<EndpointBuilder> convention) => _conventions.Add(convention); void IEndpointConventionBuilder.Finally(Action<EndpointBuilder> finalConvention) => _finallyConventions.Add(finalConvention); private sealed class GroupEndpointDataSource : EndpointDataSource, IDisposable { private readonly RouteGroupBuilder _routeGroupBuilder; private CompositeEndpointDataSource? _compositeDataSource; public GroupEndpointDataSource(RouteGroupBuilder groupRouteBuilder) { _routeGroupBuilder = groupRouteBuilder; } public override IReadOnlyList<Endpoint> Endpoints => GetGroupedEndpointsWithNullablePrefix(null, Array.Empty<Action<EndpointBuilder>>(), Array.Empty<Action<EndpointBuilder>>(), _routeGroupBuilder._outerEndpointRouteBuilder.ServiceProvider); public override IReadOnlyList<Endpoint> GetGroupedEndpoints(RouteGroupContext context) => GetGroupedEndpointsWithNullablePrefix(context.Prefix, context.Conventions, context.FinallyConventions, context.ApplicationServices); public IReadOnlyList<Endpoint> GetGroupedEndpointsWithNullablePrefix( RoutePattern? prefix, IReadOnlyList<Action<EndpointBuilder>> conventions, IReadOnlyList<Action<EndpointBuilder>> finallyConventions, IServiceProvider applicationServices) { return _routeGroupBuilder._dataSources.Count switch { 0 => Array.Empty<Endpoint>(), 1 => _routeGroupBuilder._dataSources[0].GetGroupedEndpoints(GetNextRouteGroupContext(prefix, conventions, finallyConventions, applicationServices)), _ => SelectEndpointsFromAllDataSources(GetNextRouteGroupContext(prefix, conventions, finallyConventions, applicationServices)), }; } public override IChangeToken GetChangeToken() => _routeGroupBuilder._dataSources.Count switch { 0 => NullChangeToken.Singleton, 1 => _routeGroupBuilder._dataSources[0].GetChangeToken(), _ => GetCompositeChangeToken(), }; public void Dispose() { _compositeDataSource?.Dispose(); foreach (var dataSource in _routeGroupBuilder._dataSources) { (dataSource as IDisposable)?.Dispose(); } } private RouteGroupContext GetNextRouteGroupContext( RoutePattern? prefix, IReadOnlyList<Action<EndpointBuilder>> conventions, IReadOnlyList<Action<EndpointBuilder>> finallyConventions, IServiceProvider applicationServices) { var fullPrefix = RoutePatternFactory.Combine(prefix, _routeGroupBuilder._partialPrefix); // Apply conventions passed in from the outer group first so their metadata is added earlier in the list at a lower precedent. var combinedConventions = RoutePatternFactory.CombineLists(conventions, _routeGroupBuilder._conventions); var combinedFinallyConventions = RoutePatternFactory.CombineLists(_routeGroupBuilder._finallyConventions, finallyConventions); return new RouteGroupContext { Prefix = fullPrefix, Conventions = combinedConventions, FinallyConventions = combinedFinallyConventions, ApplicationServices = applicationServices }; } private IReadOnlyList<Endpoint> SelectEndpointsFromAllDataSources(RouteGroupContext context) { var groupedEndpoints = new List<Endpoint>(); foreach (var dataSource in _routeGroupBuilder._dataSources) { groupedEndpoints.AddRange(dataSource.GetGroupedEndpoints(context)); } return groupedEndpoints; } private IChangeToken GetCompositeChangeToken() { // We are not guarding against concurrent RouteGroupBuilder._dataSources mutation. // This is only to avoid double initialization of _compositeDataSource if GetChangeToken() is called concurrently. lock (_routeGroupBuilder._dataSources) { _compositeDataSource ??= new CompositeEndpointDataSource(_routeGroupBuilder._dataSources); } return _compositeDataSource.GetChangeToken(); } } }