Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
98454f0
Implement ElementCacheService with HybridCache backing and database c…
lauraneto Apr 1, 2026
428beb3
Add element navigation service, publish status tracking, and breadth-…
lauraneto Apr 7, 2026
a280c0e
Fix element CacheNodeFactory to set IsDraft from preview parameter
lauraneto Apr 7, 2026
4ae8f7c
Use ElementTree lock instead of ContentTree in ElementCacheService
lauraneto Apr 7, 2026
fbb0cdc
Add ElementCacheServiceTests and fix PublishStatusServiceTests for ab…
lauraneto Apr 7, 2026
c5654c3
Add IPublishedElementCache facade for public element cache access
lauraneto Apr 7, 2026
ef55f6e
Add ElementHybridCacheTests and ElementHybridCacheElementTypeTests
lauraneto Apr 7, 2026
352cc8f
Merge branch 'v18/dev' into v18/feature/element-cache
lauraneto Apr 7, 2026
7ae58ec
Fix element navigation to include containers and support breadth-firs…
lauraneto Apr 8, 2026
ad677e3
Merge remote-tracking branch 'origin/v18/dev' into v18/feature/elemen…
lauraneto Apr 8, 2026
c295fab
Add ElementContentTypeSeedKeyProvider for content-type-based element …
lauraneto Apr 8, 2026
bb16866
Fix ContentNavigationServiceTest mocks for multi-objectType repositor…
lauraneto Apr 8, 2026
1494549
Skip Cannot_Get_Published_Again_After_Trashing test
lauraneto Apr 8, 2026
f0cc846
Merge branch 'v18/dev' into v18/feature/element-cache
lauraneto Apr 9, 2026
b4daa57
Replace unsafe casts with StaticServiceProvider in obsolete constructors
lauraneto Apr 9, 2026
02437fb
Remove duplicate XML doc summary in GetElementCultureDataForNodes
lauraneto Apr 9, 2026
f46c520
Add obsolete constructors for backward compatibility
lauraneto Apr 9, 2026
80d9455
Pass cancellationToken to ExistsAsync in ElementCacheService.SeedAsync
lauraneto Apr 9, 2026
fb0ec4e
Fix DocumentUrlServiceTests to use IDocumentPublishStatusQueryService
lauraneto Apr 9, 2026
d1edf98
Merge branch 'v18/dev' into v18/feature/element-cache
lauraneto Apr 9, 2026
34d1d4f
Merge branch 'v18/dev' into v18/feature/element-cache
lauraneto Apr 10, 2026
57a5e34
Trigger Build
AndyButland Apr 16, 2026
42516cc
Merge remote-tracking branch 'origin/v18/dev' into v18/feature/elemen…
lauraneto Apr 16, 2026
f98b3b9
Address PR review feedback
lauraneto Apr 16, 2026
3d3647e
Invalidate element cache entries when trashed
lauraneto Apr 16, 2026
af5d2dd
Move element trash cache tests to ElementHybridCacheTests
lauraneto Apr 16, 2026
3512cf0
Add element hybrid cache variant tests
lauraneto Apr 16, 2026
50f564a
Merge remote-tracking branch 'origin/v18/dev' into v18/feature/elemen…
lauraneto Apr 20, 2026
139d667
Rename IPublishedElementCache.GetByIdAsync to GetByKeyAsync
lauraneto Apr 20, 2026
49c58bd
Align IDocumentPublishStatusQueryService method names with element eq…
lauraneto Apr 20, 2026
7a54846
Keep INNER JOIN for document/media navigation queries
lauraneto Apr 20, 2026
4b41b3a
Consolidate breadth-first seed key provider logic into base class
lauraneto Apr 20, 2026
9f3c2ce
Revert "Rename IPublishedElementCache.GetByIdAsync to GetByKeyAsync"
lauraneto Apr 20, 2026
73c4f1e
Merge branch 'v18/dev' into v18/feature/element-cache
lauraneto Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 97 additions & 18 deletions src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection;

Check notice on line 1 in src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v18/dev)

✅ No longer an issue: Overall Code Complexity

