public void AddConventionalLinkGenerationRoute( List <Endpoint> endpoints, HashSet <string> routeNames, HashSet <string> keys, ConventionalRouteEntry route, IReadOnlyList <Action <EndpointBuilder> > conventions) { if (endpoints == null) { throw new ArgumentNullException(nameof(endpoints)); } if (keys == null) { throw new ArgumentNullException(nameof(keys)); } if (conventions == null) { throw new ArgumentNullException(nameof(conventions)); } var requiredValues = new RouteValueDictionary(); foreach (var key in keys) { if (route.Pattern.GetParameter(key) != null) { // Parameter (allow any) requiredValues[key] = RoutePattern.RequiredValueAny; } else if (route.Pattern.Defaults.TryGetValue(key, out var value)) { requiredValues[key] = value; } else { requiredValues[key] = null; } } // We have to do some massaging of the pattern to try and get the // required values to be correct. var pattern = _routePatternTransformer.SubstituteRequiredValues(route.Pattern, requiredValues); if (pattern == null) { // We don't expect this to happen, but we want to know if it does because it will help diagnose the bug. throw new InvalidOperationException("Failed to create a conventional route for pattern: " + route.Pattern); } var builder = new RouteEndpointBuilder(context => Task.CompletedTask, pattern, route.Order) { DisplayName = "Route: " + route.Pattern.RawText, Metadata = { new SuppressMatchingMetadata(), }, }; if (route.RouteName != null) { builder.Metadata.Add(new RouteNameMetadata(route.RouteName)); } // See comments on the other usage of EndpointNameMetadata in this class. // // The set of cases for a conventional route are much simpler. We don't need to check // for Endpoint Name already exising here because there's no way to add an attribute to // a conventional route. if (route.RouteName != null && routeNames.Add(route.RouteName)) { builder.Metadata.Add(new EndpointNameMetadata(route.RouteName)); } for (var i = 0; i < conventions.Count; i++) { conventions[i](builder); } for (var i = 0; i < route.Conventions.Count; i++) { route.Conventions[i](builder); } endpoints.Add((RouteEndpoint)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) { // This kind of thing can happen when a route pattern uses a *reserved* route value such as `action`. // See: https://github.com/aspnet/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 parmaeter 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()); } }