diff --git a/src/Umbraco.Web/Routing/DefaultRoutesCache.cs b/src/Umbraco.Web/Routing/DefaultRoutesCache.cs
index b4daefaea2..55a2c08e75 100644
--- a/src/Umbraco.Web/Routing/DefaultRoutesCache.cs
+++ b/src/Umbraco.Web/Routing/DefaultRoutesCache.cs
@@ -6,7 +6,7 @@ using Umbraco.Core;
namespace Umbraco.Web.Routing
{
///
- /// The default implementation of IRoutesCache
+ /// Provides a default implementation of .
///
internal class DefaultRoutesCache : IRoutesCache
{
@@ -14,23 +14,31 @@ namespace Umbraco.Web.Routing
private Dictionary _routes;
private Dictionary _nodeIds;
+ ///
+ /// Initializes a new instance of the class.
+ ///
public DefaultRoutesCache()
{
Clear();
- // here we should register handlers to clear the cache when content changes
- // at the moment this is done by library, which clears everything when content changed
- //
- // but really, we should do some partial refreshes!
- // otherwise, we could even cache 404 errors...
- //
- // these are the two events used by library in legacy code...
- // is it enough?
+ //FIXME:
//
+ // here we must register handlers to clear the cache when content changes
+ // this was done by presentation.library, which cleared everything when content changed
+ // but really, we should do some partial refreshes
+
+ // these are the two events that were used by presentation.library
+ // are they enough?
+
global::umbraco.content.AfterRefreshContent += (sender, e) => Clear();
global::umbraco.content.AfterUpdateDocumentCache += (sender, e) => Clear();
}
+ ///
+ /// Stores a route for a node.
+ ///
+ /// The node identified.
+ /// The route.
public void Store(int nodeId, string route)
{
using (new WriteLock(_lock))
@@ -40,6 +48,11 @@ namespace Umbraco.Web.Routing
}
}
+ ///
+ /// Gets a route for a node.
+ ///
+ /// The node identifier.
+ /// The route for the node, else null.
public string GetRoute(int nodeId)
{
lock (new ReadLock(_lock))
@@ -48,6 +61,11 @@ namespace Umbraco.Web.Routing
}
}
+ ///
+ /// Gets a node for a route.
+ ///
+ /// The route.
+ /// The node identified for the route, else zero.
public int GetNodeId(string route)
{
using (new ReadLock(_lock))
@@ -56,6 +74,10 @@ namespace Umbraco.Web.Routing
}
}
+ ///
+ /// Clears the route for a node.
+ ///
+ /// The node identifier.
public void ClearNode(int nodeId)
{
using (var lck = new UpgradeableReadLock(_lock))
@@ -69,6 +91,9 @@ namespace Umbraco.Web.Routing
}
}
+ ///
+ /// Clears all routes.
+ ///
public void Clear()
{
using (new WriteLock(_lock))
diff --git a/src/Umbraco.Web/Routing/DocumentRequest.cs b/src/Umbraco.Web/Routing/DocumentRequest.cs
index d9b450b2a5..bc25fc0fbc 100644
--- a/src/Umbraco.Web/Routing/DocumentRequest.cs
+++ b/src/Umbraco.Web/Routing/DocumentRequest.cs
@@ -155,7 +155,7 @@ namespace Umbraco.Web.Routing
Trace.TraceInformation("{0}Uri=\"{1}\"", tracePrefix, this.Uri);
// try to find a domain matching the current request
- var domainAndUri = Domains.ApplicableDomains(Domain.GetDomains(), RoutingContext.UmbracoContext.UmbracoUrl, false);
+ var domainAndUri = Domains.DomainMatch(Domain.GetDomains(), RoutingContext.UmbracoContext.UmbracoUrl, false);
// handle domain
if (domainAndUri != null)
diff --git a/src/Umbraco.Web/Routing/Domains.cs b/src/Umbraco.Web/Routing/Domains.cs
index 01b5def78a..72454c3718 100644
--- a/src/Umbraco.Web/Routing/Domains.cs
+++ b/src/Umbraco.Web/Routing/Domains.cs
@@ -8,20 +8,48 @@ using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
+ ///
+ /// Provides utilities to handle domains.
+ ///
public class Domains
{
+ ///
+ /// Represents an Umbraco domain and its normalized uri.
+ ///
+ ///
+ /// In Umbraco it is valid to create domains with name such as example.com, https://www.example.com, example.com/foo/.
+ /// The normalized uri of a domain begins with a scheme and ends with no slash, eg http://example.com/, https://www.example.com/, http://example.com/foo/.
+ ///
public class DomainAndUri
{
+ ///
+ /// The Umbraco domain.
+ ///
public Domain Domain;
+
+ ///
+ /// The normalized uri of the domain.
+ ///
public Uri Uri;
+ ///
+ /// Gets a string that represents the instance.
+ ///
+ /// A string that represents the current instance.
public override string ToString()
{
return string.Format("{{ \"{0}\", \"{1}\" }}", Domain.Name, Uri);
}
}
- public static DomainAndUri ApplicableDomains(IEnumerable domains, Uri current, bool defaultToFirst)
+ ///
+ /// Finds the domain that best matches the current uri, into an enumeration of domains.
+ ///
+ /// The enumeration of Umbraco domains.
+ /// The uri of the current request, or null.
+ /// A value indicating whether to return the first domain of the list when no domain matches.
+ /// The domain and its normalized uri, that best matches the current uri, else the first domain (if defaultToFirst is true), else null.
+ public static DomainAndUri DomainMatch(IEnumerable domains, Uri current, bool defaultToFirst)
{
if (!domains.Any())
return null;
@@ -57,6 +85,29 @@ namespace Umbraco.Web.Routing
return domainAndUri;
}
+ ///
+ /// Gets an enumeration of matching an enumeration of Umbraco domains.
+ ///
+ /// The enumeration of Umbraco domains.
+ /// The uri of the current request, or null.
+ /// The enumeration of matching the enumeration of Umbraco domains.
+ public static IEnumerable DomainMatches(IEnumerable domains, Uri current)
+ {
+ var scheme = current == null ? Uri.UriSchemeHttp : current.Scheme;
+ var domainsAndUris = domains
+ .Select(d => new { Domain = d, UriString = UriUtility.TrimPathEndSlash(UriUtility.StartWithScheme(d.Name, scheme)) })
+ .OrderByDescending(t => t.UriString)
+ .Select(t => new DomainAndUri { Domain = t.Domain, Uri = new Uri(t.UriString) });
+ return domainsAndUris;
+ }
+
+ ///
+ /// Returns the part of a path relative to the uri of a domain.
+ ///
+ /// The normalized uri of the domain.
+ /// The full path of the uri.
+ /// The path part relative to the uri of the domain.
+ /// Eg the relative part of /foo/bar/nil to domain example.com/foo is /bar/nil.
public static string PathRelativeToDomain(Uri domainUri, string path)
{
return path.Substring(domainUri.AbsolutePath.Length).EnsureStartsWith('/');
diff --git a/src/Umbraco.Web/Routing/IRoutesCache.cs b/src/Umbraco.Web/Routing/IRoutesCache.cs
index 0771fa556f..9033bc746e 100644
--- a/src/Umbraco.Web/Routing/IRoutesCache.cs
+++ b/src/Umbraco.Web/Routing/IRoutesCache.cs
@@ -1,19 +1,44 @@
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
- //
-
+ ///
+ /// Represents a bi-directional cache that binds node identifiers and routes.
+ ///
+ ///
+ /// The cache is used both for inbound (map a route to a node) and outbound (map a node to a url).
+ /// A route is [rootId]/path/to/node where rootId is the id of the node holding an Umbraco domain, or -1.
+ ///
internal interface IRoutesCache
{
+ ///
+ /// Stores a route for a node.
+ ///
+ /// The node identified.
+ /// The route.
void Store(int nodeId, string route);
+
+ ///
+ /// Gets a route for a node.
+ ///
+ /// The node identifier.
+ /// The route for the node, else null.
string GetRoute(int nodeId);
+
+ ///
+ /// Gets a node for a route.
+ ///
+ /// The route.
+ /// The node identified for the route, else zero.
int GetNodeId(string route);
+
+ ///
+ /// Clears the route for a node.
+ ///
+ /// The node identifier.
void ClearNode(int nodeId);
+
+ ///
+ /// Clears all routes.
+ ///
void Clear();
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Routing/NiceUrlProvider.cs b/src/Umbraco.Web/Routing/NiceUrlProvider.cs
index 00a72b264c..a124ba1d13 100644
--- a/src/Umbraco.Web/Routing/NiceUrlProvider.cs
+++ b/src/Umbraco.Web/Routing/NiceUrlProvider.cs
@@ -13,10 +13,15 @@ using umbraco.cms.businesslogic.web;
namespace Umbraco.Web.Routing
{
///
- /// Resolves NiceUrls for a given node id
+ /// Provides nice urls for a nodes.
///
internal class NiceUrlProvider
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The content store.
+ /// The Umbraco context.
public NiceUrlProvider(ContentStore contentStore, UmbracoContext umbracoContext)
{
_umbracoContext = umbracoContext;
@@ -104,20 +109,67 @@ namespace Umbraco.Web.Routing
/// Gets the nice urls of a node.
///
/// The node id.
+ /// The current url.
/// An enumeration of all valid urls for the node.
/// The urls are absolute. A node can have more than one url if more than one domain is defined.
- public IEnumerable GetNiceUrls(int nodeId)
+ public IEnumerable GetNiceUrls(int nodeId, Uri current)
{
- // fixme - to be implemented
- // this is for editContent.aspx which currently has its own, highly buggy, implementation of NiceUrl...
- throw new NotImplementedException();
+ // this is for editContent.aspx which had its own, highly buggy, implementation of NiceUrl...
+ //TODO: finalize & test implementation then replace in editContent.aspx
+
+ string path;
+ IEnumerable domainUris;
+
+ // will not read cache if previewing!
+ var route = _umbracoContext.InPreviewMode
+ ? null
+ : _umbracoContext.RoutesCache.GetRoute(nodeId);
+
+ if (route != null)
+ {
+ // route is / eg "-1/", "-1/foo", "123/", "123/foo/bar"...
+ int pos = route.IndexOf('/');
+ path = route.Substring(pos);
+ int id = int.Parse(route.Substring(0, pos)); // will be -1 or 1234
+ domainUris = id > 0 ? DomainUrisAtNode(id, current) : new Uri[] { };
+ }
+ else
+ {
+ var node = _contentStore.GetNodeById(nodeId);
+ if (node == null)
+ return new string[] { "#" }; // legacy wrote to the log here...
+
+ var pathParts = new List();
+ int id = nodeId;
+ domainUris = DomainUrisAtNode(id, current);
+ while (!domainUris.Any() && id > 0)
+ {
+ pathParts.Add(_contentStore.GetNodeProperty(node, UrlNameProperty));
+ node = _contentStore.GetNodeParent(node);
+ id = int.Parse(_contentStore.GetNodeProperty(node, "@id")); // will be -1 or 1234
+ domainUris = id > 0 ? DomainUrisAtNode(id, current) : new Uri[] { };
+ }
+
+ // no domain, respect HideTopLevelNodeFromPath for legacy purposes
+ if (!domainUris.Any() && global::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;
+
+ if (!_umbracoContext.InPreviewMode)
+ _umbracoContext.RoutesCache.Store(nodeId, route);
+ }
+
+ return AssembleUrls(domainUris, path, current).Select(uri => uri.ToString());
}
- Uri AssembleUrl(Uri domain, string path, Uri current, bool absolute)
+ Uri AssembleUrl(Uri domainUri, string path, Uri current, bool absolute)
{
Uri uri;
- if (domain == null)
+ if (domainUri == null)
{
// no domain was found : return a relative url, add vdir if any
uri = new Uri(global::umbraco.IO.SystemDirectories.Root + path, UriKind.Relative);
@@ -126,15 +178,28 @@ namespace Umbraco.Web.Routing
{
// 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
+ if (!absolute && current != null && domainUri.GetLeftPart(UriPartial.Authority) == current.GetLeftPart(UriPartial.Authority))
+ uri = new Uri(domainUri.AbsolutePath.TrimEnd('/') + path, UriKind.Relative); // relative
else
- uri = new Uri(domain.GetLeftPart(UriPartial.Path).TrimEnd('/') + path); // absolute
+ uri = new Uri(domainUri.GetLeftPart(UriPartial.Path).TrimEnd('/') + path); // absolute
}
return UriFromUmbraco(uri);
}
+ IEnumerable AssembleUrls(IEnumerable domainUris, string path, Uri current)
+ {
+ if (domainUris.Any())
+ {
+ return domainUris.Select(domainUri => new Uri(domainUri.GetLeftPart(UriPartial.Path).TrimEnd('/') + path));
+ }
+ else
+ {
+ // no domain was found : return a relative url, add vdir if any
+ return new Uri[] { new Uri(global::umbraco.IO.SystemDirectories.Root + path, UriKind.Relative) };
+ }
+ }
+
Uri DomainUriAtNode(int nodeId, Uri current)
{
// be safe
@@ -142,10 +207,20 @@ namespace Umbraco.Web.Routing
return null;
// apply filter on domains defined on that node
- var domainAndUri = Domains.ApplicableDomains(Domain.GetDomainsById(nodeId), current, true);
+ var domainAndUri = Domains.DomainMatch(Domain.GetDomainsById(nodeId), current, true);
return domainAndUri == null ? null : domainAndUri.Uri;
}
+ IEnumerable DomainUrisAtNode(int nodeId, Uri current)
+ {
+ // be safe
+ if (nodeId <= 0)
+ return new Uri[] { };
+
+ var domainAndUris = Domains.DomainMatches(Domain.GetDomainsById(nodeId), current);
+ return domainAndUris.Select(d => d.Uri);
+ }
+
#endregion
#region Map public urls to/from umbraco urls
diff --git a/src/Umbraco.Web/Routing/RoutesCacheResolver.cs b/src/Umbraco.Web/Routing/RoutesCacheResolver.cs
index 98d1114866..04a49f4e66 100644
--- a/src/Umbraco.Web/Routing/RoutesCacheResolver.cs
+++ b/src/Umbraco.Web/Routing/RoutesCacheResolver.cs
@@ -3,12 +3,22 @@ using Umbraco.Core.Resolving;
namespace Umbraco.Web.Routing
{
+ ///
+ /// Resolves the implementation.
+ ///
class RoutesCacheResolver : SingleResolverBase
{
+ ///
+ /// Initializes a new instance of the class with an implementation.
+ ///
+ /// The implementation.
internal RoutesCacheResolver(IRoutesCache routesCache)
: base(routesCache)
{ }
+ ///
+ /// Gets or sets the implementation.
+ ///
public IRoutesCache RoutesCache
{
get { return this.Value; }
diff --git a/src/Umbraco.Web/Routing/RoutingContext.cs b/src/Umbraco.Web/Routing/RoutingContext.cs
index 19aa35fea8..dbb3123034 100644
--- a/src/Umbraco.Web/Routing/RoutingContext.cs
+++ b/src/Umbraco.Web/Routing/RoutingContext.cs
@@ -5,11 +5,17 @@ namespace Umbraco.Web.Routing
{
///
- /// represents a request for one specified Umbraco document to be rendered by one specified template,
- /// using one particular culture.
+ /// Provides context for the routing of a request.
///
internal class RoutingContext
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Umbraco context.
+ /// The document lookups resolver.
+ /// The content store.
+ /// The nice urls resolver.
public RoutingContext(
UmbracoContext umbracoContext,
DocumentLookupsResolver documentLookupsResolver,
@@ -22,9 +28,24 @@ namespace Umbraco.Web.Routing
this.NiceUrlProvider = niceUrlResolver;
}
+ ///
+ /// Gets the Umbraco context.
+ ///
public UmbracoContext UmbracoContext { get; private set; }
+
+ ///
+ /// Gets the document lookups resolver.
+ ///
public DocumentLookupsResolver DocumentLookupsResolver { get; private set; }
+
+ ///
+ /// Gets the content store.
+ ///
public ContentStore ContentStore { get; private set; }
+
+ ///
+ /// Gets the nice urls provider.
+ ///
public NiceUrlProvider NiceUrlProvider { get; private set; }
}
}
\ No newline at end of file