From 7c8367efcab47f23a2d44b7661b89eacf25ae9f5 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 25 Aug 2025 15:37:04 +0200 Subject: [PATCH 1/3] Add request caching around published content factory. --- .../Factories/IPublishedContentFactory.cs | 15 +- .../Factories/PublishedContentFactory.cs | 197 +++++++++++++----- .../Factories/PublishedContentFactoryTests.cs | 147 +++++++++++++ 3 files changed, 301 insertions(+), 58 deletions(-) create mode 100644 tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactoryTests.cs diff --git a/src/Umbraco.PublishedCache.HybridCache/Factories/IPublishedContentFactory.cs b/src/Umbraco.PublishedCache.HybridCache/Factories/IPublishedContentFactory.cs index c5bfe4fe9efb..46cb65d5e4b9 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Factories/IPublishedContentFactory.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Factories/IPublishedContentFactory.cs @@ -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; +/// +/// Defines a factory to create and from a or . +/// internal interface IPublishedContentFactory { + /// + /// Converts a to an if document type. + /// IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview); + + /// + /// Converts a to an of media type. + /// IPublishedContent? ToIPublishedMedia(ContentCacheNode contentCacheNode); + /// + /// Converts a to an . + /// IPublishedMember ToPublishedMember(IMember member); } diff --git a/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs b/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs index 57863dc98698..2a617d0087ac 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs @@ -1,91 +1,175 @@ -using Umbraco.Cms.Core.Models; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Cache; +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; +/// +/// Defines a factory to create and from a or . +/// internal sealed class PublishedContentFactory : IPublishedContentFactory { private readonly IElementsCache _elementsCache; private readonly IVariationContextAccessor _variationContextAccessor; private readonly IPublishedContentTypeCache _publishedContentTypeCache; + private readonly ILogger _logger; + private readonly AppCaches _appCaches; - + /// + /// Initializes a new instance of the class. + /// public PublishedContentFactory( IElementsCache elementsCache, IVariationContextAccessor variationContextAccessor, - IPublishedContentTypeCache publishedContentTypeCache) + IPublishedContentTypeCache publishedContentTypeCache, + ILogger logger, + AppCaches appCaches) { _elementsCache = elementsCache; _variationContextAccessor = variationContextAccessor; _publishedContentTypeCache = publishedContentTypeCache; + _logger = logger; + _appCaches = appCaches; } + /// public IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview) { - IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Content, contentCacheNode.ContentTypeId); - var contentNode = new ContentNode( - contentCacheNode.Id, - contentCacheNode.Key, - contentCacheNode.SortOrder, - contentCacheNode.CreateDate, - contentCacheNode.CreatorId, - contentType, - preview ? contentCacheNode.Data : null, - preview ? null : contentCacheNode.Data); - - IPublishedContent? model = GetModel(contentNode, preview); - - if (preview) + string cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}"; + IPublishedContent? publishedContent = _appCaches.RequestCache.GetCacheItem(cacheKey); + if (publishedContent is null) + { + _logger.LogDebug( + "Creating IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).", + contentCacheNode.Id, + contentCacheNode.Data?.Name ?? "No Name"); + + IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Content, contentCacheNode.ContentTypeId); + var contentNode = new ContentNode( + contentCacheNode.Id, + contentCacheNode.Key, + contentCacheNode.SortOrder, + contentCacheNode.CreateDate, + contentCacheNode.CreatorId, + contentType, + preview ? contentCacheNode.Data : null, + preview ? null : contentCacheNode.Data); + + publishedContent = GetModel(contentNode, preview); + + if (preview) + { + publishedContent ??= GetPublishedContentAsDraft(publishedContent); + } + + if (publishedContent is not null) + { + _appCaches.RequestCache.Set(cacheKey, publishedContent); + } + } + else { - return model ?? GetPublishedContentAsDraft(model); + _logger.LogDebug( + "Using cached IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).", + contentCacheNode.Id, + contentCacheNode.Data?.Name ?? "No Name"); } - return model; + return publishedContent; } + /// public IPublishedContent? ToIPublishedMedia(ContentCacheNode contentCacheNode) { - IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Media, contentCacheNode.ContentTypeId); - var contentNode = new ContentNode( - contentCacheNode.Id, - contentCacheNode.Key, - contentCacheNode.SortOrder, - contentCacheNode.CreateDate, - contentCacheNode.CreatorId, - contentType, - null, - contentCacheNode.Data); - - return GetModel(contentNode, false); + string cacheKey = $"{nameof(PublishedContentFactory)}MediaCache_{contentCacheNode.Id}"; + IPublishedContent? publishedContent = _appCaches.RequestCache.GetCacheItem(cacheKey); + if (publishedContent is null) + { + _logger.LogDebug( + "Creating IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).", + contentCacheNode.Id, + contentCacheNode.Data?.Name ?? "No Name"); + + IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Media, contentCacheNode.ContentTypeId); + var contentNode = new ContentNode( + contentCacheNode.Id, + contentCacheNode.Key, + contentCacheNode.SortOrder, + contentCacheNode.CreateDate, + contentCacheNode.CreatorId, + contentType, + null, + contentCacheNode.Data); + + publishedContent = GetModel(contentNode, false); + + if (publishedContent is not null) + { + _appCaches.RequestCache.Set(cacheKey, publishedContent); + } + } + else + { + _logger.LogDebug( + "Using cached IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).", + contentCacheNode.Id, + contentCacheNode.Data?.Name ?? "No Name"); + } + + return publishedContent; } + /// public IPublishedMember ToPublishedMember(IMember member) { - IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId); - - // Members are only "mapped" never cached, so these default values are a bit wierd, but they are not used. - var contentData = new ContentData( - member.Name, - null, - 0, - member.UpdateDate, - member.CreatorId, - null, - true, - GetPropertyValues(contentType, member), - null); - - var contentNode = new ContentNode( - member.Id, - member.Key, - member.SortOrder, - member.UpdateDate, - member.CreatorId, - contentType, - null, - contentData); - return new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor); + string cacheKey = $"{nameof(PublishedContentFactory)}MemberCache_{member.Id}"; + IPublishedMember? publishedMember = _appCaches.RequestCache.GetCacheItem(cacheKey); + if (publishedMember is null) + { + _logger.LogDebug( + "Creating IPublishedMember for member {MemberName} ({MemberId}).", + member.Id, + member.Username); + + 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( + member.Name, + null, + 0, + member.UpdateDate, + member.CreatorId, + null, + true, + GetPropertyValues(contentType, member), + null); + + var contentNode = new ContentNode( + member.Id, + member.Key, + member.SortOrder, + member.UpdateDate, + member.CreatorId, + contentType, + null, + contentData); + publishedMember = new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor); + + _appCaches.RequestCache.Set(cacheKey, publishedMember); + } + else + { + _logger.LogDebug( + "Using cached IPublishedMember for member {MemberName} ({MemberId}).", + member.Id, + member.Username); + } + + return publishedMember; } private static Dictionary GetPropertyValues(IPublishedContentType contentType, IMember member) @@ -134,7 +218,6 @@ private static void AddIf(IPublishedContentType contentType, IDictionary content == null ? null : // an object in the cache is either an IPublishedContentOrMedia, @@ -149,7 +232,7 @@ private static PublishedContent UnwrapIPublishedContent(IPublishedContent conten content = wrapped.Unwrap(); } - if (!(content is PublishedContent inner)) + if (content is not PublishedContent inner) { throw new InvalidOperationException("Innermost content is not PublishedContent."); } diff --git a/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactoryTests.cs b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactoryTests.cs new file mode 100644 index 000000000000..3d7be44d695c --- /dev/null +++ b/tests/Umbraco.Tests.Integration/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactoryTests.cs @@ -0,0 +1,147 @@ +using NUnit.Framework; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.HybridCache; +using Umbraco.Cms.Infrastructure.HybridCache.Factories; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Tests.Common.Testing; +using Umbraco.Cms.Tests.Integration.Testing; + +namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +internal sealed class PublishedContentFactoryTests : UmbracoIntegrationTestWithContent +{ + private IPublishedContentFactory PublishedContentFactory => GetRequiredService(); + + private IPublishedValueFallback PublishedValueFallback => GetRequiredService(); + + private IMediaService MediaService => GetRequiredService(); + + private IMediaTypeService MediaTypeService => GetRequiredService(); + + private IMemberService MemberService => GetRequiredService(); + + private IMemberTypeService MemberTypeService => GetRequiredService(); + + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + var requestCache = new DictionaryAppCache(); + var appCaches = new AppCaches( + NoAppCache.Instance, + requestCache, + new IsolatedCaches(type => NoAppCache.Instance)); + builder.Services.AddUnique(appCaches); + } + + [Test] + public void Can_Create_Published_Content_For_Document() + { + var contentCacheNode = new ContentCacheNode + { + Id = Textpage.Id, + Key = Textpage.Key, + ContentTypeId = Textpage.ContentType.Id, + CreateDate = Textpage.CreateDate, + CreatorId = Textpage.CreatorId, + SortOrder = Textpage.SortOrder, + Data = new ContentData( + Textpage.Name, + "text-page", + Textpage.VersionId, + Textpage.UpdateDate, + Textpage.WriterId, + Textpage.TemplateId, + true, + new Dictionary + { + { + "title", new[] + { + new PropertyData + { + Value = "Test title", + Culture = string.Empty, + Segment = string.Empty, + }, + } + }, + }, + null), + }; + var result = PublishedContentFactory.ToIPublishedContent(contentCacheNode, false); + Assert.IsNotNull(result); + Assert.AreEqual(Textpage.Id, result.Id); + Assert.AreEqual(Textpage.Name, result.Name); + Assert.AreEqual("Test title", result.Properties.Single(x => x.Alias == "title").Value(PublishedValueFallback)); + + // Verify that requesting the same content again returns the same instance (from request cache). + var result2 = PublishedContentFactory.ToIPublishedContent(contentCacheNode, false); + Assert.AreSame(result, result2); + } + + [Test] + public async Task Can_Create_Published_Content_For_Media() + { + var mediaType = new MediaTypeBuilder().Build(); + mediaType.AllowedAsRoot = true; + await MediaTypeService.CreateAsync(mediaType, Constants.Security.SuperUserKey); + + var media = new MediaBuilder() + .WithMediaType(mediaType) + .WithName("Media 1") + .Build(); + MediaService.Save(media); + + var contentCacheNode = new ContentCacheNode + { + Id = media.Id, + Key = media.Key, + ContentTypeId = media.ContentType.Id, + Data = new ContentData( + media.Name, + null, + 0, + media.UpdateDate, + media.WriterId, + null, + false, + new Dictionary(), + null), + }; + var result = PublishedContentFactory.ToIPublishedMedia(contentCacheNode); + Assert.IsNotNull(result); + Assert.AreEqual(media.Id, result.Id); + Assert.AreEqual(media.Name, result.Name); + + // Verify that requesting the same content again returns the same instance (from request cache). + var result2 = PublishedContentFactory.ToIPublishedMedia(contentCacheNode); + Assert.AreSame(result, result2); + } + + [Test] + public async Task Can_Create_Published_Member_For_Member() + { + var memberType = new MemberTypeBuilder().Build(); + await MemberTypeService.CreateAsync(memberType, Constants.Security.SuperUserKey); + + var member = new MemberBuilder() + .WithMemberType(memberType) + .WithName("Member 1") + .Build(); + MemberService.Save(member); + + var result = PublishedContentFactory.ToPublishedMember(member); + Assert.IsNotNull(result); + Assert.AreEqual(member.Id, result.Id); + Assert.AreEqual(member.Name, result.Name); + + // Verify that requesting the same content again returns the same instance (from request cache). + var result2 = PublishedContentFactory.ToPublishedMember(member); + Assert.AreSame(result, result2); + } +} From 6dd64c91f0ad2ae21d762aecdec0017a9eaada24 Mon Sep 17 00:00:00 2001 From: Andy Butland Date: Mon, 25 Aug 2025 15:55:17 +0200 Subject: [PATCH 2/3] Fixed ordering of log message parameters. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Factories/PublishedContentFactory.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs b/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs index 2a617d0087ac..0953b1016805 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs @@ -44,8 +44,8 @@ public PublishedContentFactory( { _logger.LogDebug( "Creating IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).", - contentCacheNode.Id, - contentCacheNode.Data?.Name ?? "No Name"); + contentCacheNode.Data?.Name ?? "No Name", + contentCacheNode.Id); IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Content, contentCacheNode.ContentTypeId); var contentNode = new ContentNode( @@ -74,8 +74,8 @@ public PublishedContentFactory( { _logger.LogDebug( "Using cached IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).", - contentCacheNode.Id, - contentCacheNode.Data?.Name ?? "No Name"); + contentCacheNode.Data?.Name ?? "No Name", + contentCacheNode.Id); } return publishedContent; @@ -90,8 +90,8 @@ public PublishedContentFactory( { _logger.LogDebug( "Creating IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).", - contentCacheNode.Id, - contentCacheNode.Data?.Name ?? "No Name"); + contentCacheNode.Data?.Name ?? "No Name", + contentCacheNode.Id); IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Media, contentCacheNode.ContentTypeId); var contentNode = new ContentNode( @@ -115,8 +115,8 @@ public PublishedContentFactory( { _logger.LogDebug( "Using cached IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).", - contentCacheNode.Id, - contentCacheNode.Data?.Name ?? "No Name"); + contentCacheNode.Data?.Name ?? "No Name", + contentCacheNode.Id); } return publishedContent; @@ -131,8 +131,8 @@ public IPublishedMember ToPublishedMember(IMember member) { _logger.LogDebug( "Creating IPublishedMember for member {MemberName} ({MemberId}).", - member.Id, - member.Username); + member.Username, + member.Id); IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Member, member.ContentTypeId); @@ -165,8 +165,8 @@ public IPublishedMember ToPublishedMember(IMember member) { _logger.LogDebug( "Using cached IPublishedMember for member {MemberName} ({MemberId}).", - member.Id, - member.Username); + member.Username, + member.Id); } return publishedMember; From 69edb812acf0abca3aef1fc380850915f0d40ff2 Mon Sep 17 00:00:00 2001 From: mole Date: Wed, 27 Aug 2025 12:29:19 +0200 Subject: [PATCH 3/3] Invert if to reduce nesting --- .../Factories/PublishedContentFactory.cs | 183 +++++++++--------- 1 file changed, 92 insertions(+), 91 deletions(-) diff --git a/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs b/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs index 0953b1016805..5f3370571634 100644 --- a/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs +++ b/src/Umbraco.PublishedCache.HybridCache/Factories/PublishedContentFactory.cs @@ -38,44 +38,44 @@ public PublishedContentFactory( /// public IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview) { - string cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}"; + var cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}"; IPublishedContent? publishedContent = _appCaches.RequestCache.GetCacheItem(cacheKey); - if (publishedContent is null) + if (publishedContent is not null) { _logger.LogDebug( - "Creating IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).", + "Using cached IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).", contentCacheNode.Data?.Name ?? "No Name", contentCacheNode.Id); + return publishedContent; + } - IPublishedContentType contentType = _publishedContentTypeCache.Get(PublishedItemType.Content, contentCacheNode.ContentTypeId); - var contentNode = new ContentNode( - contentCacheNode.Id, - contentCacheNode.Key, - contentCacheNode.SortOrder, - contentCacheNode.CreateDate, - contentCacheNode.CreatorId, - contentType, - preview ? contentCacheNode.Data : null, - preview ? null : contentCacheNode.Data); - - publishedContent = GetModel(contentNode, preview); - - if (preview) - { - publishedContent ??= GetPublishedContentAsDraft(publishedContent); - } - - if (publishedContent is not null) - { - _appCaches.RequestCache.Set(cacheKey, 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, + contentCacheNode.SortOrder, + contentCacheNode.CreateDate, + contentCacheNode.CreatorId, + contentType, + preview ? contentCacheNode.Data : null, + preview ? null : contentCacheNode.Data); + + publishedContent = GetModel(contentNode, preview); + + if (preview) + { + publishedContent ??= GetPublishedContentAsDraft(publishedContent); } - else + + if (publishedContent is not null) { - _logger.LogDebug( - "Using cached IPublishedContent for document {ContentCacheNodeName} ({ContentCacheNodeId}).", - contentCacheNode.Data?.Name ?? "No Name", - contentCacheNode.Id); + _appCaches.RequestCache.Set(cacheKey, publishedContent); } return publishedContent; @@ -84,39 +84,39 @@ public PublishedContentFactory( /// public IPublishedContent? ToIPublishedMedia(ContentCacheNode contentCacheNode) { - string cacheKey = $"{nameof(PublishedContentFactory)}MediaCache_{contentCacheNode.Id}"; + var cacheKey = $"{nameof(PublishedContentFactory)}MediaCache_{contentCacheNode.Id}"; IPublishedContent? publishedContent = _appCaches.RequestCache.GetCacheItem(cacheKey); - if (publishedContent is null) + if (publishedContent is not null) { _logger.LogDebug( - "Creating IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).", + "Using cached 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, - contentCacheNode.SortOrder, - contentCacheNode.CreateDate, - contentCacheNode.CreatorId, - contentType, - null, - contentCacheNode.Data); - - publishedContent = GetModel(contentNode, false); - - if (publishedContent is not null) - { - _appCaches.RequestCache.Set(cacheKey, publishedContent); - } + return publishedContent; } - else + + _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, + contentCacheNode.SortOrder, + contentCacheNode.CreateDate, + contentCacheNode.CreatorId, + contentType, + null, + contentCacheNode.Data); + + publishedContent = GetModel(contentNode, false); + + if (publishedContent is not null) { - _logger.LogDebug( - "Using cached IPublishedContent for media {ContentCacheNodeName} ({ContentCacheNodeId}).", - contentCacheNode.Data?.Name ?? "No Name", - contentCacheNode.Id); + _appCaches.RequestCache.Set(cacheKey, publishedContent); } return publishedContent; @@ -127,48 +127,49 @@ public IPublishedMember ToPublishedMember(IMember member) { string cacheKey = $"{nameof(PublishedContentFactory)}MemberCache_{member.Id}"; IPublishedMember? publishedMember = _appCaches.RequestCache.GetCacheItem(cacheKey); - if (publishedMember is null) - { - _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( - member.Name, - null, - 0, - member.UpdateDate, - member.CreatorId, - null, - true, - GetPropertyValues(contentType, member), - null); - - var contentNode = new ContentNode( - member.Id, - member.Key, - member.SortOrder, - member.UpdateDate, - member.CreatorId, - contentType, - null, - contentData); - publishedMember = new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor); - - _appCaches.RequestCache.Set(cacheKey, publishedMember); - } - else + 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( + member.Name, + null, + 0, + member.UpdateDate, + member.CreatorId, + null, + true, + GetPropertyValues(contentType, member), + null); + + var contentNode = new ContentNode( + member.Id, + member.Key, + member.SortOrder, + member.UpdateDate, + member.CreatorId, + contentType, + null, + contentData); + publishedMember = new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor); + + _appCaches.RequestCache.Set(cacheKey, publishedMember); + return publishedMember; }