public Route(string url, string areaName, Type handlerType, bool localised, bool personalised) { this.areaName = areaName; this.HandlerType = handlerType; this.Localised = localised; this.Personalised = personalised; if (string.IsNullOrEmpty(url)) { throw new ArgumentException("URL must have a value", nameof(url)); } if (!url.StartsWith("/")) { throw new ArgumentException("URL `" + url + "` must start with a /", nameof(url)); } this.Path = url; this.parsedRoute = ParsedRoute.Parse(url); if (localised) { if (url != "/") { this.parsedLocalisedRoute = ParsedRoute.Parse("/{regionCode}/{cultureCode}/" + url.TrimStart('/')); } else { this.parsedLocalisedRoute = ParsedRoute.Parse("/{regionCode}/{cultureCode}"); } } }
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); }