Skip to content

Commit 4f6fda7

Browse files
authored
Merge pull request #17779 from umbraco/v15/hotfix/fix-friendly-content-extension-performance
* Refactor .Children to use PublishStatusQuery * Fix descendants * Fix ancestors * Fix siblings * Handle empty string in published status service * Fix unit test * Fixes issue found in tests --------- Co-authored-by: Bjarke Berg <[email protected]>
2 parents 968cb1a + c22dc2f commit 4f6fda7

File tree

13 files changed

+2375
-223
lines changed

13 files changed

+2375
-223
lines changed

src/Umbraco.Core/DeliveryApi/ApiContentRouteBuilder.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
using Microsoft.Extensions.Options;
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Options;
23
using Umbraco.Cms.Core.Configuration.Models;
4+
using Umbraco.Cms.Core.DependencyInjection;
35
using Umbraco.Cms.Core.Models.DeliveryApi;
46
using Umbraco.Cms.Core.Models.PublishedContent;
57
using Umbraco.Cms.Core.PublishedCache;
@@ -16,6 +18,7 @@ public sealed class ApiContentRouteBuilder : IApiContentRouteBuilder
1618
private readonly IRequestPreviewService _requestPreviewService;
1719
private readonly IPublishedContentCache _contentCache;
1820
private readonly IDocumentNavigationQueryService _navigationQueryService;
21+
private readonly IPublishStatusQueryService _publishStatusQueryService;
1922
private RequestHandlerSettings _requestSettings;
2023

2124
public ApiContentRouteBuilder(
@@ -25,18 +28,41 @@ public ApiContentRouteBuilder(
2528
IRequestPreviewService requestPreviewService,
2629
IOptionsMonitor<RequestHandlerSettings> requestSettings,
2730
IPublishedContentCache contentCache,
28-
IDocumentNavigationQueryService navigationQueryService)
31+
IDocumentNavigationQueryService navigationQueryService,
32+
IPublishStatusQueryService publishStatusQueryService)
2933
{
3034
_apiContentPathProvider = apiContentPathProvider;
3135
_variationContextAccessor = variationContextAccessor;
3236
_requestPreviewService = requestPreviewService;
3337
_contentCache = contentCache;
3438
_navigationQueryService = navigationQueryService;
39+
_publishStatusQueryService = publishStatusQueryService;
3540
_globalSettings = globalSettings.Value;
3641
_requestSettings = requestSettings.CurrentValue;
3742
requestSettings.OnChange(settings => _requestSettings = settings);
3843
}
3944

45+
[Obsolete("Use constructor that takes an IPublishStatusQueryService instead, scheduled for removal in v17")]
46+
public ApiContentRouteBuilder(
47+
IApiContentPathProvider apiContentPathProvider,
48+
IOptions<GlobalSettings> globalSettings,
49+
IVariationContextAccessor variationContextAccessor,
50+
IRequestPreviewService requestPreviewService,
51+
IOptionsMonitor<RequestHandlerSettings> requestSettings,
52+
IPublishedContentCache contentCache,
53+
IDocumentNavigationQueryService navigationQueryService)
54+
: this(
55+
apiContentPathProvider,
56+
globalSettings,
57+
variationContextAccessor,
58+
requestPreviewService,
59+
requestSettings,
60+
contentCache,
61+
navigationQueryService,
62+
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
63+
{
64+
}
65+
4066
public IApiContentRoute? Build(IPublishedContent content, string? culture = null)
4167
{
4268
if (content.ItemType != PublishedItemType.Content)
@@ -105,7 +131,7 @@ private IPublishedContent GetRoot(IPublishedContent content, bool isPreview)
105131
{
106132
if (isPreview is false)
107133
{
108-
return content.Root(_contentCache, _navigationQueryService);
134+
return content.Root(_variationContextAccessor, _contentCache, _navigationQueryService, _publishStatusQueryService);
109135
}
110136

111137
_navigationQueryService.TryGetRootKeys(out IEnumerable<Guid> rootKeys);
@@ -114,6 +140,6 @@ private IPublishedContent GetRoot(IPublishedContent content, bool isPreview)
114140
// in very edge case scenarios during preview, content.Root() does not map to the root.
115141
// we'll code our way around it for the time being.
116142
return rootContent.FirstOrDefault(root => root.IsAncestorOrSelf(content))
117-
?? content.Root(_contentCache, _navigationQueryService);
143+
?? content.Root(_variationContextAccessor, _contentCache, _navigationQueryService, _publishStatusQueryService);
118144
}
119145
}

src/Umbraco.Core/Extensions/PublishedContentExtensions.cs

Lines changed: 2182 additions & 159 deletions
Large diffs are not rendered by default.

src/Umbraco.Core/Routing/ContentFinderByUrlAlias.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
using Microsoft.Extensions.DependencyInjection;
12
using Microsoft.Extensions.Logging;
3+
using Umbraco.Cms.Core.DependencyInjection;
24
using Umbraco.Cms.Core.Models.PublishedContent;
35
using Umbraco.Cms.Core.PublishedCache;
46
using Umbraco.Cms.Core.Services.Navigation;
@@ -24,6 +26,7 @@ public class ContentFinderByUrlAlias : IContentFinder
2426
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
2527
private readonly IPublishedContentCache _contentCache;
2628
private readonly IDocumentNavigationQueryService _documentNavigationQueryService;
29+
private readonly IPublishStatusQueryService _publishStatusQueryService;
2730
private readonly IVariationContextAccessor _variationContextAccessor;
2831

2932
/// <summary>
@@ -35,16 +38,37 @@ public ContentFinderByUrlAlias(
3538
IVariationContextAccessor variationContextAccessor,
3639
IUmbracoContextAccessor umbracoContextAccessor,
3740
IPublishedContentCache contentCache,
38-
IDocumentNavigationQueryService documentNavigationQueryService)
41+
IDocumentNavigationQueryService documentNavigationQueryService,
42+
IPublishStatusQueryService publishStatusQueryService)
3943
{
4044
_publishedValueFallback = publishedValueFallback;
4145
_variationContextAccessor = variationContextAccessor;
4246
_umbracoContextAccessor = umbracoContextAccessor;
4347
_contentCache = contentCache;
4448
_documentNavigationQueryService = documentNavigationQueryService;
49+
_publishStatusQueryService = publishStatusQueryService;
4550
_logger = logger;
4651
}
4752

