// CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values // Because of default values it is possible for a route pattern to resolve to multiple endpoints private int CreateEndpoints( List <Endpoint> endpoints, ref StringBuilder patternStringBuilder, ActionDescriptor action, int routeOrder, RoutePattern routePattern, IReadOnlyDictionary <string, object> allDefaults, IReadOnlyDictionary <string, object> nonInlineDefaults, string name, RouteValueDictionary dataTokens, IDictionary <string, IList <IParameterPolicy> > allParameterPolicies, bool suppressLinkGeneration, bool suppressPathMatching) { var newPathSegments = routePattern.PathSegments.ToList(); for (var i = 0; i < newPathSegments.Count; i++) { // Check if the pattern can be shortened because the remaining parameters are optional // // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index // can resolve to the following endpoints: // - /Home/Index/{id?} // - /Home // - / if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, allDefaults, newPathSegments)) { var subPathSegments = newPathSegments.Take(i); var subEndpoint = CreateEndpoint( action, name, GetPattern(ref patternStringBuilder, subPathSegments), subPathSegments, nonInlineDefaults, routeOrder++, dataTokens, suppressLinkGeneration, suppressPathMatching); endpoints.Add(subEndpoint); } List <RoutePatternPart> segmentParts = null; // Initialize only as needed var segment = newPathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; if (part.IsParameter && part is RoutePatternParameterPart parameterPart && action.RouteValues.ContainsKey(parameterPart.Name)) { if (segmentParts == null) { segmentParts = segment.Parts.ToList(); } if (allParameterPolicies == null) { allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); } var parameterRouteValue = action.RouteValues[parameterPart.Name]; // Replace parameter with literal value if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) { // Check if the parameter has a transformer policy // Use the first transformer policy for (var k = 0; k < parameterPolicies.Count; k++) { if (parameterPolicies[k] is IParameterTransformer parameterTransformer) { parameterRouteValue = parameterTransformer.Transform(parameterRouteValue); break; } } } segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); } } // A parameter part was replaced so replace segment with updated parts if (segmentParts != null) { newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); } } var endpoint = CreateEndpoint( action, name, GetPattern(ref patternStringBuilder, newPathSegments), newPathSegments, nonInlineDefaults, routeOrder++, dataTokens, suppressLinkGeneration, suppressPathMatching); endpoints.Add(endpoint); return(routeOrder); string GetPattern(ref StringBuilder sb, IEnumerable <RoutePatternPathSegment> segments) { if (sb == null) { sb = new StringBuilder(); } RoutePatternWriter.WriteString(sb, segments); var rawPattern = sb.ToString(); sb.Length = 0; return(rawPattern); } }
private void UpdateEndpoints() { lock (_lock) { var endpoints = new List <Endpoint>(); StringBuilder patternStringBuilder = null; foreach (var action in _actions.ActionDescriptors.Items) { if (action.AttributeRouteInfo == null) { // In traditional conventional routing setup, the routes defined by a user have a static order // defined by how they are added into the list. We would like to maintain the same order when building // up the endpoints too. // // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. var conventionalRouteOrder = 0; // 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 the // area/controller/action parameter parts replaced with literals // // e.g. {controller}/{action} with HomeController.Index and HomeController.Login // would result in endpoints: // - Home/Index // - Home/Login foreach (var endpointInfo in ConventionalEndpointInfos) { // An 'endpointInfo' 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 isApplicable = true; foreach (var routeKey in action.RouteValues.Keys) { if (!MatchRouteValue(action, endpointInfo, routeKey)) { isApplicable = false; break; } } if (!isApplicable) { continue; } var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList(); for (var i = 0; i < newPathSegments.Count; i++) { // Check if the pattern can be shortened because the remaining parameters are optional // // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index // can resolve to the following endpoints: // - /Home/Index/{id?} // - /Home // - / if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, endpointInfo, newPathSegments)) { var subPathSegments = newPathSegments.Take(i); var subEndpoint = CreateEndpoint( action, endpointInfo.Name, GetPattern(ref patternStringBuilder, subPathSegments), subPathSegments, endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, endpointInfo.DataTokens, suppressLinkGeneration: false, suppressPathMatching: false); endpoints.Add(subEndpoint); } List <RoutePatternPart> segmentParts = null; // Initialize only as needed var segment = newPathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; if (part.IsParameter && part is RoutePatternParameterPart parameterPart && action.RouteValues.ContainsKey(parameterPart.Name)) { if (segmentParts == null) { segmentParts = segment.Parts.ToList(); } // Replace parameter with literal value segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]); } } // A parameter part was replaced so replace segment with updated parts if (segmentParts != null) { newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); } } var endpoint = CreateEndpoint( action, endpointInfo.Name, GetPattern(ref patternStringBuilder, newPathSegments), newPathSegments, endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, endpointInfo.DataTokens, suppressLinkGeneration: false, suppressPathMatching: false); endpoints.Add(endpoint); } } else { var endpoint = CreateEndpoint( action, action.AttributeRouteInfo.Name, action.AttributeRouteInfo.Template, RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments, nonInlineDefaults: null, action.AttributeRouteInfo.Order, action.AttributeRouteInfo, dataTokens: null, suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration, suppressPathMatching: action.AttributeRouteInfo.SuppressPathMatching); endpoints.Add(endpoint); } } // See comments in DefaultActionDescriptorCollectionProvider. These steps are done // in a specific order to ensure callers always see a consistent state. // Step 1 - capture old token var oldCancellationTokenSource = _cancellationTokenSource; // Step 2 - update endpoints _endpoints = endpoints; // Step 3 - create new change token _cancellationTokenSource = new CancellationTokenSource(); _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); // Step 4 - trigger old token oldCancellationTokenSource?.Cancel(); } string GetPattern(ref StringBuilder sb, IEnumerable <RoutePatternPathSegment> segments) { if (sb == null) { sb = new StringBuilder(); } RoutePatternWriter.WriteString(sb, segments); var rawPattern = sb.ToString(); sb.Length = 0; return(rawPattern); } }
// CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values // Because of default values it is possible for a route pattern to resolve to multiple endpoints private int CreateEndpoints( List <Endpoint> endpoints, ref StringBuilder patternStringBuilder, ActionDescriptor action, int routeOrder, RoutePattern routePattern, IReadOnlyDictionary <string, object> allDefaults, IReadOnlyDictionary <string, object> nonInlineDefaults, string name, RouteValueDictionary dataTokens, IDictionary <string, IList <IParameterPolicy> > allParameterPolicies, bool suppressLinkGeneration, bool suppressPathMatching) { var newPathSegments = routePattern.PathSegments.ToList(); var hasLinkGenerationEndpoint = false; // This is required because we create modified copies of the route pattern using its segments // A segment with a parameter will automatically include its policies // Non-parameter policies need to be manually included var nonParameterPolicyValues = routePattern.ParameterPolicies .Where(p => routePattern.GetParameter(p.Key ?? string.Empty) == null && p.Value.Count > 0 && p.Value.First().ParameterPolicy != null) // Only GetParameter is required. Extra is for safety .Select(p => new KeyValuePair <string, object>(p.Key, p.Value.First().ParameterPolicy)) // Can only pass a single non-parameter to RouteParameter .ToArray(); var nonParameterPolicies = RouteValueDictionary.FromArray(nonParameterPolicyValues); // Create a mutable copy var nonInlineDefaultsCopy = nonInlineDefaults != null ? new RouteValueDictionary(nonInlineDefaults) : null; var resolvedRouteValues = ResolveActionRouteValues(action, allDefaults); for (var i = 0; i < newPathSegments.Count; i++) { // Check if the pattern can be shortened because the remaining parameters are optional // // e.g. Matching pattern {controller=Home}/{action=Index} against HomeController.Index // can resolve to the following endpoints: (sorted by RouteEndpoint.Order) // - / // - /Home // - /Home/Index if (UseDefaultValuePlusRemainingSegmentsOptional( i, action, resolvedRouteValues, allDefaults, ref nonInlineDefaultsCopy, newPathSegments)) { // The route pattern has matching default values AND an optional parameter // For link generation we need to include an endpoint with parameters and default values // so the link is correctly shortened // e.g. {controller=Home}/{action=Index}/{id=17} if (!hasLinkGenerationEndpoint) { var ep = CreateEndpoint( action, resolvedRouteValues, name, GetPattern(ref patternStringBuilder, newPathSegments), nonParameterPolicies, newPathSegments, nonInlineDefaultsCopy, routeOrder++, dataTokens, suppressLinkGeneration, true); endpoints.Add(ep); hasLinkGenerationEndpoint = true; } var subPathSegments = newPathSegments.Take(i); var subEndpoint = CreateEndpoint( action, resolvedRouteValues, name, GetPattern(ref patternStringBuilder, subPathSegments), nonParameterPolicies, subPathSegments, nonInlineDefaultsCopy, routeOrder++, dataTokens, suppressLinkGeneration, suppressPathMatching); endpoints.Add(subEndpoint); } UpdatePathSegments(i, action, resolvedRouteValues, routePattern, newPathSegments, ref allParameterPolicies); } var finalEndpoint = CreateEndpoint( action, resolvedRouteValues, name, GetPattern(ref patternStringBuilder, newPathSegments), nonParameterPolicies, newPathSegments, nonInlineDefaultsCopy, routeOrder++, dataTokens, suppressLinkGeneration, suppressPathMatching); endpoints.Add(finalEndpoint); return(routeOrder); string GetPattern(ref StringBuilder sb, IEnumerable <RoutePatternPathSegment> segments) { if (sb == null) { sb = new StringBuilder(); } RoutePatternWriter.WriteString(sb, segments); var rawPattern = sb.ToString(); sb.Length = 0; return(rawPattern); } }
// CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values // Because of default values it is possible for a route pattern to resolve to multiple endpoints private int CreateEndpoints( List <Endpoint> endpoints, ref StringBuilder patternStringBuilder, ActionDescriptor action, int routeOrder, RoutePattern routePattern, IReadOnlyDictionary <string, object> allDefaults, IReadOnlyDictionary <string, object> nonInlineDefaults, string name, RouteValueDictionary dataTokens, IDictionary <string, IList <IParameterPolicy> > allParameterPolicies, bool suppressLinkGeneration, bool suppressPathMatching, List <Action <EndpointModel> > conventions) { var newPathSegments = routePattern.PathSegments.ToList(); var hasLinkGenerationEndpoint = false; // Create a mutable copy var nonInlineDefaultsCopy = nonInlineDefaults != null ? new RouteValueDictionary(nonInlineDefaults) : null; var resolvedRouteValues = ResolveActionRouteValues(action, allDefaults); for (var i = 0; i < newPathSegments.Count; i++) { // Check if the pattern can be shortened because the remaining parameters are optional // // e.g. Matching pattern {controller=Home}/{action=Index} against HomeController.Index // can resolve to the following endpoints: (sorted by RouteEndpoint.Order) // - / // - /Home // - /Home/Index if (UseDefaultValuePlusRemainingSegmentsOptional( i, action, resolvedRouteValues, allDefaults, ref nonInlineDefaultsCopy, newPathSegments)) { // The route pattern has matching default values AND an optional parameter // For link generation we need to include an endpoint with parameters and default values // so the link is correctly shortened // e.g. {controller=Home}/{action=Index}/{id=17} if (!hasLinkGenerationEndpoint) { var ep = CreateEndpoint( action, resolvedRouteValues, name, GetPattern(ref patternStringBuilder, newPathSegments), newPathSegments, nonInlineDefaultsCopy, routeOrder++, dataTokens, suppressLinkGeneration, true, conventions); endpoints.Add(ep); hasLinkGenerationEndpoint = true; } var subPathSegments = newPathSegments.Take(i); var subEndpoint = CreateEndpoint( action, resolvedRouteValues, name, GetPattern(ref patternStringBuilder, subPathSegments), subPathSegments, nonInlineDefaultsCopy, routeOrder++, dataTokens, suppressLinkGeneration, suppressPathMatching, conventions); endpoints.Add(subEndpoint); } UpdatePathSegments(i, action, resolvedRouteValues, routePattern, newPathSegments, ref allParameterPolicies); } var finalEndpoint = CreateEndpoint( action, resolvedRouteValues, name, GetPattern(ref patternStringBuilder, newPathSegments), newPathSegments, nonInlineDefaultsCopy, routeOrder++, dataTokens, suppressLinkGeneration, suppressPathMatching, conventions); endpoints.Add(finalEndpoint); return(routeOrder); string GetPattern(ref StringBuilder sb, IEnumerable <RoutePatternPathSegment> segments) { if (sb == null) { sb = new StringBuilder(); } RoutePatternWriter.WriteString(sb, segments); var rawPattern = sb.ToString(); sb.Length = 0; return(rawPattern); } }
private List <Endpoint> CreateEndpoints() { var endpoints = new List <Endpoint>(); StringBuilder patternStringBuilder = null; foreach (var action in _actions.ActionDescriptors.Items) { if (action.AttributeRouteInfo == null) { // In traditional conventional routing setup, the routes defined by a user have a static order // defined by how they are added into the list. We would like to maintain the same order when building // up the endpoints too. // // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. var conventionalRouteOrder = 0; // 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 the // area/controller/action parameter parts replaced with literals // // e.g. {controller}/{action} with HomeController.Index and HomeController.Login // would result in endpoints: // - Home/Index // - Home/Login foreach (var endpointInfo in ConventionalEndpointInfos) { if (MatchRouteValue(action, endpointInfo, "Area") && MatchRouteValue(action, endpointInfo, "Controller") && MatchRouteValue(action, endpointInfo, "Action")) { var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList(); for (var i = 0; i < newPathSegments.Count; i++) { // Check if the pattern can be shortened because the remaining parameters are optional // // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index // can resolve to the following endpoints: // - /Home/Index/{id?} // - /Home // - / if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments)) { var subPathSegments = newPathSegments.Take(i); var subEndpoint = CreateEndpoint( action, endpointInfo.Name, GetPattern(ref patternStringBuilder, subPathSegments), subPathSegments, endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, suppressLinkGeneration: false); endpoints.Add(subEndpoint); } List <RoutePatternPart> segmentParts = null; // Initialize only as needed var segment = newPathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; if (part.IsParameter && part is RoutePatternParameterPart parameterPart && IsMvcParameter(parameterPart.Name)) { if (segmentParts == null) { segmentParts = segment.Parts.ToList(); } // Replace parameter with literal value segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]); } } // A parameter part was replaced so replace segment with updated parts if (segmentParts != null) { newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); } } var endpoint = CreateEndpoint( action, endpointInfo.Name, GetPattern(ref patternStringBuilder, newPathSegments), newPathSegments, endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, suppressLinkGeneration: false); endpoints.Add(endpoint); } } } else { var endpoint = CreateEndpoint( action, action.AttributeRouteInfo.Name, action.AttributeRouteInfo.Template, RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments, nonInlineDefaults: null, action.AttributeRouteInfo.Order, action.AttributeRouteInfo, suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration); endpoints.Add(endpoint); } } return(endpoints); string GetPattern(ref StringBuilder sb, IEnumerable <RoutePatternPathSegment> segments) { if (sb == null) { sb = new StringBuilder(); } RoutePatternWriter.WriteString(sb, segments); var rawPattern = sb.ToString(); sb.Length = 0; return(rawPattern); } }