From 5fbf4379cce52f20ae2cfa39a784762c10c28265 Mon Sep 17 00:00:00 2001 From: sgay Date: Fri, 20 Jul 2012 18:54:59 -0200 Subject: [PATCH] refactor routing --- src/Umbraco.Core/UriExtensions.cs | 102 +++++++-- src/Umbraco.Web/NiceUrls.cs | 196 +++++++++--------- src/Umbraco.Web/Routing/DocumentRequest.cs | 91 ++------ src/Umbraco.Web/Routing/Domains.cs | 64 ++++++ src/Umbraco.Web/Routing/LookupByAlias.cs | 6 +- src/Umbraco.Web/Routing/LookupById.cs | 4 +- src/Umbraco.Web/Routing/LookupByPath.cs | 7 +- .../Routing/LookupByPathWithTemplate.cs | 11 +- src/Umbraco.Web/Routing/LookupByProfile.cs | 6 +- src/Umbraco.Web/Routing/LookupFor404.cs | 2 +- src/Umbraco.Web/Routing/RoutesCache.cs | 8 + src/Umbraco.Web/Routing/RoutingEnvironment.cs | 4 - src/Umbraco.Web/UmbracoModule.cs | 27 ++- .../{UrlUtility.cs => UriUtility.cs} | 76 ++++--- .../umbraco.presentation/library.cs | 165 +-------------- src/umbraco.sln | 2 +- 16 files changed, 368 insertions(+), 403 deletions(-) create mode 100644 src/Umbraco.Web/Routing/Domains.cs rename src/Umbraco.Web/{UrlUtility.cs => UriUtility.cs} (71%) diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 8611e8bd71..7a81c62a0b 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -5,18 +5,96 @@ namespace Umbraco.Core { internal static class UriExtensions { - public static Uri Rewrite(this Uri uri, string path, string query) - { - var pathAndQuery = new StringBuilder(); + // Creates a new Uri with a rewritten path + // Everything else is unchanged but for the fragment which is lost + public static Uri Rewrite(this Uri uri, string path) + { + if (uri.IsAbsoluteUri) + { + return new Uri(uri.GetLeftPart(UriPartial.Authority) + path + uri.Query); + } + else + { + // cannot get .Query on relative uri (InvalidOperation) + var s = uri.OriginalString; + var posq = s.IndexOf("?"); + var posf = s.IndexOf("#"); + var query = posq < 0 ? null : (posf < 0 ? s.Substring(posq) : s.Substring(posq, posf - posq)); - if (!path.StartsWith("/")) - pathAndQuery.Append("/"); - pathAndQuery.Append(path); - if (!query.StartsWith("?")) - pathAndQuery.Append("?"); - pathAndQuery.Append(query); + return new Uri(path + query, UriKind.Relative); + } + } - return new Uri(uri, pathAndQuery.ToString()); - } - } + // Creates a new Uri with a rewritten path and query + // Everything else is unchanged but for the fragment which is lost + public static Uri Rewrite(this Uri uri, string path, string query) + { + if (uri.IsAbsoluteUri) + { + return new Uri(uri.GetLeftPart(UriPartial.Authority) + path + query); + } + else + { + return new Uri(path + query, UriKind.Relative); + } + } + + // Gets the absolute path of the Uri + // Works both for Absolute and Relative Uris + public static string GetSafeAbsolutePath(this Uri uri) + { + if (uri.IsAbsoluteUri) + { + return uri.AbsolutePath; + } + else + { + // cannot get .AbsolutePath on relative uri (InvalidOperation) + var s = uri.OriginalString; + var posq = s.IndexOf("?"); + var posf = s.IndexOf("#"); + var pos = posq > 0 ? posq : (posf > 0 ? posf : 0); + var path = pos > 0 ? s.Substring(0, pos) : s; + return path; + } + } + + // Creates a new Uri with path ending with a slash + // Everything else is unchanged but for the fragment which is lost + public static Uri EndPathWithSlash(this Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + if (uri.IsAbsoluteUri) + { + if (path != "/" && !path.EndsWith("/")) + uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path + "/" + uri.Query); + return uri; + } + else + { + if (path != "/" && !path.EndsWith("/")) + uri = new Uri(path + "/" + uri.Query, UriKind.Relative); + } + return uri; + } + + // Creates a new Uri with path trimmed of trailing slash + // Everything else is unchanged but for the fragment which is lost + // If path is "/" it remains "/". + public static Uri TrimPathEndSlash(this Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + if (uri.IsAbsoluteUri) + { + if (path != "/") + uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path.TrimEnd('/') + uri.Query); + } + else + { + if (path != "/") + uri = new Uri(path.TrimEnd('/') + uri.Query, UriKind.Relative); + } + return uri; + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/NiceUrls.cs b/src/Umbraco.Web/NiceUrls.cs index ae2ff6cf70..efb70b7a96 100644 --- a/src/Umbraco.Web/NiceUrls.cs +++ b/src/Umbraco.Web/NiceUrls.cs @@ -23,129 +23,123 @@ namespace Umbraco.Web // note: this could be a parameter... const string UrlNameProperty = "@urlName"; - public virtual string GetNiceUrl(int nodeId) + public string GetNiceUrl(int nodeId) { - int startNodeDepth = 1; - if (GlobalSettings.HideTopLevelNodeFromPath) - startNodeDepth = 2; - - return GetNiceUrl(nodeId, startNodeDepth, false); + return GetNiceUrl(nodeId, FIXME*Current.UmbracoUrl, false); } - public virtual string GetNiceUrl(int nodeId, int startNodeDepth, bool forceDomain) + public string GetNiceUrl(int nodeId, Uri current, bool absolute) { - string route; - string path; + string path; + Uri domainUri; + + string route = _routesCache.GetRoute(nodeId); // will get null if previewing - route = _routesCache.GetRoute(nodeId); // will not read cache if previewing if (route != null) { + // route is / eg "-1/", "-1/foo", "123/", "123/foo/bar"... int pos = route.IndexOf('/'); path = route.Substring(pos); - - if (UmbracoSettings.UseDomainPrefixes || forceDomain) - { - int rootNodeId = int.Parse(route.Substring(0, pos)); - if (rootNodeId > 0) - return DomainAtNode(rootNodeId) + path; - } - - return path; - } - - // else there was not route in the cache, must build route... - + int id = int.Parse(route.Substring(0, pos)); // will be -1 or 1234 + domainUri = id > 0 ? DomainUriAtNode(id, current) : null; + } + else + { var node = _contentStore.GetNodeById(nodeId); if (node == null) - return "#"; // legacy wrote to the log here... - - var parts = new List(); - var depth = int.Parse(_contentStore.GetNodeProperty(node, "@level")); - var id = nodeId; - string domain = null; - while (depth >= 1) - { - // if not hiding that depth, add urlName - if (depth >= startNodeDepth) - parts.Add(_contentStore.GetNodeProperty(node, UrlNameProperty)); - - var tmp = DomainAtNode(id); - if (tmp != null) - { - if (UmbracoSettings.UseDomainPrefixes || forceDomain) - domain = tmp; - break; // break to capture the id - } + return "#"; + var pathParts = new List(); + int id = nodeId; + domainUri = DomainUriAtNode(id, current); + while (domainUri == null && id > 0) + { + pathParts.Add(_contentStore.GetNodeProperty(node, UrlNameProperty)); node = _contentStore.GetNodeParent(node); id = int.Parse(_contentStore.GetNodeProperty(node, "@id")); - depth--; + domainUri = id > 0 ? DomainUriAtNode(id, current) : null; } - parts.Reverse(); - path = "/" + string.Join("/", parts); - route = string.Format("{0}{1}", id, path); + // no domain, respect HideTopLevelNodeFromPath for legacy purposes + if (domainUri == null && umbraco.GlobalSettings.HideTopLevelNodeFromPath) + pathParts.RemoveAt(pathParts.Count - 1); + + pathParts.Reverse(); + path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc + route = id.ToString() + path; _routesCache.Store(nodeId, route); // will not write if previewing - - return FormatUrl(domain, path); } - protected string DomainAtNode(int nodeId) - { - // be safe - if (nodeId <= 0) - return null; - - // get domains defined on that node - Domain[] domains = Domain.GetDomainsById(nodeId); - - // no domain set on that node, return null - if (domains.Length == 0) - return null; - - // else try to find the first domain that matches the current request - // else take the first domain of the list - Domain domain = domains.FirstOrDefault(d => UrlUtility.IsBaseOf(d.Name, _umbracoContext.OriginalUrl)) ?? domains[0]; - - var domainName = domain.Name.TrimEnd('/'); - domainName = UrlUtility.EnsureScheme(domainName, _umbracoContext.OriginalUrl.Scheme); - var pos = domainName.IndexOf("//"); - pos = domainName.IndexOf("/", pos + 2); - if (pos > 0) - domainName = domainName.Substring(0, pos); - - // return a scheme + host eg http://example.com with no trailing slash - return domainName; + return AssembleUrl(domainUri, path, current, absolute).ToString(); } - protected string FormatUrl(string domain, string path) - { - if (domain == null) // else vdir needs to be in the domain - { - // get the application virtual dir (empty if no vdir) - string vdir = SystemDirectories.Root; - if (!string.IsNullOrEmpty(vdir)) - domain = "/" + vdir; - } + Uri AssembleUrl(Uri domain, string path, Uri current, bool absolute) + { + Uri uri; - string url = (domain ?? "") + path; - if (path != "/") - { - // not at root - if (GlobalSettings.UseDirectoryUrls) - { - // add trailing / if required - if (UmbracoSettings.AddTrailingSlash) - url += "/"; - } - else - { - // add .aspx - url += ".aspx"; - } - } + if (domain == null) + { + // no domain was found : return a relative url, add vdir if any + uri = new Uri(umbraco.IO.SystemDirectories.Root + path, UriKind.Relative); + } + else + { + // a domain was found : return an absolute or relative url + // cannot handle vdir, has to be in domain uri + if (!absolute && current != null && domain.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority)) + uri = new Uri(domain.AbsolutePath.TrimEnd('/') + path, UriKind.Relative); // relative + else + uri = new Uri(domain.GetLeftPart(UriPartial.Path).TrimEnd('/') + path); // absolute + } - return url; - } + return UriFromUmbraco(uri); + } + + Uri DomainUriAtNode(int nodeId, Uri current) + { + // be safe + if (nodeId <= 0) + return null; + + // apply filter on domains defined on that node + var domainAndUri = Domains.ApplicableDomains(Domain.GetDomainsById(nodeId), current, true); + return domainAndUri == null ? null : domainAndUri.Uri; + } + + #endregion + + #region Map public urls to/from umbraco urls + + // fixme - what about vdir? + // path = path.Substring(UriUtility.AppVirtualPathPrefix.Length); // remove virtual directory + + public static Uri UriFromUmbraco(Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + if (path == "/") + return uri; + + if (!umbraco.GlobalSettings.UseDirectoryUrls) + path += ".aspx"; + else if (umbraco.UmbracoSettings.AddTrailingSlash) + path += "/"; + + return uri.Rewrite(path); + } + + public static Uri UriToUmbraco(Uri uri) + { + var path = uri.GetSafeAbsolutePath(); + + path = path.ToLower(); + if (path != "/") + path = path.TrimEnd('/'); + if (path.EndsWith(".aspx")) + path = path.Substring(0, path.Length - ".aspx".Length); + + return uri.Rewrite(path); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/DocumentRequest.cs b/src/Umbraco.Web/Routing/DocumentRequest.cs index efa44a1881..761929b15e 100644 --- a/src/Umbraco.Web/Routing/DocumentRequest.cs +++ b/src/Umbraco.Web/Routing/DocumentRequest.cs @@ -12,47 +12,19 @@ using umbraco.cms.businesslogic.member; using umbraco.cms.businesslogic.language; namespace Umbraco.Web.Routing { + // represents a request for one specified Umbraco document to be rendered + // by one specified template, using one particular culture. + // internal class DocumentRequest { static readonly TraceSource Trace = new TraceSource("DocumentRequest"); public DocumentRequest(Uri uri, RoutingEnvironment lookups, UmbracoContext umbracoContext, NiceUrls niceUrls) { - // register lookups + this.Uri = uri; _environment = lookups; _umbracoContext = umbracoContext; _niceUrls = niceUrls; - - // prepare the host - var host = uri.Host; - - // fixme - //var serverVarHost = httpContext.Request.ServerVariables["X_UMBRACO_HOST"]; - //if (!string.IsNullOrWhiteSpace(serverVarHost)) - //{ - // host = serverVarHost; - // RequestContext.Current.Trace.Write(TraceCategory, "Domain='" + host + "' (X_UMBRACO_HOST)"); - //} - - // initialize the host - this.Host = host; - - // prepare the path - var path = uri.AbsolutePath; - path = path.Substring(UrlUtility.AppVirtualPathPrefix.Length); // remove virtual directory - path = path.TrimEnd('/'); // remove trailing / - if (!path.StartsWith("/")) // ensure it starts with / - path = "/" + path; - path = path.ToLower(); // make it all lowercase - //url = url.Replace('\'', '_'); // make it xpath compatible !! was in legacy, should be handled in xpath query, not here - if (path.EndsWith(".aspx")) // remove trailing .aspx - path = path.Substring(0, path.Length - ".aspx".Length); - - // initialize the path - this.Path = path; - - // initialize the query - this.QueryString = uri.Query.TrimStart('?'); } #region Properties @@ -63,29 +35,15 @@ namespace Umbraco.Web.Routing // the requested node, if any, else null. XmlNode _node = null; - /// - /// Gets the request host name. - /// - /// This is the original uri's host, unless modified (fixme). - public string Host { get; private set; } - - /// - /// Gets the request path. - /// - /// This is the original uri's path, cleaned up, without vdir, without .aspx, etc. - public string Path { get; private set; } - - /// - /// Gets the request query string. - /// - /// This is the original uri's querystring, without the initial '?'. - public string QueryString { get; private set; } + public Uri Uri { get; private set; } /// /// Gets or sets the document request's domain. /// public Domain Domain { get; private set; } + public Uri DomainUri { get; private set; } + /// /// Gets a value indicating whether the document request has a domain. /// @@ -186,38 +144,35 @@ namespace Umbraco.Web.Routing /// The host name part of the http request, eg. www.example.com. /// The url part of the http request, starting with a slash, eg. /foo/bar. /// A value indicating whether a domain was found. - public bool ResolveSiteRoot() + public bool ResolveDomain() { - const string tracePrefix = "ResolveSiteRoot: "; + const string tracePrefix = "ResolveDomain: "; // note - we are not handling schemes nor ports here. - Trace.TraceInformation("{0}Host=\"{1}\"", tracePrefix, this.Host); - - Domain domain = null; - - // get domains, those with a slash coming first, so that 'foo.com/en' takes precedence over 'foo.com'. - // domains should NOT begin with 'http://'. - var domains = Domain.GetDomains().OrderByDescending(od => od.Name.IndexOf('/')); + Trace.TraceInformation("{0}Uri=\"{1}\"", tracePrefix, this.Uri); // try to find a domain matching the current request - string urlWithDomain = UrlUtility.EnsureScheme(this.Host + this.Path, "http"); // FIXME-NICEURL - current Uri?! - domain = domains.FirstOrDefault(d => UrlUtility.IsBaseOf(d.Name, urlWithDomain)); + var domainAndUri = Domains.ApplicableDomains(Domain.GetDomains(), RequestContext.Current.UmbracoUrl, false); // handle domain - if (domain != null) + if (domainAndUri != null) { // matching an existing domain Trace.TraceInformation("{0}Matches domain=\"{1}\", rootId={2}, culture=\"{3}\"", tracePrefix, - domain.Name, domain.RootNodeId, domain.Language.CultureAlias); + domainAndUri.Domain.Name, domainAndUri.Domain.RootNodeId, domainAndUri.Domain.Language.CultureAlias); - this.Domain = domain; - this.Culture = new CultureInfo(domain.Language.CultureAlias); + this.Domain = domainAndUri.Domain; + this.DomainUri = domainAndUri.Uri; + this.Culture = new CultureInfo(domainAndUri.Domain.Language.CultureAlias); - // canonical? - // FIXME - NOT IMPLEMENTED AT THE MOMENT + THEN WE WOULD RETURN THE CANONICAL DOMAIN AND ASK FOR REDIRECT - // but then how do I translate if domain is bar.com/en ? will depend on how we handle domains + // canonical? not implemented at the moment + // if (...) + // { + // this.RedirectUrl = "..."; + // return true; + // } } else { @@ -239,7 +194,7 @@ namespace Umbraco.Web.Routing public bool ResolveDocument() { const string tracePrefix = "ResolveDocument: "; - Trace.TraceInformation("{0}Path=\"{1}\"", tracePrefix, this.Path); + Trace.TraceInformation("{0}Path=\"{1}\"", tracePrefix, this.Uri.AbsolutePath); // look for the document // the first successful lookup, if any, will set this.Node, and may also set this.Template diff --git a/src/Umbraco.Web/Routing/Domains.cs b/src/Umbraco.Web/Routing/Domains.cs new file mode 100644 index 0000000000..9fb27d5b06 --- /dev/null +++ b/src/Umbraco.Web/Routing/Domains.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using umbraco.cms.businesslogic.web; + +namespace Umbraco.Web.Routing +{ + public class Domains + { + public class DomainAndUri + { + public Domain Domain; + public Uri Uri; + + public override string ToString() + { + return string.Format("{{ \"{0}\", \"{1}\" }}", Domain.Name, Uri); + } + } + + public static DomainAndUri ApplicableDomains(IEnumerable domains, Uri current, bool defaultToFirst) + { + if (!domains.Any()) + return null; + + // sanitize the list to have proper uris for comparison (scheme, path end with /) + // we need to end with / because example.com/foo cannot match example.com/foobar + // we need to order so example.com/foo matches before example.com/ + var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme; + var domainsAndUris = domains + .Select(d => new { Domain = d, UriString = UriUtility.EndPathWithSlash(UriUtility.StartWithScheme(d.Name, scheme)) }) + .OrderByDescending(t => t.UriString) + .Select(t => new DomainAndUri { Domain = t.Domain, Uri = new Uri(t.UriString) }); + + DomainAndUri domainAndUri; + if (current == null) + { + // take the first one by default + domainAndUri = domainsAndUris.First(); + } + else + { + // look for a domain that would be the base of the hint + // else take the first one by default + var hintWithSlash = current.EndPathWithSlash(); + domainAndUri = domainsAndUris + .FirstOrDefault(t => t.Uri.IsBaseOf(hintWithSlash)); + if (domainAndUri == null && defaultToFirst) + domainAndUri = domainsAndUris.First(); + } + + if (domainAndUri != null) + domainAndUri.Uri = domainAndUri.Uri.TrimPathEndSlash(); + return domainAndUri; + } + + public static string PathRelativeToDomain(Uri domainUri, string path) + { + return path.Substring(domainUri.AbsolutePath.Length).AtStart('/'); + } + } +} diff --git a/src/Umbraco.Web/Routing/LookupByAlias.cs b/src/Umbraco.Web/Routing/LookupByAlias.cs index 8d9cf2c081..7ed499f107 100644 --- a/src/Umbraco.Web/Routing/LookupByAlias.cs +++ b/src/Umbraco.Web/Routing/LookupByAlias.cs @@ -26,12 +26,12 @@ namespace Umbraco.Web.Routing { XmlNode node = null; - if (docreq.Path != "/") // no alias if "/" + if (docreq.Uri.AbsolutePath != "/") // no alias if "/" { - node = _contentStore.GetNodeByUrlAlias(docreq.HasDomain ? docreq.Domain.RootNodeId : 0, docreq.Path); + node = _contentStore.GetNodeByUrlAlias(docreq.HasDomain ? docreq.Domain.RootNodeId : 0, docreq.Uri.AbsolutePath); if (node != null) { - Trace.TraceInformation("Path \"{0}\" is an alias for id={1}", docreq.Path, docreq.NodeId); + Trace.TraceInformation("Path \"{0}\" is an alias for id={1}", docreq.Uri.AbsolutePath, docreq.NodeId); docreq.Node = node; } } diff --git a/src/Umbraco.Web/Routing/LookupById.cs b/src/Umbraco.Web/Routing/LookupById.cs index 8370df4ecc..a01330edec 100644 --- a/src/Umbraco.Web/Routing/LookupById.cs +++ b/src/Umbraco.Web/Routing/LookupById.cs @@ -33,9 +33,9 @@ namespace Umbraco.Web.Routing XmlNode node = null; int nodeId = -1; - if (docreq.Path != "/") // no id if "/" + if (docreq.Uri.AbsolutePath != "/") // no id if "/" { - string noSlashPath = docreq.Path.Substring(1); + string noSlashPath = docreq.Uri.AbsolutePath.Substring(1); if (!Int32.TryParse(noSlashPath, out nodeId)) nodeId = -1; diff --git a/src/Umbraco.Web/Routing/LookupByPath.cs b/src/Umbraco.Web/Routing/LookupByPath.cs index d5e9dbba19..b3e0332c45 100644 --- a/src/Umbraco.Web/Routing/LookupByPath.cs +++ b/src/Umbraco.Web/Routing/LookupByPath.cs @@ -22,7 +22,12 @@ namespace Umbraco.Web.Routing public virtual bool LookupDocument(DocumentRequest docreq) { - var route = docreq.HasDomain ? (docreq.Domain.RootNodeId.ToString() + docreq.Path) : docreq.Path; + string route; + if (docreq.HasDomain) + route = docreq.Domain.RootNodeId.ToString() + Domains.PathRelativeToDomain(docreq.DomainUri, docreq.Uri.AbsolutePath); + else + route = docreq.Uri.AbsolutePath; + var node = LookupDocumentNode(docreq, route); return node != null; } diff --git a/src/Umbraco.Web/Routing/LookupByPathWithTemplate.cs b/src/Umbraco.Web/Routing/LookupByPathWithTemplate.cs index 1b91be23aa..91450fdabd 100644 --- a/src/Umbraco.Web/Routing/LookupByPathWithTemplate.cs +++ b/src/Umbraco.Web/Routing/LookupByPathWithTemplate.cs @@ -21,12 +21,15 @@ namespace Umbraco.Web.Routing public override bool LookupDocument(DocumentRequest docreq) { XmlNode node = null; + string path = docreq.Uri.AbsolutePath; - if (docreq.Path != "/") // no template if "/" + if (docreq.HasDomain) + path = Domains.PathRelativeToDomain(docreq.DomainUri, path); + if (path != "/") // no template if "/" { - var pos = docreq.Path.LastIndexOf('/'); - var templateAlias = docreq.Path.Substring(pos + 1); - var path = docreq.Path.Substring(0, pos); + var pos = docreq.Uri.AbsolutePath.LastIndexOf('/'); + var templateAlias = docreq.Uri.AbsolutePath.Substring(pos + 1); + path = path.Substring(0, pos); var template = Template.GetByAlias(templateAlias); if (template != null) diff --git a/src/Umbraco.Web/Routing/LookupByProfile.cs b/src/Umbraco.Web/Routing/LookupByProfile.cs index a65eafac2e..5e7cac1247 100644 --- a/src/Umbraco.Web/Routing/LookupByProfile.cs +++ b/src/Umbraco.Web/Routing/LookupByProfile.cs @@ -29,11 +29,11 @@ namespace Umbraco.Web.Routing XmlNode node = null; bool isProfile = false; - var pos = docreq.Path.LastIndexOf('/'); + var pos = docreq.Uri.AbsolutePath.LastIndexOf('/'); if (pos > 0) { - var memberLogin = docreq.Path.Substring(pos + 1); - var path = docreq.Path.Substring(0, pos); + var memberLogin = docreq.Uri.AbsolutePath.Substring(pos + 1); + var path = docreq.Uri.AbsolutePath.Substring(0, pos); if (path == GlobalSettings.ProfileUrl) { diff --git a/src/Umbraco.Web/Routing/LookupFor404.cs b/src/Umbraco.Web/Routing/LookupFor404.cs index 08bc6ec0cd..f3a2354112 100644 --- a/src/Umbraco.Web/Routing/LookupFor404.cs +++ b/src/Umbraco.Web/Routing/LookupFor404.cs @@ -22,7 +22,7 @@ namespace Umbraco.Web.Routing public bool LookupDocument(DocumentRequest docRequest) { - docRequest.Node = HandlePageNotFound(docRequest.Path); + docRequest.Node = HandlePageNotFound(docRequest.Uri.AbsolutePath); return docRequest.HasNode; } diff --git a/src/Umbraco.Web/Routing/RoutesCache.cs b/src/Umbraco.Web/Routing/RoutesCache.cs index d366dc23f9..cb99ceb2de 100644 --- a/src/Umbraco.Web/Routing/RoutesCache.cs +++ b/src/Umbraco.Web/Routing/RoutesCache.cs @@ -2,6 +2,14 @@ using System.Collections.Generic; namespace Umbraco.Web.Routing { + // this is a bi-directional cache that contains + // - nodeId to route (used for NiceUrl) + // - route to nodeId (used for inbound requests) + // + // a route is [rootId]/path/to/node + // where rootId is the id of the "site root" node + // if missing then the "site root" is the content root + // internal class RoutesCache { private readonly object _lock = new object(); diff --git a/src/Umbraco.Web/Routing/RoutingEnvironment.cs b/src/Umbraco.Web/Routing/RoutingEnvironment.cs index cf848625be..8e63f223cd 100644 --- a/src/Umbraco.Web/Routing/RoutingEnvironment.cs +++ b/src/Umbraco.Web/Routing/RoutingEnvironment.cs @@ -3,10 +3,6 @@ using System.Linq; namespace Umbraco.Web.Routing { - // represents a request for one specified Umbraco document to be rendered - // by one specified template, using one particular culture. - // - internal class RoutingEnvironment { public RoutingEnvironment( diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index fd978332ba..178e691695 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -36,6 +36,9 @@ namespace Umbraco.Web var umbracoContext = new UmbracoContext(new HttpContextWrapper(httpContext), ApplicationContext.Current); UmbracoContext.Current = umbracoContext; + // NO! + // these are application-wide singletons! + //create a content store var contentStore = new ContentStore(umbracoContext); //create the routes cache @@ -47,7 +50,10 @@ namespace Umbraco.Web ApplicationContext.Current.Plugins.ResolveLookups().ToArray(), new LookupFor404(contentStore), contentStore); - // create the new document request which will cleanup the uri once and for all + + // NOT HERE BUT SEE **THERE** BELOW + + // create the new document request which will cleanup the uri once and for all var docreq = new DocumentRequest(uri, routingEnvironment, umbracoContext, niceUrls); // initialize the document request on the UmbracoContext (this is circular dependency!!!) @@ -88,7 +94,9 @@ namespace Umbraco.Web // legacy - no idea what this is LegacyCleanUmbPageFromQueryString(ref uri, ref lpath); - docreq.ResolveSiteRoot(); + //**THERE** we should create the doc request + // before, we're not sure we handling a doc request + docreq.ResolveDomain(); if (docreq.IsRedirect) httpContext.Response.Redirect(docreq.RedirectUrl, true); Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = docreq.Culture; @@ -99,9 +107,8 @@ namespace Umbraco.Web if (docreq.Is404) httpContext.Response.StatusCode = 404; - // fixme - should use an IComponent here, so that we could hand the request over to MVC Trace.TraceInformation("Transfer to UmbracoDefault (default.aspx)"); - TransferRequest("~/default.aspx?" + docreq.QueryString); + TransferRequest("~/default.aspx" + docreq.Uri.Query); // it is up to default.aspx to figure out what to display in case // there is no document (ugly 404 page?) or no template (blank page?) @@ -163,7 +170,7 @@ namespace Umbraco.Web string bootUrl = null; if (UmbracoSettings.EnableSplashWhileLoading) // legacy - should go { - string configPath = UrlUtility.ToAbsolute(SystemDirectories.Config); + string configPath = UriUtility.ToAbsolute(SystemDirectories.Config); bootUrl = string.Format("{0}/splashes/booting.aspx?url={1}", configPath, HttpUtility.UrlEncode(uri.ToString())); // fixme ?orgurl=... ?retry=... } @@ -171,14 +178,14 @@ namespace Umbraco.Web //TODO: I like the idea of this new setting, but lets get this in to the core at a later time, for now lets just get the basics working. //else if (!string.IsNullOrWhiteSpace(Settings.BootSplashPage)) //{ - // bootUrl = UrlUtility.ToAbsolute(Settings.BootSplashPage); + // bootUrl = UriUtility.ToAbsolute(Settings.BootSplashPage); //} else { // fixme - default.aspx has to be ready for RequestContext.DocumentRequest==null // fixme - in fact we should transfer to an empty html page... - bootUrl = UrlUtility.ToAbsolute("~/default.aspx"); + bootUrl = UriUtility.ToAbsolute("~/default.aspx"); } TransferRequest(bootUrl); return false; @@ -195,7 +202,7 @@ namespace Umbraco.Web if (!ApplicationContext.Current.IsConfigured) { Trace.TraceEvent(TraceEventType.Warning, 0, "Umbraco is not configured"); - string installPath = UrlUtility.ToAbsolute(SystemDirectories.Install); + string installPath = UriUtility.ToAbsolute(SystemDirectories.Install); string installUrl = string.Format("{0}/default.aspx?redir=true&url={1}", installPath, HttpUtility.UrlEncode(uri.ToString())); httpContext.Response.Redirect(installUrl, true); return false; @@ -213,7 +220,7 @@ namespace Umbraco.Web // by clean WebAPI. // fixme - do it once when initializing the module - string baseUrl = UrlUtility.ToAbsolute(SystemDirectories.Base).ToLower(); + string baseUrl = UriUtility.ToAbsolute(SystemDirectories.Base).ToLower(); if (!baseUrl.EndsWith("/")) baseUrl += "/"; if (lpath.StartsWith(baseUrl)) @@ -292,8 +299,6 @@ namespace Umbraco.Web } } - - #endregion #region IHttpModule diff --git a/src/Umbraco.Web/UrlUtility.cs b/src/Umbraco.Web/UriUtility.cs similarity index 71% rename from src/Umbraco.Web/UrlUtility.cs rename to src/Umbraco.Web/UriUtility.cs index 94ea704a9f..febb0069ad 100644 --- a/src/Umbraco.Web/UrlUtility.cs +++ b/src/Umbraco.Web/UriUtility.cs @@ -4,24 +4,27 @@ using System.Web; namespace Umbraco.Web { - static class UrlUtility + static class UriUtility { static readonly string _appVirtualPath; static readonly string _appVirtualPathPrefix; - static UrlUtility() + static UriUtility() { + // Virtual path _appVirtualPath = HttpRuntime.AppDomainAppVirtualPath ?? "/"; _appVirtualPathPrefix = _appVirtualPath; if (_appVirtualPathPrefix == "/") _appVirtualPathPrefix = string.Empty; } + // will be "/" or "/foo" public static string AppVirtualPath { get { return _appVirtualPath; } } + // will be "" or "/foo" public static string AppVirtualPathPrefix { get { return _appVirtualPathPrefix; } @@ -104,42 +107,53 @@ namespace Umbraco.Web #endregion - #region Utilities + #region Uri string utilities - public static bool HasScheme(string uri) - { - return uri.StartsWith("http://", StringComparison.CurrentCultureIgnoreCase) - || uri.StartsWith("https://", StringComparison.CurrentCultureIgnoreCase); - } + public static bool HasScheme(string uri) + { + return uri.IndexOf("://") > 0; + } - public static string EnsureScheme(string uri, string scheme) - { - return HasScheme(uri) ? uri : string.Format("{0}://{1}", scheme, uri); - } + public static string StartWithScheme(string uri) + { + return StartWithScheme(uri, null); + } - public static string WithTrailingSlash(string uri) - { - return uri.EndsWith("/") ? uri : uri + "/"; - } + public static string StartWithScheme(string uri, string scheme) + { + return HasScheme(uri) ? uri : string.Format("{0}://{1}", scheme ?? Uri.UriSchemeHttp, uri); + } - // indicates whether uri2 is within uri1 - public static bool IsBaseOf(string uri1, string uri2) - { - uri2 = WithTrailingSlash(uri2); - Uri testUri2 = new Uri(uri2); + public static string EndPathWithSlash(string uri) + { + var pos1 = Math.Max(0, uri.IndexOf('?')); + var pos2 = Math.Max(0, uri.IndexOf('#')); + var pos = Math.Min(pos1, pos2); - uri1 = WithTrailingSlash(uri1); - uri1 = EnsureScheme(uri1, testUri2.Scheme); - Uri testUri1 = new Uri(uri1); + var path = pos > 0 ? uri.Substring(0, pos) : uri; + path = path.AtEnd('/'); - return testUri1.IsBaseOf(testUri2); - } + if (pos > 0) + path += uri.Substring(pos); - public static bool IsBaseOf(string uri1, Uri uri2) - { - return IsBaseOf(uri1, uri2.ToString()); - } + return path; + } - #endregion + public static string TrimPathEndSlash(string uri) + { + var pos1 = Math.Max(0, uri.IndexOf('?')); + var pos2 = Math.Max(0, uri.IndexOf('#')); + var pos = Math.Min(pos1, pos2); + + var path = pos > 0 ? uri.Substring(0, pos) : uri; + path = path.TrimEnd('/'); + + if (pos > 0) + path += uri.Substring(pos); + + return path; + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index c678073c64..1d1e3dacb4 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -43,15 +43,6 @@ namespace umbraco /// public class library { - internal static void ClearNiceUrlCache() - { - lock (locker) - { - niceUrlCache.Clear(); - } - } - - private static object locker = new object(); #region Declarations @@ -364,18 +355,7 @@ namespace umbraco /// String with a friendly url from a node public static string NiceUrl(int nodeID) { - try - { - int startNode = 1; - if (GlobalSettings.HideTopLevelNodeFromPath) - startNode = 2; - - return niceUrlDo(nodeID, startNode, false); - } - catch - { - return "#"; - } + return Umbraco.Core.UmbracoContainer.Get().GetNiceUrl(nodeID); } /// @@ -384,9 +364,10 @@ namespace umbraco /// /// Identifier for the node that should be returned /// String with a friendly url from a node + [Obsolete] public static string NiceUrlFullPath(int nodeID) { - return niceUrlDo(nodeID, 1, false); + throw new NotImplementedException("It was broken anyway..."); } /// @@ -396,7 +377,7 @@ namespace umbraco /// String with a friendly url with full domain from a node public static string NiceUrlWithDomain(int nodeID) { - return niceUrlDo(nodeID, 1, true); + return Umbraco.Core.UmbracoContainer.Get().GetNiceUrl(nodeID, Umbraco.Web.RequestContext.Current.UmbracoUrl, true); } @@ -405,144 +386,6 @@ namespace umbraco return IOHelper.ResolveUrl(path); } - private static IDictionary niceUrlCache = new Dictionary(); - - /// - /// This is used in the requesthandler for domain lookups to ensure that we don't use the cache - /// - /// - /// - /// - /// - private static string niceUrlDo(int nodeID, int startNodeDepth, bool forceDomain) - { - int key = nodeID + (forceDomain ? 9999999 : 0); - if (!niceUrlCache.ContainsKey(key)) - { - lock (locker) - { - if (!niceUrlCache.ContainsKey(key)) - { - string tempUrl = NiceUrlFetch(nodeID, startNodeDepth, forceDomain); - if (!String.IsNullOrEmpty(tempUrl)) - { - niceUrlCache.Add(key, tempUrl); - } - } - } - } - - return niceUrlCache[key]; - } - - internal static string niceUrlJuno(int nodeId, int startNodeDepth, string currentDomain, bool forceDomain) - { - string parentUrl = String.Empty; - XmlElement node = UmbracoContext.Current.GetXml().GetElementById(nodeId.ToString()); - - if (node == null) - { - ArgumentException arEx = - new ArgumentException( - string.Format( - "Couldn't find any page with the nodeId = {0}. This is most likely caused by the page isn't published!", - nodeId), "nodeId"); - Log.Add(LogTypes.Error, nodeId, arEx.Message); - throw arEx; - } - if (node.ParentNode.Name.ToLower() != "root" || UmbracoSettings.UseDomainPrefixes || forceDomain) - { - if (UmbracoSettings.UseDomainPrefixes || forceDomain) - { - Domain[] domains = - Domain.GetDomainsById(nodeId); - // when there's a domain on a url we'll just return the domain rather than the parent path - if (domains.Length > 0) - { - return GetDomainIfExists(currentDomain, nodeId); - } - } - - if (parentUrl == String.Empty && (int.Parse(node.Attributes.GetNamedItem("level").Value) > startNodeDepth || UmbracoSettings.UseDomainPrefixes || forceDomain)) - { - if (node.ParentNode.Name != "root") - { - parentUrl = niceUrlJuno(int.Parse(node.ParentNode.Attributes.GetNamedItem("id").Value), startNodeDepth, currentDomain, forceDomain); - } - } - } - - // only return the current node url if we're at the startnodedepth or higher - if (int.Parse(node.Attributes.GetNamedItem("level").Value) >= startNodeDepth) - return parentUrl + "/" + node.Attributes.GetNamedItem("urlName").Value; - else if (node.PreviousSibling != null) - return "/" + node.Attributes.GetNamedItem("urlName").Value; - else - return "/"; - } - - private static string GetDomainIfExists(string currentDomain, int nodeId) - { - Domain[] domains = Domain.GetDomainsById(nodeId); - if (domains.Length > 0) - { - if (currentDomain != String.Empty) - { - foreach (Domain d in domains) - { - // if there's multiple domains we'll prefer to use the same domain as the current request - if (currentDomain == d.Name.ToLower() || d.Name.EndsWith(currentDomain, StringComparison.CurrentCultureIgnoreCase)) - { - if (d.Name.StartsWith("http", StringComparison.CurrentCultureIgnoreCase)) - { - return d.Name; - } - else - { - return string.Format("{0}://{1}", UmbracoContext.Current.Request.Url.Scheme, d.Name); - } - } - } - } - - if (domains[0].Name.StartsWith("http", StringComparison.CurrentCultureIgnoreCase)) - { - return domains[0].Name; - } - else - { - return string.Format("{0}://{1}", UmbracoContext.Current.Request.Url.Scheme, domains[0].Name); - } - } - return null; - } - - internal static string NiceUrlFetch(int nodeID, int startNodeDepth, bool forceDomain) - { - bool directoryUrls = GlobalSettings.UseDirectoryUrls; - string baseUrl = SystemDirectories.Root; // SystemDirectories.Umbraco; - string junoUrl = niceUrlJuno(nodeID, startNodeDepth, HttpContext.Current.Request.ServerVariables["SERVER_NAME"].ToLower(), forceDomain); - return appendUrlExtension(baseUrl, directoryUrls, junoUrl); - - } - - private static string appendUrlExtension(string baseUrl, bool directoryUrls, string tempUrl) - { - if (!directoryUrls) - { - // append .aspx extension if the url includes other than just the domain name - if (!String.IsNullOrEmpty(tempUrl) && tempUrl != "/" && - (!tempUrl.StartsWith("http://") || tempUrl.LastIndexOf("/") > 7)) - tempUrl = baseUrl + tempUrl + ".aspx"; - } - else - { - tempUrl = baseUrl + tempUrl; - if (tempUrl != "/" && UmbracoSettings.AddTrailingSlash) - tempUrl += "/"; - } - return tempUrl; - } /// /// Returns a string with the data from the given element of a node. Both elements (data-fields) diff --git a/src/umbraco.sln b/src/umbraco.sln index 06efaad422..8867ca9346 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F0242771-6DE6-4E03-BD3A-7B79BA79105B}" ProjectSection(SolutionItems) = preProject