private RouteValueDictionary CreateRouteConstraints(RouteSpecification routeSpec) { var constraints = new RouteValueDictionary(); // Default constraints constraints.Add("httpMethod", new RestfulHttpMethodConstraint(routeSpec.HttpMethods)); // Attribute-based constraints foreach (var constraintAttribute in routeSpec.ConstraintAttributes.Where(c => !constraints.ContainsKey(c.Key))) { constraints.Add(constraintAttribute.Key, constraintAttribute.Constraint); } var detokenizedUrl = DetokenizeUrl(CreateRouteUrl(routeSpec)); var urlParameterNames = GetUrlParameterNames(detokenizedUrl); // Convention-based constraints foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { var pattern = defaultConstraint.Key; var matchedUrlParameterNames = urlParameterNames.Where(n => Regex.IsMatch(n, pattern)); foreach (var urlParameterName in matchedUrlParameterNames.Where(n => !constraints.ContainsKey(n))) { constraints.Add(urlParameterName, defaultConstraint.Value); } } return(constraints); }
private RouteValueDictionary CreateRouteConstraints(RouteSpecification routeSpec) { var constraints = new RouteValueDictionary(); // Default constraints constraints.Add("httpMethod", new RestfulHttpMethodConstraint(routeSpec.HttpMethod)); // Attribute-based constraints foreach (var constraintAttribute in routeSpec.ConstraintAttributes.Where(c => !constraints.ContainsKey(c.Key))) constraints.Add(constraintAttribute.Key, constraintAttribute.Constraint); var detokenizedUrl = DetokenizeUrl(CreateRouteUrl(routeSpec)); var urlParameterNames = GetUrlParameterNames(detokenizedUrl); // Convention-based constraints foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { var pattern = defaultConstraint.Key; var matchedUrlParameterNames = urlParameterNames.Where(n => Regex.IsMatch(n, pattern)); foreach (var urlParameterName in matchedUrlParameterNames.Where(n => !constraints.ContainsKey(n))) constraints.Add(urlParameterName, defaultConstraint.Value); } return constraints; }
private IEnumerable<AttributeRoute> Build(RouteSpecification routeSpec) { var route = new AttributeRoute(CreateRouteUrl(routeSpec), CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec), _configuration) { RouteName = CreateRouteName(routeSpec), Translations = CreateRouteTranslations(routeSpec), Subdomain = routeSpec.Subdomain }; // Yield the default route first yield return route; // Then yield the translations if (route.Translations == null) yield break; foreach (var translation in route.Translations) { // Backreference the default route. translation.DefaultRoute = route; yield return translation; } }
private RouteValueDictionary CreateRouteDefaults(RouteSpecification routeSpec) { var defaults = new RouteValueDictionary { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } }; foreach (var defaultAttribute in routeSpec.DefaultAttributes.Where(d => !defaults.ContainsKey(d.Key))) { defaults.Add(defaultAttribute.Key, defaultAttribute.Value); } // Inspect the url for optional parameters, specified with a leading ? var optionalParameterDefaults = from parameter in GetUrlParameterContents(routeSpec.Url) where parameter.StartsWith("?") let parameterName = parameter.TrimStart('?') select new RouteDefaultAttribute(parameterName, UrlParameter.Optional); foreach (var defautAttribute in optionalParameterDefaults.Where(d => !defaults.ContainsKey(d.Key))) { defaults.Add(defautAttribute.Key, defautAttribute.Value); } return(defaults); }
private IEnumerable <IAttributeRoute> CreateRouteTranslations(RouteSpecification routeSpec) { // If no translation provider, then get out of here. if (!_configuration.TranslationProviders.Any()) { yield break; } // Merge all the culture names from the various providers. var cultureNames = _configuration.GetTranslationProviderCultureNames(); // Built the route translations, // choosing the first available translated route component from among the providers foreach (var cultureName in cultureNames) { string translatedRouteUrl = null, translatedRoutePrefix = null, translatedAreaUrl = null; foreach (var provider in _configuration.TranslationProviders) { translatedRouteUrl = translatedRouteUrl ?? provider.TranslateRouteUrl(cultureName, routeSpec); translatedRoutePrefix = translatedRoutePrefix ?? provider.TranslateRoutePrefix(cultureName, routeSpec); translatedAreaUrl = translatedAreaUrl ?? provider.TranslateAreaUrl(cultureName, routeSpec); } // If nothing is translated, then bail. if (translatedRouteUrl == null && translatedRoutePrefix == null && translatedAreaUrl == null) { continue; } //********************************************* // Otherwise, build a translated route var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, translatedAreaUrl ?? routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl, routeSpec.UseLowercaseRoute); var translatedRoute = _routeFactory.CreateAttributeRoute(routeUrl, CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec)); var routeName = CreateRouteName(routeSpec); if (routeName != null) { translatedRoute.RouteName = routeName; translatedRoute.DataTokens.Add("routeName", routeName); } translatedRoute.CultureName = cultureName; translatedRoute.DataTokens.Add("cultureName", cultureName); yield return(translatedRoute); } }
private string CreateRouteUrl(IDictionary <string, object> defaults, RouteSpecification routeSpec) { return(CreateRouteUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, defaults, routeSpec)); }
private string CreateRouteUrl(RouteSpecification routeSpec) { return(CreateRouteUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl, routeSpec.UseLowercaseRoute)); }
private RouteValueDictionary CreateRouteConstraints(RouteSpecification routeSpec) { var constraints = new RouteValueDictionary(); // Default constraints if (routeSpec.HttpMethods.Any()) { constraints.Add("httpMethod", new RestfulHttpMethodConstraint(routeSpec.HttpMethods)); } // Inline constraints foreach (var parameter in GetUrlParameterContents(routeSpec.Url).Where(p => Regex.IsMatch(p, @"^.*\(.*\)$"))) { var indexOfOpenParen = parameter.IndexOf('('); var parameterName = parameter.Substring(0, indexOfOpenParen); if (constraints.ContainsKey(parameterName)) { continue; } var regexPattern = parameter.Substring(indexOfOpenParen + 1, parameter.Length - indexOfOpenParen - 2); constraints.Add(parameterName, new RegexRouteConstraint(regexPattern)); } // Attribute-based constraints foreach (var constraintAttribute in routeSpec.ConstraintAttributes) { if (constraints.ContainsKey(constraintAttribute.Key)) { continue; } constraints.Add(constraintAttribute.Key, constraintAttribute.Constraint); } var detokenizedUrl = DetokenizeUrl(CreateRouteUrl(routeSpec)); var urlParameterNames = GetUrlParameterNames(detokenizedUrl); // Convention-based constraints foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { var pattern = defaultConstraint.Key; foreach (var urlParameterName in urlParameterNames.Where(n => Regex.IsMatch(n, pattern))) { if (constraints.ContainsKey(urlParameterName)) { continue; } constraints.Add(urlParameterName, defaultConstraint.Value); } } return(constraints); }
public AttributeRoute Build(RouteSpecification routeSpec) { return(new AttributeRoute(CreateRouteName(routeSpec), CreateRouteUrl(routeSpec), CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec), _configuration.UseLowercaseRoutes)); }
public AttributeRoute Build(RouteSpecification routeSpec) { return new AttributeRoute(CreateRouteName(routeSpec), CreateRouteUrl(routeSpec), CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec), _configuration.UseLowercaseRoutes); }
private string CreateRouteName(RouteSpecification routeSpec) { if (routeSpec.RouteName.HasValue()) { return(routeSpec.RouteName); } return(_configuration.AutoGenerateRouteNames ? _configuration.RouteNameBuilder(routeSpec) : null); }
private IEnumerable <IAttributeRoute> BuildRoutes(RouteSpecification routeSpec) { // Get info needed to construct IAttributeRoute via factory method. IDictionary <string, object> defaults; IDictionary <string, object> queryStringDefaults; CreateRouteDefaults(routeSpec, out defaults, out queryStringDefaults); IDictionary <string, object> constraints; IDictionary <string, object> queryStringConstraints; CreateRouteConstraints(routeSpec, out constraints, out queryStringConstraints); var dataTokens = CreateRouteDataTokens(routeSpec); var url = CreateRouteUrl(defaults, routeSpec); // Build the route. var routes = _routeFactory.CreateAttributeRoutes(url, defaults, constraints, dataTokens); // Extend the factory generated route: foreach (var route in routes) { var routeName = CreateRouteName(routeSpec); if (routeName.HasValue()) { route.RouteName = routeName; route.DataTokens.Add("routeName", routeName); } route.QueryStringConstraints = queryStringConstraints; route.QueryStringDefaults = queryStringDefaults; route.Translations = CreateRouteTranslations(routeSpec); route.Subdomain = routeSpec.Subdomain; route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; route.PreserveCaseForUrlParameters = routeSpec.PreserveCaseForUrlParameters; route.AppendTrailingSlash = routeSpec.AppendTrailingSlash; // Yield the default route first yield return(route); // Then yield the translations if (route.Translations != null) { foreach (var translation in route.Translations) { // Backreference the default route. translation.SourceLanguageRoute = route; yield return(translation); } } } }
private RouteValueDictionary CreateRouteDefaults(RouteSpecification routeSpec) { var defaults = new RouteValueDictionary { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } }; var urlParameters = GetUrlParameterContents(routeSpec.RouteUrl); // Inspect the url for optional parameters, specified with a leading or trailing (or both) ? foreach (var parameter in urlParameters.Where(p => Regex.IsMatch(p, @"^\?|\?$"))) { var parameterName = parameter.Trim('?'); if (defaults.ContainsKey(parameterName)) { continue; } defaults.Add(parameterName, UrlParameter.Optional); } // Inline defaults foreach (var parameter in urlParameters.Where(p => Regex.IsMatch(p, @"^.*=.*$"))) { var indexOfEquals = parameter.IndexOf('='); var parameterName = parameter.Substring(0, indexOfEquals); if (defaults.ContainsKey(parameterName)) { continue; } var defaultValue = parameter.Substring(indexOfEquals + 1, parameter.Length - indexOfEquals - 1); defaults.Add(parameterName, defaultValue); } // Attribute-based defaults foreach (var defaultAttribute in routeSpec.DefaultAttributes) { if (defaults.ContainsKey(defaultAttribute.Key)) { continue; } defaults.Add(defaultAttribute.Key, defaultAttribute.Value); } return(defaults); }
public string Execute(RouteSpecification routeSpec) { var areaPart = routeSpec.AreaName.HasValue() ? "{0}_".FormatWith(routeSpec.AreaName) : null; var routeName = "{0}{1}_{2}".FormatWith(areaPart, routeSpec.ControllerName, routeSpec.ActionName); // Only register route names once, so first in wins. if (!_registeredRouteNames.Contains(routeName)) { _registeredRouteNames.Add(routeName); return(routeName); } return(null); }
public string Execute(RouteSpecification routeSpec) { var areaPart = routeSpec.AreaName.HasValue() ? "{0}_".FormatWith(routeSpec.AreaName) : null; var routeName = "{0}{1}_{2}".FormatWith(areaPart, routeSpec.ControllerName, routeSpec.ActionName); // Only register route names once, so first in wins. if (!_registeredRouteNames.Contains(routeName)) { _registeredRouteNames.Add(routeName); return routeName; } return null; }
private IDictionary <string, object> CreateRouteDefaults(RouteSpecification routeSpec) { var defaults = new Dictionary <string, object> { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } }; var urlParameters = GetUrlParameterContents(routeSpec.RouteUrl).ToList(); // Inspect the url for optional parameters, specified with a trailing ? foreach (var parameter in urlParameters.Where(p => p.EndsWith("?"))) { var parameterName = parameter.TrimEnd('?'); if (parameterName.Contains(':')) { parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); } if (defaults.ContainsKey(parameterName)) { continue; } defaults.Add(parameterName, _parameterFactory.Optional()); } // Inline defaults foreach (var parameter in urlParameters.Where(p => p.Contains('='))) { var indexOfEquals = parameter.IndexOf('='); var parameterName = parameter.Substring(0, indexOfEquals); if (parameterName.Contains(':')) { parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); } if (defaults.ContainsKey(parameterName)) { continue; } var defaultValue = parameter.Substring(indexOfEquals + 1, parameter.Length - indexOfEquals - 1); defaults.Add(parameterName, defaultValue); } return(defaults); }
private string CreateRouteUrl(RouteSpecification routeSpec) { var detokenizedUrl = DetokenizeUrl(routeSpec.Url); var urlParameterNames = GetUrlParameterNames(detokenizedUrl); // {controller} and {action} tokens are not valid if (urlParameterNames.Any(n => n.ValueEquals("controller"))) { throw new AttributeRoutingException("{controller} is not a valid url parameter."); } if (urlParameterNames.Any(n => n.ValueEquals("action"))) { throw new AttributeRoutingException("{action} is not a valid url parameter."); } // Explicitly defined area routes are not valid if (urlParameterNames.Any(n => n.ValueEquals("area"))) { throw new AttributeRoutingException( "{area} url parameters are not allowed. Specify the area name by using the RouteAreaAttribute."); } var urlBuilder = new StringBuilder(detokenizedUrl); // If this is not an absolute url, prefix with a route prefix or area name if (!routeSpec.IsAbsoluteUrl) { var delimitedRouteUrl = routeSpec.Url + "/"; if (routeSpec.RoutePrefix.HasValue()) { var delimitedRoutePrefix = routeSpec.RoutePrefix + "/"; if (!delimitedRouteUrl.StartsWith(delimitedRoutePrefix)) { urlBuilder.Insert(0, delimitedRoutePrefix); } } if (routeSpec.AreaUrl.HasValue()) { var delimitedAreaUrl = routeSpec.AreaUrl + "/"; if (!delimitedRouteUrl.StartsWith(delimitedAreaUrl)) { urlBuilder.Insert(0, delimitedAreaUrl); } } } return(urlBuilder.ToString().Trim('/')); }
private IEnumerable <AttributeRoute> CreateRouteTranslations(RouteSpecification routeSpec) { // If no translation provider, then get out of here. if (!_configuration.TranslationProviders.Any()) { yield break; } // Merge all the culture names from the various providers. var cultureNames = _configuration.GetTranslationProviderCultureNames(); // Built the route translations, // choosing the first available translated route component from among the providers foreach (var cultureName in cultureNames) { string translatedRouteUrl = null, translatedRoutePrefix = null, translatedAreaUrl = null; foreach (var provider in _configuration.TranslationProviders) { translatedRouteUrl = translatedRouteUrl ?? provider.TranslateRouteUrl(cultureName, routeSpec); translatedRoutePrefix = translatedRoutePrefix ?? provider.TranslateRoutePrefix(cultureName, routeSpec); translatedAreaUrl = translatedAreaUrl ?? provider.TranslateAreaUrl(cultureName, routeSpec); } if (translatedRouteUrl == null && translatedRoutePrefix == null && translatedAreaUrl == null) { continue; } var translatedRoute = new AttributeRoute(CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, translatedAreaUrl ?? routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl), CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec), _configuration) { CultureName = cultureName, }; translatedRoute.DataTokens.Add("cultureName", cultureName); yield return(translatedRoute); } }
private string CreateRouteName(RouteSpecification routeSpec) { if (routeSpec.RouteName.HasValue()) { return(routeSpec.RouteName); } if (_configuration.AutoGenerateRouteNames) { var area = (routeSpec.AreaName.HasValue()) ? routeSpec.AreaName + "_" : null; return("{0}{1}_{2}".FormatWith(area, routeSpec.ControllerName, routeSpec.ActionName)); } return(null); }
private IDictionary <string, object> CreateRouteDataTokens(RouteSpecification routeSpec) { var dataTokens = new Dictionary <string, object> { { "namespaces", new[] { routeSpec.ControllerType.Namespace } } }; if (routeSpec.AreaName.HasValue()) { dataTokens.Add("area", routeSpec.AreaName); dataTokens.Add("UseNamespaceFallback", false); } return(dataTokens); }
private IDictionary <string, object> CreateRouteDataTokens(RouteSpecification routeSpec) { var dataTokens = new Dictionary <string, object> { { "namespaces", new[] { routeSpec.ControllerType.Namespace } }, { "actionMethod", routeSpec.ActionMethod }, { "defaultSubdomain", _configuration.DefaultSubdomain } }; if (routeSpec.HttpMethods.Any()) { dataTokens.Add("httpMethods", routeSpec.HttpMethods); } if (routeSpec.AreaName.HasValue()) { dataTokens.Add("area", routeSpec.AreaName); dataTokens.Add("UseNamespaceFallback", false); } return(dataTokens); }
public string Execute(RouteSpecification routeSpec) { var routeNameBuilder = new StringBuilder(); if (routeSpec.AreaName.HasValue()) { routeNameBuilder.AppendFormat("{0}_", routeSpec.AreaName); } routeNameBuilder.Append(routeSpec.ControllerName); routeNameBuilder.AppendFormat("_{0}", routeSpec.ActionName); // Ensure route names are unique. var routeName = routeNameBuilder.ToString(); var routeNameIsRegistered = _registeredRouteNames.Contains(routeName); if (routeNameIsRegistered) { // Prefix with the first verb (assuming this is the primary verb) if not a GET route. if (routeSpec.HttpMethods.Length > 0 && !routeSpec.HttpMethods.Contains("GET")) { routeNameBuilder.AppendFormat("_{0}", routeSpec.HttpMethods.FirstOrDefault()); } // Suffixing with an index if necessary to disambiguate. routeName = routeNameBuilder.ToString(); var count = _registeredRouteNames.Count(n => n == routeName || n.StartsWith(routeName + "_")); if (count > 0) { routeNameBuilder.AppendFormat("_{0}", count); } } routeName = routeNameBuilder.ToString(); _registeredRouteNames.Add(routeName); return(routeName); }
private IEnumerable <IAttributeRoute> Build(RouteSpecification routeSpec) { var route = _routeFactory.CreateAttributeRoute(CreateRouteUrl(routeSpec), CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec)); var routeName = CreateRouteName(routeSpec); if (routeName.HasValue()) { route.RouteName = routeName; route.DataTokens.Add("routeName", routeName); } route.Translations = CreateRouteTranslations(routeSpec); route.Subdomain = routeSpec.Subdomain; route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; route.PreserveCaseForUrlParameters = routeSpec.PreserveCaseForUrlParameters; route.AppendTrailingSlash = routeSpec.AppendTrailingSlash; // Yield the default route first yield return(route); // Then yield the translations if (route.Translations == null) { yield break; } foreach (var translation in route.Translations) { // Backreference the default route. translation.DefaultRouteContainer = route; yield return(translation); } }
public string Execute(RouteSpecification routeSpec) { var routeNameBuilder = new StringBuilder(); if (routeSpec.AreaName.HasValue()) routeNameBuilder.AppendFormat("{0}_", routeSpec.AreaName); routeNameBuilder.Append(routeSpec.ControllerName); routeNameBuilder.AppendFormat("_{0}", routeSpec.ActionName); // Ensure route names are unique. var routeName = routeNameBuilder.ToString(); var routeNameIsRegistered = _registeredRouteNames.Contains(routeName); if (routeNameIsRegistered) { // Prefix with the first verb (assuming this is the primary verb) if not a GET route. if (routeSpec.HttpMethods.Length > 0 && !routeSpec.HttpMethods.Contains("GET")) { routeNameBuilder.AppendFormat("_{0}", routeSpec.HttpMethods.FirstOrDefault()); } // Suffixing with an index if necessary to disambiguate. routeName = routeNameBuilder.ToString(); var count = _registeredRouteNames.Count(n => n == routeName || n.StartsWith(routeName + "_")); if (count > 0) { routeNameBuilder.AppendFormat("_{0}", count); } } routeName = routeNameBuilder.ToString(); _registeredRouteNames.Add(routeName); return routeName; }
private void CreateRouteConstraints(RouteSpecification routeSpec, out IDictionary <string, object> constraints, out IDictionary <string, object> queryStringConstraints) { // Going to return individual collections for: // - path routes constraints (which will go into the generated route's Constraints prop), // - and query string route constraints (which will not work perfectly with the MS bits, and need special treatment by IAttributeRoute impls). constraints = new Dictionary <string, object>(); queryStringConstraints = new Dictionary <string, object>(); // Default constraints if (routeSpec.HttpMethods.Any()) { constraints.Add("inboundHttpMethod", _routeConstraintFactory.CreateInboundHttpMethodConstraint(routeSpec.HttpMethods)); } // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); // Need to keep track of which are path and query params. var pathOnlyUrl = RemoveQueryString(tokenizedUrl); var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl); var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); // Inline constraints foreach (var parameter in urlParameters) { // Keep track of whether this param is optional or in the querystring, // because we wrap the final constraint if so. var parameterIsOptional = parameter.EndsWith("?"); var parameterIsInQueryString = queryStringParameters.Contains(parameter); // If this is a path parameter and doesn't have a constraint, then skip it. if (!parameterIsInQueryString && !parameter.Contains(":")) { continue; } // Strip off everything related to defaults and break into sections. var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); var sections = cleanParameter.SplitAndTrim(":"); // Do not override default constraints var parameterName = sections.First(); if (constraints.ContainsKey(parameterName)) { continue; } // Add constraints for each inline definition var inlineConstraints = new List <object>(); var constraintDefinitions = sections.Skip(1); foreach (var definition in constraintDefinitions) { string constraintName; object constraint; if (ConstraintParamsRegex.IsMatch(definition)) { // Constraint of the form "firstName:string(50)" var indexOfOpenParen = definition.IndexOf('('); constraintName = definition.Substring(0, indexOfOpenParen); // Parse constraint params. // NOTE: Splitting on commas only applies to non-regex constraints. var constraintParamsRaw = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2); var constraintParams = constraintName.ValueEquals("regex") ? new[] { constraintParamsRaw } : constraintParamsRaw.SplitAndTrim(","); constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); } else { // Constraint of the form "id:int" constraintName = definition; constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName); } if (constraint == null) { throw new AttributeRoutingException( "Could not find an available inline constraint for \"{0}\".".FormatWith(constraintName)); } inlineConstraints.Add(constraint); } // Wrap constraints in the following priority: object finalConstraint; // 1. If more than one constraint, wrap in a compound constraint. if (inlineConstraints.Count > 1) { finalConstraint = _routeConstraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); } else { finalConstraint = inlineConstraints.FirstOrDefault(); } // 2. If the constraint is in the querystring, wrap in a query string constraint. if (parameterIsInQueryString) { finalConstraint = _routeConstraintFactory.CreateQueryStringRouteConstraint(finalConstraint); } // 3. If the constraint is optional, wrap in an optional constraint. if (parameterIsOptional) { finalConstraint = _routeConstraintFactory.CreateOptionalRouteConstraint(finalConstraint); } // Add the constraints to the appropriate collection. if (parameterIsInQueryString) { queryStringConstraints.Add(parameterName, finalConstraint); } else { constraints.Add(parameterName, finalConstraint); } } // ... go to next parameter // Globally configured constraints - have to treat path params differently than query params. var detokenizedUrl = DetokenizeUrl(tokenizedUrl); string path, query; detokenizedUrl.GetPathAndQuery(out path, out query); var urlPathParameterNames = GetUrlParameterContents(path).ToArray(); var urlQueryParameterNames = GetUrlParameterContents(query).ToArray(); foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { ApplyDefaultRouteConstraint(urlPathParameterNames, defaultConstraint, constraints, false); ApplyDefaultRouteConstraint(urlQueryParameterNames, defaultConstraint, queryStringConstraints, true); } }
/// <summary> /// Looks at _configuration.ApiVersions to see what versions are supported, and generates a list /// of versions for each between min and max. /// If no versions are defined in configuration, always returns one null version. /// </summary> /// <param name="minVersion"></param> /// <param name="maxVersion"></param> /// <returns></returns> private IEnumerable<SemanticVersion> GenerateRouteVersions(RouteSpecification routeSpec) { if (!routeSpec.IsVersioned || (_configuration.ApiVersions == null) && (_configuration.ApiVersions.Count == 0)) { return new List<SemanticVersion>() { null }; } return (from version in _configuration.ApiVersions where (routeSpec.MinVersion == null || version >= routeSpec.MinVersion) && (routeSpec.MaxVersion == null || version <= routeSpec.MaxVersion) select version); }
private IDictionary<string, object> CreateRouteDefaults(RouteSpecification routeSpec) { var defaults = new Dictionary<string, object> { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } }; var urlParameters = GetUrlParameterContents(routeSpec.RouteUrl).ToList(); // Inspect the url for optional parameters, specified with a trailing ? foreach (var parameter in urlParameters.Where(p => p.EndsWith("?"))) { var parameterName = parameter.TrimEnd('?'); if (parameterName.Contains(':')) parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); if (defaults.ContainsKey(parameterName)) continue; defaults.Add(parameterName, _parameterFactory.Optional()); } // Inline defaults foreach (var parameter in urlParameters.Where(p => p.Contains('='))) { var indexOfEquals = parameter.IndexOf('='); var parameterName = parameter.Substring(0, indexOfEquals); if (parameterName.Contains(':')) parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); if (defaults.ContainsKey(parameterName)) continue; var defaultValue = parameter.Substring(indexOfEquals + 1, parameter.Length - indexOfEquals - 1); defaults.Add(parameterName, defaultValue); } return defaults; }
private IDictionary <string, object> CreateRouteConstraints(RouteSpecification routeSpec) { var constraints = new Dictionary <string, object>(); // Default constraints if (routeSpec.HttpMethods.Any()) { constraints.Add("httpMethod", _routeConstraintFactory.CreateRestfulHttpMethodConstraint(routeSpec.HttpMethods)); } // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); // Inline constraints var constraintFactory = _configuration.RouteConstraintFactory; foreach (var parameter in urlParameters.Where(p => p.Contains(":"))) { // Keep track of whether this param is optional, // because we wrap the final constraint if so. var parameterIsOptional = parameter.EndsWith("?"); // Strip off everything related to defaults. var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); var sections = cleanParameter.SplitAndTrim(":"); var parameterName = sections.First(); // Do not override default or legacy inline constraints if (constraints.ContainsKey(parameterName)) { continue; } // Add constraints for each inline definition var inlineConstraints = new List <object>(); var constraintDefinitions = sections.Skip(1); foreach (var definition in constraintDefinitions) { string constraintName; object constraint; if (Regex.IsMatch(definition, @"^.*\(.*\)$")) { // Constraint of the form "firstName:string(50)" var indexOfOpenParen = definition.IndexOf('('); constraintName = definition.Substring(0, indexOfOpenParen); var constraintParams = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2).SplitAndTrim(","); constraint = constraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); } else { // Constraint of the form "id:int" constraintName = definition; constraint = constraintFactory.CreateInlineRouteConstraint(constraintName); } if (constraint == null) { throw new AttributeRoutingException( "Could not find an available inline constraint for \"{0}\".".FormatWith(constraintName)); } inlineConstraints.Add(constraint); } // Apply the constraint to the parameter. // Wrap multiple constraints in a compound constraint wrapper. // Wrap constraints for optional params in an optional constraint wrapper. var finalConstraint = (inlineConstraints.Count == 1) ? inlineConstraints.Single() : constraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); if (parameterIsOptional) { constraints.Add(parameterName, constraintFactory.CreateOptionalRouteConstraint(finalConstraint)); } else { constraints.Add(parameterName, finalConstraint); } } // Globally configured constraints var detokenizedPrefixedUrl = DetokenizeUrl(tokenizedUrl); var urlParameterNames = GetUrlParameterContents(detokenizedPrefixedUrl).ToList(); foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { var pattern = defaultConstraint.Key; foreach (var urlParameterName in urlParameterNames.Where(n => Regex.IsMatch(n, pattern))) { if (constraints.ContainsKey(urlParameterName)) { continue; } constraints.Add(urlParameterName, defaultConstraint.Value); } } return(constraints); }
private string CreateRouteUrl(RouteSpecification routeSpec) { var detokenizedUrl = DetokenizeUrl(routeSpec.Url); var urlParameterNames = GetUrlParameterNames(detokenizedUrl); // {controller} and {action} tokens are not valid if (urlParameterNames.Any(n => n.ValueEquals("controller"))) throw new AttributeRoutingException("{controller} is not a valid url parameter."); if (urlParameterNames.Any(n => n.ValueEquals("action"))) throw new AttributeRoutingException("{action} is not a valid url parameter."); // Explicitly defined area routes are not valid if (urlParameterNames.Any(n => n.ValueEquals("area"))) throw new AttributeRoutingException( "{area} url parameters are not allowed. Specify the area name by using the RouteAreaAttribute."); var urlBuilder = new StringBuilder(detokenizedUrl); // If this is not an absolute url, prefix with a route prefix or area name if (!routeSpec.IsAbsoluteUrl) { if (routeSpec.RoutePrefix.HasValue() && !routeSpec.Url.StartsWith(routeSpec.RoutePrefix)) urlBuilder.Insert(0, routeSpec.RoutePrefix + "/"); if (routeSpec.AreaUrl.HasValue() && !routeSpec.Url.StartsWith(routeSpec.AreaUrl)) urlBuilder.Insert(0, routeSpec.AreaUrl + "/"); } return urlBuilder.ToString().Trim('/'); }
private void CreateRouteDefaults(RouteSpecification routeSpec, out IDictionary <string, object> defaults, out IDictionary <string, object> queryStringDefaults) { // Going to return individual collections for: // - path routes defaults (which will go into the generated route's Defaults prop), // - and query string route defaults (which will not work perfectly with the MS bits, and need special treatment by IAttributeRoute impls). defaults = new Dictionary <string, object> { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } }; queryStringDefaults = new Dictionary <string, object>(); // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); // Need to keep track of which are path and query params. var pathOnlyUrl = RemoveQueryString(tokenizedUrl); var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl).ToList(); var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); // Inspect the url path for optional parameters and default values. foreach (var parameter in urlParameters) { // Does this param have a default value or is it optional? var isOptional = parameter.EndsWith("?"); var indexOfEquals = parameter.IndexOf('='); if (!isOptional && indexOfEquals == -1) { continue; } // Keep track if this is a querystring param var parameterIsInQueryString = queryStringParameters.Contains(parameter); // Strip off inline constraints, defaults, and optional tokens. var parameterName = DetokenizeUrlParamContentsRegex.Replace(parameter, ""); // Do not override default defaults. if (defaults.ContainsKey(parameterName)) { continue; } object defaultValue; if (isOptional) { defaultValue = _parameterFactory.Optional(); } else // It has a default value. { defaultValue = parameter.Substring(indexOfEquals + 1, parameter.Length - indexOfEquals - 1); } if (parameterIsInQueryString) { queryStringDefaults.Add(parameterName, defaultValue); } else { defaults.Add(parameterName, defaultValue); } } }
private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, IDictionary<string, object> defaults, RouteSpecification routeSpec) { var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, routeSpec); var tokenizedPath = RemoveQueryString(tokenizedUrl); var detokenizedPath = DetokenizeUrl(tokenizedPath); var urlParameterNames = GetUrlParameterContents(detokenizedPath).ToList(); var urlBuilder = new StringBuilder(detokenizedPath); // Replace {controller} URL param with default value. if (urlParameterNames.Any(n => n.ValueEquals("controller"))) { urlBuilder.Replace("{controller}", (string)defaults["controller"]); } // Replace {action} URL param with default value. if (urlParameterNames.Any(n => n.ValueEquals("action"))) { urlBuilder.Replace("{action}", (string)defaults["action"]); } // Explicitly defined area routes are not valid if (urlParameterNames.Any(n => n.ValueEquals("area"))) { throw new AttributeRoutingException( "{area} url parameters are not allowed. Specify the area name by using the RouteAreaAttribute."); } // If we are lowercasing routes, then lowercase everything but the route params var lower = routeSpec.UseLowercaseRoute.GetValueOrDefault(_configuration.UseLowercaseRoutes); if (lower) { for (var i = 0; i < urlBuilder.Length; i++) { var c = urlBuilder[i]; if (Char.IsUpper(c)) { urlBuilder[i] = Char.ToLower(c); } else if (c == '{') { while (urlBuilder[i] != '}' && i < urlBuilder.Length) { i++; } } } } return urlBuilder.ToString().Trim('/'); }
private RouteValueDictionary CreateRouteDefaults(RouteSpecification routeSpec) { var defaults = new RouteValueDictionary { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } }; foreach (var defaultAttribute in routeSpec.DefaultAttributes.Where(d => !defaults.ContainsKey(d.Key))) defaults.Add(defaultAttribute.Key, defaultAttribute.Value); // Inspect the url for optional parameters, specified with a leading ? var optionalParameterDefaults = from parameter in GetUrlParameterContents(routeSpec.Url) where parameter.StartsWith("?") let parameterName = parameter.TrimStart('?') select new RouteDefaultAttribute(parameterName, UrlParameter.Optional); foreach (var defautAttribute in optionalParameterDefaults.Where(d => !defaults.ContainsKey(d.Key))) defaults.Add(defautAttribute.Key, defautAttribute.Value); return defaults; }
private IEnumerable<IAttributeRoute> CreateRouteTranslations(RouteSpecification routeSpec) { // If no translation provider, then get out of here. if (!_configuration.TranslationProviders.Any()) yield break; // Merge all the culture names from the various providers. var cultureNames = _configuration.GetTranslationProviderCultureNames(); // Built the route translations, // choosing the first available translated route component from among the providers foreach (var cultureName in cultureNames) { string translatedRouteUrl = null, translatedRoutePrefix = null, translatedAreaUrl = null; foreach (var provider in _configuration.TranslationProviders) { translatedRouteUrl = translatedRouteUrl ?? provider.TranslateRouteUrl(cultureName, routeSpec); translatedRoutePrefix = translatedRoutePrefix ?? provider.TranslateRoutePrefix(cultureName, routeSpec); translatedAreaUrl = translatedAreaUrl ?? provider.TranslateAreaUrl(cultureName, routeSpec); } // If nothing is translated, then bail. if (translatedRouteUrl == null && translatedRoutePrefix == null && translatedAreaUrl == null) { continue; } //********************************************* // Otherwise, build a translated route // REVIEW: Could probably forgo processing defaults, constraints, and data tokens for translated routes. IDictionary<string, object> defaults; IDictionary<string, object> queryStringDefaults; CreateRouteDefaults(routeSpec, out defaults, out queryStringDefaults); IDictionary<string, object> constraints; IDictionary<string, object> queryStringConstraints; CreateRouteConstraints(routeSpec, out constraints, out queryStringConstraints); var dataTokens = CreateRouteDataTokens(routeSpec); var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, translatedAreaUrl ?? routeSpec.AreaUrl, defaults, routeSpec); var translatedRoutes = _routeFactory.CreateAttributeRoutes(routeUrl, defaults, constraints, dataTokens); foreach (var translatedRoute in translatedRoutes) { var routeName = CreateRouteName(routeSpec); if (routeName != null) { translatedRoute.RouteName = routeName; translatedRoute.DataTokens.Add("routeName", routeName); } translatedRoute.QueryStringConstraints = queryStringConstraints; translatedRoute.QueryStringDefaults = queryStringDefaults; translatedRoute.CultureName = cultureName; translatedRoute.DataTokens.Add("cultureName", cultureName); yield return translatedRoute; } } }
private string CreateRouteUrl(IDictionary<string, object> defaults, RouteSpecification routeSpec) { return CreateRouteUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, defaults, routeSpec); }
private string CreateRouteName(RouteSpecification routeSpec) { if (routeSpec.RouteName.HasValue()) { return routeSpec.RouteName; } return _configuration.AutoGenerateRouteNames ? _configuration.RouteNameBuilder(routeSpec) : null; }
private RouteValueDictionary CreateRouteConstraints(RouteSpecification routeSpec, bool isTranslation = false) { var constraints = new RouteValueDictionary(); // Default constraints if (routeSpec.HttpMethods.Any()) constraints.Add("httpMethod", new RestfulHttpMethodConstraint(routeSpec.HttpMethods)); // Inline constraints foreach (var parameter in GetUrlParameterContents(routeSpec.RouteUrl).Where(p => Regex.IsMatch(p, @"^.*\(.*\)$"))) { var indexOfOpenParen = parameter.IndexOf('('); var parameterName = parameter.Substring(0, indexOfOpenParen); if (constraints.ContainsKey(parameterName)) continue; var regexPattern = parameter.Substring(indexOfOpenParen + 1, parameter.Length - indexOfOpenParen - 2); constraints.Add(parameterName, new RegexRouteConstraint(regexPattern)); } // Attribute-based constraints foreach (var constraintAttribute in routeSpec.ConstraintAttributes) { if (constraints.ContainsKey(constraintAttribute.Key)) continue; constraints.Add(constraintAttribute.Key, constraintAttribute.Constraint); } var detokenizedUrl = DetokenizeUrl(CreateRouteUrl(routeSpec)); var urlParameterNames = GetUrlParameterContents(detokenizedUrl); // Convention-based constraints foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { var pattern = defaultConstraint.Key; foreach (var urlParameterName in urlParameterNames.Where(n => Regex.IsMatch(n, pattern))) { if (constraints.ContainsKey(urlParameterName)) continue; constraints.Add(urlParameterName, defaultConstraint.Value); } } return constraints; }
private IDictionary<string, object> CreateRouteDataTokens(RouteSpecification routeSpec) { var dataTokens = new Dictionary<string, object> { { "namespaces", new[] { routeSpec.ControllerType.Namespace } }, { "actionMethod", routeSpec.ActionMethod }, { "defaultSubdomain", _configuration.DefaultSubdomain} }; if (routeSpec.HttpMethods.Any()) { dataTokens.Add("httpMethods", routeSpec.HttpMethods); } if (routeSpec.AreaName.HasValue()) { dataTokens.Add("area", routeSpec.AreaName); dataTokens.Add("UseNamespaceFallback", false); } return dataTokens; }
private void CreateRouteConstraints(RouteSpecification routeSpec, out IDictionary<string, object> constraints, out IDictionary<string, object> queryStringConstraints) { // Going to return individual collections for: // - path routes constraints (which will go into the generated route's Constraints prop), // - and query string route constraints (which will not work perfectly with the MS bits, and need special treatment by IAttributeRoute impls). constraints = new Dictionary<string, object>(); queryStringConstraints = new Dictionary<string, object>(); // Default constraints if (routeSpec.HttpMethods.Any()) { constraints.Add("inboundHttpMethod", _routeConstraintFactory.CreateInboundHttpMethodConstraint(routeSpec.HttpMethods)); } // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); // Need to keep track of which are path and query params. var pathOnlyUrl = RemoveQueryString(tokenizedUrl); var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl); var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); // Inline constraints foreach (var parameter in urlParameters) { // Keep track of whether this param is optional or in the querystring, // because we wrap the final constraint if so. var parameterIsOptional = parameter.EndsWith("?"); var parameterIsInQueryString = queryStringParameters.Contains(parameter); // If this is a path parameter and doesn't have a constraint, then skip it. if (!parameterIsInQueryString && !parameter.Contains(":")) { continue; } // Strip off everything related to defaults and break into sections. var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); var sections = cleanParameter.SplitAndTrim(":"); // Do not override default constraints var parameterName = sections.First(); if (constraints.ContainsKey(parameterName)) { continue; } // Add constraints for each inline definition var inlineConstraints = new List<object>(); var constraintDefinitions = sections.Skip(1); foreach (var definition in constraintDefinitions) { string constraintName; object constraint; if (ConstraintParamsRegex.IsMatch(definition)) { // Constraint of the form "firstName:string(50)" var indexOfOpenParen = definition.IndexOf('('); constraintName = definition.Substring(0, indexOfOpenParen); // Parse constraint params. // NOTE: Splitting on commas only applies to non-regex constraints. var constraintParamsRaw = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2); var constraintParams = constraintName.ValueEquals("regex") ? new[] { constraintParamsRaw } : constraintParamsRaw.SplitAndTrim(","); constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); } else { // Constraint of the form "id:int" constraintName = definition; constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName); } if (constraint == null) { throw new AttributeRoutingException( "Could not find an available inline constraint for \"{0}\".".FormatWith(constraintName)); } inlineConstraints.Add(constraint); } // Wrap constraints in the following priority: object finalConstraint; // 1. If more than one constraint, wrap in a compound constraint. if (inlineConstraints.Count > 1) { finalConstraint = _routeConstraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); } else { finalConstraint = inlineConstraints.FirstOrDefault(); } // 2. If the constraint is in the querystring, wrap in a query string constraint. if (parameterIsInQueryString) { finalConstraint = _routeConstraintFactory.CreateQueryStringRouteConstraint(finalConstraint); } // 3. If the constraint is optional, wrap in an optional constraint. if (parameterIsOptional) { finalConstraint = _routeConstraintFactory.CreateOptionalRouteConstraint(finalConstraint); } // Add the constraints to the appropriate collection. if (parameterIsInQueryString) { queryStringConstraints.Add(parameterName, finalConstraint); } else { constraints.Add(parameterName, finalConstraint); } } // ... go to next parameter // Globally configured constraints - have to treat path params differently than query params. var detokenizedUrl = DetokenizeUrl(tokenizedUrl); string path, query; detokenizedUrl.GetPathAndQuery(out path, out query); var urlPathParameterNames = GetUrlParameterContents(path).ToArray(); var urlQueryParameterNames = GetUrlParameterContents(query).ToArray(); foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { ApplyDefaultRouteConstraint(urlPathParameterNames, defaultConstraint, constraints, false); ApplyDefaultRouteConstraint(urlQueryParameterNames, defaultConstraint, queryStringConstraints, true); } }
private IEnumerable<IAttributeRoute> BuildRoutes(RouteSpecification routeSpec) { // Get info needed to construct IAttributeRoute via factory method. IDictionary<string, object> defaults; IDictionary<string, object> queryStringDefaults; CreateRouteDefaults(routeSpec, out defaults, out queryStringDefaults); IDictionary<string, object> constraints; IDictionary<string, object> queryStringConstraints; CreateRouteConstraints(routeSpec, out constraints, out queryStringConstraints); var dataTokens = CreateRouteDataTokens(routeSpec); var url = CreateRouteUrl(defaults, routeSpec); // Build the route. var routes = _routeFactory.CreateAttributeRoutes(url, defaults, constraints, dataTokens); // Extend the factory generated route: foreach (var route in routes) { var routeName = CreateRouteName(routeSpec); if (routeName.HasValue()) { route.RouteName = routeName; route.DataTokens.Add("routeName", routeName); } route.QueryStringConstraints = queryStringConstraints; route.QueryStringDefaults = queryStringDefaults; route.Translations = CreateRouteTranslations(routeSpec); route.Subdomain = routeSpec.Subdomain; route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; route.PreserveCaseForUrlParameters = routeSpec.PreserveCaseForUrlParameters; route.AppendTrailingSlash = routeSpec.AppendTrailingSlash; // Yield the default route first yield return route; // Then yield the translations if (route.Translations != null) { foreach (var translation in route.Translations) { // Backreference the default route. translation.SourceLanguageRoute = route; yield return translation; } } } }
private string CreateRouteUrl(string routeUrl, string routePrefix, string areaUrl, IDictionary <string, object> defaults, RouteSpecification routeSpec) { var tokenizedUrl = BuildTokenizedUrl(routeUrl, routePrefix, areaUrl, routeSpec); var tokenizedPath = RemoveQueryString(tokenizedUrl); var detokenizedPath = DetokenizeUrl(tokenizedPath); var urlParameterNames = GetUrlParameterContents(detokenizedPath).ToList(); var urlBuilder = new StringBuilder(detokenizedPath); // Replace {controller} URL param with default value. if (urlParameterNames.Any(n => n.ValueEquals("controller"))) { urlBuilder.Replace("{controller}", (string)defaults["controller"]); } // Replace {action} URL param with default value. if (urlParameterNames.Any(n => n.ValueEquals("action"))) { urlBuilder.Replace("{action}", (string)defaults["action"]); } // Explicitly defined area routes are not valid if (urlParameterNames.Any(n => n.ValueEquals("area"))) { throw new AttributeRoutingException( "{area} url parameters are not allowed. Specify the area name by using the RouteAreaAttribute."); } // If we are lowercasing routes, then lowercase everything but the route params var lower = routeSpec.UseLowercaseRoute.GetValueOrDefault(_configuration.UseLowercaseRoutes); if (lower) { for (var i = 0; i < urlBuilder.Length; i++) { var c = urlBuilder[i]; if (Char.IsUpper(c)) { urlBuilder[i] = Char.ToLower(c); } else if (c == '{') { while (urlBuilder[i] != '}' && i < urlBuilder.Length) { i++; } } } } return(urlBuilder.ToString().Trim('/')); }
private string BuildTokenizedUrl(string routeUrl, string routePrefixUrl, string areaUrl, RouteSpecification routeSpec) { var delimitedUrl = routeUrl + "/"; // Prepend prefix if available if (routePrefixUrl.HasValue() && !routeSpec.IgnoreRoutePrefix) { var delimitedRoutePrefix = routePrefixUrl + "/"; if (!delimitedUrl.StartsWith(delimitedRoutePrefix)) { delimitedUrl = delimitedRoutePrefix + delimitedUrl; } } // Prepend area url if available if (areaUrl.HasValue() && !routeSpec.IgnoreAreaUrl) { var delimitedAreaUrl = areaUrl + "/"; if (!delimitedUrl.StartsWith(delimitedAreaUrl)) { delimitedUrl = delimitedAreaUrl + delimitedUrl; } } return(delimitedUrl.Trim('/')); }
private string CreateRouteName(RouteSpecification routeSpec) { if (routeSpec.RouteName.HasValue()) return routeSpec.RouteName; if (_configuration.AutoGenerateRouteNames) { var area = (routeSpec.AreaName.HasValue()) ? routeSpec.AreaName + "_" : null; return "{0}{1}_{2}".FormatWith(area, routeSpec.ControllerName, routeSpec.ActionName); } return null; }
private IEnumerable<IAttributeRoute> CreateRouteTranslations(RouteSpecification routeSpec) { // If no translation provider, then get out of here. if (!_configuration.TranslationProviders.Any()) yield break; // Merge all the culture names from the various providers. var cultureNames = _configuration.GetTranslationProviderCultureNames(); // Built the route translations, // choosing the first available translated route component from among the providers foreach (var cultureName in cultureNames) { string translatedRouteUrl = null, translatedRoutePrefix = null, translatedAreaUrl = null; foreach (var provider in _configuration.TranslationProviders) { translatedRouteUrl = translatedRouteUrl ?? provider.TranslateRouteUrl(cultureName, routeSpec); translatedRoutePrefix = translatedRoutePrefix ?? provider.TranslateRoutePrefix(cultureName, routeSpec); translatedAreaUrl = translatedAreaUrl ?? provider.TranslateAreaUrl(cultureName, routeSpec); } // If nothing is translated, then bail. if (translatedRouteUrl == null && translatedRoutePrefix == null && translatedAreaUrl == null) continue; //********************************************* // Otherwise, build a translated route var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, translatedAreaUrl ?? routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl, routeSpec.UseLowercaseRoute); var translatedRoute = _routeFactory.CreateAttributeRoute(routeUrl, CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec)); var routeName = CreateRouteName(routeSpec); if (routeName != null) { translatedRoute.RouteName = routeName; translatedRoute.DataTokens.Add("routeName", routeName); } translatedRoute.CultureName = cultureName; translatedRoute.DataTokens.Add("cultureName", cultureName); yield return translatedRoute; } }
private IEnumerable <IAttributeRoute> CreateRouteTranslations(RouteSpecification routeSpec) { // If no translation provider, then get out of here. if (!_configuration.TranslationProviders.Any()) { yield break; } // Merge all the culture names from the various providers. var cultureNames = _configuration.GetTranslationProviderCultureNames(); // Built the route translations, // choosing the first available translated route component from among the providers foreach (var cultureName in cultureNames) { string translatedRouteUrl = null, translatedRoutePrefix = null, translatedAreaUrl = null; foreach (var provider in _configuration.TranslationProviders) { translatedRouteUrl = translatedRouteUrl ?? provider.TranslateRouteUrl(cultureName, routeSpec); translatedRoutePrefix = translatedRoutePrefix ?? provider.TranslateRoutePrefix(cultureName, routeSpec); translatedAreaUrl = translatedAreaUrl ?? provider.TranslateAreaUrl(cultureName, routeSpec); } // If nothing is translated, then bail. if (translatedRouteUrl == null && translatedRoutePrefix == null && translatedAreaUrl == null) { continue; } //********************************************* // Otherwise, build a translated route // REVIEW: Could probably forgo processing defaults, constraints, and data tokens for translated routes. IDictionary <string, object> defaults; IDictionary <string, object> queryStringDefaults; CreateRouteDefaults(routeSpec, out defaults, out queryStringDefaults); IDictionary <string, object> constraints; IDictionary <string, object> queryStringConstraints; CreateRouteConstraints(routeSpec, out constraints, out queryStringConstraints); var dataTokens = CreateRouteDataTokens(routeSpec); var routeUrl = CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, translatedAreaUrl ?? routeSpec.AreaUrl, defaults, routeSpec); var translatedRoutes = _routeFactory.CreateAttributeRoutes(routeUrl, defaults, constraints, dataTokens); foreach (var translatedRoute in translatedRoutes) { var routeName = CreateRouteName(routeSpec); if (routeName != null) { translatedRoute.RouteName = routeName; translatedRoute.DataTokens.Add("routeName", routeName); } translatedRoute.QueryStringConstraints = queryStringConstraints; translatedRoute.QueryStringDefaults = queryStringDefaults; translatedRoute.CultureName = cultureName; translatedRoute.DataTokens.Add("cultureName", cultureName); yield return(translatedRoute); } } }
private string BuildTokenizedUrl(string routeUrl, string routePrefixUrl, string areaUrl, SemanticVersion versionPrefix, RouteSpecification routeSpec) { var delimitedUrl = routeUrl + "/"; // Prepend prefix if available if (routePrefixUrl.HasValue() && !routeSpec.IgnoreRoutePrefix) { var delimitedRoutePrefix = routePrefixUrl + "/"; if (!delimitedUrl.StartsWith(delimitedRoutePrefix)) delimitedUrl = delimitedRoutePrefix + delimitedUrl; } if (versionPrefix != null) { var delimitedVerisonPrefix = versionPrefix.ToString() + "/"; if (!delimitedUrl.StartsWith(delimitedVerisonPrefix)) delimitedUrl = delimitedVerisonPrefix + delimitedUrl; } // Prepend area url if available if (areaUrl.HasValue() && !routeSpec.IgnoreAreaUrl) { var delimitedAreaUrl = areaUrl + "/"; if (!delimitedUrl.StartsWith(delimitedAreaUrl)) delimitedUrl = delimitedAreaUrl + delimitedUrl; } return delimitedUrl.Trim('/'); }
private RouteValueDictionary CreateRouteDataTokens(RouteSpecification routeSpec) { var dataTokens = new RouteValueDictionary { { "namespaces", new[] { routeSpec.ControllerType.Namespace } } }; if (routeSpec.AreaName.HasValue()) { dataTokens.Add("area", routeSpec.AreaName); dataTokens.Add("UseNamespaceFallback", false); } return dataTokens; }
private RouteValueDictionary CreateRouteConstraints(RouteSpecification routeSpec) { var constraints = new RouteValueDictionary(); // Default constraints if (routeSpec.HttpMethods.Any()) constraints.Add("httpMethod", new RestfulHttpMethodConstraint(routeSpec.HttpMethods)); // Inline constraints foreach (var parameter in GetUrlParameterContents(routeSpec.RouteUrl).Where(p => p.Contains(":")).Select(p => p.Trim('?'))) { var indexOfColumn = parameter.IndexOf(':'); var parameterName = parameter.Substring(0, indexOfColumn); if (constraints.ContainsKey(parameterName)) continue; var constraintDefinition = parameter.Substring(indexOfColumn + 1, parameter.Length - indexOfColumn - 1); if (constraintDefinition.Contains('=')) constraintDefinition = constraintDefinition.Substring(0, constraintDefinition.IndexOf('=')); IRouteConstraint constraint; if (Regex.IsMatch(constraintDefinition, @"^.*\(.*\)$")) { // Constraint of the form "firstName:string(50)" var indexOfOpenParen = constraintDefinition.IndexOf('('); var constraintType = constraintDefinition.Substring(0, indexOfOpenParen); var constraintParams = constraintDefinition.Substring(indexOfOpenParen + 1, constraintDefinition.Length - indexOfOpenParen - 2); constraint = RouteConstraintFactory.GetConstraint(constraintType, constraintParams.SplitAndTrim(new[] { "/" })); } else // Constraint of the form "id:int" constraint = RouteConstraintFactory.GetConstraint(constraintDefinition); constraints.Add(parameterName, constraint); } // Attribute-based constraints foreach (var constraintAttribute in routeSpec.ConstraintAttributes) { if (constraints.ContainsKey(constraintAttribute.Key)) continue; constraints.Add(constraintAttribute.Key, constraintAttribute.Constraint); } var detokenizedUrl = DetokenizeUrl(CreateRouteUrl(routeSpec)); var urlParameterNames = GetUrlParameterContents(detokenizedUrl); // Convention-based constraints foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { var pattern = defaultConstraint.Key; foreach (var urlParameterName in urlParameterNames.Where(n => Regex.IsMatch(n, pattern))) { if (constraints.ContainsKey(urlParameterName)) continue; constraints.Add(urlParameterName, defaultConstraint.Value); } } return constraints; }
private IDictionary<string, object> CreateRouteConstraints(RouteSpecification routeSpec) { var constraints = new Dictionary<string, object>(); // Default constraints if (routeSpec.HttpMethods.Any()) constraints.Add("httpMethod", _routeConstraintFactory.CreateRestfulHttpMethodConstraint(routeSpec.HttpMethods)); // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); // Inline constraints var constraintFactory = _configuration.RouteConstraintFactory; foreach (var parameter in urlParameters.Where(p => p.Contains(":"))) { // Keep track of whether this param is optional, // because we wrap the final constraint if so. var parameterIsOptional = parameter.EndsWith("?"); // Strip off everything related to defaults. var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); var sections = cleanParameter.SplitAndTrim(":"); var parameterName = sections.First(); // Do not override default or legacy inline constraints if (constraints.ContainsKey(parameterName)) continue; // Add constraints for each inline definition var inlineConstraints = new List<object>(); var constraintDefinitions = sections.Skip(1); foreach (var definition in constraintDefinitions) { string constraintName; object constraint; if (Regex.IsMatch(definition, @"^.*\(.*\)$")) { // Constraint of the form "firstName:string(50)" var indexOfOpenParen = definition.IndexOf('('); constraintName = definition.Substring(0, indexOfOpenParen); var constraintParams = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2).SplitAndTrim(","); constraint = constraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); } else { // Constraint of the form "id:int" constraintName = definition; constraint = constraintFactory.CreateInlineRouteConstraint(constraintName); } if (constraint == null) throw new AttributeRoutingException( "Could not find an available inline constraint for \"{0}\".".FormatWith(constraintName)); inlineConstraints.Add(constraint); } // Apply the constraint to the parameter. // Wrap multiple constraints in a compound constraint wrapper. // Wrap constraints for optional params in an optional constraint wrapper. var finalConstraint = (inlineConstraints.Count == 1) ? inlineConstraints.Single() : constraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); if (parameterIsOptional) constraints.Add(parameterName, constraintFactory.CreateOptionalRouteConstraint(finalConstraint)); else constraints.Add(parameterName, finalConstraint); } // Globally configured constraints var detokenizedPrefixedUrl = DetokenizeUrl(tokenizedUrl); var urlParameterNames = GetUrlParameterContents(detokenizedPrefixedUrl).ToList(); foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { var pattern = defaultConstraint.Key; foreach (var urlParameterName in urlParameterNames.Where(n => Regex.IsMatch(n, pattern))) { if (constraints.ContainsKey(urlParameterName)) continue; constraints.Add(urlParameterName, defaultConstraint.Value); } } return constraints; }
private RouteValueDictionary CreateRouteDefaults(RouteSpecification routeSpec) { var defaults = new RouteValueDictionary { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } }; var urlParameters = GetUrlParameterContents(routeSpec.RouteUrl); // Inspect the url for optional parameters, specified with a leading or trailing (or both) ? foreach (var parameter in urlParameters.Where(p => p.StartsWith("?") || p.EndsWith("?"))) { var parameterName = parameter.Trim('?'); if (parameterName.Contains(':')) parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); if (defaults.ContainsKey(parameterName)) continue; defaults.Add(parameterName, UrlParameter.Optional); } // Inline defaults foreach (var parameter in urlParameters.Where(p => p.Contains('='))) { var indexOfEquals = parameter.IndexOf('='); var parameterName = parameter.Substring(0, indexOfEquals); if (parameterName.Contains(':')) parameterName = parameterName.Substring(0, parameterName.IndexOf(':')); if (defaults.ContainsKey(parameterName)) continue; var defaultValue = parameter.Substring(indexOfEquals + 1, parameter.Length - indexOfEquals - 1); defaults.Add(parameterName, defaultValue); } // Attribute-based defaults foreach (var defaultAttribute in routeSpec.DefaultAttributes) { if (defaults.ContainsKey(defaultAttribute.Key)) continue; defaults.Add(defaultAttribute.Key, defaultAttribute.Value); } return defaults; }
private IEnumerable<IAttributeRoute> Build(RouteSpecification routeSpec) { var route = _routeFactory.CreateAttributeRoute(CreateRouteUrl(routeSpec), CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec)); var routeName = CreateRouteName(routeSpec); if (routeName.HasValue()) { route.RouteName = routeName; route.DataTokens.Add("routeName", routeName); } route.Translations = CreateRouteTranslations(routeSpec); route.Subdomain = routeSpec.Subdomain; route.UseLowercaseRoute = routeSpec.UseLowercaseRoute; route.PreserveCaseForUrlParameters = routeSpec.PreserveCaseForUrlParameters; route.AppendTrailingSlash = routeSpec.AppendTrailingSlash; // Yield the default route first yield return route; // Then yield the translations if (route.Translations == null) yield break; foreach (var translation in route.Translations) { // Backreference the default route. translation.DefaultRouteContainer = route; yield return translation; } }
private IEnumerable<AttributeRoute> CreateRouteTranslations(RouteSpecification routeSpec) { // If no translation provider, then get out of here. if (!_configuration.TranslationProviders.Any()) yield break; // Merge all the culture names from the various providers. var cultureNames = _configuration.GetTranslationProviderCultureNames(); // Built the route translations, // choosing the first available translated route component from among the providers foreach (var cultureName in cultureNames) { string translatedRouteUrl = null, translatedRoutePrefix = null, translatedAreaUrl = null; foreach (var provider in _configuration.TranslationProviders) { translatedRouteUrl = translatedRouteUrl ?? provider.TranslateRouteUrl(cultureName, routeSpec); translatedRoutePrefix = translatedRoutePrefix ?? provider.TranslateRoutePrefix(cultureName, routeSpec); translatedAreaUrl = translatedAreaUrl ?? provider.TranslateAreaUrl(cultureName, routeSpec); } if (translatedRouteUrl == null && translatedRoutePrefix == null && translatedAreaUrl == null) continue; var translatedRoute = new AttributeRoute(CreateRouteUrl(translatedRouteUrl ?? routeSpec.RouteUrl, translatedRoutePrefix ?? routeSpec.RoutePrefixUrl, translatedAreaUrl ?? routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl), CreateRouteDefaults(routeSpec), CreateRouteConstraints(routeSpec), CreateRouteDataTokens(routeSpec), _configuration) { CultureName = cultureName, }; translatedRoute.DataTokens.Add("cultureName", cultureName); yield return translatedRoute; } }
private IDictionary<string, object> CreateRouteConstraints(RouteSpecification routeSpec) { var constraints = new Dictionary<string, object>(); // Default constraints if (routeSpec.HttpMethods.Any()) constraints.Add("inboundHttpMethod", _routeConstraintFactory.CreateInboundHttpMethodConstraint(routeSpec.HttpMethods)); // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); // Need to keep track of query params. // Can do this by detokenizing URL (which strips query), // and then taking all the URL parameters except those from the path part of the URL. var pathOnlyUrl = RemoveQueryString(tokenizedUrl); var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl); var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); // Inline constraints foreach (var parameter in urlParameters) { // Keep track of whether this param is optional or in the querystring, // because we wrap the final constraint if so. var parameterIsOptional = parameter.EndsWith("?"); var parameterIsInQueryString = queryStringParameters.Contains(parameter); // If this is a path parameter and doesn't have a constraint, then skip it. if (!parameterIsInQueryString && !parameter.Contains(":")) continue; // Strip off everything related to defaults. var cleanParameter = parameter.TrimEnd('?').Split('=').FirstOrDefault(); var sections = cleanParameter.SplitAndTrim(":"); var parameterName = sections.First(); // Do not override default or legacy inline constraints if (constraints.ContainsKey(parameterName)) continue; // Add constraints for each inline definition var inlineConstraints = new List<object>(); var constraintDefinitions = sections.Skip(1); foreach (var definition in constraintDefinitions) { string constraintName; object constraint; if (Regex.IsMatch(definition, @"^.*\(.*\)$")) { // Constraint of the form "firstName:string(50)" var indexOfOpenParen = definition.IndexOf('('); constraintName = definition.Substring(0, indexOfOpenParen); // Parse constraint params. // NOTE: Splitting on commas only applies to non-regex constraints. var constraintParamsRaw = definition.Substring(indexOfOpenParen + 1, definition.Length - indexOfOpenParen - 2); var constraintParams = constraintName.ValueEquals("regex") ? new[] {constraintParamsRaw} : constraintParamsRaw.SplitAndTrim(","); constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName, constraintParams); } else { // Constraint of the form "id:int" constraintName = definition; constraint = _routeConstraintFactory.CreateInlineRouteConstraint(constraintName); } if (constraint == null) throw new AttributeRoutingException( "Could not find an available inline constraint for \"{0}\".".FormatWith(constraintName)); inlineConstraints.Add(constraint); } // Apply the constraint to the parameter, and wrap constraints in the following priority: object finalConstraint; // 1. If more than one constraint, wrap in a compound constraint. if (inlineConstraints.Count > 1) { finalConstraint = _routeConstraintFactory.CreateCompoundRouteConstraint(inlineConstraints.ToArray()); } else { finalConstraint = inlineConstraints.FirstOrDefault(); } // 2. If the constraint is in the querystring, wrap in a query string constraint. if (parameterIsInQueryString) { finalConstraint = _routeConstraintFactory.CreateQueryStringRouteConstraint(finalConstraint); } // 3. If the constraint is optional, wrap in an optional constraint. if (parameterIsOptional) { finalConstraint = _routeConstraintFactory.CreateOptionalRouteConstraint(finalConstraint); } constraints.Add(parameterName, finalConstraint); } // ... go to next parameter // Globally configured constraints var detokenizedPrefixedUrl = DetokenizeUrl(tokenizedUrl); var urlParameterNames = GetUrlParameterContents(detokenizedPrefixedUrl).ToList(); foreach (var defaultConstraint in _configuration.DefaultRouteConstraints) { var pattern = defaultConstraint.Key; foreach (var urlParameterName in urlParameterNames.Where(n => Regex.IsMatch(n, pattern))) { if (constraints.ContainsKey(urlParameterName)) continue; constraints.Add(urlParameterName, defaultConstraint.Value); } } return constraints; }
private string CreateRouteUrl(RouteSpecification routeSpec) { return CreateRouteUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec.IsAbsoluteUrl); }
private void CreateRouteDefaults(RouteSpecification routeSpec, out IDictionary<string, object> defaults, out IDictionary<string, object> queryStringDefaults) { // Going to return individual collections for: // - path routes defaults (which will go into the generated route's Defaults prop), // - and query string route defaults (which will not work perfectly with the MS bits, and need special treatment by IAttributeRoute impls). defaults = new Dictionary<string, object> { { "controller", routeSpec.ControllerName }, { "action", routeSpec.ActionName } }; queryStringDefaults = new Dictionary<string, object>(); // Work from a complete, tokenized url; ie: support constraints in area urls, route prefix urls, and route urls. var tokenizedUrl = BuildTokenizedUrl(routeSpec.RouteUrl, routeSpec.RoutePrefixUrl, routeSpec.AreaUrl, routeSpec); var urlParameters = GetUrlParameterContents(tokenizedUrl).ToList(); // Need to keep track of which are path and query params. var pathOnlyUrl = RemoveQueryString(tokenizedUrl); var pathOnlyUrlParameters = GetUrlParameterContents(pathOnlyUrl).ToList(); var queryStringParameters = urlParameters.Except(pathOnlyUrlParameters).ToList(); // Inspect the url path for optional parameters and default values. foreach (var parameter in urlParameters) { // Does this param have a default value or is it optional? var isOptional = parameter.EndsWith("?"); var indexOfEquals = parameter.IndexOf('='); if (!isOptional && indexOfEquals == -1) { continue; } // Keep track if this is a querystring param var parameterIsInQueryString = queryStringParameters.Contains(parameter); // Strip off inline constraints, defaults, and optional tokens. var parameterName = DetokenizeUrlParamContentsRegex.Replace(parameter, ""); // Do not override default defaults. if (defaults.ContainsKey(parameterName)) { continue; } object defaultValue; if (isOptional) { defaultValue = _parameterFactory.Optional(); } else // It has a default value. { defaultValue = parameter.Substring(indexOfEquals + 1, parameter.Length - indexOfEquals - 1); } if (parameterIsInQueryString) { queryStringDefaults.Add(parameterName, defaultValue); } else { defaults.Add(parameterName, defaultValue); } } }
private string BuildTokenizedUrl(string routeUrl, string routePrefixUrl, string areaUrl, RouteSpecification routeSpec) { var delimitedUrl = routeUrl + "/"; // Prepend prefix if available if (routePrefixUrl.HasValue() && !routeSpec.IgnoreRoutePrefix) { var delimitedRoutePrefix = routePrefixUrl + "/"; if (!delimitedUrl.StartsWith(delimitedRoutePrefix)) { delimitedUrl = delimitedRoutePrefix + delimitedUrl; } } // Prepend area url if available if (areaUrl.HasValue() && !routeSpec.IgnoreAreaUrl) { var delimitedAreaUrl = areaUrl + "/"; if (!delimitedUrl.StartsWith(delimitedAreaUrl)) { delimitedUrl = delimitedAreaUrl + delimitedUrl; } } return delimitedUrl.Trim('/'); }