private static int ComputeOutboundPrecedenceDigit(TemplateSegment segment) { if (segment.Parts.Count > 1) { return(4); } var part = segment.Parts[0]; if (part.IsLiteral) { return(5); } else { Debug.Assert(part.IsParameter); var digit = part.IsCatchAll ? 1 : 3; if (part.InlineConstraints != null && part.InlineConstraints.Any()) { digit++; } return(digit); } }
internal static RouteTemplate ParseTemplate(string template) { var originalTemplate = template; template = template.Trim('/'); if (template == string.Empty) { // Special case "/"; return(new RouteTemplate("/", Array.Empty <TemplateSegment>())); } var segments = template.Split('/'); var templateSegments = new TemplateSegment[segments.Length]; for (int i = 0; i < segments.Length; i++) { var segment = segments[i]; if (string.IsNullOrEmpty(segment)) { throw new InvalidOperationException( $"Invalid template '{template}'. Empty segments are not allowed."); } if (segment[0] != '{') { if (segment[segment.Length - 1] == '}') { throw new InvalidOperationException( $"Invalid template '{template}'. Missing '{{' in parameter segment '{segment}'."); } if (segment[^ 1] == '?')
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); }
// Segments have the following order: // 1 - Literal segments // 2 - Constrained parameter segments / Multi-part segments // 3 - Unconstrained parameter segments // 4 - Constrained wildcard parameter segments // 5 - Unconstrained wildcard parameter segments private static int ComputeDigit(TemplateSegment segment) { if (segment.Parts.Count > 1) { // Multi-part segments should appear after literal segments but before parameter segments return(2); } var part = segment.Parts[0]; // Literal segments always go first if (part.IsLiteral) { return(1); } else { Debug.Assert(part.IsParameter); var digit = part.IsCatchAll ? 5 : 3; // If there is a route constraint for the parameter, reduce order by 1 // Constrained parameters end up with order 2, Constrained catch alls end up with order 4 if (part.InlineConstraint != null) { digit--; } return(digit); } }
/// <summary> /// Does the specified URI represent a template? /// </summary> /// <param name="uri"> /// The URI. /// </param> /// <returns> /// <c>true</c>, if any of the URI's components are parameterised (i.e. have non-constant values); otherwise, <c>false</c>. /// </returns> public static bool IsTemplate(string uri) { if (uri == null) { throw new ArgumentNullException(nameof(uri)); } IReadOnlyList <TemplateSegment> templateSegments = TemplateSegment.Parse(uri); return(templateSegments.Any(segment => segment.IsParameterized)); }
}//method private string EvaluateExpression(EvaluationContext context, TemplateSegment segment) { try { segment.ExpressionNode.Evaluate(context, AstMode.Read); var value = context.LastResult; return(value == null ? string.Empty : value.ToString()); } catch (RuntimeException ex) { this.ErrorAnchor = this.Location + segment.Position + ex.Location; throw ex.InnerException; } }
private string EvaluateExpression(ScriptThread thread, TemplateSegment segment) { try { var value = segment.ExpressionNode.Evaluate(thread); return(value?.ToString() ?? string.Empty); } catch { //We need to catch here and set current node; ExpressionNode may have reset it, and location would be wrong //TODO: fix this - set error location to exact location inside string. thread.CurrentNode = this; throw; } }
public void Can_Parse_TemplateSegments_From_Uri_WithQuery() { IReadOnlyList <TemplateSegment> segments = TemplateSegment.Parse( "api/{controller}/{action}/{id?}/properties?propertyIds={propertyGroupIds}&diddly={dee?}&foo=bar" ); Assert.Equal(9, segments.Count); Assert.IsAssignableFrom <RootUriSegment>(segments[0]); LiteralUriSegment apiSegment = Assert.IsAssignableFrom <LiteralUriSegment>(segments[1]); Assert.Equal("api", apiSegment.Value); ParameterizedUriSegment controllerSegment = Assert.IsAssignableFrom <ParameterizedUriSegment>(segments[2]); Assert.True(controllerSegment.IsDirectory); Assert.Equal("controller", controllerSegment.TemplateParameterName); Assert.False(controllerSegment.IsOptional); ParameterizedUriSegment actionSegment = Assert.IsAssignableFrom <ParameterizedUriSegment>(segments[3]); Assert.True(actionSegment.IsDirectory); Assert.Equal("action", actionSegment.TemplateParameterName); Assert.False(actionSegment.IsOptional); ParameterizedUriSegment idSegment = Assert.IsAssignableFrom <ParameterizedUriSegment>(segments[4]); Assert.True(idSegment.IsDirectory); Assert.Equal("id", idSegment.TemplateParameterName); Assert.True(idSegment.IsOptional); LiteralUriSegment propertiesSegment = Assert.IsAssignableFrom <LiteralUriSegment>(segments[5]); Assert.False(propertiesSegment.IsDirectory); Assert.Equal("properties", propertiesSegment.Value); ParameterizedQuerySegment propertyIdsSegment = Assert.IsAssignableFrom <ParameterizedQuerySegment>(segments[6]); Assert.Equal("propertyIds", propertyIdsSegment.QueryParameterName); Assert.Equal("propertyGroupIds", propertyIdsSegment.TemplateParameterName); Assert.False(propertyIdsSegment.IsOptional); ParameterizedQuerySegment diddlySegment = Assert.IsAssignableFrom <ParameterizedQuerySegment>(segments[7]); Assert.Equal("diddly", diddlySegment.QueryParameterName); Assert.Equal("dee", diddlySegment.TemplateParameterName); Assert.True(diddlySegment.IsOptional); LiteralQuerySegment fooSegment = Assert.IsAssignableFrom <LiteralQuerySegment>(segments[8]); Assert.Equal("foo", fooSegment.QueryParameterName); Assert.Equal("bar", fooSegment.QueryParameterValue); }
private string EvaluateExpression(ScriptThread thread, TemplateSegment segment) { try { var value = segment.ExpressionNode.Evaluate(thread); return(value == null ? string.Empty : value.ToString()); } catch { thread.CurrentNode = this; throw; } }
public void Configure(IApplicationBuilder app) { //We are building a url template from scratch, segment by segemtn, oldskool var segment = new TemplateSegment(); segment.Parts.Add(TemplatePart.CreateLiteral("page")); var segment2 = new TemplateSegment(); segment2.Parts.Add( TemplatePart.CreateParameter("title", isCatchAll: true, isOptional: true, defaultValue: null, inlineConstraints: new InlineConstraint[] {}) ); var segments = new TemplateSegment [] { segment, segment2 }; var template = new RouteTemplate("page", segments.ToList()); var templateMatcher = new TemplateMatcher(template, new RouteValueDictionary()); app.Use(async(context, next) => { await context.Response.WriteAsync("We are using two segments, one with Literal Template Part ('page') and the other with Parameter Template Part ('title')"); await context.Response.WriteAsync("\n\n"); await next.Invoke(); }); app.Use(async(context, next) => { var path1 = "/page/what"; var routeData = new RouteValueDictionary();//This dictionary will be populated by the parameter template part (in this case "title") var isMatch1 = templateMatcher.TryMatch(path1, routeData); await context.Response.WriteAsync($"{path1} is match? {isMatch1} => route data value for 'title' is {routeData["title"]} \n"); await next.Invoke(); }); app.Use(async(context, next) => { var path1 = "/page/the/old/man/and/the/sea"; var routeData = new RouteValueDictionary();//This dictionary will be populated by the parameter template part (in this case "title") var isMatch1 = templateMatcher.TryMatch(path1, routeData); await context.Response.WriteAsync($"{path1} is match? {isMatch1} => route data value for 'title' is {routeData["title"]} \n"); await next.Invoke(); }); app.Run(async context => { await context.Response.WriteAsync(""); }); }
private Node Traverse(Node node, TemplateSegment segment) { if (!segment.IsSimple) { throw new InvalidOperationException("We only support simple segments."); } if (segment.Parts[0].IsLiteral) { return(node.Literals[segment.Parts[0].Text]); } return(node.Literals["*"]); }
public void Configure(IApplicationBuilder app) { //We are building a url template from scratch, segment by segemtn, oldskool var segment = new TemplateSegment(); segment.Parts.Add(TemplatePart.CreateLiteral("page")); var segment2 = new TemplateSegment(); segment2.Parts.Add(TemplatePart.CreateParameter("id", isCatchAll: false, isOptional: false, defaultValue: null, inlineConstraints: new InlineConstraint[] { new InlineConstraint("int") })); var segments = new TemplateSegment[] { segment, segment2 }; var template = new RouteTemplate("page", segments.ToList()); var templateMatcher = new TemplateMatcher(template, new RouteValueDictionary()); app.Use(async(context, next) => { await context.Response.WriteAsync("We are using one segment with two parts, one Literal Template Part ('page') and the other with Parameter Template Part ('id')."); await context.Response.WriteAsync("It is the equivalent of /page/{id:int}"); await context.Response.WriteAsync("\n\n"); await next.Invoke(); }); app.Use(async(context, next) => { var path1 = "/page/10"; var routeData = new RouteValueDictionary();//This dictionary will be populated by the parameter template part (in this case "title") var isMatch1 = templateMatcher.TryMatch(path1, routeData); await context.Response.WriteAsync($"{path1} is match? {isMatch1} => route data value for 'id' is {routeData["id"]} \n"); await next.Invoke(); }); app.Use(async(context, next) => { var path = "/page/a"; var routeData = new RouteValueDictionary();//This dictionary will be populated by the parameter template part (in this case "title") var isMatch1 = templateMatcher.TryMatch(path, routeData); await context.Response.WriteAsync($"{path} is match? {isMatch1} - as you can see TemplateMatcher does not give a damn about InlineConstraint. It is by design. \n"); }); }
/// <summary> /// Create a new URI template. /// </summary> /// <param name="template"> /// The template. /// </param> public UriTemplate(string template) { if (String.IsNullOrWhiteSpace(template)) { throw new ArgumentException("Argument cannot be null, empty, or composed entirely of whitespace: 'template'.", nameof(template)); } _template = template; IReadOnlyList <TemplateSegment> templateSegments = TemplateSegment.Parse(_template); _uriSegments = templateSegments.OfType <UriSegment>().ToArray(); if (_uriSegments.Count == 0) { throw new UriTemplateException("Invalid URI template (contains no path segments)."); } _querySegments = templateSegments.OfType <QuerySegment>().ToArray(); }
public override IEnumerable <FoldingRegion> GenerateFolds() { foreach (ISegment seg in TemplateSegments) { if (seg.EndLocation.Line - seg.TagStartLocation.Line < 1) { continue; } string name; TemplateSegment ts = seg as TemplateSegment; if (ts != null) { if (ts.Type == SegmentType.Content) { continue; } else if (ts.Type == SegmentType.Expression) { name = "<#=...#>"; } else if (ts.Type == SegmentType.Helper) { name = "<#+...#>"; } else { name = "<#...#>"; } } else { Directive dir = (Directive)seg; name = "<#@" + dir.Name + "...#>"; } DomRegion region = new DomRegion(seg.TagStartLocation.Line, seg.TagStartLocation.Column, seg.EndLocation.Line, seg.EndLocation.Column); yield return(new FoldingRegion(name, region, false)); } }
public void Configure(IApplicationBuilder app) { var apiSegment = new TemplateSegment(); apiSegment.Parts.Add(TemplatePart.CreateLiteral("api")); var serviceNameSegment = new TemplateSegment(); serviceNameSegment.Parts.Add( TemplatePart.CreateParameter("serviceName", isCatchAll: false, isOptional: true, defaultValue: null, inlineConstraints: new InlineConstraint[] { }) ); var segments = new TemplateSegment[] { apiSegment, serviceNameSegment }; var routeTemplate = new RouteTemplate("default", segments.ToList()); var templateMatcher = new TemplateMatcher(routeTemplate, new RouteValueDictionary()); app.Use(async(context, next) => { context.Response.Headers.Add("Content-type", "text/html"); var requestPath = context.Request.Path; var routeData = new RouteValueDictionary(); var isMatch = templateMatcher.TryMatch(requestPath, routeData); await context.Response.WriteAsync($"Request Path is <i>{requestPath}</i><br/>Match state is <b>{isMatch}</b><br/>Requested service name is {routeData["serviceName"]}"); await next.Invoke(); }); app.Run(async context => { await context.Response.WriteAsync(""); }); }
public void Can_Parse_TemplateSegments_From_Uri() { IReadOnlyList <TemplateSegment> segments = TemplateSegment.Parse( "api/{controller}/{action}/{id?}/properties" ); Assert.Equal(6, segments.Count); Assert.IsAssignableFrom <RootUriSegment>(segments[0]); LiteralUriSegment apiSegment = Assert.IsAssignableFrom <LiteralUriSegment>(segments[1]); Assert.Equal("api", apiSegment.Value); ParameterizedUriSegment controllerSegment = Assert.IsAssignableFrom <ParameterizedUriSegment>(segments[2]); Assert.True(controllerSegment.IsDirectory); Assert.Equal("controller", controllerSegment.TemplateParameterName); Assert.False(controllerSegment.IsOptional); ParameterizedUriSegment actionSegment = Assert.IsAssignableFrom <ParameterizedUriSegment>(segments[3]); Assert.True(actionSegment.IsDirectory); Assert.Equal("action", actionSegment.TemplateParameterName); Assert.False(actionSegment.IsOptional); ParameterizedUriSegment idSegment = Assert.IsAssignableFrom <ParameterizedUriSegment>(segments[4]); Assert.True(idSegment.IsDirectory); Assert.Equal("id", idSegment.TemplateParameterName); Assert.True(idSegment.IsOptional); LiteralUriSegment propertiesSegment = Assert.IsAssignableFrom <LiteralUriSegment>(segments[5]); Assert.False(propertiesSegment.IsDirectory); Assert.Equal("properties", propertiesSegment.Value); }
//Match complex segments a{project}b... private bool MatchComplexSegmentCore( TemplateSegment routeSegment, string requestSegment, RouteValueDictionary values, int indexOfLastSegmentUsed) { // Find last literal segment and get its last index in the string var lastIndex = requestSegment.Length; TemplatePart parameterNeedsValue = null; // Keeps track of a parameter segment that is pending a value TemplatePart lastLiteral = null; // Keeps track of the left-most literal we've encountered var outValues = new RouteValueDictionary(); while (indexOfLastSegmentUsed >= 0) { var newLastIndex = lastIndex; var part = routeSegment.Parts[indexOfLastSegmentUsed]; if (part.IsParameter) { // Hold on to the parameter so that we can fill it in when we locate the next literal parameterNeedsValue = part; } else { lastLiteral = part; var startIndex = lastIndex - 1; // If we have a pending parameter subsegment, we must leave at least one character for that if (parameterNeedsValue != null) { startIndex--; } if (startIndex < 0) { return(false); } var indexOfLiteral = requestSegment.LastIndexOf( part.Text, startIndex, System.StringComparison.OrdinalIgnoreCase); if (indexOfLiteral == -1) { // If we couldn't find this literal index, this segment cannot match return(false); } // If the first subsegment is a literal, it must match at the right-most extent of the request URI. // Without this check if your route had "/Foo/" we'd match the request URI "/somethingFoo/". // This check is related to the check we do at the very end of this function. if (indexOfLastSegmentUsed == (routeSegment.Parts.Count - 1)) { if ((indexOfLiteral + part.Text.Length) != requestSegment.Length) { return(false); } } newLastIndex = indexOfLiteral; } if ((parameterNeedsValue != null) && (((lastLiteral != null) && (part.IsLiteral)) || (indexOfLastSegmentUsed == 0))) { // If we have a pending parameter that needs a value, grab that value int parameterStartIndex; int parameterTextLength; if (lastLiteral == null) { if (indexOfLastSegmentUsed == 0) { parameterStartIndex = 0; } else { parameterStartIndex = newLastIndex; //Debug.Assert(false, "indexOfLastSegementUsed should always be 0 from the check above"); } parameterTextLength = lastIndex; } else { // If we're getting a value for a parameter that is somewhere in the middle of the segment if ((indexOfLastSegmentUsed == 0) && (part.IsParameter)) { parameterStartIndex = 0; parameterTextLength = lastIndex; } else { parameterStartIndex = newLastIndex + lastLiteral.Text.Length; parameterTextLength = lastIndex - parameterStartIndex; } } var parameterValueString = requestSegment.Substring(parameterStartIndex, parameterTextLength); if (string.IsNullOrEmpty(parameterValueString)) { // If we're here that means we have a segment that contains multiple sub-segments. // For these segments all parameters must have non-empty values. If the parameter // has an empty value it's not a match. return(false); } else { // If there's a value in the segment for this parameter, use the subsegment value outValues.Add(parameterNeedsValue.Name, parameterValueString); } parameterNeedsValue = null; lastLiteral = null; } lastIndex = newLastIndex; indexOfLastSegmentUsed--; } // If the last subsegment is a parameter, it's OK that we didn't parse all the way to the left extent of // the string since the parameter will have consumed all the remaining text anyway. If the last subsegment // is a literal then we *must* have consumed the entire text in that literal. Otherwise we end up matching // the route "Foo" to the request URI "somethingFoo". Thus we have to check that we parsed the *entire* // request URI in order for it to be a match. // This check is related to the check we do earlier in this function for LiteralSubsegments. if (lastIndex == 0 || routeSegment.Parts[0].IsParameter) { foreach (var item in outValues) { values.Add(item.Key, item.Value); } return(true); } return(false); }
internal static RouteTemplate ParseTemplate(string template) { var originalTemplate = template; template = template.Trim('/'); if (template == "") { // Special case "/"; return(new RouteTemplate("/", Array.Empty <TemplateSegment>())); } var segments = template.Split('/'); var templateSegments = new TemplateSegment[segments.Length]; for (int i = 0; i < segments.Length; i++) { var segment = segments[i]; if (string.IsNullOrEmpty(segment)) { throw new InvalidOperationException( $"Invalid template '{template}'. Empty segments are not allowed."); } if (segment[0] != '{') { if (segment[segment.Length - 1] == '}') { throw new InvalidOperationException( $"Invalid template '{template}'. Missing '{{' in parameter segment '{segment}'."); } templateSegments[i] = new TemplateSegment(originalTemplate, segment, isParameter: false); } else { if (segment[segment.Length - 1] != '}') { throw new InvalidOperationException( $"Invalid template '{template}'. Missing '}}' in parameter segment '{segment}'."); } if (segment.Length < 3) { throw new InvalidOperationException( $"Invalid template '{template}'. Empty parameter name in segment '{segment}' is not allowed."); } var invalidCharacter = segment.IndexOfAny(InvalidParameterNameCharacters, 1, segment.Length - 2); if (invalidCharacter != -1) { throw new InvalidOperationException( $"Invalid template '{template}'. The character '{segment[invalidCharacter]}' in parameter segment '{segment}' is not allowed."); } templateSegments[i] = new TemplateSegment(originalTemplate, segment.Substring(1, segment.Length - 2), isParameter: true); } } for (int i = 0; i < templateSegments.Length; i++) { var currentSegment = templateSegments[i]; if (!currentSegment.IsParameter) { continue; } for (int j = i + 1; j < templateSegments.Length; j++) { var nextSegment = templateSegments[j]; if (!nextSegment.IsParameter) { continue; } if (string.Equals(currentSegment.Value, nextSegment.Value, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( $"Invalid template '{template}'. The parameter '{currentSegment}' appears multiple times."); } } } return(new RouteTemplate(template, templateSegments)); }
public static string ToTemplateSegmentString(this TemplateSegment templateSegment) => string.Join(string.Empty, templateSegment.Parts.Select(ToTemplatePartString));
private static int GetRank(TemplateSegment xSegment) { return(xSegment switch { // Literal { IsParameter : false } => 0,
private static string ToString(TemplateSegment templateSegment) { return(string.Join(string.Empty, templateSegment.Parts.Select(p => ToString(p)))); }
public void Configure(IApplicationBuilder app) { //We are building a url template from scratch, segment by segment, oldskool var segment = new TemplateSegment(); segment.Parts.Add( TemplatePart.CreateLiteral("hello") ); var segment2 = new TemplateSegment(); segment2.Parts.Add( TemplatePart.CreateLiteral("world") ); var segments = new TemplateSegment [] { segment, segment2 }; var template = new RouteTemplate("hello", segments.ToList()); var templateMatcher = new TemplateMatcher(template, new RouteValueDictionary()); app.Use(async(context, next) => { await context.Response.WriteAsync("We are building routing from scratch using a template segment consisted of two parts: 'hello' and 'world'.\n\n"); await next.Invoke(); }); app.Use(async(context, next) => { var path1 = "hello/world"; try { var isMatch1 = templateMatcher.TryMatch(path1, new RouteValueDictionary()); await context.Response.WriteAsync($"{path1} is match? {isMatch1}\n"); } catch (Exception ex) { await context.Response.WriteAsync($"Oops {path1}: {ex?.Message}\n\n"); } finally { await next.Invoke(); } }); app.Use(async(context, next) => { var path1 = "/hello/world"; var isMatch1 = templateMatcher.TryMatch(path1, new RouteValueDictionary()); await context.Response.WriteAsync($"{path1} is match? {isMatch1}\n"); await next.Invoke(); }); app.Use(async(context, next) => { var path1 = "/hello/"; var isMatch1 = templateMatcher.TryMatch(path1, new RouteValueDictionary()); await context.Response.WriteAsync($"{path1} is match? {isMatch1}\n"); await next.Invoke(); }); app.Run(async context => { await context.Response.WriteAsync(""); }); }