public void Pattern_RawTextAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments() { // Arrange var rawText = "raw"; var literalPartA = RoutePatternFactory.LiteralPart("A"); var paramPartB = RoutePatternFactory.ParameterPart("B"); var paramPartC = RoutePatternFactory.ParameterPart("C"); var paramPartD = RoutePatternFactory.ParameterPart("D"); var segments = new[] { RoutePatternFactory.Segment(literalPartA, paramPartB), RoutePatternFactory.Segment(paramPartC, literalPartA), RoutePatternFactory.Segment(paramPartD), RoutePatternFactory.Segment(literalPartA) }; // Act var actual = RoutePatternFactory.Pattern(rawText, segments); segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E")); Array.Resize(ref segments, 2); // Assert Assert.Equal(3, actual.Parameters.Count); Assert.Same(paramPartB, actual.Parameters[0]); Assert.Same(paramPartC, actual.Parameters[1]); Assert.Same(paramPartD, actual.Parameters[2]); }
public void ToRoutePatternPathSegment() { // Arrange var literalPartA = RoutePatternFactory.LiteralPart("A"); var paramPartB = RoutePatternFactory.ParameterPart("B"); var paramPartC = RoutePatternFactory.ParameterPart("C"); var paramPartD = RoutePatternFactory.ParameterPart("D"); var separatorPartE = RoutePatternFactory.SeparatorPart("E"); var templateSegment = new TemplateSegment(RoutePatternFactory.Segment(paramPartC, literalPartA, separatorPartE, paramPartB)); // Act var routePatternPathSegment = templateSegment.ToRoutePatternPathSegment(); templateSegment.Parts[1] = new TemplatePart(RoutePatternFactory.ParameterPart("D")); templateSegment.Parts.RemoveAt(0); // Assert Assert.Equal(4, routePatternPathSegment.Parts.Count); Assert.IsType <RoutePatternParameterPart>(routePatternPathSegment.Parts[0]); Assert.Equal(paramPartC.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[0]).Name); Assert.IsType <RoutePatternLiteralPart>(routePatternPathSegment.Parts[1]); Assert.Equal(literalPartA.Content, ((RoutePatternLiteralPart)routePatternPathSegment.Parts[1]).Content); Assert.IsType <RoutePatternSeparatorPart>(routePatternPathSegment.Parts[2]); Assert.Equal(separatorPartE.Content, ((RoutePatternSeparatorPart)routePatternPathSegment.Parts[2]).Content); Assert.IsType <RoutePatternParameterPart>(routePatternPathSegment.Parts[3]); Assert.Equal(paramPartB.Name, ((RoutePatternParameterPart)routePatternPathSegment.Parts[3]).Name); }
private static void RemoveParameterDefault(List <RoutePatternPathSegment> segments, List <RoutePatternParameterPart> parameters, RoutePatternParameterPart parameter) { // We know that a parameter can only appear once, so we only need to rewrite one segment and one parameter. for (var i = 0; i < segments.Count; i++) { var segment = segments[i]; for (var j = 0; j < segment.Parts.Count; j++) { if (object.ReferenceEquals(parameter, segment.Parts[j])) { // Found it! var updatedParameter = RoutePatternFactory.ParameterPart(parameter.Name, @default: null, parameter.ParameterKind, parameter.ParameterPolicies); var updatedParts = new List <RoutePatternPart>(segment.Parts); updatedParts[j] = updatedParameter; segments[i] = RoutePatternFactory.Segment(updatedParts); for (var k = 0; k < parameters.Count; k++) { if (ReferenceEquals(parameter, parameters[k])) { parameters[k] = updatedParameter; break; } } return; } } } }
public void Segment_ArrayOfParts() { // Arrange var paramPartB = RoutePatternFactory.ParameterPart("B"); var paramPartC = RoutePatternFactory.ParameterPart("C"); var paramPartD = RoutePatternFactory.ParameterPart("D"); var parts = new[] { paramPartB, paramPartC, paramPartD }; // Act var actual = RoutePatternFactory.Segment(parts); parts[1] = RoutePatternFactory.ParameterPart("E"); Array.Resize(ref parts, 2); // Assert Assert.Equal(3, actual.Parts.Count); Assert.Same(paramPartB, actual.Parts[0]); Assert.Same(paramPartC, actual.Parts[1]); Assert.Same(paramPartD, actual.Parts[2]); }
public void Pattern_RawTextAndDefaultsAndParameterPoliciesAndArrayOfSegments_ShouldMakeCopyOfArrayOfSegments() { // Arrange var rawText = "raw"; object defaults = new { B = 12, C = 4 }; object parameterPolicies = null; var literalPartA = RoutePatternFactory.LiteralPart("A"); var paramPartB = RoutePatternFactory.ParameterPart("B"); var paramPartC = RoutePatternFactory.ParameterPart("C"); var paramPartD = RoutePatternFactory.ParameterPart("D"); var segments = new[] { RoutePatternFactory.Segment(literalPartA, paramPartB), RoutePatternFactory.Segment(paramPartC, literalPartA), RoutePatternFactory.Segment(paramPartD), RoutePatternFactory.Segment(literalPartA) }; // Act var actual = RoutePatternFactory.Pattern(rawText, defaults, parameterPolicies, segments); segments[1] = RoutePatternFactory.Segment(RoutePatternFactory.ParameterPart("E")); Array.Resize(ref segments, 2); // Assert Assert.Equal(3, actual.Parameters.Count); Assert.Equal(paramPartB.Name, actual.Parameters[0].Name); Assert.Equal(12, actual.Parameters[0].Default); Assert.Null(paramPartB.Default); Assert.NotSame(paramPartB, actual.Parameters[0]); Assert.Equal(paramPartC.Name, actual.Parameters[1].Name); Assert.Equal(4, actual.Parameters[1].Default); Assert.NotSame(paramPartC, actual.Parameters[1]); Assert.Null(paramPartC.Default); Assert.Equal(paramPartD.Name, actual.Parameters[2].Name); Assert.Null(actual.Parameters[2].Default); Assert.Same(paramPartD, actual.Parameters[2]); Assert.Null(paramPartD.Default); }
private void UpdatePathSegments( int i, ActionDescriptor action, IDictionary <string, string> resolvedRequiredValues, RoutePattern routePattern, List <RoutePatternPathSegment> newPathSegments, ref IDictionary <string, IList <IParameterPolicy> > allParameterPolicies) { 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 is RoutePatternParameterPart parameterPart) { if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var parameterRouteValue)) { if (segmentParts == null) { segmentParts = segment.Parts.ToList(); } if (allParameterPolicies == null) { allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); } // Route value could be null if it is a "known" route value. // Do not use the null value to de-normalize the route pattern, // instead leave the parameter unchanged. // e.g. // RouteValues will contain a null "page" value if there are Razor pages // Skip replacing the {page} parameter if (parameterRouteValue != null) { 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 IOutboundParameterTransformer parameterTransformer) { parameterRouteValue = parameterTransformer.TransformOutbound(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); } }
public RoutePatternPathSegment ToRoutePatternPathSegment() { var parts = Parts.Select(p => p.ToRoutePatternPart()); return(RoutePatternFactory.Segment(parts)); }
// 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); } }
private IReadOnlyList <Endpoint> Update(StaticFileOptionsProvider staticFileOptionsProvider) { const string HtmlExtension = ".html"; const string CatchAllSlugPrefix = "..."; var staticFileOptions = staticFileOptionsProvider.StaticFileOptions; var requestDelegate = CreateRequestDelegate(this.endpointRouteBuilder, staticFileOptions); var endpoints = new List <Endpoint>(); foreach (var filePath in TraverseFiles(staticFileOptions.FileProvider)) { if (!filePath.EndsWith(HtmlExtension)) { continue; } var fileWithoutHtml = filePath.Substring(0, filePath.Length - HtmlExtension.Length); var patternSegments = new List <RoutePatternPathSegment>(); var segments = fileWithoutHtml.Split('/'); // NOTE: Start at 1 because paths here always have a leading slash for (int i = 1; i < segments.Length; i++) { var segment = segments[i]; if (i == segments.Length - 1 && segment == "index") { // Skip `index` segment, match whatever we got so far. // This is so that e.g. file `/a/b/index.html` is served at path `/a/b`, as desired. // TODO: Should we also serve the same file at `/a/b/index`? Note that `/a/b/index.html` will already work // via the UseStaticFiles middleware added by `NextjsStaticHostingExtensions.UseNextjsStaticHosting`. break; } var match = slugRegex.Match(segment); if (match.Success) { string slugName = match.Groups[1].Value; if (slugName.StartsWith(CatchAllSlugPrefix)) { // Catch all route -- see: https://nextjs.org/docs/routing/dynamic-routes#catch-all-routes var parameterName = slugName.Substring(CatchAllSlugPrefix.Length); patternSegments.Add( RoutePatternFactory.Segment( RoutePatternFactory.ParameterPart(parameterName, null, RoutePatternParameterKind.CatchAll))); } else { // Dynamic route -- see: https://nextjs.org/docs/routing/dynamic-routes patternSegments.Add( RoutePatternFactory.Segment( RoutePatternFactory.ParameterPart(slugName))); } } else { // Literal match patternSegments.Add( RoutePatternFactory.Segment( RoutePatternFactory.LiteralPart(segment))); } } var endpointBuilder = new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Pattern(patternSegments), order: DefaultEndpointOrder); endpointBuilder.Metadata.Add(new StaticFileEndpointMetadata(filePath)); endpointBuilder.DisplayName = $"Next.js {filePath}"; var endpoint = endpointBuilder.Build(); endpoints.Add(endpoint); } return(endpoints); }
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); } }