void Parse () { string url = Url; parameterNames = new Dictionary <string, bool> (StringComparer.OrdinalIgnoreCase); if (!String.IsNullOrEmpty (url)) { if (url [0] == '~' || url [0] == '/') throw new ArgumentException ("Url must not start with '~' or '/'"); if (url.IndexOf ('?') >= 0) throw new ArgumentException ("Url must not contain '?'"); } else { segments = new PatternSegment [0]; tokens = new PatternToken [0]; return; } string[] parts = url.Split ('/'); int partsCount = segmentCount = parts.Length; var allTokens = new List <PatternToken> (); PatternToken tmpToken; segments = new PatternSegment [partsCount]; for (int i = 0; i < partsCount; i++) { if (haveSegmentWithCatchAll) throw new ArgumentException ("A catch-all parameter can only appear as the last segment of the route URL"); int catchAlls = 0; string part = parts [i]; int partLength = part.Length; var tokens = new List <PatternToken> (); if (partLength == 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); if (part.IndexOfAny (placeholderDelimiters) == -1) { // no placeholders here, short-circuit it tmpToken = new PatternToken (PatternTokenType.Literal, part); tokens.Add (tmpToken); allTokens.Add (tmpToken); segments [i].AllLiteral = true; segments [i].Tokens = tokens; continue; } string tmp; int from = 0, start; bool allLiteral = true; while (from < partLength) { start = part.IndexOf ('{', from); if (start >= partLength - 2) throw new ArgumentException ("Unterminated URL parameter. It must contain matching '}'"); if (start < 0) { if (part.IndexOf ('}', from) >= from) throw new ArgumentException ("Unmatched URL parameter closer '}'. A corresponding '{' must precede"); tmp = part.Substring (from); tmpToken = new PatternToken (PatternTokenType.Literal, tmp); tokens.Add (tmpToken); allTokens.Add (tmpToken); from += tmp.Length; break; } if (from == 0 && start > 0) { tmpToken = new PatternToken (PatternTokenType.Literal, part.Substring (0, start)); tokens.Add (tmpToken); allTokens.Add (tmpToken); } int end = part.IndexOf ('}', start + 1); int next = part.IndexOf ('{', start + 1); if (end < 0 || next >= 0 && next < end) throw new ArgumentException ("Unterminated URL parameter. It must contain matching '}'"); if (next == end + 1) throw new ArgumentException ("Two consecutive URL parameters are not allowed. Split into a different segment by '/', or a literal string."); if (next == -1) next = partLength; string token = part.Substring (start + 1, end - start - 1); PatternTokenType type; if (token [0] == '*') { catchAlls++; haveSegmentWithCatchAll = true; type = PatternTokenType.CatchAll; token = token.Substring (1); } else type = PatternTokenType.Standard; if (!parameterNames.ContainsKey (token)) 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."); from = end + 1; } segments [i].AllLiteral = allLiteral; segments [i].Tokens = tokens; } if (allTokens.Count > 0) this.tokens = allTokens.ToArray (); allTokens = null; }
void Parse() { string url = Url; parameterNames = new Dictionary <string, bool> (StringComparer.OrdinalIgnoreCase); if (!String.IsNullOrEmpty(url)) { if (url [0] == '~' || url [0] == '/') { throw new ArgumentException("Url must not start with '~' or '/'"); } if (url.IndexOf('?') >= 0) { throw new ArgumentException("Url must not contain '?'"); } } else { segments = new PatternSegment [0]; tokens = new PatternToken [0]; return; } string[] parts = url.Split('/'); int partsCount = segmentCount = parts.Length; var allTokens = new List <PatternToken> (); PatternToken tmpToken; segments = new PatternSegment [partsCount]; for (int i = 0; i < partsCount; i++) { if (haveSegmentWithCatchAll) { throw new ArgumentException("A catch-all parameter can only appear as the last segment of the route URL"); } int catchAlls = 0; string part = parts [i]; int partLength = part.Length; var tokens = new List <PatternToken> (); if (partLength == 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); } if (part.IndexOfAny(placeholderDelimiters) == -1) { // no placeholders here, short-circuit it tmpToken = new PatternToken(PatternTokenType.Literal, part); tokens.Add(tmpToken); allTokens.Add(tmpToken); segments [i].AllLiteral = true; segments [i].Tokens = tokens; continue; } string tmp; int from = 0, start; bool allLiteral = true; while (from < partLength) { start = part.IndexOf('{', from); if (start >= partLength - 2) { throw new ArgumentException("Unterminated URL parameter. It must contain matching '}'"); } if (start < 0) { if (part.IndexOf('}', from) >= from) { throw new ArgumentException("Unmatched URL parameter closer '}'. A corresponding '{' must precede"); } tmp = part.Substring(from); tmpToken = new PatternToken(PatternTokenType.Literal, tmp); tokens.Add(tmpToken); allTokens.Add(tmpToken); from += tmp.Length; break; } if (from == 0 && start > 0) { tmpToken = new PatternToken(PatternTokenType.Literal, part.Substring(0, start)); tokens.Add(tmpToken); allTokens.Add(tmpToken); } int end = part.IndexOf('}', start + 1); int next = part.IndexOf('{', start + 1); if (end < 0 || next >= 0 && next < end) { throw new ArgumentException("Unterminated URL parameter. It must contain matching '}'"); } if (next == end + 1) { throw new ArgumentException("Two consecutive URL parameters are not allowed. Split into a different segment by '/', or a literal string."); } if (next == -1) { next = partLength; } string token = part.Substring(start + 1, end - start - 1); PatternTokenType type; if (token [0] == '*') { catchAlls++; haveSegmentWithCatchAll = true; type = PatternTokenType.CatchAll; token = token.Substring(1); } else { type = PatternTokenType.Standard; } if (!parameterNames.ContainsKey(token)) { 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."); } from = end + 1; } segments [i].AllLiteral = allLiteral; segments [i].Tokens = tokens; } if (allTokens.Count > 0) { this.tokens = allTokens.ToArray(); } allTokens = null; }
public bool BuildUrl(Route route, RequestContext requestContext, RouteValueDictionary userValues, out string value) { value = null; if (requestContext == null) { return(false); } RouteData routeData = requestContext.RouteData; RouteValueDictionary defaultValues = route != null ? route.Defaults : null; RouteValueDictionary ambientValues = routeData.Values; if (defaultValues != null && defaultValues.Count == 0) { defaultValues = null; } if (ambientValues != null && ambientValues.Count == 0) { ambientValues = null; } if (userValues != null && userValues.Count == 0) { userValues = null; } // Check URL parameters // It is allowed to take ambient values for required parameters if: // // - there are no default values provided // - the default values dictionary contains at least one required // parameter value // bool canTakeFromAmbient; if (defaultValues == null) { canTakeFromAmbient = true; } else { canTakeFromAmbient = false; foreach (KeyValuePair <string, bool> de in parameterNames) { if (defaultValues.ContainsKey(de.Key)) { canTakeFromAmbient = true; break; } } } bool allMustBeInUserValues = false; foreach (KeyValuePair <string, bool> de in parameterNames) { string parameterName = de.Key; // Is the parameter required? if (defaultValues == null || !defaultValues.ContainsKey(parameterName)) { // Yes, it is required (no value in defaults) // Has the user provided value for it? if (userValues == null || !userValues.ContainsKey(parameterName)) { if (allMustBeInUserValues) { return(false); // partial override => no match } if (!canTakeFromAmbient || ambientValues == null || !ambientValues.ContainsKey(parameterName)) { return(false); // no value provided => no match } } else if (canTakeFromAmbient) { allMustBeInUserValues = true; } } } // Check for non-url parameters if (defaultValues != null) { foreach (var de in defaultValues) { string parameterName = de.Key; if (parameterNames.ContainsKey(parameterName)) { continue; } object parameterValue = null; // Has the user specified value for this parameter and, if // yes, is it the same as the one in defaults? if (userValues != null && userValues.TryGetValue(parameterName, out parameterValue)) { object defaultValue = de.Value; if (defaultValue is string && parameterValue is string) { if (String.Compare((string)defaultValue, (string)parameterValue, StringComparison.Ordinal) != 0) { return(false); // different value => no match } } else if (defaultValue != parameterValue) { return(false); // different value => no match } } } } // Check the constraints RouteValueDictionary constraints = route != null ? route.Constraints : null; if (constraints != null && constraints.Count > 0) { HttpContextBase context = requestContext.HttpContext; bool invalidConstraint; foreach (var de in constraints) { if (!Route.ProcessConstraintInternal(context, route, de.Value, de.Key, userValues, RouteDirection.UrlGeneration, out invalidConstraint) || invalidConstraint) { return(false); // constraint not met => no match } } } // We're a match, generate the URL var ret = new StringBuilder(); bool canTrim = true; // Going in reverse order, so that we can trim without much ado int tokensCount = tokens.Length - 1; for (int i = tokensCount; i >= 0; i--) { PatternToken token = tokens [i]; if (token == null) { if (i < tokensCount && ret.Length > 0 && ret [0] != '/') { ret.Insert(0, '/'); } continue; } if (token.Type == PatternTokenType.Literal) { ret.Insert(0, token.Name); continue; } string parameterName = token.Name; object tokenValue; #if SYSTEMCORE_DEP if (userValues.GetValue(parameterName, out tokenValue)) { if (!defaultValues.Has(parameterName, tokenValue)) { canTrim = false; if (tokenValue != null) { ret.Insert(0, tokenValue.ToString()); } continue; } if (!canTrim && tokenValue != null) { ret.Insert(0, tokenValue.ToString()); } continue; } if (defaultValues.GetValue(parameterName, out tokenValue)) { object ambientTokenValue; if (ambientValues.GetValue(parameterName, out ambientTokenValue)) { tokenValue = ambientTokenValue; } if (!canTrim && tokenValue != null) { ret.Insert(0, tokenValue.ToString()); } continue; } canTrim = false; if (ambientValues.GetValue(parameterName, out tokenValue)) { if (tokenValue != null) { ret.Insert(0, tokenValue.ToString()); } continue; } #endif } // All the values specified in userValues that aren't part of the original // URL, the constraints or defaults collections are treated as overflow // values - they are appended as query parameters to the URL if (userValues != null) { bool first = true; foreach (var de in userValues) { string parameterName = de.Key; #if SYSTEMCORE_DEP if (parameterNames.ContainsKey(parameterName) || defaultValues.Has(parameterName) || constraints.Has(parameterName)) { continue; } #endif object parameterValue = de.Value; if (parameterValue == null) { continue; } var parameterValueAsString = parameterValue as string; if (parameterValueAsString != null && parameterValueAsString.Length == 0) { continue; } if (first) { ret.Append('?'); first = false; } else { ret.Append('&'); } ret.Append(Uri.EscapeDataString(parameterName)); ret.Append('='); if (parameterValue != null) { ret.Append(Uri.EscapeDataString(de.Value.ToString())); } } } value = ret.ToString(); return(true); }