private static bool MatchSingleContentPathSegment( PathSubsegment pathSubsegment, string requestPathSegment, HttpRouteValueDictionary matchedValues ) { PathParameterSubsegment parameterSubsegment = pathSubsegment as PathParameterSubsegment; if (parameterSubsegment == null) { // Handle a single literal segment PathLiteralSubsegment literalSubsegment = pathSubsegment as PathLiteralSubsegment; Contract.Assert(literalSubsegment != null, "Invalid path segment type"); return(literalSubsegment.Literal.Equals( requestPathSegment, StringComparison.OrdinalIgnoreCase )); } else { // Handle a single parameter segment matchedValues.Add(parameterSubsegment.ParameterName, requestPathSegment); return(true); } }
// 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 GetOrder(PathContentSegment segment, IDictionary <string, object> constraints) { if (segment.Subsegments.Count > 1) { // Multi-part segments should appear after literal segments but before parameter segments return(2); } PathSubsegment subsegment = segment.Subsegments[0]; // Literal segments always go first if (subsegment is PathLiteralSubsegment) { return(1); } else { PathParameterSubsegment parameterSegment = subsegment as PathParameterSubsegment; Contract.Assert(parameterSegment != null); int order = parameterSegment.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 (constraints.ContainsKey(parameterSegment.ParameterName)) { order--; } return(order); } }
private static bool ForEachParameter( List <PathSegment> pathSegments, Func <PathParameterSubsegment, bool> action ) { for (int i = 0; i < pathSegments.Count; i++) { PathSegment pathSegment = pathSegments[i]; if (pathSegment is PathSeparatorSegment) { // We only care about parameter subsegments, so skip this continue; } else { PathContentSegment contentPathSegment = pathSegment as PathContentSegment; if (contentPathSegment != null) { for (int j = 0; j < contentPathSegment.Subsegments.Count; j++) { PathSubsegment subsegment = contentPathSegment.Subsegments[j]; PathLiteralSubsegment literalSubsegment = subsegment as PathLiteralSubsegment; if (literalSubsegment != null) { // We only care about parameter subsegments, so skip this continue; } else { PathParameterSubsegment parameterSubsegment = subsegment as PathParameterSubsegment; if (parameterSubsegment != null) { if (!action(parameterSubsegment)) { return(false); } } else { Contract.Assert(false, "Invalid path subsegment type"); } } } } else { Contract.Assert(false, "Invalid path segment type"); } } } return(true); }
private static bool MatchSingleContentPathSegment(PathSubsegment pathSubsegment, string requestPathSegment, HttpRouteValueDictionary matchedValues) { PathParameterSubsegment parameterSubsegment = pathSubsegment as PathParameterSubsegment; if (parameterSubsegment == null) { // Handle a single literal segment PathLiteralSubsegment literalSubsegment = pathSubsegment as PathLiteralSubsegment; Contract.Assert(literalSubsegment != null, "Invalid path segment type"); return literalSubsegment.Literal.Equals(requestPathSegment, StringComparison.OrdinalIgnoreCase); } else { // Handle a single parameter segment matchedValues.Add(parameterSubsegment.ParameterName, requestPathSegment); return true; } }
public BoundRouteTemplate Bind(IDictionary <string, object> currentValues, IDictionary <string, object> values, HttpRouteValueDictionary defaultValues, HttpRouteValueDictionary constraints) { if (currentValues == null) { currentValues = new HttpRouteValueDictionary(); } if (values == null) { values = new HttpRouteValueDictionary(); } if (defaultValues == null) { defaultValues = new HttpRouteValueDictionary(); } // The set of values we should be using when generating the URI in this route HttpRouteValueDictionary acceptedValues = new HttpRouteValueDictionary(); // Keep track of which new values have been used HashSet <string> unusedNewValues = new HashSet <string>(values.Keys, StringComparer.OrdinalIgnoreCase); // Step 1: Get the list of values we're going to try to use to match and generate this URI // Find out which entries in the URI are valid for the URI we want to generate. // If the URI had ordered parameters a="1", b="2", c="3" and the new values // specified that b="9", then we need to invalidate everything after it. The new // values should then be a="1", b="9", c=<no value>. ForEachParameter(PathSegments, delegate(PathParameterSubsegment parameterSubsegment) { // If it's a parameter subsegment, examine the current value to see if it matches the new value string parameterName = parameterSubsegment.ParameterName; object newParameterValue; bool hasNewParameterValue = values.TryGetValue(parameterName, out newParameterValue); if (hasNewParameterValue) { unusedNewValues.Remove(parameterName); } object currentParameterValue; bool hasCurrentParameterValue = currentValues.TryGetValue(parameterName, out currentParameterValue); if (hasNewParameterValue && hasCurrentParameterValue) { if (!RoutePartsEqual(currentParameterValue, newParameterValue)) { // Stop copying current values when we find one that doesn't match return(false); } } // If the parameter is a match, add it to the list of values we will use for URI generation if (hasNewParameterValue) { if (IsRoutePartNonEmpty(newParameterValue)) { acceptedValues.Add(parameterName, newParameterValue); } } else { if (hasCurrentParameterValue) { acceptedValues.Add(parameterName, currentParameterValue); } } return(true); }); // Add all remaining new values to the list of values we will use for URI generation foreach (var newValue in values) { if (IsRoutePartNonEmpty(newValue.Value)) { if (!acceptedValues.ContainsKey(newValue.Key)) { acceptedValues.Add(newValue.Key, newValue.Value); } } } // Add all current values that aren't in the URI at all foreach (var currentValue in currentValues) { string parameterName = currentValue.Key; if (!acceptedValues.ContainsKey(parameterName)) { PathParameterSubsegment parameterSubsegment = GetParameterSubsegment(PathSegments, parameterName); if (parameterSubsegment == null) { acceptedValues.Add(parameterName, currentValue.Value); } } } // Add all remaining default values from the route to the list of values we will use for URI generation ForEachParameter(PathSegments, delegate(PathParameterSubsegment parameterSubsegment) { if (!acceptedValues.ContainsKey(parameterSubsegment.ParameterName)) { object defaultValue; if (!IsParameterRequired(parameterSubsegment, defaultValues, out defaultValue)) { // Add the default value only if there isn't already a new value for it and // only if it actually has a default value, which we determine based on whether // the parameter value is required. acceptedValues.Add(parameterSubsegment.ParameterName, defaultValue); } } return(true); }); // All required parameters in this URI must have values from somewhere (i.e. the accepted values) bool hasAllRequiredValues = ForEachParameter(PathSegments, delegate(PathParameterSubsegment parameterSubsegment) { object defaultValue; if (IsParameterRequired(parameterSubsegment, defaultValues, out defaultValue)) { if (!acceptedValues.ContainsKey(parameterSubsegment.ParameterName)) { // If the route parameter value is required that means there's // no default value, so if there wasn't a new value for it // either, this route won't match. return(false); } } return(true); }); if (!hasAllRequiredValues) { return(null); } // All other default values must match if they are explicitly defined in the new values HttpRouteValueDictionary otherDefaultValues = new HttpRouteValueDictionary(defaultValues); ForEachParameter(PathSegments, delegate(PathParameterSubsegment parameterSubsegment) { otherDefaultValues.Remove(parameterSubsegment.ParameterName); return(true); }); foreach (var defaultValue in otherDefaultValues) { object value; if (values.TryGetValue(defaultValue.Key, out value)) { unusedNewValues.Remove(defaultValue.Key); if (!RoutePartsEqual(value, defaultValue.Value)) { // If there is a non-parameterized value in the route and there is a // new value for it and it doesn't match, this route won't match. return(null); } } } // Step 2: If the route is a match generate the appropriate URI StringBuilder uri = new StringBuilder(); StringBuilder pendingParts = new StringBuilder(); bool pendingPartsAreAllSafe = false; bool blockAllUriAppends = false; for (int i = 0; i < PathSegments.Count; i++) { PathSegment pathSegment = PathSegments[i]; // parsedRouteUriPart if (pathSegment is PathSeparatorSegment) { if (pendingPartsAreAllSafe) { // Accept if (pendingParts.Length > 0) { if (blockAllUriAppends) { return(null); } // Append any pending literals to the URI uri.Append(pendingParts.ToString()); pendingParts.Length = 0; } } pendingPartsAreAllSafe = false; // Guard against appending multiple separators for empty segments if (pendingParts.Length > 0 && pendingParts[pendingParts.Length - 1] == '/') { // Dev10 676725: Route should not be matched if that causes mismatched tokens // Dev11 86819: We will allow empty matches if all subsequent segments are null if (blockAllUriAppends) { return(null); } // Append any pending literals to the URI (without the trailing slash) and prevent any future appends uri.Append(pendingParts.ToString(0, pendingParts.Length - 1)); pendingParts.Length = 0; blockAllUriAppends = true; } else { pendingParts.Append("/"); } } else { PathContentSegment contentPathSegment = pathSegment as PathContentSegment; if (contentPathSegment != null) { // Segments are treated as all-or-none. We should never output a partial segment. // If we add any subsegment of this segment to the generated URI, we have to add // the complete match. For example, if the subsegment is "{p1}-{p2}.xml" and we // used a value for {p1}, we have to output the entire segment up to the next "/". // Otherwise we could end up with the partial segment "v1" instead of the entire // segment "v1-v2.xml". bool addedAnySubsegments = false; for (int j = 0; j < contentPathSegment.Subsegments.Count; j++) { PathSubsegment subsegment = contentPathSegment.Subsegments[j]; PathLiteralSubsegment literalSubsegment = subsegment as PathLiteralSubsegment; if (literalSubsegment != null) { // If it's a literal we hold on to it until we are sure we need to add it pendingPartsAreAllSafe = true; pendingParts.Append(literalSubsegment.Literal); } else { PathParameterSubsegment parameterSubsegment = subsegment as PathParameterSubsegment; if (parameterSubsegment != null) { if (pendingPartsAreAllSafe) { // Accept if (pendingParts.Length > 0) { if (blockAllUriAppends) { return(null); } // Append any pending literals to the URI uri.Append(pendingParts.ToString()); pendingParts.Length = 0; addedAnySubsegments = true; } } pendingPartsAreAllSafe = false; // If it's a parameter, get its value object acceptedParameterValue; bool hasAcceptedParameterValue = acceptedValues.TryGetValue(parameterSubsegment.ParameterName, out acceptedParameterValue); if (hasAcceptedParameterValue) { unusedNewValues.Remove(parameterSubsegment.ParameterName); } object defaultParameterValue; defaultValues.TryGetValue(parameterSubsegment.ParameterName, out defaultParameterValue); if (RoutePartsEqual(acceptedParameterValue, defaultParameterValue)) { // If the accepted value is the same as the default value, mark it as pending since // we won't necessarily add it to the URI we generate. pendingParts.Append(Convert.ToString(acceptedParameterValue, CultureInfo.InvariantCulture)); } else { if (blockAllUriAppends) { return(null); } // Add the new part to the URI as well as any pending parts if (pendingParts.Length > 0) { // Append any pending literals to the URI uri.Append(pendingParts.ToString()); pendingParts.Length = 0; } uri.Append(Convert.ToString(acceptedParameterValue, CultureInfo.InvariantCulture)); addedAnySubsegments = true; } } else { Contract.Assert(false, "Invalid path subsegment type"); } } } if (addedAnySubsegments) { // See comment above about why we add the pending parts if (pendingParts.Length > 0) { if (blockAllUriAppends) { return(null); } // Append any pending literals to the URI uri.Append(pendingParts.ToString()); pendingParts.Length = 0; } } } else { Contract.Assert(false, "Invalid path segment type"); } } } if (pendingPartsAreAllSafe) { // Accept if (pendingParts.Length > 0) { if (blockAllUriAppends) { return(null); } // Append any pending literals to the URI uri.Append(pendingParts.ToString()); } } // Process constraints keys if (constraints != null) { // If there are any constraints, mark all the keys as being used so that we don't // generate query string items for custom constraints that don't appear as parameters // in the URI format. foreach (var constraintsItem in constraints) { unusedNewValues.Remove(constraintsItem.Key); } } // Encode the URI before we append the query string, otherwise we would double encode the query string StringBuilder encodedUri = new StringBuilder(); encodedUri.Append(UriEncode(uri.ToString())); uri = encodedUri; // Add remaining new values as query string parameters to the URI if (unusedNewValues.Count > 0) { // Generate the query string bool firstParam = true; foreach (string unusedNewValue in unusedNewValues) { object value; if (acceptedValues.TryGetValue(unusedNewValue, out value)) { uri.Append(firstParam ? '?' : '&'); firstParam = false; uri.Append(Uri.EscapeDataString(unusedNewValue)); uri.Append('='); uri.Append(Uri.EscapeDataString(Convert.ToString(value, CultureInfo.InvariantCulture))); } } } return(new BoundRouteTemplate { BoundTemplate = uri.ToString(), Values = acceptedValues }); }