diff --git a/src/AttributeRouting/Framework/AttributeRouteVisitor.cs b/src/AttributeRouting/Framework/AttributeRouteVisitor.cs index cac2e00..fbc8f47 100644 --- a/src/AttributeRouting/Framework/AttributeRouteVisitor.cs +++ b/src/AttributeRouting/Framework/AttributeRouteVisitor.cs @@ -1,42 +1,42 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; +using System.Linq; +using System.Text.RegularExpressions; using System.Threading; -using AttributeRouting.Helpers; - -namespace AttributeRouting.Framework -{ - /// - /// Visitor-ish used to extend implementations of . - /// - /// - /// Due to different route implementations in - /// System.Web.Routing (used for MVC controller routes) and - /// System.Web.Http.Routing (used for Web API controller routes). - /// +using AttributeRouting.Helpers; + +namespace AttributeRouting.Framework +{ + /// + /// Visitor-ish used to extend implementations of . + /// + /// + /// Due to different route implementations in + /// System.Web.Routing (used for MVC controller routes) and + /// System.Web.Http.Routing (used for Web API controller routes). + /// public class AttributeRouteVisitor { - private static readonly Regex PathAndQueryRegex = new Regex(@"(?[^\?]*)(?\?.*)?"); - - private readonly IAttributeRoute _route; + private static readonly Regex PathAndQueryRegex = new Regex(@"(?[^\?]*)(?\?.*)?"); + + private readonly IAttributeRoute _route; private readonly ConfigurationBase _configuration; private string _staticLeftPartOfUrl; - - /// - /// Creates a new visitor extending implementations of IAttributeRoute with common logic. - /// - /// The route - /// The route's configuration - public AttributeRouteVisitor(IAttributeRoute route, ConfigurationBase configuration) - { - if (route == null) throw new ArgumentNullException("route"); - if (configuration == null) throw new ArgumentNullException("configuration"); - - _route = route; - _configuration = configuration; - } - + + /// + /// Creates a new visitor extending implementations of IAttributeRoute with common logic. + /// + /// The route + /// The route's configuration + public AttributeRouteVisitor(IAttributeRoute route, ConfigurationBase configuration) + { + if (route == null) throw new ArgumentNullException("route"); + if (configuration == null) throw new ArgumentNullException("configuration"); + + _route = route; + _configuration = configuration; + } + private string StaticLeftPartOfUrl { get @@ -50,12 +50,12 @@ private string StaticLeftPartOfUrl } return _staticLeftPartOfUrl; } - } - + } + /// /// Adds querystring default values to route values collection if they aren't already present. /// - /// The route values. + /// The route values. public void AddQueryStringDefaultsToRouteValues(IDictionary routeValues) { foreach (var queryStringDefault in _route.QueryStringDefaults) @@ -72,190 +72,190 @@ public void AddQueryStringDefaultsToRouteValues(IDictionary rout routeValues.Add(queryStringDefault.Key, queryStringDefault.Value); } } - } - - /// - /// Performs lowercasing and appends trailing slash if this route is so configured. - /// - /// The current virtual path, after translation - /// The final virtual path - public string GetFinalVirtualPath(string virtualPath) - { - /** - * Lowercase urls. - * NOTE: The initial lowercasing of all BUT url params occurs in RouteBuilder.CreateRouteUrl(). - * This is just a final lowercasing of the final, parameter-replaced url. - */ - - var lower = _route.UseLowercaseRoute.GetValueOrDefault(_configuration.UseLowercaseRoutes); - var preserve = _route.PreserveCaseForUrlParameters.GetValueOrDefault(_configuration.PreserveCaseForUrlParameters); - - if (lower && !preserve) - { - virtualPath = TransformVirtualPathToLowercase(virtualPath); - } - - /** - * Append trailing slashes - */ - - var appendTrailingSlash = _route.AppendTrailingSlash.GetValueOrDefault(_configuration.AppendTrailingSlash); - if (appendTrailingSlash) - { - virtualPath = AppendTrailingSlashToVirtualPath(virtualPath); - } - - return virtualPath; - } - - /// - /// Gets the translated virtual path for this route. - /// - /// - /// The type of virtual path data to be returned. - /// This varies based on whether the route is a - /// System.Web.Routing.Route or System.Web.Http.Routing.HttpRoute. - /// - /// A delegate that can get the TVirtualPathData from a translated route - /// Returns null if no translation is available. - public TVirtualPathData GetTranslatedVirtualPath(Func fromTranslation) - where TVirtualPathData : class - { - if (_route.Translations == null) - { - return null; - } - - var translations = _route.Translations.ToArray(); - if (!translations.Any()) - { - return null; - } - - var currentCultureName = Thread.CurrentThread.CurrentUICulture.Name; - - // Try and get the language-culture translation, then fall back to language translation - var translation = translations.FirstOrDefault(t => t.CultureName == currentCultureName) - ?? translations.FirstOrDefault(t => currentCultureName.StartsWith(t.CultureName)); - - if (translation == null) - { - return null; - } - - return fromTranslation(translation); } - /// - /// Tests whether the route matches the current UI culture. - /// - /// The name of the UI culture to test to test. - /// - public bool IsCultureNameMatched(string cultureName) - { - // If not constraining by culture, then do not apply this check. - if (!_configuration.ConstrainTranslatedRoutesByCurrentUICulture) - { - return true; - } - - // If no translations are available, then true. - if (!_configuration.TranslationProviders.Any()) - { - return true; - } - - // Need the neutral culture as a fallback during matching. - var neutralCultureName = cultureName.Split('-').First(); - - if (_route.SourceLanguageRoute == null) - { - // This is a source language route: - - // Match if this route has no translations. - var translations = _route.Translations.ToArray(); - if (!translations.Any()) - { - return true; - } - - // Match if this route has no translations for the current UI culture's language. - if (!translations.Any(t => t.CultureName.ValueEquals(neutralCultureName))) - { - return true; - } - } - else - { - // This is a translated route: - - var routeCultureName = _route.CultureName; - - // Match if the current UI culture is the culture of this route. - if (cultureName.ValueEquals(routeCultureName)) - { - return true; - } - - // Match if: - // - the route's culture name is neutral, - // - and it matches the current UI culture's language, - // - and no translation exists for the specific current UI culture. - if (routeCultureName.Split('-').Length == 1 /* neutral culture name */ - && neutralCultureName == routeCultureName /* matches the current UI culture's language */ - && !_route.SourceLanguageRoute.Translations.Any(t => t.CultureName.ValueEquals(cultureName))) - { - return true; - } - } - - // Otherwise, don't match. - return false; - } - - /// - /// Optimizes route matching by comparing the static left part of a route's URL with the requested path. - /// - /// The path of the requested URL. - /// True if the requested URL path starts with the static left part of the route's URL. - /// Thanks: http://samsaffron.com/archive/2011/10/13/optimising-asp-net-mvc3-routing - public bool IsStaticLeftPartOfUrlMatched(string requestedPath) - { - // Compare the left part with the requested path - var comparableRequestedPath = requestedPath.TrimEnd('/'); - return comparableRequestedPath.StartsWith(StaticLeftPartOfUrl, StringComparison.OrdinalIgnoreCase); - } - - /// - /// Tests whether the configured subdomain (if any) matches the current host. - /// - /// The subdomain part of the host from the current request - /// True if the subdomain for this route matches the current request host. - public bool IsSubdomainMatched(string requestedSubdomain) - { - // If no subdomains are mapped with AR, then yes. - if (!_configuration.MappedSubdomains.Any()) - { - return true; - } - - // Match if subdomain is null and this route has no subdomain. - if (requestedSubdomain.HasNoValue() && _route.Subdomain.HasNoValue()) - { - return true; - } - - // Match if this route is mapped to the requested host's subdomain - var routeSubdomain = _route.Subdomain ?? _configuration.DefaultSubdomain; - if (routeSubdomain.ValueEquals(requestedSubdomain)) - { - return true; - } - - // Otherwise, this route does not match the request. - return false; - } - + /// + /// Performs lowercasing and appends trailing slash if this route is so configured. + /// + /// The current virtual path, after translation + /// The final virtual path + public string GetFinalVirtualPath(string virtualPath) + { + /** + * Lowercase urls. + * NOTE: The initial lowercasing of all BUT url params occurs in RouteBuilder.CreateRouteUrl(). + * This is just a final lowercasing of the final, parameter-replaced url. + */ + + var lower = _route.UseLowercaseRoute.GetValueOrDefault(_configuration.UseLowercaseRoutes); + var preserve = _route.PreserveCaseForUrlParameters.GetValueOrDefault(_configuration.PreserveCaseForUrlParameters); + + if (lower && !preserve) + { + virtualPath = TransformVirtualPathToLowercase(virtualPath); + } + + /** + * Append trailing slashes + */ + + var appendTrailingSlash = _route.AppendTrailingSlash.GetValueOrDefault(_configuration.AppendTrailingSlash); + if (appendTrailingSlash) + { + virtualPath = AppendTrailingSlashToVirtualPath(virtualPath); + } + + return virtualPath; + } + + /// + /// Gets the translated virtual path for this route. + /// + /// + /// The type of virtual path data to be returned. + /// This varies based on whether the route is a + /// System.Web.Routing.Route or System.Web.Http.Routing.HttpRoute. + /// + /// A delegate that can get the TVirtualPathData from a translated route + /// Returns null if no translation is available. + public TVirtualPathData GetTranslatedVirtualPath(Func fromTranslation) + where TVirtualPathData : class + { + if (_route.Translations == null) + { + return null; + } + + var translations = _route.Translations.ToArray(); + if (!translations.Any()) + { + return null; + } + + var currentCultureName = Thread.CurrentThread.CurrentUICulture.Name; + + // Try and get the language-culture translation, then fall back to language translation + var translation = translations.FirstOrDefault(t => t.CultureName == currentCultureName) + ?? translations.FirstOrDefault(t => currentCultureName.StartsWith(t.CultureName)); + + if (translation == null) + { + return null; + } + + return fromTranslation(translation); + } + + /// + /// Tests whether the route matches the current UI culture. + /// + /// The name of the UI culture to test to test. + /// + public bool IsCultureNameMatched(string cultureName) + { + // If not constraining by culture, then do not apply this check. + if (!_configuration.ConstrainTranslatedRoutesByCurrentUICulture) + { + return true; + } + + // If no translations are available, then true. + if (!_configuration.TranslationProviders.Any()) + { + return true; + } + + // Need the neutral culture as a fallback during matching. + var neutralCultureName = cultureName.Split('-').First(); + + if (_route.SourceLanguageRoute == null) + { + // This is a source language route: + + // Match if this route has no translations. + var translations = _route.Translations.ToArray(); + if (!translations.Any()) + { + return true; + } + + // Match if this route has no translations for the current UI culture's language. + if (!translations.Any(t => t.CultureName.ValueEquals(neutralCultureName))) + { + return true; + } + } + else + { + // This is a translated route: + + var routeCultureName = _route.CultureName; + + // Match if the current UI culture is the culture of this route. + if (cultureName.ValueEquals(routeCultureName)) + { + return true; + } + + // Match if: + // - the route's culture name is neutral, + // - and it matches the current UI culture's language, + // - and no translation exists for the specific current UI culture. + if (routeCultureName.Split('-').Length == 1 /* neutral culture name */ + && neutralCultureName == routeCultureName /* matches the current UI culture's language */ + && !_route.SourceLanguageRoute.Translations.Any(t => t.CultureName.ValueEquals(cultureName))) + { + return true; + } + } + + // Otherwise, don't match. + return false; + } + + /// + /// Optimizes route matching by comparing the static left part of a route's URL with the requested path. + /// + /// The path of the requested URL. + /// True if the requested URL path starts with the static left part of the route's URL. + /// Thanks: http://samsaffron.com/archive/2011/10/13/optimising-asp-net-mvc3-routing + public bool IsStaticLeftPartOfUrlMatched(string requestedPath) + { + // Compare the left part with the requested path + var comparableRequestedPath = requestedPath.TrimEnd('/').TrimStart('/'); + return comparableRequestedPath.StartsWith(StaticLeftPartOfUrl, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Tests whether the configured subdomain (if any) matches the current host. + /// + /// The subdomain part of the host from the current request + /// True if the subdomain for this route matches the current request host. + public bool IsSubdomainMatched(string requestedSubdomain) + { + // If no subdomains are mapped with AR, then yes. + if (!_configuration.MappedSubdomains.Any()) + { + return true; + } + + // Match if subdomain is null and this route has no subdomain. + if (requestedSubdomain.HasNoValue() && _route.Subdomain.HasNoValue()) + { + return true; + } + + // Match if this route is mapped to the requested host's subdomain + var routeSubdomain = _route.Subdomain ?? _configuration.DefaultSubdomain; + if (routeSubdomain.ValueEquals(requestedSubdomain)) + { + return true; + } + + // Otherwise, this route does not match the request. + return false; + } + /// /// Processes query constraints separately from route constraints. /// @@ -270,7 +270,7 @@ public bool IsSubdomainMatched(string requestedSubdomain) /// that is not present in the url template. See logic in: /// - System.Web.Http.Routing.HttpParsedRoute.Bind(...) /// - System.Web.Routing.ParsedRoute.Bind(...) - /// + /// public bool ProcessQueryStringConstraints(Func processConstraint) { foreach (var queryStringConstraint in _route.QueryStringConstraints) @@ -285,27 +285,27 @@ public bool ProcessQueryStringConstraints(Func processCons } return true; - } - - private static string AppendTrailingSlashToVirtualPath(string virtualPath) - { - string path, query; - virtualPath.GetPathAndQuery(out path, out query); - - if (path.HasValue() && !path.EndsWith("/")) - { - path += "/"; - } - - return path + query; - } - - private static string TransformVirtualPathToLowercase(string virtualPath) - { - string path, query; - virtualPath.GetPathAndQuery(out path, out query); - - return path.ToLowerInvariant() + query; - } - } -} + } + + private static string AppendTrailingSlashToVirtualPath(string virtualPath) + { + string path, query; + virtualPath.GetPathAndQuery(out path, out query); + + if (path.HasValue() && !path.EndsWith("/")) + { + path += "/"; + } + + return path + query; + } + + private static string TransformVirtualPathToLowercase(string virtualPath) + { + string path, query; + virtualPath.GetPathAndQuery(out path, out query); + + return path.ToLowerInvariant() + query; + } + } +}