Performance: Cache published content instances at cache service level (#20681)
Cache published content instances at cache service level
This commit is contained in:
@@ -1,10 +1,7 @@
|
||||
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;
|
||||
|
||||
@@ -16,8 +13,6 @@ 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.
|
||||
@@ -25,40 +20,16 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
|
||||
public PublishedContentFactory(
|
||||
IElementsCache elementsCache,
|
||||
IVariationContextAccessor variationContextAccessor,
|
||||
IPublishedContentTypeCache publishedContentTypeCache,
|
||||
ILogger<PublishedContentFactory> logger,
|
||||
AppCaches appCaches)
|
||||
IPublishedContentTypeCache publishedContentTypeCache)
|
||||
{
|
||||
_elementsCache = elementsCache;
|
||||
_variationContextAccessor = variationContextAccessor;
|
||||
_publishedContentTypeCache = publishedContentTypeCache;
|
||||
_logger = logger;
|
||||
_appCaches = appCaches;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPublishedContent? ToIPublishedContent(ContentCacheNode contentCacheNode, bool preview)
|
||||
{
|
||||
var cacheKey = $"{nameof(PublishedContentFactory)}DocumentCache_{contentCacheNode.Id}_{preview}_{contentCacheNode.Data?.VersionDate.Ticks ?? 0}";
|
||||
IPublishedContent? publishedContent = null;
|
||||
if (_appCaches.RequestCache.IsAvailable)
|
||||
{
|
||||
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(
|
||||
@@ -71,44 +42,19 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
|
||||
preview ? contentCacheNode.Data : null,
|
||||
preview ? null : contentCacheNode.Data);
|
||||
|
||||
publishedContent = GetModel(contentNode, preview);
|
||||
IPublishedContent? publishedContent = GetModel(contentNode, preview);
|
||||
|
||||
if (preview)
|
||||
{
|
||||
publishedContent ??= GetPublishedContentAsDraft(publishedContent);
|
||||
}
|
||||
|
||||
if (_appCaches.RequestCache.IsAvailable && publishedContent is not null)
|
||||
{
|
||||
_appCaches.RequestCache.Set(cacheKey, publishedContent);
|
||||
}
|
||||
|
||||
return publishedContent;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPublishedContent? ToIPublishedMedia(ContentCacheNode contentCacheNode)
|
||||
{
|
||||
var cacheKey = $"{nameof(PublishedContentFactory)}MediaCache_{contentCacheNode.Id}";
|
||||
IPublishedContent? publishedContent = null;
|
||||
if (_appCaches.RequestCache.IsAvailable)
|
||||
{
|
||||
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(
|
||||
@@ -121,40 +67,12 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
|
||||
null,
|
||||
contentCacheNode.Data);
|
||||
|
||||
publishedContent = GetModel(contentNode, false);
|
||||
|
||||
if (_appCaches.RequestCache.IsAvailable && publishedContent is not null)
|
||||
{
|
||||
_appCaches.RequestCache.Set(cacheKey, publishedContent);
|
||||
}
|
||||
|
||||
return publishedContent;
|
||||
return GetModel(contentNode, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IPublishedMember ToPublishedMember(IMember member)
|
||||
{
|
||||
string cacheKey = $"{nameof(PublishedContentFactory)}MemberCache_{member.Id}";
|
||||
IPublishedMember? publishedMember = null;
|
||||
if (_appCaches.RequestCache.IsAvailable)
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -179,14 +97,7 @@ internal sealed class PublishedContentFactory : IPublishedContentFactory
|
||||
contentType,
|
||||
null,
|
||||
contentData);
|
||||
publishedMember = new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor);
|
||||
|
||||
if (_appCaches.RequestCache.IsAvailable)
|
||||
{
|
||||
_appCaches.RequestCache.Set(cacheKey, publishedMember);
|
||||
}
|
||||
|
||||
return publishedMember;
|
||||
return new PublishedMember(member, contentNode, _elementsCache, _variationContextAccessor);
|
||||
}
|
||||
|
||||
private static Dictionary<string, PropertyData[]> GetPropertyValues(IPublishedContentType contentType, IMember member)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#if DEBUG
|
||||
using System.Diagnostics;
|
||||
#endif
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -35,6 +36,8 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
private readonly ILogger<DocumentCacheService> _logger;
|
||||
private HashSet<Guid>? _seedKeys;
|
||||
|
||||
private readonly ConcurrentDictionary<string, IPublishedContent> _publishedContentCache = [];
|
||||
|
||||
private HashSet<Guid> SeedKeys
|
||||
{
|
||||
get
|
||||
@@ -108,6 +111,11 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
{
|
||||
var cacheKey = GetCacheKey(key, preview);
|
||||
|
||||
if (preview is false && _publishedContentCache.TryGetValue(cacheKey, out IPublishedContent? cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync(
|
||||
cacheKey,
|
||||
async cancel =>
|
||||
@@ -137,7 +145,13 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
return null;
|
||||
}
|
||||
|
||||
return _publishedContentFactory.ToIPublishedContent(contentCacheNode, preview).CreateModel(_publishedModelFactory);
|
||||
IPublishedContent? result = _publishedContentFactory.ToIPublishedContent(contentCacheNode, preview).CreateModel(_publishedModelFactory);
|
||||
if (result is not null)
|
||||
{
|
||||
_publishedContentCache[cacheKey] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool GetPreview() => _previewService.IsInPreview();
|
||||
@@ -174,7 +188,9 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
ContentCacheNode? publishedNode = await _databaseCacheRepository.GetContentSourceAsync(key, false);
|
||||
if (publishedNode is not null && _publishStatusQueryService.HasPublishedAncestorPath(publishedNode.Key))
|
||||
{
|
||||
await _hybridCache.SetAsync(GetCacheKey(publishedNode.Key, false), publishedNode, GetEntryOptions(publishedNode.Key, false), GenerateTags(key));
|
||||
var cacheKey = GetCacheKey(publishedNode.Key, false);
|
||||
await _hybridCache.SetAsync(cacheKey, publishedNode, GetEntryOptions(publishedNode.Key, false), GenerateTags(key));
|
||||
_publishedContentCache.Remove(cacheKey, out _);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
@@ -183,7 +199,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
public async Task RemoveFromMemoryCacheAsync(Guid key)
|
||||
{
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(key, true));
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(key, false));
|
||||
await ClearPublishedCacheAsync(key);
|
||||
}
|
||||
|
||||
public async Task SeedAsync(CancellationToken cancellationToken)
|
||||
@@ -300,7 +316,7 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
|
||||
if (content.PublishedState == PublishedState.Unpublishing)
|
||||
{
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(publishedCacheNode.Key, false));
|
||||
await ClearPublishedCacheAsync(publishedCacheNode.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,12 +354,19 @@ internal sealed class DocumentCacheService : IDocumentCacheService
|
||||
|
||||
foreach (ContentCacheNode content in contentByContentTypeKey)
|
||||
{
|
||||
_hybridCache.RemoveAsync(GetCacheKey(content.Key, true)).GetAwaiter().GetResult();
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(content.Key, true));
|
||||
|
||||
if (content.IsDraft is false)
|
||||
{
|
||||
_hybridCache.RemoveAsync(GetCacheKey(content.Key, false)).GetAwaiter().GetResult();
|
||||
await ClearPublishedCacheAsync(content.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ClearPublishedCacheAsync(Guid key)
|
||||
{
|
||||
var cacheKey = GetCacheKey(key, false);
|
||||
await _hybridCache.RemoveAsync(cacheKey);
|
||||
_publishedContentCache.Remove(cacheKey, out _);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#if DEBUG
|
||||
using System.Diagnostics;
|
||||
#endif
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Caching.Hybrid;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -31,6 +32,8 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
private readonly ILogger<MediaCacheService> _logger;
|
||||
private readonly CacheSettings _cacheSettings;
|
||||
|
||||
private readonly ConcurrentDictionary<string, IPublishedContent> _publishedContentCache = [];
|
||||
|
||||
private HashSet<Guid>? _seedKeys;
|
||||
private HashSet<Guid> SeedKeys
|
||||
{
|
||||
@@ -103,6 +106,12 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
private async Task<IPublishedContent?> GetNodeAsync(Guid key)
|
||||
{
|
||||
var cacheKey = $"{key}";
|
||||
|
||||
if (_publishedContentCache.TryGetValue(cacheKey, out IPublishedContent? cached))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
|
||||
ContentCacheNode? contentCacheNode = await _hybridCache.GetOrCreateAsync(
|
||||
cacheKey, // Unique key to the cache entry
|
||||
async cancel =>
|
||||
@@ -122,7 +131,13 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
return null;
|
||||
}
|
||||
|
||||
return _publishedContentFactory.ToIPublishedMedia(contentCacheNode).CreateModel(_publishedModelFactory);
|
||||
IPublishedContent? result = _publishedContentFactory.ToIPublishedMedia(contentCacheNode).CreateModel(_publishedModelFactory);
|
||||
if (result is not null)
|
||||
{
|
||||
_publishedContentCache[cacheKey] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> HasContentByIdAsync(int id)
|
||||
@@ -141,6 +156,7 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
using ICoreScope scope = _scopeProvider.CreateCoreScope();
|
||||
var cacheNode = _cacheNodeFactory.ToContentCacheNode(media);
|
||||
await _databaseCacheRepository.RefreshMediaAsync(cacheNode);
|
||||
_publishedContentCache.Remove(GetCacheKey(media.Key, false), out _);
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
@@ -219,7 +235,9 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
ContentCacheNode? publishedNode = await _databaseCacheRepository.GetMediaSourceAsync(key);
|
||||
if (publishedNode is not null)
|
||||
{
|
||||
await _hybridCache.SetAsync(GetCacheKey(publishedNode.Key, false), publishedNode, GetEntryOptions(publishedNode.Key));
|
||||
var cacheKey = GetCacheKey(publishedNode.Key, false);
|
||||
await _hybridCache.SetAsync(cacheKey, publishedNode, GetEntryOptions(publishedNode.Key));
|
||||
_publishedContentCache.Remove(cacheKey, out _);
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
@@ -234,7 +252,7 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
}
|
||||
|
||||
public async Task RemoveFromMemoryCacheAsync(Guid key)
|
||||
=> await _hybridCache.RemoveAsync(GetCacheKey(key, false));
|
||||
=> await ClearPublishedCacheAsync(key);
|
||||
|
||||
public async Task RebuildMemoryCacheByContentTypeAsync(IEnumerable<int> mediaTypeIds)
|
||||
{
|
||||
@@ -244,11 +262,11 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
|
||||
foreach (ContentCacheNode content in contentByContentTypeKey)
|
||||
{
|
||||
_hybridCache.RemoveAsync(GetCacheKey(content.Key, true)).GetAwaiter().GetResult();
|
||||
await _hybridCache.RemoveAsync(GetCacheKey(content.Key, true));
|
||||
|
||||
if (content.IsDraft is false)
|
||||
{
|
||||
_hybridCache.RemoveAsync(GetCacheKey(content.Key, false)).GetAwaiter().GetResult();
|
||||
await ClearPublishedCacheAsync(content.Key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +287,7 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
|
||||
foreach (ContentCacheNode media in mediaCacheNodesByContentTypeKey)
|
||||
{
|
||||
_hybridCache.RemoveAsync(GetCacheKey(media.Key, false));
|
||||
ClearPublishedCacheAsync(media.Key).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
@@ -313,4 +331,11 @@ internal sealed class MediaCacheService : IMediaCacheService
|
||||
// We use the tags to be able to clear all cache entries that are related to a given content item.
|
||||
// Tags for now are only content/media, but can be expanded with draft/published later.
|
||||
private static HashSet<string> GenerateTags(Guid? key) => key is null ? [] : [Constants.Cache.Tags.Media];
|
||||
|
||||
private async Task ClearPublishedCacheAsync(Guid key)
|
||||
{
|
||||
var cacheKey = GetCacheKey(key, false);
|
||||
await _hybridCache.RemoveAsync(cacheKey);
|
||||
_publishedContentCache.Remove(cacheKey, out _);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,17 +85,50 @@ internal sealed class DocumentHybridCacheTests : UmbracoIntegrationTestWithConte
|
||||
Assert.IsFalse(textPage.IsPublished());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Cannot_get_unpublished_content()
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public async Task Can_Get_Unpublished_Content_By_Key(bool preview)
|
||||
{
|
||||
// Arrange
|
||||
var unpublishAttempt = await ContentPublishingService.UnpublishAsync(PublishedTextPage.Key.Value, null, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(unpublishAttempt.Success);
|
||||
|
||||
//Act
|
||||
var textPage = await PublishedContentHybridCache.GetByIdAsync(PublishedTextPageId, false);
|
||||
// Act
|
||||
var textPage = await PublishedContentHybridCache.GetByIdAsync(PublishedTextPage.Key.Value, preview);
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(textPage);
|
||||
if (preview)
|
||||
{
|
||||
Assert.IsNotNull(textPage);
|
||||
Assert.IsFalse(textPage.IsPublished());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(textPage);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public async Task Can_Get_Unpublished_Content_By_Id(bool preview)
|
||||
{
|
||||
// Arrange
|
||||
var unpublishAttempt = await ContentPublishingService.UnpublishAsync(PublishedTextPage.Key.Value, null, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(unpublishAttempt.Success);
|
||||
|
||||
// Act
|
||||
var textPage = await PublishedContentHybridCache.GetByIdAsync(PublishedTextPageId, preview);
|
||||
|
||||
// Assert
|
||||
if (preview)
|
||||
{
|
||||
Assert.IsNotNull(textPage);
|
||||
Assert.IsFalse(textPage.IsPublished());
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(textPage);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache;
|
||||
|
||||
public class DocumentPropertyCacheLevelTests : PropertyCacheLevelTestsBase
|
||||
{
|
||||
private static readonly Guid _documentKey = new("9A526E75-DE41-4A81-8883-3E63F11A388D");
|
||||
|
||||
private IDocumentCacheService DocumentCacheService => GetRequiredService<IDocumentCacheService>();
|
||||
|
||||
private IContentEditingService ContentEditingService => GetRequiredService<IContentEditingService>();
|
||||
|
||||
private IContentPublishingService ContentPublishingService => GetRequiredService<IContentPublishingService>();
|
||||
|
||||
private IContentTypeEditingService ContentTypeEditingService => GetRequiredService<IContentTypeEditingService>();
|
||||
|
||||
[SetUp]
|
||||
public async Task SetUpTest()
|
||||
{
|
||||
PropertyValueLevelDetectionTestsConverter.Reset();
|
||||
|
||||
var contentTypeCreateModel = ContentTypeEditingBuilder.CreateSimpleContentType();
|
||||
var contentTypeAttempt = await ContentTypeEditingService.CreateAsync(contentTypeCreateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(contentTypeAttempt.Success);
|
||||
|
||||
var contentCreateModel = ContentEditingBuilder.CreateSimpleContent(contentTypeAttempt.Result.Key);
|
||||
contentCreateModel.Key = _documentKey;
|
||||
var contentAttempt = await ContentEditingService.CreateAsync(contentCreateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(contentAttempt.Success);
|
||||
|
||||
await PublishPage();
|
||||
}
|
||||
|
||||
[TestCase(PropertyCacheLevel.None, false, 1, 10)]
|
||||
[TestCase(PropertyCacheLevel.None, true, 2, 10)]
|
||||
[TestCase(PropertyCacheLevel.Element, false, 1, 1)]
|
||||
[TestCase(PropertyCacheLevel.Element, true, 2, 2)]
|
||||
[TestCase(PropertyCacheLevel.Elements, false, 1, 1)]
|
||||
[TestCase(PropertyCacheLevel.Elements, true, 1, 1)]
|
||||
public async Task Property_Value_Conversion_Respects_Property_Cache_Level(PropertyCacheLevel cacheLevel, bool preview, int expectedSourceConverts, int expectedInterConverts)
|
||||
{
|
||||
PropertyValueLevelDetectionTestsConverter.SetCacheLevel(cacheLevel);
|
||||
|
||||
var publishedContent1 = await DocumentCacheService.GetByKeyAsync(_documentKey, preview);
|
||||
Assert.IsNotNull(publishedContent1);
|
||||
|
||||
var publishedContent2 = await DocumentCacheService.GetByKeyAsync(_documentKey, preview);
|
||||
Assert.IsNotNull(publishedContent2);
|
||||
|
||||
if (preview)
|
||||
{
|
||||
Assert.AreNotSame(publishedContent1, publishedContent2);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.AreSame(publishedContent1, publishedContent2);
|
||||
}
|
||||
|
||||
var titleValue1 = publishedContent1.Value<string>("title");
|
||||
Assert.IsNotNull(titleValue1);
|
||||
|
||||
var titleValue2 = publishedContent2.Value<string>("title");
|
||||
Assert.IsNotNull(titleValue2);
|
||||
|
||||
Assert.AreEqual(titleValue1, titleValue2);
|
||||
|
||||
// fetch title values 10 times in total, 5 times from each published content instance
|
||||
titleValue1 = publishedContent1.Value<string>("title");
|
||||
titleValue1 = publishedContent1.Value<string>("title");
|
||||
titleValue1 = publishedContent1.Value<string>("title");
|
||||
titleValue1 = publishedContent1.Value<string>("title");
|
||||
|
||||
titleValue2 = publishedContent2.Value<string>("title");
|
||||
titleValue2 = publishedContent2.Value<string>("title");
|
||||
titleValue2 = publishedContent2.Value<string>("title");
|
||||
titleValue2 = publishedContent2.Value<string>("title");
|
||||
|
||||
Assert.AreEqual(expectedSourceConverts, PropertyValueLevelDetectionTestsConverter.SourceConverts);
|
||||
Assert.AreEqual(expectedInterConverts, PropertyValueLevelDetectionTestsConverter.InterConverts);
|
||||
}
|
||||
|
||||
[TestCase(PropertyCacheLevel.None, false)]
|
||||
[TestCase(PropertyCacheLevel.None, true)]
|
||||
[TestCase(PropertyCacheLevel.Element, false)]
|
||||
[TestCase(PropertyCacheLevel.Element, true)]
|
||||
[TestCase(PropertyCacheLevel.Elements, false)]
|
||||
[TestCase(PropertyCacheLevel.Elements, true)]
|
||||
public async Task Property_Value_Conversion_Is_Triggered_After_Cache_Refresh(PropertyCacheLevel cacheLevel, bool preview)
|
||||
{
|
||||
PropertyValueLevelDetectionTestsConverter.SetCacheLevel(cacheLevel);
|
||||
|
||||
var publishedContent1 = await DocumentCacheService.GetByKeyAsync(_documentKey, preview);
|
||||
Assert.IsNotNull(publishedContent1);
|
||||
|
||||
var titleValue1 = publishedContent1.Value<string>("title");
|
||||
Assert.IsNotNull(titleValue1);
|
||||
|
||||
// re-publish the page to trigger a cache refresh for the page
|
||||
await PublishPage();
|
||||
|
||||
var publishedContent2 = await DocumentCacheService.GetByKeyAsync(_documentKey, preview);
|
||||
Assert.IsNotNull(publishedContent2);
|
||||
|
||||
Assert.AreNotSame(publishedContent1, publishedContent2);
|
||||
|
||||
var titleValue2 = publishedContent2.Value<string>("title");
|
||||
Assert.IsNotNull(titleValue2);
|
||||
|
||||
Assert.AreEqual(titleValue1, titleValue2);
|
||||
|
||||
// expect conversions for each published content instance, due to the cache refresh
|
||||
Assert.AreEqual(2, PropertyValueLevelDetectionTestsConverter.SourceConverts);
|
||||
Assert.AreEqual(2, PropertyValueLevelDetectionTestsConverter.InterConverts);
|
||||
}
|
||||
|
||||
private async Task PublishPage()
|
||||
{
|
||||
var publishAttempt = await ContentPublishingService.PublishAsync(
|
||||
_documentKey,
|
||||
[new() { Culture = "*", }],
|
||||
Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(publishAttempt.Success);
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
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<IPublishedContentFactory>();
|
||||
|
||||
private IPublishedValueFallback PublishedValueFallback => GetRequiredService<IPublishedValueFallback>();
|
||||
|
||||
private IMediaService MediaService => GetRequiredService<IMediaService>();
|
||||
|
||||
private IMediaTypeService MediaTypeService => GetRequiredService<IMediaTypeService>();
|
||||
|
||||
private IMemberService MemberService => GetRequiredService<IMemberService>();
|
||||
|
||||
private IMemberTypeService MemberTypeService => GetRequiredService<IMemberTypeService>();
|
||||
|
||||
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<string, PropertyData[]>
|
||||
{
|
||||
{
|
||||
"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<string>(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<string, PropertyData[]>(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache;
|
||||
|
||||
public class MediaPropertyCacheLevelTests : PropertyCacheLevelTestsBase
|
||||
{
|
||||
private static readonly Guid _mediaKey = new("B4507763-591F-4E32-AD14-7EA67C6AE0D3");
|
||||
|
||||
private IMediaCacheService MediaCacheService => GetRequiredService<IMediaCacheService>();
|
||||
|
||||
private IMediaEditingService MediaEditingService => GetRequiredService<IMediaEditingService>();
|
||||
|
||||
private IMediaTypeEditingService MediaTypeEditingService => GetRequiredService<IMediaTypeEditingService>();
|
||||
|
||||
[SetUp]
|
||||
public async Task SetUpTest()
|
||||
{
|
||||
PropertyValueLevelDetectionTestsConverter.Reset();
|
||||
|
||||
var mediaTypeCreateModel = MediaTypeEditingBuilder.CreateMediaTypeWithOneProperty(propertyAlias: "title");
|
||||
var mediaTypeAttempt = await MediaTypeEditingService.CreateAsync(mediaTypeCreateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(mediaTypeAttempt.Success);
|
||||
|
||||
var mediaCreateModel = MediaEditingBuilder.CreateMediaWithAProperty(mediaTypeAttempt.Result.Key, "My Media", null, propertyAlias: "title", propertyValue: "The title");
|
||||
mediaCreateModel.Key = _mediaKey;
|
||||
var mediaAttempt = await MediaEditingService.CreateAsync(mediaCreateModel, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(mediaAttempt.Success);
|
||||
}
|
||||
|
||||
[TestCase(PropertyCacheLevel.None, 1, 10)]
|
||||
[TestCase(PropertyCacheLevel.Element, 1, 1)]
|
||||
[TestCase(PropertyCacheLevel.Elements, 1, 1)]
|
||||
public async Task Property_Value_Conversion_Respects_Property_Cache_Level(PropertyCacheLevel cacheLevel, int expectedSourceConverts, int expectedInterConverts)
|
||||
{
|
||||
PropertyValueLevelDetectionTestsConverter.SetCacheLevel(cacheLevel);
|
||||
|
||||
var publishedContent1 = await MediaCacheService.GetByKeyAsync(_mediaKey);
|
||||
Assert.IsNotNull(publishedContent1);
|
||||
|
||||
var publishedContent2 = await MediaCacheService.GetByKeyAsync(_mediaKey);
|
||||
Assert.IsNotNull(publishedContent2);
|
||||
|
||||
Assert.AreSame(publishedContent1, publishedContent2);
|
||||
|
||||
var titleValue1 = publishedContent1.Value<string>("title");
|
||||
Assert.IsNotNull(titleValue1);
|
||||
|
||||
var titleValue2 = publishedContent2.Value<string>("title");
|
||||
Assert.IsNotNull(titleValue2);
|
||||
|
||||
Assert.AreEqual(titleValue1, titleValue2);
|
||||
|
||||
// fetch title values 10 times in total, 5 times from each published content instance
|
||||
titleValue1 = publishedContent1.Value<string>("title");
|
||||
titleValue1 = publishedContent1.Value<string>("title");
|
||||
titleValue1 = publishedContent1.Value<string>("title");
|
||||
titleValue1 = publishedContent1.Value<string>("title");
|
||||
|
||||
titleValue2 = publishedContent2.Value<string>("title");
|
||||
titleValue2 = publishedContent2.Value<string>("title");
|
||||
titleValue2 = publishedContent2.Value<string>("title");
|
||||
titleValue2 = publishedContent2.Value<string>("title");
|
||||
|
||||
Assert.AreEqual(expectedSourceConverts, PropertyValueLevelDetectionTestsConverter.SourceConverts);
|
||||
Assert.AreEqual(expectedInterConverts, PropertyValueLevelDetectionTestsConverter.InterConverts);
|
||||
}
|
||||
|
||||
[TestCase(PropertyCacheLevel.None)]
|
||||
[TestCase(PropertyCacheLevel.Element)]
|
||||
[TestCase(PropertyCacheLevel.Elements)]
|
||||
public async Task Property_Value_Conversion_Is_Triggered_After_Cache_Refresh(PropertyCacheLevel cacheLevel)
|
||||
{
|
||||
PropertyValueLevelDetectionTestsConverter.SetCacheLevel(cacheLevel);
|
||||
|
||||
var publishedContent1 = await MediaCacheService.GetByKeyAsync(_mediaKey);
|
||||
Assert.IsNotNull(publishedContent1);
|
||||
|
||||
var titleValue1 = publishedContent1.Value<string>("title");
|
||||
Assert.AreEqual("The title", titleValue1);
|
||||
|
||||
// save the media to trigger a cache refresh for the media
|
||||
var mediaAttempt = await MediaEditingService.UpdateAsync(
|
||||
_mediaKey,
|
||||
new ()
|
||||
{
|
||||
Properties = [new () { Alias = "title", Value = "New title" }],
|
||||
Variants = [new() { Name = publishedContent1.Name }],
|
||||
},
|
||||
Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(mediaAttempt.Success);
|
||||
|
||||
var publishedContent2 = await MediaCacheService.GetByKeyAsync(_mediaKey);
|
||||
Assert.IsNotNull(publishedContent2);
|
||||
|
||||
Assert.AreNotSame(publishedContent1, publishedContent2);
|
||||
|
||||
var titleValue2 = publishedContent2.Value<string>("title");
|
||||
Assert.AreEqual("New title", titleValue2);
|
||||
|
||||
// expect conversions for each published content instance, due to the cache refresh
|
||||
Assert.AreEqual(2, PropertyValueLevelDetectionTestsConverter.SourceConverts);
|
||||
Assert.AreEqual(2, PropertyValueLevelDetectionTestsConverter.InterConverts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Models;
|
||||
using Umbraco.Cms.Core.Models.ContentEditing;
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.PublishedCache;
|
||||
using Umbraco.Cms.Core.Services;
|
||||
using Umbraco.Cms.Core.Services.ContentTypeEditing;
|
||||
using Umbraco.Cms.Infrastructure.HybridCache.Services;
|
||||
using Umbraco.Cms.Tests.Common.Builders;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache;
|
||||
|
||||
public class MemberPropertyCacheLevelTests : PropertyCacheLevelTestsBase
|
||||
{
|
||||
private static readonly Guid _memberKey = new("1ADC9048-E437-460B-95DC-3B8E19239CBD");
|
||||
|
||||
private IMemberCacheService MemberCacheService => GetRequiredService<IMemberCacheService>();
|
||||
|
||||
private IMemberEditingService MemberEditingService => GetRequiredService<IMemberEditingService>();
|
||||
|
||||
private IMemberTypeService MemberTypeService => GetRequiredService<IMemberTypeService>();
|
||||
|
||||
[SetUp]
|
||||
public void SetUpTest()
|
||||
=> PropertyValueLevelDetectionTestsConverter.Reset();
|
||||
|
||||
[TestCase(PropertyCacheLevel.None, 2, 10)]
|
||||
[TestCase(PropertyCacheLevel.Element, 2, 2)]
|
||||
[TestCase(PropertyCacheLevel.Elements, 2, 10)]
|
||||
public async Task Property_Value_Conversion_Respects_Property_Cache_Level(PropertyCacheLevel cacheLevel, int expectedSourceConverts, int expectedInterConverts)
|
||||
{
|
||||
PropertyValueLevelDetectionTestsConverter.SetCacheLevel(cacheLevel);
|
||||
|
||||
var member = await CreateMember();
|
||||
|
||||
var publishedMember1 = await MemberCacheService.Get(member);
|
||||
Assert.IsNotNull(publishedMember1);
|
||||
|
||||
var publishedMember2 = await MemberCacheService.Get(member);
|
||||
Assert.IsNotNull(publishedMember2);
|
||||
|
||||
Assert.AreNotSame(publishedMember1, publishedMember2);
|
||||
|
||||
var titleValue1 = publishedMember1.Value<string>("title");
|
||||
Assert.AreEqual("The title", titleValue1);
|
||||
|
||||
var titleValue2 = publishedMember2.Value<string>("title");
|
||||
Assert.IsNotNull(titleValue2);
|
||||
|
||||
Assert.AreEqual("The title", titleValue2);
|
||||
|
||||
// fetch title values 10 times in total, 5 times from each published member instance
|
||||
titleValue1 = publishedMember1.Value<string>("title");
|
||||
titleValue1 = publishedMember1.Value<string>("title");
|
||||
titleValue1 = publishedMember1.Value<string>("title");
|
||||
titleValue1 = publishedMember1.Value<string>("title");
|
||||
|
||||
titleValue2 = publishedMember2.Value<string>("title");
|
||||
titleValue2 = publishedMember2.Value<string>("title");
|
||||
titleValue2 = publishedMember2.Value<string>("title");
|
||||
titleValue2 = publishedMember2.Value<string>("title");
|
||||
|
||||
Assert.AreEqual(expectedSourceConverts, PropertyValueLevelDetectionTestsConverter.SourceConverts);
|
||||
Assert.AreEqual(expectedInterConverts, PropertyValueLevelDetectionTestsConverter.InterConverts);
|
||||
}
|
||||
|
||||
private IUser SuperUser() => GetRequiredService<IUserService>().GetAsync(Constants.Security.SuperUserKey).GetAwaiter().GetResult();
|
||||
|
||||
private async Task<IMember> CreateMember()
|
||||
{
|
||||
IMemberType memberType = MemberTypeBuilder.CreateSimpleMemberType();
|
||||
var memberTypeCreateResult = await MemberTypeService.UpdateAsync(memberType, Constants.Security.SuperUserKey);
|
||||
Assert.IsTrue(memberTypeCreateResult.Success);
|
||||
|
||||
var createModel = new MemberCreateModel
|
||||
{
|
||||
Key = _memberKey,
|
||||
Email = "test@test.com",
|
||||
Username = "test",
|
||||
Password = "SuperSecret123",
|
||||
IsApproved = true,
|
||||
ContentTypeKey = memberType.Key,
|
||||
Roles = [],
|
||||
Variants = [new() { Name = "T. Est" }],
|
||||
Properties = [new() { Alias = "title", Value = "The title" }],
|
||||
};
|
||||
|
||||
var memberCreateResult = await MemberEditingService.CreateAsync(createModel, SuperUser());
|
||||
Assert.IsTrue(memberCreateResult.Success);
|
||||
Assert.IsNotNull(memberCreateResult.Result.Content);
|
||||
|
||||
return memberCreateResult.Result.Content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Core.Cache;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.Models.PublishedContent;
|
||||
using Umbraco.Cms.Core.Notifications;
|
||||
using Umbraco.Cms.Core.PropertyEditors;
|
||||
using Umbraco.Cms.Core.Sync;
|
||||
using Umbraco.Cms.Tests.Common.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Testing;
|
||||
using Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Integration.Umbraco.PublishedCache.HybridCache;
|
||||
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
public abstract class PropertyCacheLevelTestsBase : UmbracoIntegrationTest
|
||||
{
|
||||
protected override void CustomTestSetup(IUmbracoBuilder builder)
|
||||
{
|
||||
builder.AddNotificationHandler<ContentTreeChangeNotification, ContentTreeChangeDistributedCacheNotificationHandler>();
|
||||
builder.AddNotificationHandler<MediaTreeChangeNotification, MediaTreeChangeDistributedCacheNotificationHandler>();
|
||||
builder.Services.AddUnique<IServerMessenger, ContentEventsTests.LocalServerMessenger>();
|
||||
|
||||
builder.PropertyValueConverters().Append<PropertyValueLevelDetectionTestsConverter>();
|
||||
}
|
||||
|
||||
[HideFromTypeFinder]
|
||||
public class PropertyValueLevelDetectionTestsConverter : PropertyValueConverterBase
|
||||
{
|
||||
private static PropertyCacheLevel _cacheLevel;
|
||||
|
||||
public static void Reset()
|
||||
=> SourceConverts = InterConverts = 0;
|
||||
|
||||
public static void SetCacheLevel(PropertyCacheLevel cacheLevel)
|
||||
=> _cacheLevel = cacheLevel;
|
||||
|
||||
public static int SourceConverts { get; private set; }
|
||||
|
||||
public static int InterConverts { get; private set; }
|
||||
|
||||
public override bool IsConverter(IPublishedPropertyType propertyType)
|
||||
=> propertyType.EditorAlias is Constants.PropertyEditors.Aliases.TextBox or Constants.PropertyEditors.Aliases.TextArea;
|
||||
|
||||
public override Type GetPropertyValueType(IPublishedPropertyType propertyType)
|
||||
=> typeof(string);
|
||||
|
||||
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType)
|
||||
=> _cacheLevel;
|
||||
|
||||
public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview)
|
||||
{
|
||||
SourceConverts++;
|
||||
return base.ConvertSourceToIntermediate(owner, propertyType, source, preview);
|
||||
}
|
||||
|
||||
public override object? ConvertIntermediateToObject(
|
||||
IPublishedElement owner,
|
||||
IPublishedPropertyType propertyType,
|
||||
PropertyCacheLevel referenceCacheLevel,
|
||||
object inter,
|
||||
bool preview)
|
||||
{
|
||||
InterConverts++;
|
||||
return base.ConvertIntermediateToObject(owner, propertyType, referenceCacheLevel, inter, preview);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user