Merge branch 'main' into v17/dev

# Conflicts:
#	src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs
This commit is contained in:
kjac
2025-09-02 10:22:39 +02:00
477 changed files with 13778 additions and 7100 deletions

View File

@@ -1,12 +1,25 @@
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
namespace Umbraco.Cms.Infrastructure.HybridCache.Factories;
/// <summary>
/// Defines a factory to create <see cref="IPublishedContent"/> and <see cref="IPublishedMember"/> from a <see cref="ContentCacheNode"/> or <see cref="IMember"/>.
/// </summary>
internal interface IPublishedContentFactory
{
/// <summary>
/// Converts a <see cref="ContentCacheNode"/> to an <see cref="IPublishedContent"/> if document type.
/// </summary>
IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview);
/// <summary>
/// Converts a <see cref="ContentCacheNode"/> to an <see cref="IPublishedContent"/> of media type.
/// </summary>
IPublishedContent? ToIPublishedMedia(ContentCacheNode contentCacheNode);
/// <summary>
/// Converts a <see cref="IMember"/> to an <see cref="IPublishedMember"/>.
/// </summary>
IPublishedMember ToPublishedMember(IMember member);
}

View File

@@ -1,30 +1,62 @@
using Microsoft.Extensions.Logging;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Extensions;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Extensions;
namespace Umbraco.Cms.Infrastructure.HybridCache.Factories;
/// <summary>
/// Defines a factory to create <see cref="IPublishedContent"/> and <see cref="IPublishedMember"/> from a <see cref="ContentCacheNode"/> or <see cref="IMember"/>.
/// </summary>
internal sealed class PublishedContentFactory : IPublishedContentFactory
{
private readonly IElementsCache _elementsCache;
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IPublishedContentTypeCache _publishedContentTypeCache;
private readonly ILogger<PublishedContentFactory> _logger;
private readonly AppCaches _appCaches;
/// <summary>
/// Initializes a new instance of the <see cref="PublishedContentFactory"/> class.
/// </summary>
public PublishedContentFactory(
IElementsCache elementsCache,
IVariationContextAccessor variationContextAccessor,
IPublishedContentTypeCache publishedContentTypeCache)
IPublishedContentTypeCache publishedContentTypeCache,
ILogger<PublishedContentFactory> logger,
AppCaches appCaches)
{
_elementsCache = elementsCache;
_variationContextAccessor = variationContextAccessor;
_publishedContentTypeCache = publishedContentTypeCache;
_logger = logger;
_appCaches = appCaches;
}
/// <inheritdoc/>
public IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview)
{
IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Content, contentCacheNode.ContentTypeId);
var cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}";
IPublishedContent? publishedContent = _appCaches.RequestCache.GetCacheItem<IPublishedContent?>(cacheKey);
if (publishedContent is not null)
{
_logger.LogDebug(
"Using cached IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).",
contentCacheNode.Data?.Name ?? "No Name",
contentCacheNode.Id);
return publishedContent;
}
_logger.LogDebug(
"Creating IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).",
contentCacheNode.Data?.Name ?? "No Name",
contentCacheNode.Id);
IPublishedContentType contentType =
_publishedContentTypeCache.Get(PublishedItemType.Content, contentCacheNode.ContentTypeId);
var contentNode = new ContentNode(
contentCacheNode.Id,
contentCacheNode.Key,
@@ -35,19 +67,42 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
preview ? contentCacheNode.Data : null,
preview ? null : contentCacheNode.Data);
IPublishedContent? model = GetModel(contentNode, preview);
publishedContent = GetModel(contentNode, preview);
if (preview)
{
return model ?? GetPublishedContentAsDraft(model);
publishedContent ??= GetPublishedContentAsDraft(publishedContent);
}
return model;
if (publishedContent is not null)
{
_appCaches.RequestCache.Set(cacheKey, publishedContent);
}
return publishedContent;
}
/// <inheritdoc/>
public IPublishedContent? ToIPublishedMedia(ContentCacheNode contentCacheNode)
{
IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Media, contentCacheNode.ContentTypeId);
var cacheKey = $"{nameof(PublishedContentFactory)}MediaCache_{contentCacheNode.Id}";
IPublishedContent? publishedContent = _appCaches.RequestCache.GetCacheItem<IPublishedContent?>(cacheKey);
if (publishedContent is not null)
{
_logger.LogDebug(
"Using cached IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).",
contentCacheNode.Data?.Name ?? "No Name",
contentCacheNode.Id);
return publishedContent;
}
_logger.LogDebug(
"Creating IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).",
contentCacheNode.Data?.Name ?? "No Name",
contentCacheNode.Id);
IPublishedContentType contentType =
_publishedContentTypeCache.Get(PublishedItemType.Media, contentCacheNode.ContentTypeId);
var contentNode = new ContentNode(
contentCacheNode.Id,
contentCacheNode.Key,
@@ -58,12 +113,38 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
null,
contentCacheNode.Data);
return GetModel(contentNode, false);
publishedContent = GetModel(contentNode, false);
if (publishedContent is not null)
{
_appCaches.RequestCache.Set(cacheKey, publishedContent);
}
return publishedContent;
}
/// <inheritdoc/>
public IPublishedMember ToPublishedMember(IMember member)
{
IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId);
string cacheKey = $"{nameof(PublishedContentFactory)}MemberCache_{member.Id}";
IPublishedMember? publishedMember = _appCaches.RequestCache.GetCacheItem<IPublishedMember?>(cacheKey);
if (publishedMember is not null)
{
_logger.LogDebug(
"Using cached IPublishedMember for member {MemberName} ({MemberId}).",
member.Username,
member.Id);
return publishedMember;
}
_logger.LogDebug(
"Creating IPublishedMember for member {MemberName} ({MemberId}).",
member.Username,
member.Id);
IPublishedContentType contentType =
_publishedContentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId);
// Members are only "mapped" never cached, so these default values are a bit weird, but they are not used.
var contentData = new ContentData(
@@ -86,7 +167,11 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
contentType,
null,
contentData);
return new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor);
publishedMember = new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor);
_appCaches.RequestCache.Set(cacheKey, publishedMember);
return publishedMember;
}
private static Dictionary<string, PropertyData[]> GetPropertyValues(IPublishedContentType contentType, IMember member)
@@ -135,7 +220,6 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
_variationContextAccessor);
}
private static IPublishedContent? GetPublishedContentAsDraft(IPublishedContent? content) =>
content == null ? null :
// an object in the cache is either an IPublishedContentOrMedia,
@@ -150,7 +234,7 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
content = wrapped.Unwrap();
}
if (!(content is PublishedContent inner))
if (content is not PublishedContent inner)
{
throw new InvalidOperationException("Innermost content is not PublishedContent.");
}