53+
[Obsolete("Please use constructor that takes an IPublishStatusQueryService instead. Scheduled removal in v17")]
54+
public ContentFinderByUrlAlias(
55+
ILogger<ContentFinderByUrlAlias> logger,
56+
IPublishedValueFallback publishedValueFallback,
57+
IVariationContextAccessor variationContextAccessor,
58+
IUmbracoContextAccessor umbracoContextAccessor,
59+
IPublishedContentCache contentCache,
60+
IDocumentNavigationQueryService documentNavigationQueryService)
61+
: this(
62+
logger,
63+
publishedValueFallback,
64+
variationContextAccessor,
65+
umbracoContextAccessor,
66+
contentCache,
67+
documentNavigationQueryService,
68+
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
69+
{
70+
}
71+
4872
/// <summary>
4973
/// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>.
5074
/// </summary>
@@ -145,14 +169,14 @@ bool IsMatch(IPublishedContent c, string a1, string a2)
145169
if (rootNodeId > 0)
146170
{
147171
IPublishedContent? rootNode = cache?.GetById(rootNodeId);
148-
return rootNode?.Descendants(_variationContextAccessor, _contentCache, _documentNavigationQueryService).FirstOrDefault(x => IsMatch(x, test1, test2));
172+
return rootNode?.Descendants(_variationContextAccessor, _contentCache, _documentNavigationQueryService, _publishStatusQueryService).FirstOrDefault(x => IsMatch(x, test1, test2));
149173
}
150174

151175
if (cache is not null)
152176
{
153177
foreach (IPublishedContent rootContent in cache.GetAtRoot())
154178
{
155-
IPublishedContent? c = rootContent.DescendantsOrSelf(_variationContextAccessor, _contentCache, _documentNavigationQueryService)
179+
IPublishedContent? c = rootContent.DescendantsOrSelf(_variationContextAccessor, _contentCache, _documentNavigationQueryService, _publishStatusQueryService)
156180
.FirstOrDefault(x => IsMatch(x, test1, test2));
157181
if (c != null)
158182
{

src/Umbraco.Core/Routing/UrlProvider.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,48 @@ public class UrlProvider : IPublishedUrlProvider
2727
/// <param name="variationContextAccessor">The current variation accessor.</param>
2828
/// <param name="contentCache">The content cache.</param>
2929
/// <param name="navigationQueryService">The query service for the in-memory navigation structure.</param>
30+
/// <param name="publishStatusQueryService">The publish status query service, to query if a given content is published in a given culture.</param>
3031
public UrlProvider(
3132
IUmbracoContextAccessor umbracoContextAccessor,
3233
IOptions<WebRoutingSettings> routingSettings,
3334
UrlProviderCollection urlProviders,
3435
MediaUrlProviderCollection mediaUrlProviders,
3536
IVariationContextAccessor variationContextAccessor,
3637
IPublishedContentCache contentCache,
37-
IDocumentNavigationQueryService navigationQueryService)
38+
IDocumentNavigationQueryService navigationQueryService,
39+
IPublishStatusQueryService publishStatusQueryService)
3840
{
3941
_umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor));
4042
_urlProviders = urlProviders;
4143
_mediaUrlProviders = mediaUrlProviders;
4244
_variationContextAccessor = variationContextAccessor ?? throw new ArgumentNullException(nameof(variationContextAccessor));
4345
_contentCache = contentCache;
4446
_navigationQueryService = navigationQueryService;
47+
_publishStatusQueryService = publishStatusQueryService;
4548
Mode = routingSettings.Value.UrlProviderMode;
4649
}
4750

51+
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
52+
public UrlProvider(
53+
IUmbracoContextAccessor umbracoContextAccessor,
54+
IOptions<WebRoutingSettings> routingSettings,
55+
UrlProviderCollection urlProviders,
56+
MediaUrlProviderCollection mediaUrlProviders,
57+
IVariationContextAccessor variationContextAccessor,
58+
IPublishedContentCache contentCache,
59+
IDocumentNavigationQueryService navigationQueryService)
60+
: this(
61+
umbracoContextAccessor,
62+
routingSettings,
63+
urlProviders,
64+
mediaUrlProviders,
65+
variationContextAccessor,
66+
contentCache,
67+
navigationQueryService,
68+
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
69+
{
70+
}
71+
4872
[Obsolete("Use the constructor that takes all parameters. Scheduled for removal in V17.")]
4973
public UrlProvider(
5074
IUmbracoContextAccessor umbracoContextAccessor,
@@ -59,7 +83,8 @@ public UrlProvider(
5983
mediaUrlProviders,
6084
variationContextAccessor,
6185
StaticServiceProvider.Instance.GetRequiredService<IPublishedContentCache>(),
62-
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>())
86+
StaticServiceProvider.Instance.GetRequiredService<IDocumentNavigationQueryService>(),
87+
StaticServiceProvider.Instance.GetRequiredService<IPublishStatusQueryService>())
6388
{
6489
}
6590

@@ -69,6 +94,7 @@ public UrlProvider(
6994
private readonly IVariationContextAccessor _variationContextAccessor;
7095
private readonly IPublishedContentCache _contentCache;
7196
private readonly IDocumentNavigationQueryService _navigationQueryService;
97+
private readonly IPublishStatusQueryService _publishStatusQueryService;
7298

7399
/// <summary>
74100
/// Gets or sets the provider URL mode.
@@ -147,7 +173,7 @@ public string GetUrl(IPublishedContent? content, UrlMode mode = UrlMode.Default,
147173
// be nice with tests, assume things can be null, ultimately fall back to invariant
148174
// (but only for variant content of course)
149175
// We need to check all ancestors because urls are variant even for invariant content, if an ancestor is variant.
150-
if (culture == null && content.AncestorsOrSelf(_contentCache, _navigationQueryService).Any(x => x.ContentType.VariesByCulture()))
176+
if (culture == null && content.AncestorsOrSelf(_variationContextAccessor, _contentCache, _navigationQueryService, _publishStatusQueryService).Any(x => x.ContentType.VariesByCulture()))
151177
{
152178
culture = _variationContextAccessor?.VariationContext?.Culture ?? string.Empty;
153179
}

src/Umbraco.Core/Services/PublishStatus/PublishStatusService.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.Collections.Concurrent;
2+
using Microsoft.Extensions.DependencyInjection;
23
using Microsoft.Extensions.Logging;
4+
using Umbraco.Cms.Core.DependencyInjection;
35
using Umbraco.Cms.Core.Persistence.Repositories;
46
using Umbraco.Cms.Core.Scoping;
57

@@ -10,15 +12,31 @@ public class PublishStatusService : IPublishStatusManagementService, IPublishSta
1012
private readonly ILogger<PublishStatusService> _logger;
1113
private readonly IPublishStatusRepository _publishStatusRepository;
1214
private readonly ICoreScopeProvider _coreScopeProvider;
15+
private readonly ILanguageService _languageService;
1316
private readonly IDictionary<Guid, ISet<string>> _publishedCultures = new Dictionary<Guid, ISet<string>>();
17+
18+
private string? DefaultCulture { get; set; }
19+
20+
[Obsolete("Use non-obsolete constructor. This will be removed in Umbraco 17.")]
1421
public PublishStatusService(
1522
ILogger<PublishStatusService> logger,
1623
IPublishStatusRepository publishStatusRepository,
1724
ICoreScopeProvider coreScopeProvider)
25+
: this(logger, publishStatusRepository, coreScopeProvider, StaticServiceProvider.Instance.GetRequiredService<ILanguageService>())
26+
{
27+
28+
}
29+
30+
public PublishStatusService(
31+
ILogger<PublishStatusService> logger,
32+
IPublishStatusRepository publishStatusRepository,
33+
ICoreScopeProvider coreScopeProvider,
34+
ILanguageService languageService)
1835
{
1936
_logger = logger;
2037
_publishStatusRepository = publishStatusRepository;
2138
_coreScopeProvider = coreScopeProvider;
39+
_languageService = languageService;
2240
}
2341

2442
public async Task InitializeAsync(CancellationToken cancellationToken)
@@ -39,11 +57,16 @@ public async Task InitializeAsync(CancellationToken cancellationToken)
3957
}
4058
}
4159

42-
60+
DefaultCulture = await _languageService.GetDefaultIsoCodeAsync();
4361
}
4462

4563
public bool IsDocumentPublished(Guid documentKey, string culture)
4664
{
65+
if (string.IsNullOrEmpty(culture) && DefaultCulture is not null)
66+
{
67+
culture = DefaultCulture;
68+
}
69+
4770
if (_publishedCultures.TryGetValue(documentKey, out ISet<string>? publishedCultures))
4871
{
4972
return publishedCultures.Contains(culture, StringComparer.InvariantCultureIgnoreCase);

0 commit comments

Comments
 (0)