The mean cyclomatic complexity in this module is no longer above the threshold
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models;
Expand Down Expand Up @@ -36,13 +36,51 @@
private readonly IContentService _contentService;
private readonly IDocumentCacheService _documentCacheService;
private readonly ICacheManager _cacheManager;
private readonly IPublishStatusManagementService _publishStatusManagementService;
private readonly IDocumentPublishStatusManagementService _publishStatusManagementService;
private readonly IIdKeyMap _idKeyMap;

/// <summary>
/// Initializes a new instance of the <see cref="ContentCacheRefresher"/> class.
/// </summary>
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public ContentCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
IIdKeyMap idKeyMap,
IDomainService domainService,
IEventAggregator eventAggregator,
ICacheRefresherNotificationFactory factory,
IDocumentUrlService documentUrlService,
IDocumentUrlAliasService documentUrlAliasService,
IDomainCacheService domainCacheService,
IDocumentNavigationQueryService documentNavigationQueryService,
IDocumentNavigationManagementService documentNavigationManagementService,
IContentService contentService,
IDocumentPublishStatusManagementService publishStatusManagementService,
IDocumentCacheService documentCacheService,
ICacheManager cacheManager)
: base(appCaches, serializer, eventAggregator, factory)
{
_idKeyMap = idKeyMap;
_domainService = domainService;
_domainCacheService = domainCacheService;
_documentUrlService = documentUrlService;
_documentUrlAliasService = documentUrlAliasService;
_documentNavigationQueryService = documentNavigationQueryService;
_documentNavigationManagementService = documentNavigationManagementService;
_contentService = contentService;
_documentCacheService = documentCacheService;
_publishStatusManagementService = publishStatusManagementService;

// TODO: Ideally we should inject IElementsCache
// this interface is in infrastructure, and changing this is very breaking
// so as long as we have the cache manager, which casts the IElementsCache to a simple AppCache we might as well use that.
_cacheManager = cacheManager;
}

/// <summary>
/// Initializes a new instance of the <see cref="ContentCacheRefresher"/> class.
/// </summary>
[Obsolete("Please use the non-obsolete constructor. Scheduled for removal in Umbraco 19.")]
Comment thread
AndyButland marked this conversation as resolved.
public ContentCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
Expand Down Expand Up @@ -71,7 +109,7 @@
documentNavigationQueryService,
documentNavigationManagementService,
contentService,
publishStatusManagementService,
StaticServiceProvider.Instance.GetRequiredService<IDocumentPublishStatusManagementService>(),

Check warning on line 112 in src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v18/dev)

❌ New issue: Code Duplication

The module contains 3 functions with similar structure: ContentCacheRefresher,ContentCacheRefresher,ContentCacheRefresher. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.

Check notice on line 112 in src/Umbraco.Core/Cache/Refreshers/Implement/ContentCacheRefresher.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v18/dev)

✅ Getting better: Constructor Over-Injection

ContentCacheRefresher decreases from 15 to 14 arguments, max arguments = 5. This constructor has too many arguments, indicating an object with low cohesion or missing function argument abstraction. Avoid adding more arguments.
documentCacheService,
cacheManager)
{
Expand All @@ -80,6 +118,7 @@
/// <summary>
/// Initializes a new instance of the <see cref="ContentCacheRefresher"/> class.
/// </summary>
[Obsolete("Please use the non-obsolete constructor instead. Scheduled for removal in Umbraco 19.")]
public ContentCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
Expand All @@ -96,23 +135,63 @@
IPublishStatusManagementService publishStatusManagementService,
IDocumentCacheService documentCacheService,
ICacheManager cacheManager)
: base(appCaches, serializer, eventAggregator, factory)
: this(
appCaches,
serializer,
idKeyMap,
domainService,
eventAggregator,
factory,
documentUrlService,
documentUrlAliasService,
domainCacheService,
documentNavigationQueryService,
documentNavigationManagementService,
contentService,
StaticServiceProvider.Instance.GetRequiredService<IDocumentPublishStatusManagementService>(),
documentCacheService,
cacheManager)
{
_idKeyMap = idKeyMap;
_domainService = domainService;
_domainCacheService = domainCacheService;
_documentUrlService = documentUrlService;
_documentUrlAliasService = documentUrlAliasService;
_documentNavigationQueryService = documentNavigationQueryService;
_documentNavigationManagementService = documentNavigationManagementService;
_contentService = contentService;
_documentCacheService = documentCacheService;
_publishStatusManagementService = publishStatusManagementService;
}

