U4-9121 - improve url perfs
This commit is contained in:
@@ -1,20 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Columns;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Diagnostics.Windows;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Reports;
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Validators;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
|
||||
@@ -1,19 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using BenchmarkDotNet.Running;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
class Program
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var summary = BenchmarkRunner.Run<BulkInsertBenchmarks>();
|
||||
|
||||
Console.ReadLine();
|
||||
if (args.Length == 0)
|
||||
{
|
||||
var summary = BenchmarkRunner.Run<BulkInsertBenchmarks>();
|
||||
Console.ReadLine();
|
||||
}
|
||||
else if (args.Length == 1)
|
||||
{
|
||||
var type = Assembly.GetExecutingAssembly().GetType("Umbraco.Tests.Benchmarks." +args[0]);
|
||||
if (type == null)
|
||||
{
|
||||
Console.WriteLine("Unknown benchmark.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var summary = BenchmarkRunner.Run(type);
|
||||
Console.ReadLine();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
Version 1.0.0.3 - Initial release to NuGet, pre-release.
|
||||
|
||||
TraceEvent has been available from the site http://bcl.codeplex.com/wikipage?title=TraceEvent for some time now
|
||||
this NuGet Version of the library supersedes that one. WHile the 'core' part of the library is unchanged,
|
||||
we did change lesser used features, and change the namespace and DLL name, which will cause break. We anticipate
|
||||
it will take an hour or so to 'port' to this version from the old one. Below are specific details on what
|
||||
has changed to help in this port.
|
||||
|
||||
* The DLL has been renamed from TraceEvent.dll to Microsoft.Diagnostics.Tracing.TraceEvent.dll
|
||||
* The name spaces for all classes have been changed. The easiest way to port is to simply place
|
||||
the following using clauses at the top of any file that uses TraceEvent classes
|
||||
using Microsoft.Diagnostics.Symbols;
|
||||
using Microsoft.Diagnostics.Tracing;
|
||||
using Microsoft.Diagnostics.Tracing.Etlx;
|
||||
using Microsoft.Diagnostics.Tracing.Parsers.Clr;
|
||||
using Microsoft.Diagnostics.Tracing.Parsers.Kernel;
|
||||
using Microsoft.Diagnostics.Tracing.Session;
|
||||
using Microsoft.Diagnostics.Tracing.Stacks;
|
||||
* Any method with the name RelMSec in it has been changed to be RelativeMSec. The easiest port is to
|
||||
simply globally rename RelMSec to RelativeMSec
|
||||
* Any property in the Trace* classes that has the form Max*Index has been renamed to Count.
|
||||
* A number of methods have been declared obsolete, these are mostly renames and the warning will tell you
|
||||
how to update them.
|
||||
* The following classes have been rename
|
||||
SymPath -> SymbolPath
|
||||
SymPathElement -> SymbolPathElement
|
||||
SymbolReaderFlags -> SymbolReaderOptions
|
||||
* TraceEventSession is now StopOnDispose (it will stop the session when TraceEventSesssion dies), by default
|
||||
If you were relying on the kernel session living past the process that started it, you must now set
|
||||
the StopOnDispose explicitly
|
||||
* There used to be XmlAttrib extensions methods on StringBuilder for use in manifest generated TraceEventParsers
|
||||
These have been moved to protected members of TraceEvent. The result is that in stead of writing
|
||||
sb.XmlAttrib(...) you write XmlAttrib(sb, ...)
|
||||
* References to Pdb in names have been replaced with 'Symbol' to conform to naming guidelines.
|
||||
|
||||
***********************************************************************************************
|
||||
Version 1.0.0.4 - Initial stable release
|
||||
|
||||
Mostly this was insuring that the library was cleaned up in preparation
|
||||
for release the TraceParserGen tool
|
||||
|
||||
Improved the docs, removed old code, fixed some naming convention stuff
|
||||
|
||||
* Additional changes from the PreRelease copy to the first Stable release
|
||||
|
||||
* The arguments to AddCallbackForProviderEvent were reversed!!!! (now provider than event)
|
||||
* The arguments to Observe(string, string)!!!! (now provider than event)
|
||||
* Event names for these APIs must include a / between the Task and Opcode names
|
||||
|
||||
* Many Events in KernelTraceEventParser were harmonized to be consistent with other conventions
|
||||
* Events of the form PageFault* were typically renamed to Memory*
|
||||
* The 'End' suffix was renamed to 'Stop' (its official name)
|
||||
* PerfInfoSampleProf -> PerfInfoSample
|
||||
* PerfInfoSampleProf -> PerfInfoSample
|
||||
* ReadyThread -> DispatcherReadyThread
|
||||
* StackWalkTraceData -> StackWalkStackTraceData
|
||||
* FileIo -> FileIO
|
||||
* DiskIo -> DiskIO
|
||||
|
||||
* Many Events in SymbolTraceEventParser were harmonized to be consistent with other conventions
|
||||
* names with Symbol -> ImageID
|
||||
@@ -99,20 +99,16 @@
|
||||
<Compile Include="BulkInsertBenchmarks.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="XmlBenchmarks.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="_TraceEventProgrammersGuide.docx" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
|
||||
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.CSharp.Analyzers.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="TraceEvent.ReadMe.txt" />
|
||||
<Content Include="TraceEvent.ReleaseNotes.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Umbraco.Core\Umbraco.Core.csproj">
|
||||
<Project>{31785bc3-256c-4613-b2f5-a1b0bdded8c1}</Project>
|
||||
|
||||
123
src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs
Normal file
123
src/Umbraco.Tests.Benchmarks/XmlBenchmarks.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnostics.Windows;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
[Config(typeof(Config))]
|
||||
public class XmlBenchmarks
|
||||
{
|
||||
private class Config : ManualConfig
|
||||
{
|
||||
public Config()
|
||||
{
|
||||
Add(new MemoryDiagnoser());
|
||||
//Add(ExecutionValidator.FailOnError);
|
||||
|
||||
//The 'quick and dirty' settings, so it runs a little quicker
|
||||
// see benchmarkdotnet FAQ
|
||||
Add(Job.Default
|
||||
.WithLaunchCount(1) // benchmark process will be launched only once
|
||||
.WithIterationTime(100) // 100ms per iteration
|
||||
.WithWarmupCount(3) // 3 warmup iteration
|
||||
.WithTargetCount(3)); // 3 target iteration
|
||||
}
|
||||
}
|
||||
|
||||
[Setup]
|
||||
public void Setup()
|
||||
{
|
||||
var templateId = 0;
|
||||
var xmlText = @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<!DOCTYPE root[
|
||||
<!ELEMENT Home ANY>
|
||||
<!ATTLIST Home id ID #REQUIRED>
|
||||
<!ELEMENT CustomDocument ANY>
|
||||
<!ATTLIST CustomDocument id ID #REQUIRED>
|
||||
]>
|
||||
<root id=""-1"">
|
||||
<Home id=""1046"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""1"" createDate=""2012-06-12T14:13:17"" updateDate=""2012-07-20T18:50:43"" nodeName=""Home"" urlName=""home"" writerName=""admin"" creatorName=""admin"" path=""-1,1046"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[this/is/my/alias, anotheralias]]></umbracoUrlAlias>
|
||||
<umbracoNaviHide>1</umbracoNaviHide>
|
||||
<Home id=""1173"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-20T18:06:45"" updateDate=""2012-07-20T19:07:31"" nodeName=""Sub1"" urlName=""sub1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173"" isDoc="""">
|
||||
<content><![CDATA[<div>This is some content</div>]]></content>
|
||||
<umbracoUrlAlias><![CDATA[page2/alias, 2ndpagealias]]></umbracoUrlAlias>
|
||||
<Home id=""1174"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-20T18:07:54"" updateDate=""2012-07-20T19:10:27"" nodeName=""Sub2"" urlName=""sub2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1174"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
<umbracoUrlAlias><![CDATA[only/one/alias]]></umbracoUrlAlias>
|
||||
<creatorName><![CDATA[Custom data with same property name as the member name]]></creatorName>
|
||||
</Home>
|
||||
<Home id=""1176"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-20T18:08:08"" updateDate=""2012-07-20T19:10:52"" nodeName=""Sub 3"" urlName=""sub-3"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1176"" isDoc="""">
|
||||
<content><![CDATA[]]></content>
|
||||
</Home>
|
||||
<CustomDocument id=""1177"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""custom sub 1"" urlName=""custom-sub-1"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1177"" isDoc="""" />
|
||||
<CustomDocument id=""1178"" parentID=""1173"" level=""3"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""4"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-16T14:23:35"" nodeName=""custom sub 2"" urlName=""custom-sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1173,1178"" isDoc="""" />
|
||||
</Home>
|
||||
<Home id=""1175"" parentID=""1046"" level=""2"" writerID=""0"" creatorID=""0"" nodeType=""1044"" template=""" + templateId + @""" sortOrder=""3"" createDate=""2012-07-20T18:08:01"" updateDate=""2012-07-20T18:49:32"" nodeName=""Sub 2"" urlName=""sub-2"" writerName=""admin"" creatorName=""admin"" path=""-1,1046,1175"" isDoc=""""><content><![CDATA[]]></content>
|
||||
</Home>
|
||||
</Home>
|
||||
<CustomDocument id=""1172"" parentID=""-1"" level=""1"" writerID=""0"" creatorID=""0"" nodeType=""1234"" template=""" + templateId + @""" sortOrder=""2"" createDate=""2012-07-16T15:26:59"" updateDate=""2012-07-18T14:23:35"" nodeName=""Test"" urlName=""test-page"" writerName=""admin"" creatorName=""admin"" path=""-1,1172"" isDoc="""" />
|
||||
</root>";
|
||||
_xml = new XmlDocument();
|
||||
_xml.LoadXml(xmlText);
|
||||
}
|
||||
|
||||
[Cleanup]
|
||||
public void Cleanup()
|
||||
{
|
||||
_xml = null;
|
||||
}
|
||||
|
||||
private XmlDocument _xml;
|
||||
|
||||
[Benchmark]
|
||||
public void XmlWithXPath()
|
||||
{
|
||||
var xpath = "/root/* [@isDoc and @urlName='home']//* [@isDoc and @urlName='sub1']//* [@isDoc and @urlName='sub2']";
|
||||
var elt = _xml.SelectSingleNode(xpath);
|
||||
if (elt == null) Console.WriteLine("ERR");
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void XmlWithNavigation()
|
||||
{
|
||||
var elt = _xml.DocumentElement;
|
||||
var id = NavigateElementRoute(elt, new[] {"home", "sub1", "sub2"});
|
||||
if (id <= 0) Console.WriteLine("ERR");
|
||||
}
|
||||
|
||||
private const bool UseLegacySchema = false;
|
||||
|
||||
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 = UseLegacySchema
|
||||
? child.Name != "node"
|
||||
: 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@ using umbraco;
|
||||
using System.Linq;
|
||||
using umbraco.BusinessLogic;
|
||||
using umbraco.presentation.preview;
|
||||
using Umbraco.Core.Services;
|
||||
using GlobalSettings = umbraco.GlobalSettings;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
@@ -26,6 +28,13 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
private readonly RoutesCache _routesCache = new RoutesCache(!UnitTesting);
|
||||
|
||||
private DomainHelper _domainHelper;
|
||||
|
||||
private DomainHelper GetDomainHelper(IDomainService domainService)
|
||||
{
|
||||
return _domainHelper ?? (_domainHelper = new DomainHelper(domainService));
|
||||
}
|
||||
|
||||
// for INTERNAL, UNIT TESTS use ONLY
|
||||
internal RoutesCache RoutesCache { get { return _routesCache; } }
|
||||
|
||||
@@ -99,6 +108,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); // might be cached already in case of collision
|
||||
if (loopId == 0)
|
||||
{
|
||||
@@ -130,62 +146,141 @@ 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(umbracoContext, preview, xpath, vars == null ? null : vars.ToArray());
|
||||
var id = NavigateRoute(umbracoContext, preview, startNodeId, path, hideTopLevelNode);
|
||||
if (id > 0) return GetById(umbracoContext, 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(umbracoContext, preview, xpath, vars == null ? null : vars.ToArray());
|
||||
var id2 = NavigateRoute(umbracoContext, preview, startNodeId, path, false);
|
||||
if (id2 > 0) return GetById(umbracoContext, preview, id2);
|
||||
}
|
||||
|
||||
return content;
|
||||
return null;
|
||||
}
|
||||
|
||||
private int NavigateRoute(UmbracoContext umbracoContext, bool preview, int startNodeId, string path, bool hideTopLevelNode)
|
||||
{
|
||||
var xml = GetXml(umbracoContext, 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 static bool UseLegacySchema
|
||||
{
|
||||
get { return UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; }
|
||||
}
|
||||
|
||||
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 = UseLegacySchema
|
||||
? child.Name != "node"
|
||||
: 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(UmbracoContext umbracoContext, bool preview, int contentId)
|
||||
{
|
||||
var node = GetById(umbracoContext, preview, contentId);
|
||||
if (node == null)
|
||||
return null;
|
||||
var elt = GetXml(umbracoContext, preview).GetElementById(contentId.ToString(CultureInfo.InvariantCulture));
|
||||
if (elt == null) return null;
|
||||
|
||||
var domainHelper = new DomainHelper(umbracoContext.Application.Services.DomainService);
|
||||
var domainHelper = GetDomainHelper(umbracoContext.Application.Services.DomainService);
|
||||
|
||||
// walk up from that node until we hit a node with a domain,
|
||||
// or we reach the content root, collecting urls in the way
|
||||
var pathParts = new List<string>();
|
||||
var n = node;
|
||||
var hasDomains = domainHelper.NodeHasDomains(n.Id);
|
||||
while (hasDomains == false && n != null) // n is null at root
|
||||
var eltId = int.Parse(elt.GetAttribute("id"));
|
||||
var eltParentId = int.Parse(((XmlElement) elt.ParentNode).GetAttribute("id"));
|
||||
var e = elt;
|
||||
var id = eltId;
|
||||
var hasDomains = domainHelper.NodeHasDomains(id);
|
||||
while (hasDomains == false && id != -1)
|
||||
{
|
||||
// get the url
|
||||
var urlName = n.UrlName;
|
||||
var urlName = e.GetAttribute("urlName");
|
||||
pathParts.Add(urlName);
|
||||
|
||||
// move to parent node
|
||||
n = n.Parent;
|
||||
hasDomains = n != null && domainHelper.NodeHasDomains(n.Id);
|
||||
e = (XmlElement) e.ParentNode;
|
||||
id = int.Parse(e.GetAttribute("id"));
|
||||
hasDomains = id != -1 && domainHelper.NodeHasDomains(id);
|
||||
}
|
||||
|
||||
// no domain, respect HideTopLevelNodeFromPath for legacy purposes
|
||||
if (hasDomains == false && global::umbraco.GlobalSettings.HideTopLevelNodeFromPath)
|
||||
ApplyHideTopLevelNodeFromPath(umbracoContext, node, pathParts);
|
||||
if (hasDomains == false && GlobalSettings.HideTopLevelNodeFromPath)
|
||||
ApplyHideTopLevelNodeFromPath(umbracoContext, eltId, eltParentId, pathParts);
|
||||
|
||||
// assemble the route
|
||||
pathParts.Reverse();
|
||||
var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc
|
||||
var route = (n == null ? "" : n.Id.ToString(CultureInfo.InvariantCulture)) + path;
|
||||
var route = (id == -1 ? "" : id.ToString(CultureInfo.InvariantCulture)) + path;
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, IPublishedContent node, IList<string> pathParts)
|
||||
static void ApplyHideTopLevelNodeFromPath(UmbracoContext umbracoContext, int nodeId, int parentId, IList<string> pathParts)
|
||||
{
|
||||
// in theory if hideTopLevelNodeFromPath is true, then there should be only once
|
||||
// top-level node, or else domains should be assigned. but for backward compatibility
|
||||
@@ -195,12 +290,12 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
// "/foo" fails (looking for "/*/foo") we try also "/foo".
|
||||
// this does not make much sense anyway esp. if both "/foo/" and "/bar/foo" exist, but
|
||||
// that's the way it works pre-4.10 and we try to be backward compat for the time being
|
||||
if (node.Parent == null)
|
||||
if (parentId == -1)
|
||||
{
|
||||
var rootNode = umbracoContext.ContentCache.GetByRoute("/", true);
|
||||
if (rootNode == null)
|
||||
throw new Exception("Failed to get node at /.");
|
||||
if (rootNode.Id == node.Id) // remove only if we're the default node
|
||||
if (rootNode.Id == nodeId) // remove only if we're the default node
|
||||
pathParts.RemoveAt(pathParts.Count - 1);
|
||||
}
|
||||
else
|
||||
@@ -217,12 +312,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
{
|
||||
public int Version { get; private set; }
|
||||
|
||||
public static string Root { get { return "/root"; } }
|
||||
public string RootDocuments { get; private set; }
|
||||
public string DescendantDocumentById { get; private set; }
|
||||
public string ChildDocumentByUrlName { get; private set; }
|
||||
public string ChildDocumentByUrlNameVar { get; private set; }
|
||||
public string RootDocumentWithLowestSortOrder { get; private set; }
|
||||
|
||||
public XPathStringsDefinition(int version)
|
||||
{
|
||||
@@ -233,19 +323,11 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
// legacy XML schema
|
||||
case 0:
|
||||
RootDocuments = "/root/node";
|
||||
DescendantDocumentById = "//node [@id={0}]";
|
||||
ChildDocumentByUrlName = "/node [@urlName='{0}']";
|
||||
ChildDocumentByUrlNameVar = "/node [@urlName=${0}]";
|
||||
RootDocumentWithLowestSortOrder = "/root/node [not(@sortOrder > ../node/@sortOrder)][1]";
|
||||
break;
|
||||
|
||||
// default XML schema as of 4.10
|
||||
case 1:
|
||||
RootDocuments = "/root/* [@isDoc]";
|
||||
DescendantDocumentById = "//* [@isDoc and @id={0}]";
|
||||
ChildDocumentByUrlName = "/* [@isDoc and @urlName='{0}']";
|
||||
ChildDocumentByUrlNameVar = "/* [@isDoc and @urlName=${0}]";
|
||||
RootDocumentWithLowestSortOrder = "/root/* [@isDoc and not(@sortOrder > ../* [@isDoc]/@sortOrder)][1]";
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -421,84 +503,6 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
|
||||
|
||||
static readonly char[] SlashChar = new[] { '/' };
|
||||
|
||||
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(XPathStringsDefinition.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();
|
||||
int partsIndex = 0;
|
||||
List<XPathVariable> varsList = null;
|
||||
|
||||
if (startNodeId == 0)
|
||||
{
|
||||
if (hideTopLevelNodeFromPath)
|
||||
xpathBuilder.Append(XPathStrings.RootDocuments); // first node is not in the url
|
||||
else
|
||||
xpathBuilder.Append(XPathStringsDefinition.Root);
|
||||
}
|
||||
else
|
||||
{
|
||||
xpathBuilder.AppendFormat(XPathStringsDefinition.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 = string.Format("var{0}", 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 Detached
|
||||
|
||||
Reference in New Issue
Block a user