Skip to content

Commit 965a7fc

Browse files
Fixes: U4-2925 Tree performance is very poor in 6.1.x when having event subscriptions
1 parent abbf8ae commit 965a7fc

File tree

6 files changed

+354
-89
lines changed

6 files changed

+354
-89
lines changed

src/Umbraco.Core/TypeHelper.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ internal static class TypeHelper
1616
private static readonly ConcurrentDictionary<Type, FieldInfo[]> GetFieldsCache = new ConcurrentDictionary<Type, FieldInfo[]>();
1717
private static readonly ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]> GetPropertiesCache = new ConcurrentDictionary<Tuple<Type, bool, bool, bool>, PropertyInfo[]>();
1818

19+
/// <summary>
20+
/// Checks if the method is actually overriding a base method
21+
/// </summary>
22+
/// <param name="m"></param>
23+
/// <returns></returns>
24+
public static bool IsOverride(MethodInfo m)
25+
{
26+
return m.GetBaseDefinition().DeclaringType != m.DeclaringType;
27+
}
28+
1929
/// <summary>
2030
/// Find all assembly references that are referencing the assignTypeFrom Type's assembly found in the assemblyList
2131
/// </summary>
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using NUnit.Framework;
6+
using umbraco.cms.presentation.Trees;
7+
8+
namespace Umbraco.Tests.Trees
9+
{
10+
11+
[TestFixture]
12+
public class BaseMediaTreeTests
13+
{
14+
15+
[TearDown]
16+
public void TestTearDown()
17+
{
18+
BaseTree.AfterTreeRender -= EventHandler;
19+
BaseTree.BeforeTreeRender -= EventHandler;
20+
}
21+
22+
[Test]
23+
public void Run_Optimized()
24+
{
25+
var tree = new MyOptimizedMediaTree("media");
26+
27+
Assert.IsTrue(tree.UseOptimizedRendering);
28+
}
29+
30+
[Test]
31+
public void Not_Optimized_Events_AfterRender()
32+
{
33+
var tree = new MyOptimizedMediaTree("media");
34+
35+
BaseTree.AfterTreeRender += EventHandler;
36+
37+
Assert.IsFalse(tree.UseOptimizedRendering);
38+
}
39+
40+
[Test]
41+
public void Not_Optimized_Events_BeforeRender()
42+
{
43+
var tree = new MyOptimizedMediaTree("media");
44+
45+
BaseTree.BeforeTreeRender += EventHandler;
46+
47+
Assert.IsFalse(tree.UseOptimizedRendering);
48+
}
49+
50+
private void EventHandler(object sender, TreeEventArgs treeEventArgs)
51+
{
52+
53+
}
54+
55+
public class MyOptimizedMediaTree : BaseMediaTree
56+
{
57+
public MyOptimizedMediaTree(string application)
58+
: base(application)
59+
{
60+
}
61+
62+
protected override void CreateRootNode(ref XmlTreeNode rootNode)
63+
{
64+
65+
}
66+
}
67+
68+
69+
}
70+
71+
[TestFixture]
72+
public class BaseContentTreeTests
73+
{
74+
75+
[TearDown]
76+
public void TestTearDown()
77+
{
78+
BaseTree.AfterTreeRender -= EventHandler;
79+
BaseTree.BeforeTreeRender -= EventHandler;
80+
}
81+
82+
[Test]
83+
public void Run_Optimized()
84+
{
85+
var tree = new MyOptimizedContentTree("content");
86+
87+
Assert.IsTrue(tree.UseOptimizedRendering);
88+
}
89+
90+
[Test]
91+
public void Not_Optimized_Events_AfterRender()
92+
{
93+
var tree = new MyOptimizedContentTree("content");
94+
95+
BaseTree.AfterTreeRender += EventHandler;
96+
97+
Assert.IsFalse(tree.UseOptimizedRendering);
98+
}
99+
100+
[Test]
101+
public void Not_Optimized_Events_BeforeRender()
102+
{
103+
var tree = new MyOptimizedContentTree("content");
104+
105+
BaseTree.BeforeTreeRender += EventHandler;
106+
107+
Assert.IsFalse(tree.UseOptimizedRendering);
108+
}
109+
110+
[Test]
111+
public void Not_Optimized_Overriden_Method()
112+
{
113+
var tree = new MyNotOptimizedContentTree("content");
114+
115+
Assert.IsFalse(tree.UseOptimizedRendering);
116+
}
117+
118+
private void EventHandler(object sender, TreeEventArgs treeEventArgs)
119+
{
120+
121+
}
122+
123+
public class MyOptimizedContentTree : BaseContentTree
124+
{
125+
public MyOptimizedContentTree(string application)
126+
: base(application)
127+
{
128+
}
129+
130+
protected override void CreateRootNode(ref XmlTreeNode rootNode)
131+
{
132+
133+
}
134+
}
135+
136+
public class MyNotOptimizedContentTree : BaseContentTree
137+
{
138+
public MyNotOptimizedContentTree(string application)
139+
: base(application)
140+
{
141+
}
142+
143+
protected override void CreateRootNode(ref XmlTreeNode rootNode)
144+
{
145+
146+
}
147+
148+
protected override void OnRenderNode(ref XmlTreeNode xNode, umbraco.cms.businesslogic.web.Document doc)
149+
{
150+
base.OnRenderNode(ref xNode, doc);
151+
}
152+
}
153+
154+
155+
}
156+
}