// TODO: Ideally we should inject IElementsCache
// this interface is in infrastructure, and changing this is very breaking
// so as long as we have the cache manager, which casts the IElementsCache to a simple AppCache we might as well use that.
_cacheManager = cacheManager;
/// <summary>
/// Initializes a new instance of the <see cref="ContentCacheRefresher"/> class.
/// </summary>
[Obsolete("Please use the non-obsolete constructor instead. Scheduled for removal in Umbraco 19.")]
public ContentCacheRefresher(
AppCaches appCaches,
IJsonSerializer serializer,
IIdKeyMap idKeyMap,
IDomainService domainService,
IEventAggregator eventAggregator,
ICacheRefresherNotificationFactory factory,
IDocumentUrlService documentUrlService,
IDocumentUrlAliasService documentUrlAliasService,
IDomainCacheService domainCacheService,
IDocumentNavigationQueryService documentNavigationQueryService,
IDocumentNavigationManagementService documentNavigationManagementService,
IContentService contentService,
IPublishStatusManagementService publishStatusManagementService,
IDocumentPublishStatusManagementService documentPublishStatusManagementService,
IDocumentCacheService documentCacheService,
ICacheManager cacheManager)
: this(
appCaches,
serializer,
idKeyMap,
domainService,
eventAggregator,
factory,
documentUrlService,
documentUrlAliasService,
domainCacheService,
documentNavigationQueryService,
documentNavigationManagementService,
contentService,
documentPublishStatusManagementService,
documentCacheService,
cacheManager)
{
}

#region Indirect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Umbraco.Cms.Core.Serialization;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Services.Changes;
using Umbraco.Cms.Core.Services.Navigation;
using Umbraco.Extensions;

namespace Umbraco.Cms.Core.Cache;
Expand All @@ -25,6 +26,7 @@
private readonly IIdKeyMap _idKeyMap;
private readonly IElementCacheService _elementCacheService;
private readonly ICacheManager _cacheManager;
private readonly IElementPublishStatusManagementService _publishStatusManagementService;

/// <summary>
/// Initializes a new instance of the <see cref="ElementCacheRefresher"/> class.
Expand All @@ -36,11 +38,13 @@
IEventAggregator eventAggregator,
ICacheRefresherNotificationFactory factory,
IElementCacheService elementCacheService,
ICacheManager cacheManager)
ICacheManager cacheManager,
IElementPublishStatusManagementService publishStatusManagementService)
: base(appCaches, serializer, eventAggregator, factory)
{
_idKeyMap = idKeyMap;
_elementCacheService = elementCacheService;
_publishStatusManagementService = publishStatusManagementService;

// TODO ELEMENTS: Use IElementsCache instead of ICacheManager, see ContentCacheRefresher for more information.
_cacheManager = cacheManager;
Expand Down Expand Up @@ -128,9 +132,7 @@
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IElement, Guid?>(payload.Key));

HandleMemoryCache(payload);

// TODO ELEMENTS: if we need published status caching for elements (e.g. for seeding purposes), make sure
// it is kept in sync here (see ContentCacheRefresher)
HandlePublishStatus(payload);

if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
{
Expand Down Expand Up @@ -169,6 +171,27 @@
}
}

private void HandlePublishStatus(JsonPayload payload)
Comment thread
AndyButland marked this conversation as resolved.
Outdated
{
if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshAll))
{
_publishStatusManagementService.InitializeAsync(CancellationToken.None).GetAwaiter().GetResult();
}

if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
{
_publishStatusManagementService.RemoveAsync(payload.Key, CancellationToken.None).GetAwaiter().GetResult();
}
else if ((payload.ChangeTypes.HasType(TreeChangeTypes.RefreshNode) || payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) && HasPublishStatusUpdates(payload))

Check warning on line 185 in src/Umbraco.Core/Cache/Refreshers/Implement/ElementCacheRefresher.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v18/dev)

❌ New issue: Complex Conditional

HandlePublishStatus has 1 complex conditionals with 2 branches, threshold = 2. A complex conditional is an expression inside a branch (e.g. if, for, while) which consists of multiple, logical operators such as AND/OR. The more logical operators in an expression, the more severe the code smell.
{
_publishStatusManagementService.AddOrUpdateStatusAsync(payload.Key, CancellationToken.None).GetAwaiter().GetResult();
}
}

