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;
}
}