/// <summary> /// /// </summary> /// <param name="userValues"></param> /// <returns></returns> public string BuildPath(RouteValues userValues) { userValues = userValues ?? new RouteValues(); if (this.parameterNames.Any(de => !userValues.ContainsKey(de.Key))) { log.Error("User route values did not contain a necessary parameter to build path. Url: {url}, User values: {userValues}", this.Url, userValues.Keys); return(null); } var urlBuilder = new StringBuilder(); int tokensCount = this.tokens.Length - 1; for (int i = tokensCount; i >= 0; i--) { PatternToken token = this.tokens[i]; if (token == null) { if (i < tokensCount && urlBuilder.Length > 0 && urlBuilder[0] != '/') { urlBuilder.Insert(0, '/'); } continue; } if (token.Type == PatternTokenType.Literal) { urlBuilder.Insert(0, token.Name); continue; } var tokenValue = userValues[token.Name]; if (tokenValue != null) { urlBuilder.Insert(0, tokenValue.ToString()); } } urlBuilder.Insert(0, '/'); return(urlBuilder.ToString()); }
public static ParsedRoute Parse(string url) { var parsedRoute = new ParsedRoute(); parsedRoute.Url = url; parsedRoute.parameterNames = new Dictionary <string, bool>(StringComparer.OrdinalIgnoreCase); if (!string.IsNullOrEmpty(url)) { if (url.IndexOf('?') >= 0) { throw new ArgumentException("Url must not contain '?'"); } } else { parsedRoute.segments = new PatternSegment[0]; parsedRoute.tokens = new PatternToken[0]; return(parsedRoute); } var parts = url.TrimStart('/').Split('/'); var partsCount = parsedRoute.segmentCount = parts.Length; var allTokens = new List <PatternToken>(); parsedRoute.segments = new PatternSegment[partsCount]; for (var i = 0; i < partsCount; i++) { if (parsedRoute.hasCatchAllSegment) { throw new ArgumentException("A catch-all parameter can only appear as the last segment of the route URL"); } var catchAlls = 0; var part = parts[i]; var partLength = part.Length; var tokens = new List <PatternToken>(); if (partLength == 0 && i > 0 && i < partsCount - 1) { throw new ArgumentException("Consecutive URL segment separators '/' are not allowed"); } if (part.IndexOf("{}") != -1) { throw new ArgumentException("Empty URL parameter name is not allowed"); } if (i > 0) { allTokens.Add(null); } PatternToken tmpToken; if (part.IndexOfAny(placeholderDelimiters) == -1) { // no placeholders here, short-circuit it tmpToken = new PatternToken(PatternTokenType.Literal, part); tokens.Add(tmpToken); allTokens.Add(tmpToken); parsedRoute.segments[i].AllTokensAreLiteral = true; parsedRoute.segments[i].Tokens = tokens; continue; } parsedRoute.IsDynamic = true; var currentIndex = 0; var allLiteral = true; while (currentIndex < partLength) { var openParameterIndex = part.IndexOf('{', currentIndex); if (openParameterIndex >= partLength - 2) { throw new ArgumentException("Unterminated URL parameter. It must contain matching '}'"); } // No open tag, must be a literal if (openParameterIndex < 0) { if (part.IndexOf('}', currentIndex) >= currentIndex) { throw new ArgumentException("Unmatched URL parameter closer '}'. A corresponding '{' must precede"); } var tmp = part.Substring(currentIndex); tmpToken = new PatternToken(PatternTokenType.Literal, tmp); tokens.Add(tmpToken); allTokens.Add(tmpToken); break; } // parameter found later in the segment, this bit is a literal if (currentIndex == 0 && openParameterIndex > 0) { tmpToken = new PatternToken(PatternTokenType.Literal, part.Substring(0, openParameterIndex)); tokens.Add(tmpToken); allTokens.Add(tmpToken); } var end = part.IndexOf('}', openParameterIndex + 1); var next = part.IndexOf('{', openParameterIndex + 1); if (end < 0 || next >= 0 && next < end) { throw new ArgumentException($"Unterminated URL parameter `{url}`. It must contain matching '}}'"); } if (next == end + 1) { throw new ArgumentException($"Two consecutive URL parameters are not allowed `{url}`. Split into a different segment by '/', or a literal string."); } if (next == -1) { next = partLength; } var token = part.Substring(openParameterIndex + 1, end - openParameterIndex - 1); PatternTokenType type; if (token[0] == '*') { catchAlls++; parsedRoute.hasCatchAllSegment = true; type = PatternTokenType.CatchAll; token = token.Substring(1); } else { type = PatternTokenType.Standard; } if (!parsedRoute.parameterNames.ContainsKey(token)) { parsedRoute.parameterNames.Add(token, true); } tmpToken = new PatternToken(type, token); tokens.Add(tmpToken); allTokens.Add(tmpToken); allLiteral = false; if (end < partLength - 1) { token = part.Substring(end + 1, next - end - 1); tmpToken = new PatternToken(PatternTokenType.Literal, token); tokens.Add(tmpToken); allTokens.Add(tmpToken); end += token.Length; } if (catchAlls > 1 || (catchAlls == 1 && tokens.Count > 1)) { throw new ArgumentException("A path segment that contains more than one section, such as a literal section or a parameter, cannot contain a catch-all parameter."); } currentIndex = end + 1; } parsedRoute.segments[i].AllTokensAreLiteral = allLiteral; parsedRoute.segments[i].Tokens = tokens; } if (allTokens.Count > 0) { parsedRoute.tokens = allTokens.ToArray(); } return(parsedRoute); }