private static bool HasPublishStatusUpdates(JsonPayload payload) =>
(payload.PublishedCultures is not null && payload.PublishedCultures.Length > 0) ||
(payload.UnpublishedCultures is not null && payload.UnpublishedCultures.Length > 0);

// These events should never trigger. Everything should be PAYLOAD/JSON.

/// <inheritdoc/>
Expand Down
5 changes: 5 additions & 0 deletions src/Umbraco.Core/Constants-Cache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ public static class Tags
/// The cache tag for media items.
/// </summary>
public const string Media = "media";

/// <summary>
/// The cache tag for element items.
/// </summary>
public const string Element = "element";
}

/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Umbraco.Core/Constants-SqlTemplates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ public static class NuCacheDatabaseDataSource
/// </summary>
public const string MediaSourcesCount = "Umbraco.Web.PublishedCache.NuCache.DataSource.MediaSourcesCount";

/// <summary>
/// The SQL template name for selecting element sources.
/// </summary>
public const string ElementSourcesSelect =
"Umbraco.Web.PublishedCache.NuCache.DataSource.ElementSourcesSelect";

/// <summary>
/// The SQL template name for filtering by object type excluding trashed items.
/// </summary>
Expand Down
63 changes: 62 additions & 1 deletion src/Umbraco.Core/DeliveryApi/ApiContentRouteBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Microsoft.Extensions.DependencyInjection;

Check warning on line 1 in src/Umbraco.Core/DeliveryApi/ApiContentRouteBuilder.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v18/dev)

❌ New issue: Missing Arguments Abstractions

The average number of function arguments in this module is 4.63 across 8 functions. The average arguments threshold is 4.00. The functions in this file have too many arguments, indicating a lack of encapsulation or too many responsibilities in the same functions. Avoid adding more.
using Microsoft.Extensions.Options;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.DependencyInjection;
Expand All @@ -22,7 +22,7 @@
private readonly IRequestPreviewService _requestPreviewService;
private readonly IPublishedContentCache _contentCache;
private readonly IDocumentNavigationQueryService _navigationQueryService;
private readonly IPublishStatusQueryService _publishStatusQueryService;
private readonly IDocumentPublishStatusQueryService _publishStatusQueryService;
private readonly IDocumentUrlService _documentUrlService;
private RequestHandlerSettings _requestSettings;

Expand All @@ -38,6 +38,7 @@
/// <param name="navigationQueryService">The document navigation query service.</param>
/// <param name="publishStatusQueryService">The publish status query service.</param>
/// <param name="documentUrlService">The document URL service.</param>
[Obsolete("Please use the non-obsolete constructor instead. Scheduled for removal in Umbraco 19.")]
public ApiContentRouteBuilder(
IApiContentPathProvider apiContentPathProvider,
IOptions<GlobalSettings> globalSettings,
Expand All @@ -48,6 +49,66 @@
IDocumentNavigationQueryService navigationQueryService,
IPublishStatusQueryService publishStatusQueryService,
IDocumentUrlService documentUrlService)
: this(
apiContentPathProvider,
globalSettings,
variationContextAccessor,
requestPreviewService,
requestSettings,
contentCache,
navigationQueryService,
(IDocumentPublishStatusQueryService)publishStatusQueryService,
documentUrlService)
{
}

Check warning on line 63 in src/Umbraco.Core/DeliveryApi/ApiContentRouteBuilder.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v18/dev)

❌ New issue: Code Duplication

The module contains 2 functions with similar structure: ApiContentRouteBuilder,ApiContentRouteBuilder. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.

