public void AddEndpoints( List <Endpoint> endpoints, HashSet <string> routeNames, ActionDescriptor action, IReadOnlyList <ConventionalRouteEntry> routes, IReadOnlyList <Action <EndpointBuilder> > conventions, bool createInertEndpoints) { if (endpoints == null) { throw new ArgumentNullException(nameof(endpoints)); } if (routeNames == null) { throw new ArgumentNullException(nameof(routeNames)); } if (action == null) { throw new ArgumentNullException(nameof(action)); } if (routes == null) { throw new ArgumentNullException(nameof(routes)); } if (conventions == null) { throw new ArgumentNullException(nameof(conventions)); } if (createInertEndpoints) { var builder = new InertEndpointBuilder() { DisplayName = action.DisplayName, RequestDelegate = _requestDelegate, }; AddActionDataToBuilder( builder, routeNames, action, routeName: null, dataTokens: null, suppressLinkGeneration: false, suppressPathMatching: false, conventions, Array.Empty <Action <EndpointBuilder> >()); endpoints.Add(builder.Build()); } if (action.AttributeRouteInfo?.Template == null) { // Check each of the conventional patterns to see if the action would be reachable. // If the action and pattern are compatible then create an endpoint with action // route values on the pattern. foreach (var route in routes) { // A route is applicable if: // 1. It has a parameter (or default value) for 'required' non-null route value // 2. It does not have a parameter (or default value) for 'required' null route value var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, action.RouteValues); if (updatedRoutePattern == null) { continue; } var requestDelegate = CreateRequestDelegate(action, route.DataTokens) ?? _requestDelegate; // We suppress link generation for each conventionally routed endpoint. We generate a single endpoint per-route // to handle link generation. var builder = new RouteEndpointBuilder(requestDelegate, updatedRoutePattern, route.Order) { DisplayName = action.DisplayName, }; AddActionDataToBuilder( builder, routeNames, action, route.RouteName, route.DataTokens, suppressLinkGeneration: true, suppressPathMatching: false, conventions, route.Conventions); endpoints.Add(builder.Build()); } } else { var requestDelegate = CreateRequestDelegate(action) ?? _requestDelegate; var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template); // Modify the route and required values to ensure required values can be successfully subsituted. // Subsitituting required values into an attribute route pattern should always succeed. var(resolvedRoutePattern, resolvedRouteValues) = ResolveDefaultsAndRequiredValues(action, attributeRoutePattern); var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(resolvedRoutePattern, resolvedRouteValues); if (updatedRoutePattern == null) { // This kind of thing can happen when a route pattern uses a *reserved* route value such as `action`. // See: https://github.com/dotnet/aspnetcore/issues/14789 var formattedRouteKeys = string.Join(", ", resolvedRouteValues.Keys.Select(k => $"'{k}'")); throw new InvalidOperationException( $"Failed to update the route pattern '{resolvedRoutePattern.RawText}' with required route values. " + $"This can occur when the route pattern contains parameters with reserved names such as: {formattedRouteKeys} " + $"and also uses route constraints such as '{{action:int}}'. " + "To fix this error, choose a different parameter name."); } var builder = new RouteEndpointBuilder(requestDelegate, updatedRoutePattern, action.AttributeRouteInfo.Order) { DisplayName = action.DisplayName, }; AddActionDataToBuilder( builder, routeNames, action, action.AttributeRouteInfo.Name, dataTokens: null, action.AttributeRouteInfo.SuppressLinkGeneration, action.AttributeRouteInfo.SuppressPathMatching, conventions, perRouteConventions: Array.Empty <Action <EndpointBuilder> >()); endpoints.Add(builder.Build()); } }
public void AddEndpoints( List <Endpoint> endpoints, HashSet <string> routeNames, ActionDescriptor action, IReadOnlyList <ConventionalRouteEntry> routes, IReadOnlyList <Action <EndpointBuilder> > conventions, bool createInertEndpoints) { if (endpoints == null) { throw new ArgumentNullException(nameof(endpoints)); } if (routeNames == null) { throw new ArgumentNullException(nameof(routeNames)); } if (action == null) { throw new ArgumentNullException(nameof(action)); } if (routes == null) { throw new ArgumentNullException(nameof(routes)); } if (conventions == null) { throw new ArgumentNullException(nameof(conventions)); } if (createInertEndpoints) { var builder = new InertEndpointBuilder() { DisplayName = action.DisplayName, RequestDelegate = _requestDelegate, }; AddActionDataToBuilder( builder, routeNames, action, routeName: null, dataTokens: null, suppressLinkGeneration: false, suppressPathMatching: false, conventions, Array.Empty <Action <EndpointBuilder> >()); endpoints.Add(builder.Build()); } if (action.AttributeRouteInfo == null) { // Check each of the conventional patterns to see if the action would be reachable. // If the action and pattern are compatible then create an endpoint with action // route values on the pattern. foreach (var route in routes) { // A route is applicable if: // 1. It has a parameter (or default value) for 'required' non-null route value // 2. It does not have a parameter (or default value) for 'required' null route value var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, action.RouteValues); if (updatedRoutePattern == null) { continue; } // We suppress link generation for each conventionally routed endpoint. We generate a single endpoint per-route // to handle link generation. var builder = new RouteEndpointBuilder(_requestDelegate, updatedRoutePattern, route.Order) { DisplayName = action.DisplayName, }; AddActionDataToBuilder( builder, routeNames, action, route.RouteName, route.DataTokens, suppressLinkGeneration: true, suppressPathMatching: false, conventions, route.Conventions); endpoints.Add(builder.Build()); } } else { var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template); // Modify the route and required values to ensure required values can be successfully subsituted. // Subsitituting required values into an attribute route pattern should always succeed. var(resolvedRoutePattern, resolvedRouteValues) = ResolveDefaultsAndRequiredValues(action, attributeRoutePattern); var updatedRoutePattern = _routePatternTransformer.SubstituteRequiredValues(resolvedRoutePattern, resolvedRouteValues); if (updatedRoutePattern == null) { throw new InvalidOperationException("Failed to update route pattern with required values."); } var builder = new RouteEndpointBuilder(_requestDelegate, updatedRoutePattern, action.AttributeRouteInfo.Order) { DisplayName = action.DisplayName, }; AddActionDataToBuilder( builder, routeNames, action, action.AttributeRouteInfo.Name, dataTokens: null, action.AttributeRouteInfo.SuppressLinkGeneration, action.AttributeRouteInfo.SuppressPathMatching, conventions, perRouteConventions: Array.Empty <Action <EndpointBuilder> >()); endpoints.Add(builder.Build()); } }