src/Umbraco.Tests/Umbraco.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@
350350
<Compile Include="TestHelpers\Entities\MockedContentTypes.cs" />
351351
<Compile Include="TestHelpers\Entities\MockedEntity.cs" />
352352
<Compile Include="TestHelpers\Entities\MockedMedia.cs" />
353+
<Compile Include="Trees\BaseContentTreeTests.cs" />
353354
<Compile Include="UmbracoExamine\ContentServiceTest.cs" />
354355
<Compile Include="UmbracoExamine\EventsTest.cs" />
355356
<Compile Include="TypeHelperTests.cs" />

src/Umbraco.Web/umbraco.presentation/umbraco/Trees/BaseContentTree.cs

Lines changed: 73 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
using System.Collections.Generic;
33
using System.Globalization;
44
using System.Linq;
5+
using System.Reflection;
56
using System.Text;
67
using System.Web;
8+
using Umbraco.Core;
79
using Umbraco.Core.Models;
810
using umbraco.BasePages;
911
using umbraco.BusinessLogic;
@@ -73,61 +75,64 @@ function openContent(id) {
7375
/// Renders the specified tree item.
7476
/// </summary>
7577
/// <param name="Tree">The tree.</param>
76-
/*public override void Render(ref XmlTree Tree)
78+
public override void Render(ref XmlTree Tree)
7779
{
78-
//get documents to render
79-
Document[] docs = Document.GetChildrenForTree(m_id);
80+
if (UseOptimizedRendering == false)
81+
{
82+
//We cannot run optimized mode since there are subscribers to events/methods that require document instances
83+
// so we'll render the original way by looking up the docs.
8084

81-
var args = new TreeEventArgs(Tree);
82-
OnBeforeTreeRender(docs, args);
85+
//get documents to render
86+
var docs = Document.GetChildrenForTree(m_id);
8387

84-
foreach (Document dd in docs)
85-
{
86-
List<IAction> allowedUserOptions = GetUserActionsForNode(dd);
87-
if (CanUserAccessNode(dd, allowedUserOptions))
88+
var args = new TreeEventArgs(Tree);
89+
OnBeforeTreeRender(docs, args);
90+
91+
foreach (var dd in docs)
8892
{
93+
var allowedUserOptions = GetUserActionsForNode(dd);
94+
if (CanUserAccessNode(dd, allowedUserOptions))
95+
{
8996

90-
XmlTreeNode node = CreateNode(dd, allowedUserOptions);
97+
var node = CreateNode(dd, allowedUserOptions);
9198

92-
OnRenderNode(ref node, dd);
99+
OnRenderNode(ref node, dd);
93100

94-
OnBeforeNodeRender(ref Tree, ref node, EventArgs.Empty);
95-
if (node != null)
96-
{
97-
Tree.Add(node);
98-
OnAfterNodeRender(ref Tree, ref node, EventArgs.Empty);
101+
OnBeforeNodeRender(ref Tree, ref node, EventArgs.Empty);
102+
if (node != null)
103+
{
104+
Tree.Add(node);
105+
OnAfterNodeRender(ref Tree, ref node, EventArgs.Empty);
106+
}
99107
}
100108
}
109+
OnAfterTreeRender(docs, args);
101110
}
102-
OnAfterTreeRender(docs, args);
103-
}*/
104-
public override void Render(ref XmlTree Tree)
105-
{
106-
//get documents to render
107-
var entities = Services.EntityService.GetChildren(m_id, UmbracoObjectTypes.Document).ToArray();
108-
109-
var args = new TreeEventArgs(Tree);
110-
OnBeforeTreeRender(entities, args, true);
111-
112-
foreach (var entity in entities)
111+
else
113112
{
114-
var e = entity as UmbracoEntity;
115-
List<IAction> allowedUserOptions = GetUserActionsForNode(e);
116-
if (CanUserAccessNode(e, allowedUserOptions))
117-
{
118-
XmlTreeNode node = CreateNode(e, allowedUserOptions);
119113

120-
OnRenderNode(ref node, new Document(entity, LoadMinimalDocument));
114+
//We ARE running in optmized mode, this means we will NOT be raising the BeforeTreeRender or AfterTreeRender
115+
// events and NOT calling the OnRenderNode method - we've already detected that there are not subscribers or implementations
116+
// to call so that is fine.
121117

122-
OnBeforeNodeRender(ref Tree, ref node, EventArgs.Empty);
123-
if (node != null)
118+
var entities = Services.EntityService.GetChildren(m_id, UmbracoObjectTypes.Document).ToArray();
119+
foreach (var entity in entities)
120+
{
121+
var e = entity as UmbracoEntity;
122+
var allowedUserOptions = GetUserActionsForNode(e);
123+
if (CanUserAccessNode(e, allowedUserOptions))
124124
{
125-
Tree.Add(node);
126-
OnAfterNodeRender(ref Tree, ref node, EventArgs.Empty);
125+
var node = CreateNode(e, allowedUserOptions);
126+
127+
OnBeforeNodeRender(ref Tree, ref node, EventArgs.Empty);
128+
if (node != null)
129+
{
130+
Tree.Add(node);
131+
OnAfterNodeRender(ref Tree, ref node, EventArgs.Empty);
132+
}
127133
}
128134
}
129135
}
130-
OnAfterTreeRender(entities, args, true);
131136
}
132137

133138
#region Tree Create-node-helper Methods - Legacy
@@ -475,5 +480,35 @@ protected List<IAction> RemoveDuplicateMenuDividers(List<IAction> actions)
475480
return umbraco.BusinessLogic.Actions.Action.FromString(fullMenu);
476481
}
477482

483+
/// <summary>
484+
/// Returns true if we can use the EntityService to render the tree or revert to the original way
485+
/// using normal documents
486+
/// </summary>
487+
/// <remarks>
488+
/// We determine this by:
489+
/// * If there are any subscribers to the events: BeforeTreeRender or AfterTreeRender - then we cannot run optimized
490+
/// * If there are any overrides of the method: OnRenderNode - then we cannot run optimized
491+
/// </remarks>
492+
internal bool UseOptimizedRendering
493+
{
494+
get
495+
{
496+
if (HasEntityBasedEventSubscribers)
497+
{
498+
return false;
499+
}
500+
501+
//now we need to check if the current tree type has OnRenderNode overridden with a custom implementation
502+
//Strangely - this even works in med trust!
503+
var method = this.GetType().GetMethod("OnRenderNode", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(XmlTreeNode).MakeByRefType(), typeof(Document) }, null);
504+
if (TypeHelper.IsOverride(method))
505+
{
506+
return false;
507+
}
508+
509+
return true;
510+
}
511+
}
512+
478513
}
479514
}

0 commit comments

Comments
 (0)