/// <summary>
/// Initializes a new instance of the <see cref="ApiContentRouteBuilder"/> class.
/// </summary>
/// <param name="apiContentPathProvider">The API content path provider.</param>
/// <param name="globalSettings">The global settings.</param>
/// <param name="variationContextAccessor">The variation context accessor.</param>
/// <param name="requestPreviewService">The request preview service.</param>
/// <param name="requestSettings">The request handler settings.</param>
/// <param name="contentCache">The published content cache.</param>
/// <param name="navigationQueryService">The document navigation query service.</param>
/// <param name="publishStatusQueryService">The publish status query service.</param>
/// <param name="documentUrlService">The document URL service.</param>
[Obsolete("Please use the non-obsolete constructor instead. Scheduled for removal in Umbraco 19.")]
public ApiContentRouteBuilder(
IApiContentPathProvider apiContentPathProvider,
IOptions<GlobalSettings> globalSettings,
IVariationContextAccessor variationContextAccessor,
IRequestPreviewService requestPreviewService,
IOptionsMonitor<RequestHandlerSettings> requestSettings,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService,
IPublishStatusQueryService publishStatusQueryService,
IDocumentPublishStatusQueryService documentPublishStatusQueryService,
IDocumentUrlService documentUrlService)
: this(
apiContentPathProvider,
globalSettings,
variationContextAccessor,
requestPreviewService,
requestSettings,
contentCache,
navigationQueryService,
documentPublishStatusQueryService,
documentUrlService)
{
}

public ApiContentRouteBuilder(
IApiContentPathProvider apiContentPathProvider,
IOptions<GlobalSettings> globalSettings,
IVariationContextAccessor variationContextAccessor,
IRequestPreviewService requestPreviewService,
IOptionsMonitor<RequestHandlerSettings> requestSettings,
IPublishedContentCache contentCache,
IDocumentNavigationQueryService navigationQueryService,
IDocumentPublishStatusQueryService publishStatusQueryService,
IDocumentUrlService documentUrlService)
{
_apiContentPathProvider = apiContentPathProvider;
_variationContextAccessor = variationContextAccessor;
Expand Down
18 changes: 14 additions & 4 deletions src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,10 +386,20 @@
Services.AddUnique<MediaNavigationService, MediaNavigationService>();
Services.AddUnique<IMediaNavigationQueryService>(x => x.GetRequiredService<MediaNavigationService>());
Services.AddUnique<IMediaNavigationManagementService>(x => x.GetRequiredService<MediaNavigationService>());

Services.AddUnique<PublishStatusService, PublishStatusService>();
Services.AddUnique<IPublishStatusManagementService>(x => x.GetRequiredService<PublishStatusService>());
Services.AddUnique<IPublishStatusQueryService>(x => x.GetRequiredService<PublishStatusService>());
Services.AddUnique<ElementNavigationService, ElementNavigationService>();
Services.AddUnique<IElementNavigationQueryService>(x => x.GetRequiredService<ElementNavigationService>());
Services.AddUnique<IElementNavigationManagementService>(x => x.GetRequiredService<ElementNavigationService>());

Services.AddUnique<DocumentPublishStatusService, DocumentPublishStatusService>();
Services.AddUnique<IDocumentPublishStatusQueryService>(x => x.GetRequiredService<DocumentPublishStatusService>());
Services.AddUnique<IDocumentPublishStatusManagementService>(x => x.GetRequiredService<DocumentPublishStatusService>());
Services.AddUnique<ElementPublishStatusService, ElementPublishStatusService>();
Services.AddUnique<IElementPublishStatusQueryService>(x => x.GetRequiredService<ElementPublishStatusService>());
Services.AddUnique<IElementPublishStatusManagementService>(x => x.GetRequiredService<ElementPublishStatusService>());
#pragma warning disable CS0618 // Type or member is obsolete
Services.AddUnique<IPublishStatusManagementService>(x => x.GetRequiredService<DocumentPublishStatusService>());
Services.AddUnique<IPublishStatusQueryService>(x => x.GetRequiredService<DocumentPublishStatusService>());
#pragma warning restore CS0618 // Type or member is obsolete

Check warning on line 402 in src/Umbraco.Core/DependencyInjection/UmbracoBuilder.cs

View check run for this annotation

CodeScene Delta Analysis / CodeScene Code Health Review (v18/dev)

❌ Getting worse: Large Method

AddCoreServices increases from 226 to 236 lines of code, threshold = 70. Large functions with many lines of code are generally harder to understand and lower the code health. Avoid adding more lines to this function.

Services.AddUnique<IPublishedContentStatusFilteringService, PublishedContentStatusFilteringService>();
Services.AddUnique<IPublishedMediaStatusFilteringService, PublishedMediaStatusFilteringService>();
Expand Down
Loading
Loading