Merge origin/dev-v7-deploy into dev-v8-zbwip
This commit is contained in:
@@ -11,6 +11,8 @@ using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Routing;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Services;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
@@ -132,6 +134,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
// - non-colliding, adds one complete "by route" lookup, only on the first time a url is computed (then it's cached anyways)
|
||||
// - colliding, adds one "by route" lookup, the first time the url is computed, then one dictionary looked each time it is computed again
|
||||
// assuming no collisions, the impact is one complete "by route" lookup the first time each url is computed
|
||||
//
|
||||
// U4-9121 - this lookup is too expensive when computing a large amount of urls on a front-end (eg menu)
|
||||
// ... thinking about moving the lookup out of the path into its own async task, so we are not reporting errors
|
||||
// in the back-office anymore, but at least we are not polluting the cache
|
||||
// instead, refactored DeterminedIdByRoute to stop using XPath, with a 16x improvement according to benchmarks
|
||||
// will it be enough?
|
||||
|
||||
var loopId = preview ? 0 : (_routesCache?.GetNodeId(route) ?? 0); // might be cached already in case of collision
|
||||
if (loopId == 0)
|
||||
{
|
||||
@@ -168,30 +177,98 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
var pos = route.IndexOf('/');
|
||||
var path = pos == 0 ? route : route.Substring(pos);
|
||||
var startNodeId = pos == 0 ? 0 : int.Parse(route.Substring(0, pos));
|
||||
IEnumerable<XPathVariable> vars;
|
||||
|
||||
var xpath = CreateXpathQuery(startNodeId, path, hideTopLevelNode, out vars);
|
||||
|
||||
//check if we can find the node in our xml cache
|
||||
var content = GetSingleByXPath(preview, xpath, vars?.ToArray());
|
||||
var id = NavigateRoute(preview, startNodeId, path, hideTopLevelNode);
|
||||
if (id > 0) return GetById(preview, id);
|
||||
|
||||
// if hideTopLevelNodePath is true then for url /foo we looked for /*/foo
|
||||
// but maybe that was the url of a non-default top-level node, so we also
|
||||
// have to look for /foo (see note in ApplyHideTopLevelNodeFromPath).
|
||||
if (content == null && hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0)
|
||||
if (hideTopLevelNode && path.Length > 1 && path.IndexOf('/', 1) < 0)
|
||||
{
|
||||
xpath = CreateXpathQuery(startNodeId, path, false, out vars);
|
||||
content = GetSingleByXPath(preview, xpath, vars?.ToArray());
|
||||
var id2 = NavigateRoute(preview, startNodeId, path, false);
|
||||
if (id2 > 0) return GetById(preview, id2);
|
||||
}
|
||||
|
||||
return content;
|
||||
return null;
|
||||
}
|
||||
|
||||
private int NavigateRoute(bool preview, int startNodeId, string path, bool hideTopLevelNode)
|
||||
{
|
||||
var xml = GetXml(preview);
|
||||
XmlElement elt;
|
||||
|
||||
// empty path
|
||||
if (path == string.Empty || path == "/")
|
||||
{
|
||||
if (startNodeId > 0)
|
||||
{
|
||||
elt = xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture));
|
||||
return elt == null ? -1 : startNodeId;
|
||||
}
|
||||
|
||||
elt = null;
|
||||
var min = int.MaxValue;
|
||||
foreach (XmlElement e in xml.DocumentElement.ChildNodes)
|
||||
{
|
||||
var sortOrder = int.Parse(e.GetAttribute("sortOrder"));
|
||||
if (sortOrder < min)
|
||||
{
|
||||
min = sortOrder;
|
||||
elt = e;
|
||||
}
|
||||
}
|
||||
return elt == null ? -1 : int.Parse(elt.GetAttribute("id"));
|
||||
}
|
||||
|
||||
// non-empty path
|
||||
elt = startNodeId <= 0
|
||||
? xml.DocumentElement
|
||||
: xml.GetElementById(startNodeId.ToString(CultureInfo.InvariantCulture));
|
||||
if (elt == null) return -1;
|
||||
|
||||
var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (hideTopLevelNode && startNodeId <= 0)
|
||||
{
|
||||
foreach (XmlElement e in elt.ChildNodes)
|
||||
{
|
||||
var id = NavigateElementRoute(e, urlParts);
|
||||
if (id > 0) return id;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return NavigateElementRoute(elt, urlParts);
|
||||
}
|
||||
|
||||
private int NavigateElementRoute(XmlElement elt, string[] urlParts)
|
||||
{
|
||||
var found = true;
|
||||
var i = 0;
|
||||
while (found && i < urlParts.Length)
|
||||
{
|
||||
found = false;
|
||||
foreach (XmlElement child in elt.ChildNodes)
|
||||
{
|
||||
var noNode = child.GetAttributeNode("isDoc") == null;
|
||||
if (noNode) continue;
|
||||
if (child.GetAttribute("urlName") != urlParts[i]) continue;
|
||||
|
||||
found = true;
|
||||
elt = child;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return found ? int.Parse(elt.GetAttribute("id")) : -1;
|
||||
}
|
||||
|
||||
string DetermineRouteById(bool preview, int contentId)
|
||||
{
|
||||
var node = GetById(preview, contentId);
|
||||
if (node == null)
|
||||
return null;
|
||||
if (node == null) return null;
|
||||
|
||||
// walk up from that node until we hit a node with a domain,
|
||||
// or we reach the content root, collecting urls in the way
|
||||
@@ -253,10 +330,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
public const string Root = "/root";
|
||||
public const string RootDocuments = "/root/* [@isDoc]";
|
||||
public const string DescendantDocumentById = "//* [@isDoc and @id={0}]";
|
||||
public const string ChildDocumentByUrlName = "/* [@isDoc and @urlName='{0}']";
|
||||
public const string ChildDocumentByUrlNameVar = "/* [@isDoc and @urlName=${0}]";
|
||||
public const string RootDocumentWithLowestSortOrder = "/root/* [@isDoc and not(@sortOrder > ../* [@isDoc]/@sortOrder)][1]";
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -436,82 +509,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
static readonly char[] SlashChar = { '/' };
|
||||
|
||||
protected string CreateXpathQuery(int startNodeId, string path, bool hideTopLevelNodeFromPath, out IEnumerable<XPathVariable> vars)
|
||||
{
|
||||
string xpath;
|
||||
vars = null;
|
||||
|
||||
if (path == string.Empty || path == "/")
|
||||
{
|
||||
// if url is empty
|
||||
if (startNodeId > 0)
|
||||
{
|
||||
// if in a domain then use the root node of the domain
|
||||
xpath = string.Format(XPathStrings.Root + XPathStrings.DescendantDocumentById, startNodeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// if not in a domain - what is the default page?
|
||||
// let's say it is the first one in the tree, if any -- order by sortOrder
|
||||
|
||||
// but!
|
||||
// umbraco does not consistently guarantee that sortOrder starts with 0
|
||||
// so the one that we want is the one with the smallest sortOrder
|
||||
// read http://stackoverflow.com/questions/1128745/how-can-i-use-xpath-to-find-the-minimum-value-of-an-attribute-in-a-set-of-elemen
|
||||
|
||||
// so that one does not work, because min(@sortOrder) maybe 1
|
||||
// xpath = "/root/*[@isDoc and @sortOrder='0']";
|
||||
|
||||
// and we can't use min() because that's XPath 2.0
|
||||
// that one works
|
||||
xpath = XPathStrings.RootDocumentWithLowestSortOrder;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if url is not empty, then use it to try lookup a matching page
|
||||
var urlParts = path.Split(SlashChar, StringSplitOptions.RemoveEmptyEntries);
|
||||
var xpathBuilder = new StringBuilder();
|
||||
var partsIndex = 0;
|
||||
List<XPathVariable> varsList = null;
|
||||
|
||||
if (startNodeId == 0)
|
||||
{
|
||||
// if hiding, first node is not in the url
|
||||
xpathBuilder.Append(hideTopLevelNodeFromPath ? XPathStrings.RootDocuments : XPathStrings.Root);
|
||||
}
|
||||
else
|
||||
{
|
||||
xpathBuilder.AppendFormat(XPathStrings.Root + XPathStrings.DescendantDocumentById, startNodeId);
|
||||
// always "hide top level" when there's a domain
|
||||
}
|
||||
|
||||
while (partsIndex < urlParts.Length)
|
||||
{
|
||||
var part = urlParts[partsIndex++];
|
||||
if (part.Contains('\'') || part.Contains('"'))
|
||||
{
|
||||
// use vars, escaping gets ugly pretty quickly
|
||||
varsList = varsList ?? new List<XPathVariable>();
|
||||
var varName = $"var{partsIndex}";
|
||||
varsList.Add(new XPathVariable(varName, part));
|
||||
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlNameVar, varName);
|
||||
}
|
||||
else
|
||||
{
|
||||
xpathBuilder.AppendFormat(XPathStrings.ChildDocumentByUrlName, part);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
xpath = xpathBuilder.ToString();
|
||||
if (varsList != null)
|
||||
vars = varsList.ToArray();
|
||||
}
|
||||
|
||||
return xpath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Content types
|
||||
|
||||
Reference in New Issue
Block a user